首页 > 代码库 > A Look at the Razor View Engine in ASP.NET MVC

A Look at the Razor View Engine in ASP.NET MVC

The biggest architectural difference that exists between ASP.NET MVC and ASP.NET Web Forms is the neat separation between the two key phases of processing the request and generating the response. In general, rendering an ASP.NET Web Forms page means looping through the page’s server controls and iteratively have each control add its own markup to a buffer so as to ensure that the results form a HTML-compliant string. In ASP.NET Web Forms, the rendering process is rooted on the page as a resource and server controls play a central role. For each request, server controls are used to read the input and render the output. Server controls are live instances that have the same lifecycle as the request.

The internal architecture of ASP.NET MVC is quite different in the way requests are processed. Not only  is each request mapped to a controller object, but The controller does anything necessary to produce the raw data that will be served back to the requesting browser. Any controller method ends with a call to an internal method that just packages the raw data into the preferred format, be it HTML, JSON, binary, or plain text. Architecturally speaking, this final step implies the presence of a separate layer-the generator of the ActionResult type. When it comes to generating HTML, the action result generator gets the more familiar name of the view engine.

The view engine works by mixing together a view template and raw data to be displayed in the final HTML. The template is expressed in an engine-specific markup language; the data is passed in by the invoking controller method packaged in dictionaries or in strongly-typed objects. An ASP.NET MVC application can have one or more view engines, which means that different views of an application can be expressed using different markup languages. The default ASP.NET MVC project supports two view engines;  Razor and WebForms. More likely, though, you have each application using just one view engine and one markup language. Of the two, Razor is by far the most concise, elegant and effective markup language.

Razor View Engine

In Razor, a view template is essentially a HTML page with a few placeholders, often referred to as code nuggets. Each placeholder contains an executable expression-much like a code snippet. The code in the snippets, which can be  C# or VB,   is evaluated when the view gets rendered and resulting markup is integrated in the HTML template. The Razor engine reads view templates from a physical location on disk. The path is retrieved using the ASP.NET virtual path provider.

The location of view templates is rooted in the Views folder. The Views folder has a number of subfolders-each named after an existing controller. Each controller-specific directory contains physical files whose name is expected to match the name of a view invoked from the controller. The extension has to be .cshtml for the Razor view engine. If you’re writing your ASP.NET MVC application in Visual Basic, then the extension must be .vbhtml. ASP.NET MVC also requires that you place each view template under the directory of the controller that uses it. In case multiple controllers are expected to invoke the same view, then you move the view template file under the Shared directory.

It is important to note that the same hierarchy of directories that exists at the project level under the Views folder must be replicated on the production server when you deploy the site. The Razor view engine defines a few properties through which you can control how view templates are located. For the internal working of the Razor view engine, it is necessary to provide a default location for master, regular and partial views both in a default project configuration and when areas are used.

Table 1 shows the location properties supported by the Razor view engine with the predefined value. 

Property

Default location format

AreaMasterLocationFormats

~/Areas/{2}/Views/{1}/{0}.cshtml

~/Areas/{2}/Views/Shared/{0}.cshtml

~/Areas/{2}/Views/{1}/{0}.vbhtml

~/Areas/{2}/Views/Shared/{0}.vbhtml

AreaPartialViewLocationFormats

~/Areas/{2}/Views/{1}/{0}.cshtml

~/Areas/{2}/Views/{1}/{0}.vbhtml

~/Areas/{2}/Views/Shared/{0}.cshtml

~/Areas/{2}/Views/Shared/{0}.vbhtml

AreaViewLocationFormats

~/Areas/{2}/Views/{1}/{0}.cshtml

~/Areas/{2}/Views/{1}/{0}.vbhtml

~/Areas/{2}/Views/Shared/{0}.cshtml

~/Areas/{2}/Views/Shared/{0}.vbhtml

MasterLocationFormats

~/Views/{1}/{0}.cshtml

~/Views/Shared/{0}.cshtml

~/Views/{1}/{0}.vbhtml

~/Views/Shared/{0}.vbhtml

PartialViewLocationFormats

~/Views/{1}/{0}.cshtml

~/Views/{1}/{0}.vbhtml

~/Views/Shared/{0}.cshtml

~/Views/Shared/{0}.vbhtml

ViewLocationFormats

~/Views/{1}/{0}.cshtml

~/Views/{1}/{0}.vbhtml

~/Views/Shared/{0}.cshtml

~/Views/Shared/{0}.vbhtml

FileExtensions

.cshtml, .vbhtml

Table 1.  The default location formats of the Razor view engine.

As you can see, locations are not fully qualified paths but contain up to three placeholders. The placeholder {0} refers to the name of the view, as it is being invoked from the controller method. The placeholder {1} refers to the controller name as it is used in the URL. Finally, the controller {2}, if specified, refers to the area name.

I’ve already mentioned that an ASP.NET MVC application has two view engines registered by default. This means that you can have views expressed in either format.  As long as each view can be resolved unambiguously, everything works just fine. For every view or partial view that is necessary to render, registered view engines are given a chance to process the request in the order in which they are registered. Note that the ASPX engine always takes precedence over the Razor engine. This means that if you happen to have two view files such as default.aspx and default.cshtml the former will be picked up. At any rate, you can modify the order (and number) of registered view engines in global.asax just when the application starts up. Here’s a sample implementation of Application_Start that removes the ASPX engine that only supports the Razor engine.

 

 
protected void Application_Start()
{
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new RazorViewEngine());
}

 

View engines are referenced via the ViewEngines.Engines collection. In the sample code, you first clear out the collection and then add just the engine instance that you’re going to use. Using a single view engine is preferable for consistency reasons, but nothing really prevents you from mixing view engines and view files in your project.

Razor Nuggets

I’ve already described  code nuggets as being similar to ASP.NET code blocks but with a simpler and terser syntax. You denote the start of a Razor code block with a @ character. More importantly, you don’t need to close those blocks explicitly. The Razor parser uses the C# parsing logic to figure out where a line of code finishes. Here’s a first basic example of a code nugget.

 

 
<h2>@ViewBag.Header</h2>

 

Nuggets can be used in loops to generate for example the rows of a HTML table, as below:

 

 
<table>    
@foreach (var person in Model.People) {
    <tr>
       <td><%: person.Name %></td>
       <td><%: person.Country %></td>
    </tr>
}
</table>

 

Nuggets can contain any executable line of code as long as it is prefixed with @. Here’s an example of how you import a namespace:

 

 
@using System.Net;

 

And here’s how you create a HTML form:

 

 
@using (Html.BeginForm()) {
   <fieldset>
       :
   </fieldset>
}

Special Expressions  

Usually, a nugget consists of a single line of code. However, you can expand it to multiple lines by using an @{ code } block:

 

 
@{
   var city = "London";
}
:
<p>@city</p>

 

Any variable you create can be retrieved and used later as if the code belonged to a single all-encompassing block. The content of an @{...} block can mix code and markup. It is essential, however, that the parser can figure out exactly where code ends and markup begins and vice versa. Look at the following nugget:

 

 
@{
    var theString = GetSomeString();
    if(theString.Length > 10)
      <p>Text too long</p>
    else
      <text>Text ok</text>
}

 

If the markup content you’re emitting is wrapped up by HTML tags, then the parser will properly recognize it as markup. If it’s plain text (i.e., just a text string) then you must wrap it in a Razor-specific <text> for the parser to handle it correctly.

If you  wish to emit  a value that  results from an expression,  then you can wrap it  in round brackets:

 

 
<span> @("I love " + city) </span>

 

Note that any content emitted by Razor is automatically encoded. If your code returns HTML markup that you want to insert in the page as-is,  without encoding, then you can resort to using the Html.Raw helper method:

 

 
@Html.Raw(Strings.HtmlMessage)

 

Most of the time, the Razor parser is smart enough to figure out from the context the reason why you’re using the @ symbol, whether it’s to denote a code nugget or perhaps a literal email address. If you hit an edge case that the parser can’t successfully solve, using @@ makes it clear that you want the symbol @ to be taken literally and not as the start of a code nugget.

Finally, when inside multiline code nuggets @{ … }, you use the C# syntax to place comments. You can comment out an entire block of Razor code using the @* … *@ syntax instead:

 

 
@*
<div> Razor markup here </div>
*@

 

The Visual Studio toolbar buttons for commenting blocks in and out support the Razor syntax nicely.

Conditional Nuggets in ASP.NET MVC 5

Razor now supports conditional attributes that are emitted only if the Razor expression evaluates to a non-null string. Let’s consider the following markup:

 

 
<div class="@cssClassName">
   ...
</div>

 

If the variable cssClassName is empty or null you may not want the class attribute to be emitted. In ASP.NET MVC 5, some conditional logic built into the Razor engine makes it happen automagically.

This is a useful feature as it may save a lot of boring code in many views. Less code in the view also gives a significant contribution to keeping markup cleaner and more readable. It should also be noted that conditional nuggets are not limited to strings but applies also to Boolean expressions.

URL Auto-completion

 For quite some time in Razor you had to explicitly complete the URL for images and script files using the Url.Content method, as shown below:

 

 
<script src="http://www.mamicode.com/@Url.Content("~/content/scripts/somefile.js")"></script>

 

It is now possible to avoid that as Razor automatically expands the tilde character in HTML elements.

 

 
<script src="http://www.mamicode.com/~/content/scripts/somefile.js"></script>

 

It is interesting to notice that this will work only within HTML elements.  In other words, if your page contains C# or script code that defines a relative URL with a tilde  then it  won’t be automatically expanded and so the only alternative is to  resort to Url.Content.

Properties of the Razor View Object

When the Razor view engine is used, the resulting view object is an instance of the WebViewPage class defined in the System.Web.Mvc assembly. This class incorporates the logic to parse markup and render HTML. Public properties on this class are available to any code nuggets you might write in actual templates. The properties of the Razor view object are summarized in the table below:

Property

Description

Ajax

Gets an instance of the AjaxHelper class used to reference Ajax HTML helpers around the template.

Culture

Gets and sets the ID of the culture associated with the current request.

Href

Converts any path you create in code (which can include the ~ operator) to a path that the browser can understand.

Html

Gets an instance of the HtmlHelper class used to reference HTML helpers in the template.

Context

Gets the central repository to gain access to various ASP.NET intrinsic objects: Request, Response, Server, User, and so forth.

IsAjax

Returns true if the current request was initiated by the browser’s Ajax object.

IsPost

Returns true if the current request was placed through an HTTP POST verb.

Layout

Gets and sets the path to the file containing the master view template.

Model

Gets a reference to the view model object (if any) containing data for the view. This property is of type dynamic.

UICulture

Gets and sets the ID of the user-interface culture associated with the current request.

ViewBag

Gets a reference to the ViewBag dictionary that might contain data the controller needs to pass to the view object.

ViewData

Gets a reference to the ViewData dictionary that might contain data the controller needs to pass to the view object.

Table 2.  The properties of the Razor view object.

As a side note, it may helpful to recall the difference between Culture and UICulture. The Culture property in the table refers to a setting that influences culture-dependent aspects of a page, such as date, number, and currency formatting. The UICulture property instead determines which resources are loaded in multilingual applications. The culture is expressed in xx-yy format, where xx indicates the language and yy the culture. For example, en-us.

Summary

The Razor syntax is now quickly becoming the only one being used in ASP.NET MVC solutions. It is terse and clean and produces more readable markup than the ASPX syntax. All the other markup languages that appeared around the time of the first release of ASP.NET MVC have disappeared. So today Razor is the recommended choice for creating views in ASP.NET MVC. This article summarized the key aspects of the Razor syntax that you more commonly require when you write HTML views.

A Look at the Razor View Engine in ASP.NET MVC