EPiServer Segments Explained, registering custom routes in EPiServer

I've previously blogged in EPiServer 7 Routing For Dummies which talked a bit about segments in Episerver. Today we're going to go one step further and create our own custom segment.

What is a segment?

In the switch over to .Net and MVC the way Url's are generated are very different.  In the old static html life world, we created folders and html files.  The websites Url structure was made up of this folder and file hierarchy. With MVC we have controllers and actions and Url's are based on the controllers name and the methods declared in them.  In the background these are wired up to views and Url's etc.. are all handled by this thing called routing. When you work with routing in MVC you will have to start playing with custom routes, like this one
       routes.MapRoute(
                "Default",
                "{controller}/{action}",
                new { controller = "Home", action = "Index" }
            );
The above snippet defines two segments {controller] and {action}. It tells MVC that when it finds a match on these two segments, do something. When we add Episerver into the mix they introduce a few of their own custom segments, the blow code defines a default Episerver route:
RouteTable.Routes.MapContentRoute(
  "myRoute", "{language}/{node}/{action}/",
  new {action = "name"});
In Episerver, as we have page types we don't do any matching on controller directly as pages can live anywhere in a virtual hierarchy in the editor.  Instead we have {node} which will contain an Episerver content reference.  This is the thing that make the current page magic works.  We also defined {language} segment to deal with multi-language if you're using it. Custom_Segment In this example then search is the first segment and about is the second segment.

Why would I need to create a custom segment?

The most common scenario of why you will need to create a custom segment is to probably SEO wise to make your url's a bit more user friendly. In the old world let's say you had a search page that you used a query string to pass in the search result
www.website.com/search/?search=searchterm
With MVC we can make the URL look a lot more cooler by using the URL to pass in the search query, like so:
www.website.com/search/Searchterm
There are loads of variations of these types of requirements like tagging, categories, news , pagination etc..

Creating a custom segment

What we're going to do is define our own segment for a search page and then populate a search term parameter so the search term is automatically passed into a controller.
    public class SearchSegment : ParameterSegment
    {
        IContentLoader _contentLoader;

        public SearchSegment(string name, IContentLoader contentLoader)
            : base(name)
        {
            _contentLoader = contentLoader;
        }

        public override bool RouteDataMatch(SegmentContext context)
        {
            var segmentPair = context.GetNextValue(context.RemainingPath);
            var searchTerm = segmentPair.Next;

            if (!string.IsNullOrEmpty(searchTerm))
            {
                return ProcessExtraSegment(context, segmentPair);
            }

            if (context.Defaults.ContainsKey(Name))
            {
                context.RouteData.Values[Name] = context.Defaults[Name];
                return true;
            }

            return false;
        }

        private bool ProcessExtraSegment(SegmentContext context, SegmentPair segmentPair)
        {
            var content = _contentLoader.Get<IContent>(context.RoutedContentLink);
            if (context is SearchPage)
            {
                context.RouteData.Values[Name] = context.Defaults[Name];
            }
            else
            {
                context.RouteData.Values[Name] = segmentPair.Next;
                context.RemainingPath = segmentPair.Remaining;
            }

            return true;
        }
So, to create a custom segment, we need to either inherit from SegmentBase or ParameterSegment.  When you inherit from SegmentBase you have to implement GetVirtualPathSegment and RouteDataMatch.  Instead of implement from ParameterSegment, we only have to implement GetVirtualPathSegment
    using EPiServer.Web.Routing.RouteParser;

    public class ParameterSegment : SegmentBase
    {
        public ParameterSegment(string name);

        EPiServer.Web.Routing.Segments.SegmentBase.Name
        public override string GetVirtualPathSegment(RequestContext requestContext, RouteValueDictionary values);

        public override bool RouteDataMatch(SegmentContext context);
    }
In GetVirtualPathSegment, what we're going to do is check that the current page request is of type search page.  If it is populate a segment called "searchterm".  This means if we have an input string called search term in our search controller it will automatically be populated.  The text it will be populated by will be the next segment, e.g. searchterm
www.website.com/search/searchterm
In our code the first thing we do is get the actual segment value itelf
            var segmentPair = context.GetNextValue(context.RemainingPath);
            var searchTerm = segmentPair.Next;
This should return us the text 'searchterm' in our example. The next bit will assign the segment value into a custom segment.
        private bool ProcessExtraSegment(SegmentContext context, SegmentPair segmentPair)
        {
            var content = _contentLoader.Get<IContent>(context.RoutedContentLink);
            if (content is SearchPage)
            {
                context.RouteData.Values[Name] = context.Defaults[Name];
            }
            else
            {
                context.RouteData.Values[Name] = segmentPair.Next;
                context.RemainingPath = segmentPair.Remaining;
            }

            return true;
        }
In this method we're using the Episerver api to check if the current page is the search page.  If it is, then we set the current segment into RouteData and assign it to [Name]. If the page isn't a search page we carry on and process the page as normal.

Registering The Segment

The last part of the puzzle is actually registering our customer segment.  This is done in the global.ascx file.  If we don't set the pages up correctly, we'll get a 404 page error as the MVC handler won't know how to translate the Url into the correct Episerver age to use. I should warn you it can be a bit of a pain to debug this when you set it up wrong.  Whenever you get a 404, what we need to do is whenever a match is made on an action, call our custom segment handler so we can tell MVC that we have a correct page match and then populate our custom search term route data.
  protected override void RegisterRoutes(RouteCollection routes)
        {
            base.RegisterRoutes(routes);

            IContentLoader contentLoader;
            ServiceLocator.Current.TryGetExistingInstance(out contentLoader);

            IUrlSegmentRouter segmentRouter;
            ServiceLocator.Current.TryGetExistingInstance(out segmentRouter);

            if (contentLoader != null && segmentRouter != null)
            {
                segmentRouter.RootResolver = sd => sd.StartPage;
                var parameters = new MapContentRouteParameters
                {
                    UrlSegmentRouter = segmentRouter,
                    BasePathResolver = EPiServer.Web.Routing.RouteCollectionExtensions.ResolveBasePath,
                    Direction = SupportedDirection.Both
                };
                var segment = new SearchSegment("action", contentLoader);
                var segmentMappings = new Dictionary<string, ISegment> { { "action", segment } };
                parameters.SegmentMappings = segmentMappings;

                routes.MapContentRoute(
                    name: "searchterm",
                    url: "{language}/{node}/{partial}/{action}/{page}",
                    defaults: new { action = "index" },
                    parameters: parameters);
            }
I'll hold my hands up that this bit was copied off another blog.

Conclusion

Today we've gone over what a segment is.  When we use MVC routing we need a way to map the url to a controller, action method or anything we want.  This is really useful for SEO friendly Url's.  Instead of using query string values we can add the old query string values directly into the Url if we want to. To add this type of feature we need to create a custom segment. We can do this by either inheriting from SegmentBase or ParameterSegment. In this example, we add the code to only add data into the routing data for search pages only.  If we wanted to populate search terms on any page, then we could simply remove the page type check. The last bit is registering the segment in the global.ascx. We use the action segment to perform the match and then register a custom route for it.

Code Sample

All the above code can be downloaded in a fully working website from my github account here. JonDJones.com.EpiSiteSearchSampleSite

Jon D Jones

Software Architect, Programmer and Technologist Jon Jones is founder and CEO of London-based tech firm Digital Prompt. He has been working in the field for nearly a decade, specializing in new technologies and technical solution research in the web business. A passionate blogger by heart , speaker & consultant from England.. always on the hunt for the next challenge

Back to top