Join me on a journey through the ASP.NET pipeline in search of Episerver. Part one: ASP.NET Routing with Episerver, the basics.

This blog post is for you, the developer, if you’re interested in how Episerver, ASP.NET MVC, ASP.NET and IIS all fit together when serving a HTTP request and ultimately returning a HTTP response.

Admittedly, it’s not easy picking apart these components, both for junior and senior developers the lines may appear blurred between application, framework(s) and server. Episerver may also throw ASP.NET MVC developers for a loop in how it handles friendly URLs (FURL). Hopefully, after you’ve read this post, it’ll all make more sense and you’ll have an easier time debugging, building and extending your Episerver-solutions by having a more solid understanding of the pipeline and its inherent components.

Just remember that for all the code written in all its brilliant simplicity, complexity and creativity it’s just about processing a HTTP request and returning a HTTP response. Keep this in mind when the code may seem daunting. I-this and I-that and handlers and modules, oh my!

Note that this does not apply to the ASP.NET Core 1.0 framework. While ASP.NET Core 1.0 has a few similarities with its OWIN-compatible pipeline, it’s a different story. Episerver is also in the process of rewriting how it handles routing with the new ASP.NET framework.

In the beginning there was IIS

We begin our journey in the built-in web server in Windows that is the IIS. Why IIS, you ask? The HTTP request, that is the pink dragon of our journey, simply has to start somewhere, and for the sake of this blog post, it’s the Windows built-in web server: IIS. However,  the principle is the same with the ASP.NET development server (IIS Express) that’s shipped with Visual Studio.

Let’s, just for a minute, rewind time as if Emmett Brown was in the driver’s seat. At the beginning ASP.NET was simply an external plugin the IIS called into as an ISAPI extension. The ASP.NET framework wasn’t aware of what was going on with the HTTP request until the later stages of the IIS pipeline. It wasn’t until IIS 7.0 and its integrated pipeline the two were as tightly coupled as we know it today. The IIS and ASP.NET pipeline virtually became one and the same with IIS 7.0 and the integrated pipeline. The IIS now had ASP.NET at ‘hello (world)’.

The pipeline of our web application and the IIS begin with the ASP.NET application file Global.asax that represents the HttpApplication class. This is where our ASP.NET web application can respond to application-level events of the IIS integrated pipeline. The first event that would be executed in the HttpApplication class would be the Application_Start event. This event is part of the application life cycle, meaning that it fires only once, when IIS loads up our web application, e.g. our application comes to life and is ready to rock’n’roll. Each subsequent request is referred to as the request life cycle. The request life cycle differs between ASP.NET MVC and ASP.NET Web Forms. But so far along in the application life cycle the Application_Start event is still just plain old ASP.NET.

Importantly, the end goal of ASP.NET at this stage is to locate and execute a HTTP handler to respond to the HTTP request. ASP.NET MVC and ASP.NET Web Forms differ on numerous points, and one is how they find and execute a HTTP handler that corresponds to the HTTP request. However, as both ASP.NET MVC and ASP.NET Web Forms are part of the ASP.NET framework, the HTTP handler is what ultimately is executed. They simply go about it differently. With ASP.NET Web Forms it’s possible to access a Web Forms page by the name of the physical file on disk, e.g. /my-url/some-page.aspx. The ASP.NET Web Forms page (some-page.aspx) implements the IHttpHandler interface directly.

ASP.NET MVC is dependent on a feature of the ASP.NET framework called routing. Routing is also the feature in ASP.NET which Episerver uses regardless if you’re implementing your web application using ASP.NET MVC or ASP.NET Web Forms. Episerver, before version 7, relied on a HTTP module to handle friendly URLs, but since then ASP.NET routing is used. Before we get into how Episerver leverages ASP.NET routing let’s first go through the basics.

ASP.NET routing, the basics

ASP.NET routing is not inherently ASP.NET MVC, though it’s a feature that ASP.NET MVC depends on. When you create an empty ASP.NET MVC project you would most likely use the extension method MapRoute in order to configure your route(s). If you were to check the source code of ASP.NET MVC on CodePlex (the new ASP.NET Core is on GitHub), you would see that this extension method is nothing but a wrapper method that disguises the inner workings of creating an ASP.NET route. The route, in itself, is but a means to map (route!) a URL to a HTTP handler. In the case of ASP.NET MVC there is additional logic performed with creating a controller for the request, performing model binding, etc.

Source code for the ASP.NET MVC MapRoute extension method stolen from the RouteCollectionExtensions class below.

Let’s exemplify how ASP.NET routes work by having a go at bypassing the ASP.NET MVC framework and constructing an ASP.NET route manually. The code below virtually does the same thing as the MapRoute extension method in the ASP.NET MVC framework. We start off in the RegisterRoutes method in the default RouteConfig class in a sample ASP.NET MVC project, adding our custom route. This is also where the Application_Start event of the HttpApplication class comes into play as the web application starts up for the first time and registers the associated routes.

As you can see we register two routes: our vanilla ASP.NET route MySampleRoute and the ASP.NET MVC route Default. The order in which they’re defined is important since no further processing is done once a matching route has been found.  In the code sample our MySampleRoute-route will match any URL before the Default-route we register with ASP.NET MVC. Not great code, but for the sake of this blog post let’s just go along.

What’s next is that we create a Route manually and assign a Route Handler to it. The Route Handler implements the IRouteHandler interface which simply defines the method GetHttpHandler which returns, and you guessed it, an IHttpHandler. The IHttpHandler interface in turn defines two members: IsReusable property and ProcessRequest method. The ProcessRequest method takes a parameter of type HttpContext which is what we use to modify the HTTP response. In the context of routing we assign the RouteData object associated with the request. This is effectively the values of our URL which ASP.NET has picked apart and added to a object of type RouteValueDictionary.

And if we run the application and navigate to the URL http://somelocalhost:1234/bat/man we’ll end up with the following response.

Routing Batman

As you can see the URL http://localhost:52521/bat/man matched the first route registered ({bruce}/{wayne}). No further processing is performed by ASP.NET and we never reach the ASP.NET MVC framework. Note that we are not registering {controller} or {action} in our route’s URL. It’s simply because these route data values only apply to the ASP.NET MVC framework and how it figures out which controller to create and which action to invoke. Again, if you would look in the ASP.NET MVC source code for its HTTP handler you’d see that where the magic for controller creation and invocation happen based on the route data.

To summarize: we registered a route with ASP.NET in the route table. The URL associated with our route and custom implementation of a route handler was a match. Further down the pipeline the associated HTTP handler, MyHttpHandler, was used to process the request and modify the HTTP response.

Let’s continue down the rabbit’s hole armed with basic knowledge of how ASP.NET routing works.

Routing in Episerver

Remember the IIS and ASP.NET pipeline where it all began with the HttpApplication class? That’s also where we’ll begin with Episerver. If you’ve created a sample project with Episerver, such as Alloy, you’ll inherit from the base class EPiServer.Global in the Global.asax file. Normally, we would register our routes in the Application_Start event, but when inheriting from the EPiServer.Global class the Episerver routes are registered in the constructor of EPiServer.Global. Episerver does this by using an extension method called RegisterRoutes on the RouteCollection type, which lives in the  EPiServer.Web.Routing.RouteCollectionExtensions-namespace.

The default route URL to handle Episerver content would be {language}/{node}/{partial}/{action}.

Now, are you wondering how a friendly URL would match such a route? Actually, it wouldn’t, at least not in the traditional ASP.NET MVC pattern. This is also where it may get slightly tricky, throwing the occasional ASP.NET MVC developer for a loop or two. Let’s dig a bit deeper to see what just gets registered to our route table as we inherit from EPiServer.Global.

By attaching an event handler to the Episerver Global class’ RoutesRegistrating event and the RoutesRegistered event we can inspect how our route table changes as Episerver registers its routes. The code written to get a hold of this information is all done in an IInitializableModule.

By inserting breakpoints in each of the events we can see how the route table changes when Episerver adds more routes to the route table.

Here’s how the route table looks before Episerver registers its routes, as we hit a breakpoint in the RoutesRegistrating event.

routes_before

And after, in the RoutesRegistered event:

routes_after

If this had been ASP.NET MVC the route(s) added would be of type Route. In Episerver we see that they’re using their own custom class, ContentRoute, which inherits from the Route class.

However, it doesn’t shed much light on how the default content URL pattern would match a friendly URL. Let’s inspect one of the ContentRoute routes added to the route table.

routes_url

What stands out here is that the Url property of the Episerver ContentRoute class returns a NotImplementedException exception. Normally we would see something like {controller}/{action} in this property if we were registering routes with ASP.NET MVC, which is the URL pattern ASP.NET MVC would try to match.

The reason for the exception in the Url property is due to the fact that Episerver does not match the URL of a HTTP request directly to a URL pattern as, for instance, ASP.NET MVC does. Instead, the way ASP.NET routing works, is that if we were to inherit from the base Route class and override GetRouteData method, then we could pass back null instead of a RouteData object if the route wasn’t valid for the URL — regardless of the URL pattern is a match or not. ASP.NET will continue down the chain of registered routes and try to match the URL if null is returned by the GetRouteData method.

Let’s have a look at this by hooking up to the RoutingContent event of the ContentRoute class and see which routes we hit. The RoutingContent event is raised when Episerver tries route content — regardless if the URL is a match or not. This is also done in an IInitializableModule, as seen below.

Add a breakpoint in the ContentRoute_RoutingContent method, bake for at least a few seconds, then inspect the RoutingEventArgs object (e) and we can have a closer look at what’s going on.

simpleaddress_route

As you can see the first route we hit is the simpleaddress route. Which actually isn’t a match for the requested URL http://localhost:57770/about-us/. If we look at the routes registered again we can see that simpleaddress is the first route (Episerver ContentRoute) registered.

simpleaddress_first

We continue down the chain of registered routes as Episerver passes back null from the GetRouteData method in the ContentRoute class for the simpleaddress route for the current request, as there is no valid simple address for the current FURL (about-us).

Eventually, we reach the pages route in the route table, in which the GetRouteData method of the ContentRoute class returns a RouteData object, having found a piece of content that matches the FURL, effectively telling ASP.NET that we’ve found a match for our route.

routing_pages

This is a key feature of ASP.NET routing which Episerver leverages, adding additional logic in the ContentRoute class to match the FURL by calling into the GetRouteData method, before moving down the chain of registered routes. If the FURL is a match then Episerver will pass back a RouteData object in the GetRouteData method, having matched the FURL to a piece of content in the CMS.

Just how is the FURL picked apart and matched to a route inside the overriden GetRouteData method, then? That’s where the ISegment interface and, perhaps most importantly, the NodeSegment class come into play. In the next blog post we’ll continue down the rabbit’s hole.

Thanks for reading!

More

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

daniel
daniel
Developer
Recommended Posts
  • Daniel Ovaska

    Nice subject! This is probably the trickiest part of EPiServer that also has very little documentation 🙂 Luckily it’s rare you need to touch it…
    Waiting for part 2 …

    • Daniel Berg

      Thanks! Will try to get part two out there as soon as possible!

  • This is the best blog post i have read so far that explains in such clear way. My understanding is the null return to getroute data is the key to make the magic happen.

    • Daniel Berg

      Thanks! Yes, it may seem a little magical, it’s also quite clever by the Episerver engineers. Not to get ahead of myself, but the way it’s done is that Episerver “walks” down the tree structure, trying to match the FURL to a page (starting at at the start page), and will continue doing so until until it’s reached the end of the line. Will be covered in part two! 🙂

  • Fredrik Haglund

    Nice work!

    • Daniel Berg

      Thanks Fredrik!

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