MVC Greedy route mapping and custom route handler
By eidias on (tags: mvc, categories: code)I’m learning to love MVC mostly for it’s flexibility. In a recent situation, I wanted to have a generic controller that will perform CRUD (Create Read Update Delete) operations on an entity. By default, this is not handled, but with a little custom code, it turned out to be quite easy.
Let me describe the use case first. The idea is to have a controller (let’s call it Foo) that looks more or less like this:
1: public class FooController<T> : ControllerBase where T: Bar
2: {
3: public ActionResult Edit()
4: {
5: //...
6: }
7:
8: public ActionResult Edit(T foo)
9: {
10: //...
11: }
12: }
The problem here was, that out of the box, this is not supported. I’d have to create an separate controller for each T and as in my case, the T was out of my control, and thus that was not an option.
So, how to handle that? Well, there are 2 things to be utilized here: greedy route mapping and custom route handler. First, here’s one of the ways to hook it all up:
Global.asax.cs
1: routes.MapRoute(
2: "Foo",
3: "Foo/{action}/{*path}",
4: new { controller = "Foo", action = "Index" }
5: ).RouteHandler = new FooRouteHandler();
Nothe the * in the path url section. What it does is that it grabs the whole url part that follows “Foo/{action}”. So in case of “Foo/Edit/bar/buz/bleach” the path part is resolved to “bar/buz/bleach” whereas without the * it would have been resolved to “bar” and the routing handler would continue to search for the remaining parts (buz and bleach would need to be mapped to something).
The other interesting thing here is the FooRouteHandler and here’s how it looks like:
1: public class FooRouteHandler : IRouteHandler
2: {
3: public IHttpHandler GetHttpHandler(RequestContext requestContext)
4: {
5: return new FooHttpHandler(requestContext);
6: }
7: }
And the FooHttphandler:
1: public class FooHttpHandler : IHttpHandler
2: {
3: public bool IsReusable { get { return false; } }
4:
5: private RequestContext RequestContext { get; set; }
6:
7: public FooHttpHandler(RequestContext requestContext)
8: {
9: RequestContext = requestContext;
10: }
11:
12: public void ProcessRequest(HttpContext context)
13: {
14: var path = (string) RequestContext.RouteData.Values["path"];
15: var pages = DependencyResolver.Current.GetService<IPageRepository>();
16:
17: var page = pages.GetSingleOrDefault(p => p.Path == path);
18: var pageType = page == null ? typeof(Page) : page.GetType();
19: var fooControllerType = typeof (FooController<>);
20:
21: var controller = (IController) DependencyResolver.Current.GetService(fooControllerType.MakeGenericType(pageType));
22: controller.Execute(RequestContext);
23: }
24: }
(yes, you guessed right, it’s a cms-ish framework)
Note that I am able utilize this mechanism, because I can determine the page type based on the url (or in general based on anything that comes along with HttpContext) – it would be hard to accomplish this otherwise.
Cheers