Join me on a journey through the ASP.NET pipeline in search of Episerver. Part two: Route selection, ISegment

This is the second blog post in the Join me on a journey through the ASP.NET pipeline in search of Episerver series. If you haven’t read the first blog post then head on over to it and get up to speed.

Previously we had a look at how the IIS, ASP.NET, ASP.NET MVC and Episerver all fit together in the context of routing a friendly URL (FURL). It’s usually best to know where you’ve been in order to know where you’re going next. Let’s recap real quick what’s happened so far. PowerPoint to the rescue!

lifecycle-episerver

As you can see we’ve both been in the Application Life Cycle and in the Request Life Cycle of IIS and ASP.NET. Last time we left off knowing where Episerver matched a FURL to a route using the ContentRoute class and overriding the GetRouteData method, but not quite how. In order to solve this piece of the puzzle we need to head back to route registration in the Application Life Cycle. This is where we’ll find our friends the RouteParser and the ISegment.

Adding ISegment(s) to a route

As you remember Episerver registers its default routes in the constructor of the EPiServer.Global class. When a route is added to the route table its registered with a URL pattern such as {language}/{node}/{partial}/{action}. The URL pattern is what Episerver uses to associate ISegment implementations to a specific route. Note that Episerver creates multiple routes with different URL patterns, and we won’t go through every ContentRoute route and its ISegment in detail, but instead focus on the most common ContentRoute named pages.

In the case of {language}/{node}/{partial}/{action} Episerver will add the following concrete implementations of the ISegment interface: LanguageSegment, NodeSegmentPartialSegment and ParameterSegment. The order in which the segments appear in the URL pattern is important, because if the entire FURL is not matched to all the segments, and in that order, then the route is not valid in an Episerver context.

How does Episerver go about this, then? Let’s say hello to the RouteParser class. The RouteParser is responsible for taking a URL pattern, such as {language}/{node}/{partial}/{action}, parsing each {something} segment into an implementation of ISegment and returning an array containing ISegment. While it is possible to use the RouteParser class to parse your custom URL pattern and ISegment implementation(s), the RouteParser is primarily used internally by Episerver when the MapContentRoute method executes.

The correlation between default Episerver {something} segments in a URL pattern and a concrete implementation of ISegment is managed by the RoutingConstants class. The RoutingConstants class contains static fields with a string value representing a segment key. Such as RoutingConstants.LanguageKey and RoutingConstants.NodeKey.

When Episerver has resolved a URL pattern to an array of ISegment using the RouteParser class, it associates the array with ISegment with a ContentRoute. Its these ISegment that will be used further down the pipeline by the ContentRoute class to determine whether a FURL is a match or not.

Let’s dive into some code and check this out for ourselves. We’ll create an IInitializableModule and hook ourselves up so that we can inspect the concrete implementations of ISegment added to the pages route.

Not that much code, but let’s go through it. In order to get ahold of the ISegment array in the ContentRoute class we make use of Reflection since the member that contains the ISegment array in the ContentRoute class is declared as private. Normally, if the field had been marked as public we would have been able to access it directly on the ContentRoute instance object.

Nevertheless, as in the previous blog post, we navigate to the sample Alloy Episerver website FURL about-us and step over the RoutingContent event until we hit the pages route which is a match for our FURL about-us. Inspecting the urlSegments variable that we defined in our sample code, which contains the private field _urlSegments, being of type ISegment[], we see the following (pro tip: click the image to get a large version).

segments

We see that for the FURL about-us, in the ContentRoute with the name pages, the following ISegment have been added: LanguageSegment, NodeSegment, PartialSegment and ParameterSegment. These are the ISegment implementations that will match a FURL to a piece of content in Episerver.

Again, note that there are additional ContentRoute routes that contain other types of ISegment implementations that will match other types of content, such as media.

Rolling your own route with a custom ISegment implementation

To better understand the inner workings of an ISegment we’ll roll our own and have a closer look at what’s going on. As before, the ISegment(s) added to a ContentRoute are used to determine if the FURL (or really any arbitrary URL) is a match or not.

How would we go about creating and adding an implementation of an ISegment to a ContentRoute, then? Unfortunately the RouteParser isn’t much use for this. Instead, we need to pass in our custom ISegment(s) as a parameter when using the MapContentRoute extension method. This is the default way in which you can add custom ISegment(s) to a ContentRoute route.

Let’s pull back the curtain on ISegment.

Note that we’re not directly inheriting from the interface ISegment, but instead SegmentBase which is an abstract base class in Episerver for segments, that of course implements the ISegment interface.

The important part of our custom segment is the overriden method RouteDataMatch, which returns a boolean, indicating whether our segment is a match for the requested FURL or not. It’s this method the ContentRoute class will call into for all its associated ISegment to check whether or not the FURL is match. In our simple sample ISegment implementation we check whether the first part in the FURL has the string value of brucewayne or not.

Note that we set the RemainingPath member of the context parameter, which is of type SegmentContext, with the value of the Remaining member. This is because we must remove the brucewayne/ part of the FURL, otherwhise the next ISegment that will be called will also try to check if the URL segment brucewayne is valid, using the same SegmentContext object, which it won’t be. With each ISegment we, in most cases, remove the part of the FURL that’s been matched until nothing remains.

Comparing this to ASP.NET MVC we can see that an ISegment implementation doesn’t have to match a single part of the FURL directly. Instead, the ISegment can match multiple parts of the FURL. This is effectively how the internal Episerver URL segment {node}, representing the NodeSegment implementation, is valid for multiple URL segments such as http://mywebsite/some/awesome/furl where the NodeSegment will recursively call itself and check if the URL segments some, awesome and finally furl exist as content in the Episerver tree structure, beginning at the start page.

As an aside, the ISegment implementation actually doesn’t have to match a part of the FURL, it could match any arbitrary value, such as HTTP headers, IP-range, time of day, etc.

How do we add a ContentRoute to the route table, which contains our custom ISegment implementation, then? Again, to the IInitializableModule!

We could have added our custom route by overriding the RegisterRoutes method in the EPiServer.Global class. But instead, in a slightly less obtrusive way, let’s add our custom ContentRoute once all other routes have been added by hooking up to the RoutesRegistered event of the EPiServer.Global class. Again, it’s important the order in which the ContentRoute routes are added to the route table, since if any other route should match the incoming FURL our custom route will never be selected. This is standard ASP.NET routing behavior.

When the SegmentMappings member contains one or more custom ISegment implementations, Episerver will add these to a dictionary of default Episerver ISegment implementations in the MapContentRoute method. Episerver will then call into the RouteParser class, passing in the URL pattern and a dictionary of available ISegment, both default Episerver and custom, matching each ISegment with the URL segment key to a value in the dictionary of ISegment. It will then return an array containing ISegment representing the URL pattern passed in and associate that with the ContentRoute.

If we pause for a second, consider the URL pattern {batman}/{language}/{node}/{partial}/{action} that we registered for our ContentRoute, we see that the languagenode, partial and action URL segments will be matched to default Episerver ISegment implementations. But as we’ve passed in our custom ISegment with the key of batman in the SegmentMappings member, that ISegment will also be added to the dictionary of available ISegment in the MapContentRoute extension method, and can be resolved by the RouteParser class to the URL segment {batman}.

If we browse to a page on the Episerver Alloy sample site, and prefixing the FURL with brucewayne, no other routes are hit since they have no idea what brucewayne means in the FURL. At least, not until we hit our ContentRoute with the name of OurCustomRoute, that contains our custom ISegment, which identifies brucewayne as a match for the {batman} URL segment. It’s also the first ISegment implementation that will be called since it appears first in the URL pattern.

The rest of the URL behaves exactly as the ordinary pages route, looking up content in the page structure and so forth for the rest of the segments {language}/{node}/{partial}/{action} and their corresponding default Episerver ISegment implementations.

brucewayne-route

And that concludes how the ContentRoute matches a FURL by leveraging its associated ISegment implementations.

The rabbit’s hole goes further still, though. When we’ve found a suitable route for our FURL we will ultimately make use of the IRouteHandler associated with our selected ContentRoute.

Thanks for reading!

More

In search of Episerver: The Basics, ContentRoute
In search of Episerver: Route selection, ISegment (this blog post)

daniel
daniel
Developer
Recommended Posts
  • Yadhavi

    Daniel, Kindly explain how could we remove a segment from Url in similar way.

    • Daniel Berg

      I’m not sure I follow. Would you like to remove a URL segment from one of the default Episerver routes? Or a custom route?

Contact Us

We're not around right now. But you can send us an email and we'll get back to you, asap.

Not readable? Change text. captcha txt

Start typing and press Enter to search