ASP.Net MVC Extreme – A Deep Dive Inside View Engines

I am back with another post in the ASP.Net MVC extreme series (as promised)! In this post lets dive into the internals of how a view is selected when an action method returns a View. MVC has the following default view engies – webform and razor and also lets us to add custom view engines. For more inforation on custom view engines, refer to my earlier post here. Custom view engines can be added using the Application_Start event as shown below:

protected void Application_Start()
{
   AreaRegistration.RegisterAllAreas();

   ViewEngines.Engines.Clear();
   ViewEngines.Engines.Add(new CustomRazorViewEngine());

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

In the above snippet, ViewEngines is a static class provided by the framework in order to manage the view engies that the framework would look for when searching for views to render. The entire action starts with the ViewResultBase class, which is the base class ViewResult which is responsible for rendering html content to the client. There are a number of other result’s such as FileResult, JsonResult, JavaScriptResult etc and all of these derive from the ActionResult class. ViewResultBase has the ViewEngineCollection property and it’s defined as given below. The engines set up in Application_Start is provided to the ViewResult class using this property. More information on how these engines are used follows soon!

// From mvc sources/reflector
public ViewEngineCollection ViewEngineCollection {
   get {
      return _viewEngineCollection ?? ViewEngines.Engines;
   }
   set {
      _viewEngineCollection = value;
   }
}

A simple action method in mvc would resemble the following:

public ActionResult SomeActionMethod()
{
   return View();
}

The action method above returns the result of the View method from the Controller class. This method returns a ViewResult instance depending upong the parameters passed from the action method. Note that it could also be a PartialViewResult instance. The Controller type has multiple overloads of the View method. Their definitions are given below:

// From mvc sources/reflector
protected internal ViewResult View() {
    return View(null /* viewName */, null /* masterName */, null /* model */);
}

protected internal ViewResult View(object model) {
    return View(null /* viewName */, null /* masterName */, model);
}

protected internal ViewResult View(string viewName) {
    return View(viewName, null /* masterName */, null /* model */);
}

protected internal ViewResult View(string viewName, string masterName) {
    return View(viewName, masterName, null /* model */);
}

protected internal ViewResult View(string viewName, object model) {
    return View(viewName, null /* masterName */, model);
}

All of these overloads eventually call the following method:

// From mvc sources/reflector
protected internal virtual ViewResult View(string viewName, string masterName, object model) {
    if (model != null) {
	ViewData.Model = model;
    }

    return new ViewResult {
	ViewName = viewName,
	MasterName = masterName,
	ViewData = ViewData,
	TempData = TempData
    };
}

Similarly, the following method takes in an IView and has the following definition:

// From mvc sources/reflector
protected internal ViewResult View(IView view) {
   return View(view, null /* model */);
}

This method eventually calls the following method:

// From mvc sources/reflector
protected internal virtual ViewResult View(IView view, object model) {
    if (model != null) {
	ViewData.Model = model;
    }

    return new ViewResult {
	View = view,
	ViewData = ViewData,
	TempData = TempData
    };
}

If you notice the implementations of View, it creates an instance of ViewResult by passing the view name, master name, view data and temp data in the case of the first method and an IView object, view data and temp data in the case of the other method.

Coding best practices(feel free to skip this if you are aware of coding best practices): When you have a method that also has corresponding overloads, make sure that you don’t repeat the same code in both (or all) the places. Consider the following case. This method of handling overloads is bad!.

// bad practice, do not use!!!
public void FooMethod(int memberForYears, string userName)
{
   int totalDaysMemberFor = memberForYears * 365; // ignoring the fact about leap years
   int _membershipPoints = GetMembershipPoints();
   Console.WriteLine("User {0} was a member for {1} days and has {2} points", userName, totalDaysMemberFor, _membershipPoints);
}

public void FooMethod(int memberForYears, string userName, int membershipPoints)
{
   int totalDaysMemberFor = memberForYears * 365; // ignoring the fact about leap years
   int _membershipPoints = membershipPoints;
   Console.WriteLine("User {0} was a member for {1} days and has {2} points", userName, totalDaysMemberFor, _membershipPoints);
}

A nicer way to do this is given below. The refactored code below does not have repetitive code but utilizes the final method to display the results to the user. If you observe the above 2 methods, the only difference is the _membershipPoints bit which gets the value using the GetMembershipPoints method (not shown). So in the snippet below, the 1st overload is called by passing in the value from the method as the 3rd parameter.

// good practice
public void FooMethod(int memberForYears, string userName)
{
   FooMethod(memberForYears, userName, GetMembershipPoints());
}

public void FooMethod(int memberForYears, string userName, int membershipPoints)
{
   int totalDaysMemberFor = memberForYears * 365; // ignoring the fact about leap years
   int _membershipPoints = membershipPoints;
   Console.WriteLine("User {0} was a member for {1} days and has {2} points", userName, totalDaysMemberFor, _membershipPoints);
}

Before we dig in to the ViewResult class lets get a deeper understanding of the how this class is put in to use.

If you can recollect from my previous post, every controller in mvc implements the IController interface and this has the Execute method. When a request arrives, the Execute method is called. Within this method a call to the ExecuteCore method of the Controller is invoked. The ExecuteCode method then handles the responsibility to the ControllerActionInvoker.InvokeActionResult method. Within this method the ExecuteResult method of the ViewResultBase class is called, after executing the content of the action method according to the request. This method itself certainly deserves a post by the way! After this step the real action starts!!! Lets get in to that :)

The ExecuteResult method is given below:

// From mvc sources/reflector
public override void ExecuteResult(ControllerContext context) {
    if (context == null) {
	throw new ArgumentNullException("context");
    }
    if (String.IsNullOrEmpty(ViewName)) {
	ViewName = context.RouteData.GetRequiredString("action");
    }

    ViewEngineResult result = null;

    if (View == null) {
	result = FindView(context);
	View = result.View;
    }

    TextWriter writer = context.HttpContext.Response.Output;
    ViewContext viewContext = new ViewContext(context, View, ViewData, TempData, writer);
    View.Render(viewContext, writer);

    if (result != null) {
	result.ViewEngine.ReleaseView(context, View);
    }
}

Note line 13 in the above snippet. This line calls the FindView overridden in the ViewResult (and PartialViewResult) class(es) to find a matching view. This method, uses the ViewEngineCollection in the ViewResultBase class to get a list of the available view engines. And then ViewEngineCollection type’s FindView method is called to find an appropriate view. This is where the engines added come in to play. The ViewEngineCollection.FindView method is given below:

// From mvc sources/reflector
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName) {
    if (controllerContext == null) {
	throw new ArgumentNullException("controllerContext");
    }
    if (string.IsNullOrEmpty(viewName)) {
	throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
    }

    return Find(e => e.FindView(controllerContext, viewName, masterName, true),
		e => e.FindView(controllerContext, viewName, masterName, false));
}

The Find method called in line 10 uses the engines defined for the application to find an appropriate view. To explore more in to this method, it’s important to see the ViewEngines class.

// From mvc sources/reflector
public static class ViewEngines {
	private readonly static ViewEngineCollection _engines = new ViewEngineCollection {
	    new WebFormViewEngine(),
	    new RazorViewEngine(),
	};

	public static ViewEngineCollection Engines {
	    get {
		return _engines;
	    }
	}
}

ViewResultBase class’s ViewEngineCollection property is set to the ViewEngines.Engines property, which is initialized with an instance of the ViewEngineCollection class created by passing the 2 default view engines available in mvc 3. If you remember (yes it’s been a long post ;) ) we use the ViewEngines.Engines property to add custom view engines. The Find method uses the engines set during instantiation to find an appropriate view. The view engine collection would either have the default engines or the custom engines or both. If this process fails, you see the generic error screen (given below) that lists all the locations searched by the framework.

As you may know, the action starts from the controller. Refer to my earlier post for more details about this. In a future post I will also try to connect the dots between these 2 posts, if I feel anything is missing. Also, I am reviewing this post to catch anything I missed, so it may be updated in the following days!

Finally, do post your comments and happy coding!

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>