Skip to main content
Logo

Route constraints

As mentioned in the defining routes article, routing constraints are about ensuring that URL matches a required format. E.g can only contain numbers.

routes.MapPageRoute(
    "movies",
    "movies/{month}/{year}/{movie}",
    "~/movies/movie.aspx",
    false,
    null,
    new RouteValueDictionary
    {
        { "month", @"\d{2}" }
        { "year", @"\d{4}" }
    }
);

In this example both the month and year parameters must contain only numbers and match a required length.

When working with Contensis, you can instead determine whether a valid entry will be returned by the route. We do this by creating a custom class to return our entry and set this class as the constraint.

Each route would have its own class and these would typically live in their own sub directory within app_code to make it easier to manage them.

Constraint files in Contensis

 

Let's take a look at what our movie constraint looks like:

using System;
using System.Linq;
using System.Web;
using System.Web.Routing;
using Zengenti.Contensis.Delivery;
using Zengenti.Search;
using Zengenti.Data public class MovieConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary urlParameters, RouteDirection routeDirection) { //Only handle incoming requests if (routeDirection == RouteDirection.IncomingRequest) { //If we already have an entry in the cache return this if (httpContext.Cache[httpContext.Request.Path] != null) { Entry movie = httpContext.Cache[httpContext.Request.Path] as Entry; httpContext.Items["movie"] = movie; httpContext.Items["movieUrl"] = httpContext.Request.Path; httpContext.Items["movieTitle"] = movie.Get<string>("entryTitle"); return true; } else { //Get the values from the parameters from the object[] searchVals = urlParameters.Values.ToArray(); string genre = searchVals[0].ToString(); string slug = searchVals[1].ToString(); //Get movie through Delivery API ContensisClient client = ContensisClient.Create(); ; Query query = new Query( Op.EqualTo("sys.contentTypeId", "movie"), Op.EqualTo("sys.slug", slug) ); //We only want to return a single entry so set page size to 1 to reduce the payload query.PageSize = 1; PagedList<Entry> movies = client.Entries.Search<Entry>(query, 1);
if(movies.TotalCount > 0){
Entry movie = movies.Items.First(); //If we have a movie, add the movie entry, URL and title so the cache for easy retrieval. //Return true to say we have a matched entry and send the user to their requested route //Otherwise return false if (movie != null) { httpContext.Cache.Insert( httpContext.Request.Path, movie, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(1) ); httpContext.Items["movie"] = movie; httpContext.Items["movieUrl"] = httpContext.Request.Path; httpContext.Items["movieTitle"] = movie.Get<string>("entryTitle"); return true; }
} return false; } } else { return true; } } }

This code is searching for an entry where the entry slug matches the {movie} parameter of the URL the user is coming in on. So in the case of this URL http://www.moviedb.com/movies/action/batman-begins our movie constraint would be looking for a movie entry where the slug had a value of batman-begins.

Now lets break the code down in more detail:

public class MovieConstraint : IRouteConstraint
{
    //How do it execute this function
    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary urlParameters, RouteDirection routeDirection)
    {
      //Code to return entry goes here
    }
}

This Match method will be triggered automatically as it is the first one in the class. Because we are inheriting from the IRouteConstraint class, all routing information for our application will get passed to our constraint class.

if (routeDirection == RouteDirection.IncomingRequest)
{
    //If we already have an entry in the cache return this
    if (httpContext.Cache[httpContext.Request.Path] != null)
    {
        Entry movie = httpContext.Cache[httpContext.Request.Path] as Entry;
        httpContext.Items["movie"] = movie;
        httpContext.Items["movieUrl"] = httpContext.Request.Path;
        httpContext.Items["movieTitle"] = movie.Get<string>("entryTitle");
        return true;
    }
}
else
{
    return true;
}

Here we are checking if the movie we are requesting has been cached previously. If we have a cached movie object then we add it to the application context. This allow you to access the data from components included in the physical page we defined in our route.

If the constraint hasn't been called from an incoming request, we can assume that we access the constraint from a custom function or method. In this scenario, we need our method to return true to ensure a valid link is returned.

//Get the values from the parameters from the
object[] searchVals = urlParameters.Values.ToArray();
string genre = searchVals[0].ToString();
string slug = searchVals[1].ToString();

If we don't have a cached movie, then we need to return one through the Contensis Delivery API. The first step is to grab the parameters from the URL through the urlParameters property and convert them to an array.

We can then get specific values and assign them to variables through specifying an array index (Zero based). The array order is based on the order they were defined in the route.

In the case of our movie route /movies/{genre}/{movie}{genre} is first (0). Followed by {movie}(1). Hardcoded parts of the route aren't passed in as parameters so aren't part of our array.

//Get movie through Delivery API
ContensisClient client = ContensisClient.Create(); ;

Query query = new Query(
    Op.EqualTo("sys.contentTypeId", "movie"),
    Op.EqualTo("sys.slug", slug)
);

//We only want to return a single entry so set page size to 1 to reduce the payload
query.PageSize = 1;

//Ensure we only get the first item returned
PagedList<Entry> movies = client.Entries.Search<Entry>(query, 1);
if(movies.TotalCount > 0){
Entry movie = movies.Items.First();
//Rest of code goes here
}

This code will run a query through the Delivery API and return an entry that matches the slug. We are returning our entry as a generic entry object but you can also implement Typed Models and use these within your routing code.

 if (movie != null)
{
    httpContext.Cache.Insert(
        httpContext.Request.Path, movie, null,
        System.Web.Caching.Cache.NoAbsoluteExpiration,
        TimeSpan.FromMinutes(1)
    );

    httpContext.Items["movie"] = movie;
    //Used for breadcrumb
    httpContext.Items["movieUrl"] = httpContext.Request.Path;
    httpContext.Items["movieTitle"] = movie.Get<string>("entryTitle");
    return true;
}
return false;

Here we are checking if a valid movie has been returned and if so, add it to the cache for one minute. We also add movie title and path to the application context for easy retrieval as in this example:

@using Zengenti.Contensis.Delivery;
@{
    Entry movie = null;
    ContensisClient client = ContensisClient.Create();

    if (!String.IsNullOrWhiteSpace(Request.QueryString["id"]))
    {
        var guid = Request.QueryString["id"];
        movie = client.Entries.Get<Entry>(guid, null, 2);
    }
    else if (HttpContext.Current.Items["movie"] != null)
    {
        movie = HttpContext.Current.Items["movie"] as Entry;
    }

    if(article != null)
    {
         //Set the page title
        AppContext.Current.Page.Title = HttpContext.Current.Items["articleTitle"].ToString();
    }  

    <div>
        <!--render movie entry content here!-->
    </div>
}

This code would usually be part of a razor view attached to the physical page defined in the route and would render an individual entry.

If we return a valid movie, our method returns true and the user is sent to their requested location, if not, return false. In this scenario, the application would then try the people route. If all routes return false, the application will return a 404 status code.

using System;
using System.Linq;
using System.Web;
using System.Web.Routing;
using Zengenti.Contensis.Delivery;
using Zengenti.Search;
using Zengenti.Data public class MovieConstraint : IRouteConstraint { public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary urlParameters, RouteDirection routeDirection) { //Only handle incoming requests if (routeDirection == RouteDirection.IncomingRequest) { //If we already have an entry in the cache return this if (httpContext.Cache[httpContext.Request.Path] != null) { Entry movie = httpContext.Cache[httpContext.Request.Path] as Entry; httpContext.Items["movie"] = movie; httpContext.Items["movieUrl"] = httpContext.Request.Path; httpContext.Items["movieTitle"] = movie.Get<string>("entryTitle"); return true; } else { //Get the values from the parameters from the object[] searchVals = urlParameters.Values.ToArray(); string genre = searchVals[0].ToString(); string slug = searchVals[1].ToString(); //Get movie through Delivery API ContensisClient client = ContensisClient.Create(); ; Query query = new Query( Op.EqualTo("sys.contentTypeId", "movie"), Op.EqualTo("sys.slug", slug) ); //We only want to return a single entry so set page size to 1 to reduce the payload query.PageSize = 1; PagedList<Entry> movies = client.Entries.Search<Entry>(query, 1);
if(movies.TotalCount > 0){
Entry movie = movies.Items.First(); //If we have a movie, add the movie entry, URL and title so the cache for easy retrieval. //Return true to say we have a matched entry and send the user to their requested route //Otherwise return false if (movie != null) { httpContext.Cache.Insert( httpContext.Request.Path, movie, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(1) ); httpContext.Items["movie"] = movie; httpContext.Items["movieUrl"] = httpContext.Request.Path; httpContext.Items["movieTitle"] = movie.Get<string>("entryTitle"); return true; }
} return false; } } else { return true; } } }

Once you've defined your routes and what content they will return, you can reference your routing code in razor views to generate URLs based on them.