The Advanced Uses of Razor Views in ASP.NET MVC

In ASP.NET MVC, Razor Views contain the HTML layout and the code that is combined with the data to be displayed in the final HTML. Dino continues his description of Razor Views by describing more advanced uses such as overridable views in multi-tenant applications and in-memory compilation of Razor templates to strings.

A Razor view is essentially just a HTML page, padded with a few C# code snippets, that serves as the template for the markup to serve back to the browser. All the code in the snippets is evaluated when the view gets rendered and the resulting markup is integrated in the HTML template. The Razor engine reads view templates from a physical location on disk, typically under a Views folder. The path is retrieved using the ASP.NET virtual path provider. Because of this internal behavior, Razor view files must be deployed as source files to the hosting server.

The Views folder usually has a number of subfolders, each named after the name of an existing controller. Razor files in each controller-specific sub-directory are named after the action names that can be invoked from controllers. In case you wish it that multiple controllers can invoke the same view, then you move the view template file to the Shared directory.

These are the basic facts about Razor views in ASP.NET MVC. In this article, I’ll focus on some rather more advanced aspects of Razor views such as overridable views in multi-tenant applications and in-memory compilation of Razor templates to strings.

The ViewResultBase Class

The typical use of a Razor view is through the View method of the Controller class. To render out a web view, in fact, you use code as below:

The View method is the endpoint of a rather sophisticated pipeline that finds, at the other end, the selected ASP.NET MVC view engine. Whenever you invoke the method View, an instance of the ViewResult class is created and returned more or less as in the code below.

ViewResult is a framework class that inherits from ActionResult. Like all such classes, ViewResult implements a method called ExecuteResult that is ultimately responsible for generating the result of the action performed through the controller. The full ASP.NET MVC stack provides many ActionResult classes, each of which is responsible for a different type of response for the browser. For example, JsonResult is responsible for packaging up JSON responses and ViewResult is responsible for the plain HTML responses.

It is interesting to note that, in ASP.NET MVC, the production of the data to store in the response and the production of the actual response for the browser are two distinct operations managed by two distinct subsystems. It is even more interesting that note that the same architecture has been maintained intact in ASP.NET Core. The controller is responsible for collecting the actual data to return to the caller; the action invoker is responsible for executing the result of the action. The action invoker calls into the Execute method that all ActionResult types must implement. In the implementation of the ExecuteResult method each ActionResult class populates and configures the response stream as appropriate. For example, the JsonResult class sets the content-type header to application/json and writes the JSON string down to the stream. The ViewResult class, instead, retrieves the matching Razor template, parses and compiles it until it gets the HTML string to return. The next step in the article is now discussing the margin left to developers to intervene in the process and customize it.

Action Taking Place in the View Engine

The ExecuteResult method of the ViewResult class calls into the view engine. Any ASP.NET MVC application can deal any number of view engines. By default, any applications have at least the Razor view engine configured. New engines can be added in lieu of Razor or added on top of it. When multiple engines are registered, they will be contacted to process the view in the order of appearance and search ends at first match. Here’s the pseudo-code for the ExecuteResult method of the ViewResult class.

Without going into too many details, there are two key things going on here. First, the method must retrieve the actual view object (built on top of a view file such as a Razor file). Second, the view must be rendered in a view context. A view context is essentially a container that packages up together all the data you can programmatically access in your Razor code: ViewData, TempData, model and the like.

Finding View Templates in Multi-tenant Applications

A multi-tenant application is an application that serves multiple tenants simultaneously out of a single running instance. Each tenant enjoys a dedicated share of the application data including configuration, users, working data, persistence, graphics and also non-strictly functional attributes such as security roles, logging or caching. A good example of multi-tenant application is WordPress. When you register to WordPress and create a blog, you get your own dedicated HTTP endpoint and can start customizing the content at will as well as the graphical theme and collection of active plugins.

When you create a multi-tenant application with ASP.NET MVC, you typically end up having a single set of controllers that provide the entry points to the entire set of the application’s functions and multiple sets of views—one set of views for each tenant. Why an entire set of views and not simply different CSS files?

Well, that mostly depends on the number of functions and the overall complexity of the application: But realistically, I would say that it is not simply a matter of customizing or replacing a few CSS files. I had the chance to work on a couple of such systems on top of ASP.NET MVC and in both cases we ended up creating a collection of controllers for all possible business-oriented actions that any possible enabled user role could request. Next, we created a hierarchy of subfolders under the systems’ Views folder, one for each tenant. Under that level, we replicated the controller-based list of view folders, as in the figure below.

D:\My Articles\SimpleTalk\2016\11-Views\Fig01.png

The same replication is, realistically, necessary for theme files such as scripts, CSS and images.

When it comes to coding, the job of linking images or stylesheets or even script files to a HTML view is no big deal. All it takes is an algorithm that builds the physical path to the auxiliary file on the host server, taking into account the currently logged user and tenant. It’s a bit more complicated, instead, when it comes to Razor views. Any controller action method that renders out some HTML ends with the following line:

The viewName parameter is the name of the Razor template file to use to produce the final markup. The viewLayoutName parameter is the name of the Razor template for the layout page in which the previous template has to be hosted. Finally, the viewModel parameter refers to the object model that carries the data to be used in the building of the final markup. The viewName parameter is a plain string that the default view engine—the Razor view engine—completes with the CSHTML extension and conventional path information. If viewName equals, say, “index” then the file index.cshtml will be sought only in a few fixed locations. If not found, an internal error exception would be thrown.

As explained in this article I wrote for Simple-Talk a year ago, you can easily extend the set of locations where Razor files will be sought. However, the set of locations is a static attribute of view engines and should be edited only if the all the routes of your application will find it helpful to look for files in an extended set of folders. In our implementation of a multi-tenant application, the team opted for writing a more sophisticated search algorithm to build view paths dynamically rather than statically, by extending the list of the view engine’s known paths.

That ended up in writing a new multi-tenant enabled view engine that supports the concept of overridable views.

Overriding Views in ASP.NET MVC

Here’s the skeleton of the new view engine we put in production.

The idea behind is fairly simple. The ASP.NET MVC infrastructure at the end of it all calls into CreateView for a regular view or CreatePartialView for a partial view. So the team opted for intervening here even though other options would probably exist such as deriving a new type from ViewResult. In the end, the approach presented here appeared by far the most direct and easy to figure out. Here is the implementation of CreateView and CreatePartialView.

The IsOverridableView method serves an internal purpose—distinguishing between views that tenants can modify from common views that stay the same for each tenant. The method just checks the view name against a static list of overridable views.

The GetOverriddenViewPath method does the magic of turning the default path of the view—the path generated by the default Razor view engine—into the actual path that takes into account the current tenant and its configuration. It is important that both CreateView and CreatePartialView receive from the ASP.NET MVC infrastructure not simply the name of the view file but the relative disk path to it. The path points to a disk location under the root system’s Views folder.

In the GetOverriddenViewPath method, you implement the business logic you need to locate the right view for the tenant. Through the controller context parameter you gain full access to the HTTP context and can check, for example, the query string of the current URL. In one of the multi-tenant applications I worked on, we used an extra query string parameter to let the system know whether the view was hosted in an IFRAME which, in turn, meant that the background color of the view had to be transparent.

This is to say that within the view engine you still have access to the entire set of information of the current HTTP request and still have the power to use that information to decide the path to the view and pass extra data to the view itself for rendering purposes. The HTTP context is relevant because most of the time it provides the information about the current tenant. The output of GetOverriddenViewPath is a plain string that identifies the physical server path (relative to Views) of the Razor template to use.

The nice thing about this approach is that you don’t have to change anything at the controller level. Your controllers will still point to views as if the system was a single-tenant application. You might want to keep default views in the regular location under Views and let tenants to provide their own views by creating new files in specific locations. How this could happen, though, is a different story. You probably need some nontrivial admin backoffice subsystem!

In-memory Compilation of Razor Views

How can I programmatically access the markup generated for a view and use it as a string? Even though there are not many realistic scenarios where you just want to programmatically check the HTML being sent out, this has been a pending question since the early days of Razor. The way to do that is widely known and popularized by many Q&A sites, most notably StackOverflow.

Before I present the few lines of code, though, I reckon even more important to illustrate a use-case for in-memory compilation of Razor views. I used that feature for quickly processing HTML templates for programmatic emails. All I do is to have a set of dedicated partial views that I can comfortably fill out with data using common programming techniques. At some point, though, you need a HTML string to pass to .NET Framework email senders or external emailing services. How can you compile a Razor view and its dynamic data into a string? Note that when the output of a view is sent to the browser, compilation takes place outside of your control, and after the call to CreateView in the view engine. Here’s the basic code you need, written as an extension method for the Controller class.

To use it, here’s what you do:

Summary

The moral of the story here is that 90 percent of the time when writing an ASP.NET MVC application you don’t need to leverage more than just the default features of views. However, the entire subsystem is extremely powerful and well designed in terms of customization so that you can easily implement some rather advanced capabilities. And it will stay the same (or even better) in ASP.NET Core.