ASP.NET MVC 3 and Custom Extensions

When playing with the latest ASP.NET MVC 3 Preview 1 bits, some people have mentioned their dislike of the .cshtml extension used for the “razor” view engine that comes with ASP.NET MVC 3 and also WebMatrix. Well there are a number of ways you can change this. For the purposes of learning and tinkering, I decided to try and register a new view engine using the new Dependency Injection support within ASP.NET MVC 3.

So, in the Global.asax.cs file I did this:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    IUnityContainer container = new UnityContainer();

    container.RegisterInstance<IControllerFactory>(new UnityControllerFactory(container));
    container.RegisterType<IViewEngine, TestViewEngine>();
   
    UnityMvcServiceLocator svcLocator = new UnityMvcServiceLocator(container);
    MvcServiceLocator.SetCurrent(svcLocator);

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

Note: Previously (and this options is still available), you would register your custom view engine by adding it to the existing Engines collections like so:

ViewEngines.Engines.Add(new TestViewEngine());

My Custom view engine looked like this:

public class TestViewEngine : VirtualPathProviderViewEngine
{
    public TestViewEngine()
    {
        base.AreaViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.glav", "~/Areas/{2}/Views/Shared/{0}.glav" };
        base.AreaMasterLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.glav", "~/Areas/{2}/Views/Shared/{0}.glav" };
        base.AreaPartialViewLocationFormats = new string[] { "~/Areas/{2}/Views/{1}/{0}.glav", "~/Areas/{2}/Views/Shared/{0}.glav" };
        base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.glav", "~/Views/Shared/{0}.glav" };
        base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.glav", "~/Views/Shared/{0}.glav" };
        base.PartialViewLocationFormats = new string[] { "~/Views/{1}/{0}.glav", "~/Views/Shared/{0}.glav" };

    }
    protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
    {
        return new CshtmlView(partialPath, "");
    }

    protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
    {
        return new CshtmlView(viewPath, masterPath);
    }
}

This view engine implementation looked for a view extension of ‘.glav’ and invokes the “razor” view to parse the document/page (CshtmlView)

ASP.NET MVC 3 will use the MvcServiceLocator that we have supplied, and call the ‘GetAllInstances’ method when determining what classes implement IViewEngine so that it can invoke the correct view engine.

By default, ASP.NET MVC 3 has the System.Web.Mvc.WebFormViewEngine and the System.Web.Mvc.CshtmlViewEngine registered. We are adding a new custom view engine to the mix by registering it with the service locator that ASP.NET MVC uses ( MvcServiceLocator ). When ASP.NET MVC goes looking for a view engine it will use the MvcServiceLocator to get all instances of IViewEngine (via a call to ‘GetAllInstances’ ) in order to try and satisfy the request to process/render a particular view.

Now all this theory is good and well, and I thought it would work however it didn’t. One of the issues with Unity (and thats what I was basing my testing on since that was the example provided with ASP.NET MVC 3) is the the ' MvcServiceLocator.GetAllInstances ‘ method, calls the ResolveAll method of the UnityContainer. Now this *only* returns all instances if they have been registered by name, so I had to change one line of my code in the Global.asax.cs from:

container.RegisterType<IViewEngine, TestViewEngine>();

to

container.RegisterType<IViewEngine, TestViewEngine>("test");

and it all worked and my new engine was invoked. Apparently this is known behaviour with Unity and may be changed in future versions, but for now, this is how it works.

Note: There are a few other ways of registering custom view engines and also associating file extensions with a particular view engine. This is just one way of doing it.

No Comments