Developer Forum »
Friendly MVC URLs
42 posts

For anyone that might be interested: Here is a quick guide to get friendly URLs working with MVC, exactly as they were working with Webforms.

There are times where you would want the old format back, e.g. /some-friendly-url, instead of /Controller/some-friendly-url. The latter format may be inconveniently long, or one may simply not feel the need to perform any natural categorization of a node through a specific controller in the address field. For whatever the reason and despite whatever philosophical MVC objections you might have against handling URLS in this manner, here is the recipe:

Presumably, when a URL is first requested, Webnode's HttpModule is called. The content (if any) is then fetched. Control is then passed to MVC's routing module. Thus, we may do whatever we want to MVC's routing module without interfering with Webnodes.

Here's a routing configuration that is backwards-compatible with Visual Studio's MVC routing configuration:

routes.MapRoute
(
"FriendlyUrl",
"{name}",
new { controller = "FriendlyUrl", action = "Index", name = UrlParameter.Optional }
);

routes.MapRoute
(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Frontpage", id = UrlParameter.Optional } // Parameter defaults
);

Add a new controller factory above Application_Start() to redirect to a suitable controller based on content type, using reflection:

class ControllerFactory : DefaultControllerFactory
{
public override IController CreateController(RequestContext requestContext, string controllerName)
{
return base.CreateController(requestContext, controllerName == "FriendlyUrl" ? WAFContext.Request.GetContent().GetType().Name : controllerName);

}


protected override Type GetControllerType(RequestContext requestContext, string controllerName)

{

return base.GetControllerType(requestContext, controllerName == "FriendlyUrl" ? WAFContext.Request.GetContent().GetType().Name : controllerName);

}
}

Set your new controller factory in Application_Start():

ControllerBuilder.Current.SetControllerFactory(new ControllerFactory());

From now on, you must always name your controller according to the following syntax: <node-type>Controller (e.g. ArticleController).

When you return a view, you must always include the parameter which specifies the view-path, e.g.:

return View("~/Views/Article/Index.cshtml", WAFContext.Request.GetContent<ArticleBase>());

The last thing to do is to add the MVC template in Webnodes and associate it with all the relevant node types. Route name: FriendlyUrl, Address parameter: name. Leave the other fields empty.

42 posts

-Emil (emil@oxx.no).

120 posts

Thanks for posting this!

Ole

62 posts

This works fine, Emil. Thank you :)

I just added some extra logic in the controller in cases where I have nodes of the same contenttype, but different views.

42 posts

Not sure if this is applicable, but the FriendlyUrl routing rule will match everything, even friendly urls that don't exist. Not sure if webnodes overrides this, but you may need to return a HttpNotFound() somewhere in your code if Session.GetContent() throws an exception (non-existing friendly-url). Please correct me if I'm wrong.

-Emil :)

42 posts

Here is version 2 of the controller factory (tested on MVC 4, MVC 3 may require minute changes). This eliminates the need to specify the full view path. The entire operation should be completely transparent now...

 

class ControllerFactory : DefaultControllerFactory

{

    public override IController CreateController(RequestContext requestContext, string controllerName)

    {

        if (controllerName == "FriendlyUrl") controllerName = requestContext.RouteData.Values["controller"] = WAFContext.Request.GetContent().GetType().Name;

        return base.CreateController(requestContext, controllerName);

    }

 

    protected override Type GetControllerType(RequestContext requestContext, string controllerName)

    {

        if (controllerName == "FriendlyUrl") controllerName = requestContext.RouteData.Values["controller"] = WAFContext.Request.GetContent().GetType().Name;

        return base.GetControllerType(requestContext, controllerName);

    }

}

1