Route constraint for Content Type in EPiServer CMS 7

When registering new ASP.NET MVC routes with EPiServer CMS you want the URL segment to be dynamic. To do that you add the {node} segment in the route URL. The node segment represents the full path to your page, e.g. “/Lorem-Products/Ipsum-Dolor/”.

An example route where you add an {id} segment would look like below:

routes.MapContentRoute( 
    name: "Epi", 
    url: "{language}/{node}/{partial}/{action}/{id}", 
    defaults: new { controller = "Product", action = "Index", id = UrlParameter.Optional } 
);

A side effect of doing this is that it matches all EPiServer pages. Every page controller can take an ID parameter now. This would probably not be such bad idea in this case, but if you are adding very specific routes you do not want them to interfere with other page types that do not offer the same functionality.

Creating a route constraint

To restrict the route to a certain page type we can add a constraint to the route by implementing IRouteConstraint. This isn’t specific to EPiServer but a concept from ASP.NET MVC.

public class ContentTypeConstraint<TContentType> : IRouteConstraint where TContentType : IContent {
    private readonly bool _matchInheritedTypes;
    private readonly IContentLoader _contentLoader;

    public ContentTypeConstraint(bool matchInheritedTypes = false) {
        _matchInheritedTypes = matchInheritedTypes;
        _contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values,
                        RouteDirection routeDirection) {

        var nodeToken = route.DataTokens[RoutingConstants.NodeKey];

        if (nodeToken == null)
            return false;

        IContent content = _contentLoader.Get<IContent>(nodeToken as ContentReference);
        Type requiredContentType = typeof(TContentType);
        Type contentType = content.GetType().BaseType; // match on BaseType because contentType is the Castle proxy type.

        if (_matchInheritedTypes)
            return contentType.IsAssignableFrom(requiredContentType);
            
        return contentType == requiredContentType;
    }
}

In short we get the current page and check if it matches the type specified. It also has a setting to check for inherited types.

Adding constraint to a route

To use this you would add the constraint to the MapContentRouteParameters parameter of MapContentRoute method like followed.

routes.MapContentRoute(
    name: "List-with-paging",
    url: "{language}/{node}/{partial}/{action}/{page}",
    defaults: new { controller = "List", action = "Index", page = 1 },
    parameters: new MapContentRouteParameters {
        Constraints = new { node = new ContentTypeConstraint<ListPage>() }
    }
);

Remember that this needs to run after EPiServer has initialized so create an InitializableModule for it. A tip is to make the RouteConfig class an InitializableModule.

published in EPiServer, ASP.NET MVC