Supporting wildcard routes in EPiServer 7 CMS

Unfortunately EPiServer does not support wildcard routes out of the box. Luckily it is not too hard to implement it using the ISegment interface.

In ASP.NET a wildcard route is a route that catches all segments from where it originate. If we have the route "{controller}/{action}/{*filter}" it would match the following URL "/home/search/apple/pear/grape/" and the filter parameter would be populated with "apple/pear/grape/".

This feature is handy for filter pages or pages where you want to build really dynamic URLs.

To support this in EPiServer we will need to create an implementation of ISegment. It will have two methods, one for routing incoming requests and one for generating URL segments.

Wildcard segment implementation

public class WildcardSegment : SegmentBase {
    public WildcardSegment(string name) : base(name) { }

    public override bool RouteDataMatch(SegmentContext context) {
        string path = context.RemainingPath;

        // set the default value if there is no remaining path
        if (string.IsNullOrEmpty(path)) {
            if (!context.Defaults.ContainsKey(Name))
                return false;

            context.RouteData.Values[Name] = context.Defaults[Name];
            return true;
        }

        context.RemainingPath = string.Empty;
        context.RouteData.Values[Name] = path;
        return true;
    }

    public override string GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values) {
        if (!values.ContainsKey(Name))
            return null;

        object value = values[Name];
        Type valueType = value.GetType();

        // if value is an enumerable or array, concatenate the list to a string
        if (valueType.IsGenericType && value is IEnumerable) {
            IEnumerable<string> valueList = from object val in value as IEnumerable
                                            select Convert.ToString(val);

            value = string.Join("/", valueList);
        }
        else if (valueType.IsArray) {
            var valueArray = value as object[];

            if (valueArray != null) {
                value = string.Join("/", valueArray);
            }
        }

        return string.Format("{0}/", value);
    }
}

RouteDataMatch

Normally in this method you would only use the current URL segment but as we are interested in getting the full remaining path we use context.RemainingPath and then sets its value to empty to mark that the path is handled.

GetVirtualPathSegment

This method will be called when you are trying to generated an URL with wildcard segments. The method could have just returned the value right away, but to also support List and Array route values some more handling was required.

Create a wildcard route

To add a new route with wildcard segment, simply add an instance of WildcardSegment to the SegmentMappings dictionary. If you think this code look verbose I agree. I have created an extension method for simplifying registration of segments.

routes.MapContentRoute(
    name: "List-with-filters",
    url: "{language}/{node}/{partial}/{filters}/{action}",
    defaults: new { controller = "List", action = "Index" },
    parameters: new MapContentRouteParameters {
        SegmentMappings = new Dictionary<string, ISegment> {
            { "filters", new WildcardSegment("filters") }
        }
    }
);

If you want to go the extra mile you could implement an IRouteParser and check if the route URL has any segments with *-star in them, and add a new WildcardSegment if that is the case.

Please note that the filters segment will match for all EPiServer pages in your site. You can use a Content Type constraint to limit it to a certain page type.

You can see the full code at Gist or download the files. I threw in an extra Model Binder there as well.

published in EPiServer