Multiple Views with MVC

In my previous two posts (one and two) discussing the use of AJAX within an ASP.NET MVC Framework application, I’ve tried to demonstrate some ways that the framework can be extended or modified. In keeping with this approach, I thought I’d show how it is possible to change the way that Views are resolved using a “View Factory”. Again, this is much more about the demonstration than the subject matter: i.e. AJAX is incidental to seeing how to use the MVC framework. Please read the previous posts if you want to follow the specific example I’m using here.

Usual disclaimers apply – and this is based on pre-release software that will no doubt change, and therefore this post will likely become outdated.

The key to this post, then, is that instead of using a single View that behaves differently according to whether or not AJAX is supported, I want to have two separate Views – one for AJAX, one for without.

To achieve this is pretty simple.

1. We need to create our own implementation of IViewFactory. This is responsible for locating and creating an instance of an IView (which both ViewPage and ViewUserControl implement).

2. To “inject” (all you DI fans excuse me borrowing the term without using a DI framework) our new View Factory into every Controller we are going to create our own IControllerFactory implementation.

3. We need to configure the framework to use our new Controller Factory.

4. Finally we can create two Views – an AJAX version and a pure HTML version.

Easy huh? Lets fly through the implementation of each of these...

View Factory

To minimise my effort I’ve sub-classed the WebFormViewFactory;

public class AjaxViewFactory : WebFormViewFactory

{

    protected override IView CreateView(

        ControllerContext controllerContext,

        string viewName,

        string masterName, object viewData)

    {

        string mode = controllerContext.RouteData.Values["mode"] as string;

        IView view = null;

        try

        {

            view = base.CreateView(controllerContext, viewName + "-" + mode,

                   masterName, viewData);

        }

        catch (InvalidOperationException)

        {

            if (view == null)

                view = base.CreateView(controllerContext, viewName,

                       masterName, viewData);

            else

                throw;

        }

        return view;

    }

}

Now I should imagine you’re all screaming now (and no, not because I don’t check “mode” isn’t null – this is demo code J). Truth is, I’d like the implementation of this to be different – perhaps by overriding WebFormViewFactory.GetTypeFromName, but some of the framework’s members are private and not virtual... hence my hack.

Basically all we do here is append the value of “mode” (which is “ajax” or “html”) to the end of the view name. So a request for a “List” view will return “List-ajax” or “List-html”. If we can’t find a View by this name, we fall back to the version without a suffix. [Important: The catching of exceptions in this way as effectively anticipated business logic is strongly not recommended – this is the ugly code I was referring to. This is hard to maintain and could easily become a performance hit if you have lots of Views without a suffix, so in production code I would put the effort in to handle this properly – I haven’t here simply to save space and complexity] . I then reuse the standard functionality of the WebFormViewFactory to do the hard work of creating an instance.

This should make it pretty obvious how you could change this behaviour to your heart’s content. There is no reason why you couldn’t completely remove any remnant of the WebFormViewFactory – as long as you return an instance of an IView you’re away!

Controller Factory

The Controller Factory is almost embarrassingly simple;

public class AjaxControllerFactory : IControllerFactory

{

    public IController CreateController(RequestContext context,

                                        Type controllerType)

    {

        Controller controller =

                  (Controller)Activator.CreateInstance(controllerType);

        controller.ViewFactory = new AjaxViewFactory();

        return controller;

    }

}

All we do here is create a new instance of our Controller, and make sure we set the View Factory to be an instance of our new class. The key to getting our new Controller Factory to be used instead of the default factory is a single call in the Application_Start event of Global.asax;

ControllerBuilder.Current.SetDefaultControllerFactory(

                          typeof(AjaxControllerFactory));

And that’s it.

Views

To get my Views working I took the existing “People.aspx” View, and copied it twice to “People-ajax.aspx” and “People-html.aspx”. I then removed the if statement below;

<% if (ViewData.EnableAjax)

   { %>

   <%= Ajax.UpdateRegionLink<AjaxSampleController>(d =>

              d.UpdatePerson(emp.Id), "Individual", emp.Name) %>

 <%}

  else

   { %>

     <%= Html.ActionLink(emp.Name, new { action = "ViewPeople",

                                         id = emp.Id })%>

<% } %>

... and instead left the relevant section in each View – i.e. the Ajax call in the “–ajax” view, and Html call in “-html” view.

Round-up

Well folks, that’s all. I hope that’s a pretty clear demonstration of how easy it is to plug into the MVC pipeline. If you’re interested in IViewFactory specifically, head on over to MVCContrib – looks like some View Factories are in the code base.

 

MvcAjax.zip