Advanced View Engines

Scott Hanselman, community program manager at Microsoft, likes to call the view engine “just an angle bracket generator.” In the simplest terms, that's exactly what it is. A view engine will take an in-memory representation of a view and turn it into whatever other format you like. Usually, this means that you will create a cshtml file containing markup and script, and ASP.NET MVC's default view engine implementation, the RazorViewEngine, will use some existing ASP.NET APIs to render your page as HTML.

View engines aren't limited to using cshtml pages, nor are they limited to rendering HTML. You'll see later how you can create alternate view engines that render output that isn't HTML, as well as unusual view engines that require a custom DSL (domain-specific language) as input.

To better understand what a view engine is, let's review the ASP.NET MVC life cycle (very simplified in Figure 15.8).

A lot more subsystems are involved than Figure 15.8 shows; this figure just highlights where the view engine comes into play — which is right after the Controller action is executed and returns a ViewResult in response to a request.

It is very important to note here that the controller itself does not render the view; it simply prepares the data (that is, the model) and decides which view to display by returning a ViewResult instance. As you saw earlier in this chapter, the Controller base class contains a simple convenience method, named View, used to return a ViewResult. Under the hood, the ViewResult calls into the current view engine to render the view.

Configuring a View Engine

As just mentioned, it's possible to have alternative view engines registered for an application. View engines are configured in Global.asax.cs. By default, there is no need to register other view engines if you stick with just using RazorViewEngine (and the WebFormViewEngine is also registered by default).

However, if you want to replace these view engines with another, you could use the following code in your Application_Start method:

protected void Application_Start() {
 ViewEngines.Engines.Clear();
 ViewEngines.Engines.Add(new MyViewEngine());
 //Other startup registration here
}

Engines is a static ViewEngineCollection used to contain all registered view engines. This is the entry point for registering view engines. You need to call the Clear method first because RazorViewEngine and WebFormViewEngine are included in that collection by default. Calling the Clear method is not necessary if you want to add your custom view engine as another option in addition to the default one, rather than replace the default view engines.

In most cases, though, it's probably unnecessary to register a view engine manually if it's available on NuGet. For example, to use the Spark view engine, after creating a default ASP.NET MVC 4 project, simply run the NuGet command Install-Package Spark.Web.Mvc. This adds and configures the Spark view engine in your project. You can quickly see it at work by renaming Index.cshtml to Index.spark. Change the markup to the following to display the message defined in the controller:

<!DOCTYPE html>
<html>
<head>
   <title>Spark Demo</title>
</head>
<body>
   <h1 if="!String.IsNullOrEmpty(ViewBag.Message)">${ViewBag.Message}</h1>
   <p>
       This is a spark view.
   </p>
</body>
</html>

The preceding snippet shows a very simple example of a Spark view. Notice the special if attribute, which contains a Boolean expression that determines whether the element it's applied to is displayed. This declarative approach to controlling markup output is a hallmark of Spark.

Finding a View

The IViewEngine interface is the key interface to implement when building a custom view engine:

public interface IViewEngine {
   ViewEngineResult FindPartialView(ControllerContext controllerContext,
       string partialViewName, bool useCache);
   ViewEngineResult FindView(ControllerContext controllerContext, string viewName,
       string masterName, bool useCache);
   void ReleaseView(ControllerContext controllerContext, IView view);
}

With the ViewEngineCollection, the implementation of FindView iterates through the registered view engines and calls FindView on each one, passing in the specified view name. This is the means by which the ViewEngineCollection can ask each view engine if it can render a particular view.

The FindView method returns an instance of ViewEngineResult, which encapsulates the answer to the question, “Can this view engine render the view?” (See Table 15.1.)

Table 15.1 ViewEngineResult Properties

Property Description
View Returns the found IView instance for the specified view name. If the view could not be located, it returns null.
ViewEngine Returns an IViewEngine instance if a view was found; otherwise, it returns null.
SearchedLocations Returns an IEnumerable<string> that contains all the locations that the view engine searched.

If the IView returned is null, the view engine was not able to locate a view corresponding to the view name. Whenever a view engine cannot locate a view, it returns the list of locations it checked. Typically, these are file paths for view engines that use a template file, but they could be something else entirely, such as database locations for view engines that store views in a database. These location strings are opaque to MVC itself; it uses them only to display a helpful error message to the developer.

Note that the FindPartialView method works in the same way as FindView, except that it focuses on finding a partial view. It is quite common for view engines to treat views and partial views differently. For example, some view engines automatically attach a master view (or layout) to the current view by convention. It's important for that view engine to know whether it's being asked for a full view or a partial view; otherwise, every partial view might have the master layout surrounding it.

The View Itself

The IView interface is the second interface you need to implement when implementing a custom view engine. Fortunately, it is quite simple, containing a single method:

public interface IView {
   void Render(ViewContext viewContext, TextWriter writer);
}

Custom views are supplied with a ViewContext instance, which provides the information that might be needed by a custom view engine, along with a TextWriter instance. The view is expected to consume the data in the ViewContext (such as the view data and model) and then call methods of the TextWriter instance to render the output.

Chapter 15-2 lists the properties exposed by ViewContext.

ViewContext Properties

Property Description
HttpContext An instance of HttpContextBase, which provides access to the ASP.NET intrinsic objects, such as Server, Session, Request, Response.
Controller An instance of ControllerBase, which provides access to the controller, making the call to the view engine.
RouteData An instance of RouteData, which provides access to the route values for the current request.
ViewData An instance of ViewDataDictionary containing the data passed from the controller to the view.
TempData An instance of TempDataDictionary containing data passed to the view by the controller in a special one-request-only cache.
View An instance of IView, which is the view being rendered.
ClientValidationEnabled Boolean value indicating whether client validation has been enabled for the view.
FormContext Contains information about the form, used in client-side validation.
FormIdGenerator Allows you to override how forms are named (“form0”-style by default).
IsChildAction Boolean value indicating whether the action is being displayed as a result of a call to Html.Action or Html.RenderAction.
ParentActionViewContext When IsChildAction is true, contains the ViewContext of this view's parent view.
Writer HtmlTextWriter to use for HTML helpers that don't return strings (that is, BeginForm), so that you remain compatible with non-Web Forms view engines.
UnobtrusiveJavaScriptEnabled This property determines whether an unobtrusive approach to client validation and Ajax should be used. When true, rather than emitting script blocks into the markup, HTML 5 data-* attributes are emitted by the helpers, which the unobtrusive scripts use as a means of attaching behavior to the markup.

Not every view needs access to all these properties to render a view, but it's good to know they are there when needed.

Alternative View Engines

When working with ASP.NET MVC for the first time, you're likely to use the view engine that comes with ASP.NET MVC: the RazorViewEngine. The many advantages to this include that it:

  • Is the default
  • Has clean, lightweight syntax
  • Has layouts
  • Has HTML encoded by default
  • Has support for scripting with C#/VB
  • Has IntelliSense support in Visual Studio

There are times, however, when you might want to use a different view engine — for example, when you:

  • Want to use a different language, such as Ruby or Python
  • Render non-HTML output, such as graphics, PDFs, RSS, and the like
  • Have legacy templates using another format

Several third-party view engines are available at the time of this writing. Table 15.3 lists some of the more well-known view engines, but there are likely many others we've never heard of.

Table 15.3 View Engines Properties

View Engine Description
Spark Spark (http://sparkviewengine.com/) is the brainchild of Louis DeJardin (now a Microsoft employee) and is being actively developed with support for both MonoRail and ASP.NET MVC. It is of note because it blurs the line between markup and code using a very declarative syntax for rendering views. Spark continues to add innovative features, including recent support for the Jade templating language first popularized on Node.js.
NHaml NHaml (hosted on GitHub at https://github.com/NHaml/NHaml), created by Andrew Peters and released on his blog in December 2007, is a port of the popular Ruby on Rails Haml View engine. It's a very terse DSL used to describe the structure of XHTML with a minimum of characters.
Brail Brail (part of the MvcContrib project, http://mvccontrib.org) is interesting for its use of the Boo Language. Boo is an object-oriented statically typed language for the CLR with a Python language style to it, such as significant white space.
StringTemplate StringTemplate (hosted at Google code, http://code.google.com/p/string-template-view-engine-mvc) is a lightweight templating engine that is interpreted rather than compiled. It's based on the Java StringTemplate engine.
NVelocity NVelocity (http://www.castleproject.org/others/nvelocity) is an open source templating engine and a port of the Apache/Jakarta Velocity project, built for Java-based applications. The NVelocity project did quite well for a few years, until 2004, when check-ins stopped and the project slowed down.
Nustache Nustache (https://github.com/jdiamond/Nustache) is a .NET implementation of the popular Mustache templating language (so named because it uses curly braces that look like sideways mustaches). Mustache is known for being a “logic-less” templating system because it intentionally doesn't support any control flow statements. The Nustache project includes an MVC view engine.
NDjango NDjango (http://ndjango.org) is an implementation of the Django template language on the .NET platform, using the F# language.
Parrot Parrot ((http://thisisparrot.com) is an interesting new view engine with a CSS-inspired view syntax, good support for enumerables and nested objects, and an extensible rendering system.

New View Engine or New ActionResult?

We are often asked when someone should create a custom view engine as opposed to a new ActionResult type. For example, suppose that you want to return objects in a custom XML format. Should you write a custom view engine or a new MyCustomXmlFormatActionResult?

The general rule of thumb for choosing between one and the other is whether it makes sense to have some sort of template file that guides how the markup is rendered. If there's only one way to convert an object to the output format, then writing a custom ActionResult type makes more sense.

For example, the ASP.NET MVC Framework includes a JsonResult, which serializes an object to JSON syntax. In general, there's only one way to serialize an object to JSON. You wouldn't change the serialization of the same object to JSON according to which action method or view is being returned. Serialization is generally not controlled via a template.

However, suppose that you wanted to use XSLT to transform XML into HTML. You may have multiple ways to transform the same XML into HTML, depending on which action you're invoking. In this case, you would create an XsltViewEngine, which uses XSLT files as the view templates.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset