November 2017

Volume 32 Number 11

[Cutting Edge]

Guidelines for ASP.NET MVC Core Views

By Dino Esposito | November 2017

Dino EspositoAlthough ASP.NET Core might seem very similar to classic ASP.NET MVC on the surface, there are a lot of differences under the hood. Controllers, Razor views and even model classes can often be easily migrated with minimal effort, yet architecturally speaking, ASP.NET Core diverges significantly from previous non-Core versions of ASP.NET.

The main reason for this is the rewritten pipeline, which provides an ASP.NET Core application at least two ways to generate an HTML-based response. As expected, an application can adopt the MVC programming model and generate HTML out of Razor views invoked via controller actions. Alternately, an application can act as a very thin Web server built around some terminating middleware, and your code in that terminating middleware can do everything, including returning a string that the browser treats as HTML. Finally, in ASP.NET Core 2.0 you can use an entirely new approach—Razor Pages. Razor Pages are Razor views that don’t need the MVC infrastructure, but can generate and serve HTML directly out of Razor files.

In this article, after a quick discussion on mini servers, terminating middleware and Razor Pages, I’ll contrast and compare the approaches you have within the MVC programming model to build views. In particular, I’ll focus on what’s new in ASP.NET Core, including tag helpers, view components and dependency injection (DI), and their impact on actual coding practices.

HTML from the Terminating Middleware

The terminating middleware is the final chunk of code in the ASP.NET Core pipeline that gets to process a request. Basically, it’s a direct (lambda) function where you process the HTTP request to produce any sort of detectable response, whether plain text, JSON, XML, binary or HTML. Here's a sample Startup class for this purpose:

public class Startup
{
  public void Configure(IApplicationBuilder app)
  {
    app.Run(async context =>
    {
    var html = BuildHtmlFromRequest(context);
    await context.Response.WriteAsync(html);
    });
  }
}

By writing HTML-formatted text in the response’s output stream, and setting the appropriate MIME type, you can serve HTML content to the browser. It all happens in a very direct way, with no filters and no intermediation, but it definitely works and is faster than anything in earlier versions of ASP.NET. Terminating middleware gives you control over the flow sooner than any other option. Clearly, coding an HTML factory right in the terminating middleware is far from being a maintainable and flexible solution, but it works.

Razor Pages

In ASP.NET Core 2.0, Razor Pages provide an additional way to serve HTML content, directly invoking a Razor template file without having to go through a controller and action. As long as the Razor page file is located in the Pages folder, and its relative path and name match the requested URL, the view engine will process the content and produce HTML.

What really differentiates Razor pages and Razor views is that a Razor page can be a single file—much like an ASPX page—that contains code and markup. A Razor Page is a CSHTML file flagged with an @page directive that can be bound to a view model that inherits from the system-provided PageModel class. As I mentioned already, all Razor Pages go under the new Pages root project folder and routing follows a simple pattern. The URL is rooted in the Pages folder and the .cshtml extension is stripped off the actual file name. For more information on Razor Pages, have a look at bit.ly/2wpOdUE. In addition, for a more advanced example, have a look at msdn.com/magazine/mt842512.

If you’re used to working with MVC controllers, I expect you’ll find Razor Pages fundamentally pointless, perhaps only minimally helpful in those rare scenarios where you have a controller method that renders out a view without any business logic around. On the other hand, if you’re new to the MVC application model, Razor Pages provide yet another option for mastering the ASP.NET Core framework. To some people, Razor Pages offer a lower barrier of entry for making progress with the framework.

Tag Helpers

The Razor syntax has always been essentially an HTML template interspersed with snippets of C# code. The @ symbol is used to tell the Razor parser where a transition occurs between HTML static content and a code snippet. Any text following the @ symbol is parsed according to the syntax rules of the C# language. The items discovered during text parsing are concatenated to form a dynamically built C# class that’s compiled on the fly using the .NET Compiler Platform (“Roslyn”). Executing the C# class just accumulates HTML text in the response stream, mixing together static content and dynamically computed content. In the end, the expressivity of the Razor language is limited to the expressivity of HTML5.

With Razor, the ASP.NET team also introduced an artifact known as HTML helpers. An HTML helper is a sort of small HTML factory that gets some input data and emits just the HTML you want. However, HTML helpers never won over developers, so in ASP.NET Core a much better tool has been added: tag helpers. Tag helpers play the same role as HTML helpers—they work as HTML factories—but provide a much more concise and natural syntax. In particular, you don’t need any C# code to tie tag helpers with the Razor template code. Instead, they look like elements of an extended HTML syntax. Here’s an example of a tag helper:

<environment names="Development">
  <script src="~/content/scripts/yourapp.dev.js" />
</environment>
<environment names="Staging, Production">
  <script src="~/content/scripts/yourapp.min.js" 
    asp-append-version="true" />
</environment>

The environment markup element isn’t emitted verbatim to the browser. Instead, its entire subtree is parsed server-side by a tag helper component that’s able to read attributes, and inspect and modify the current HTML tree. In this example, the tag helper responsible for the environment element matches the current ASP.NET Core environment to the content of the names attribute, and emits only the script elements that apply. In addition, the script element is processed by another tag helper that inspects the tree looking for an asp-append-version custom attribute. If found, the actual URL being generated is appended with a timestamp to ensure that the linked resource is never cached.

Tag helpers are C# classes inherited conventionally from a base class or declaratively bound to markup elements and attributes. Each Razor file that intends to use tag helpers must declare them using the @addTagHelper directive. The directive simply registers tag helper classes from a given .NET Core assembly. The @addTagHelper directive may appear in individual Razor files but more commonly goes in the _ViewImports.cshtml file and applies globally to all views:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Attributes and elements recognized as tag helpers are also emphasized in Visual Studio with a special color. ASP.NET Core comes with a full bag of predefined tag helpers that can be grouped in a few categories. Some affect particular HTML elements you can have in a Razor template, such as form, input, textarea, label and select. Some helpers instead exist to simplify and automate the display of validation messages within forms. All predefined tag helpers share the asp-* name prefix. More information can be found at bit.ly/2w3BSS2.

Tag helpers help keep the Razor source code quite readable and concise. In light of this, there would be no reason to avoid using helpers. Overall, I would recommend using tag helpers to automate the writing of long, repetitive blocks of markup code, rather than creating anything like a view-specific language. The more you use tag helpers, in fact, the more you drive yourself away from plain HTML and the more expensive you make the rendering of HTML views. Tag helpers are a cool piece of technology, but they represent a trade-off between expressivity and cost of server-side rendering. Speaking of benefits, it’s also worth mentioning that tag helpers don’t alter the overall markup syntax so you can easily open a Razor file with tag helpers in any HTML editor without incurring parsing issues. The same is hardly true with HTML helpers.

View Components

Technically speaking, view components are self-contained components that include both logic and view. In ASP.NET Core, they replace child actions, which were available in classic ASP.NET MVC 5. You reference views components in Razor files via a C# block and pass them any input data that’s required:

@await Component.InvokeAsync("LatestNews", new { count = 4 })

Internally, the view component will run its own logic, process the data you passed in and return a view ready for rendering. There are no predefined view components in ASP.NET Core, meaning that they’ll be created on a strict application basis. The previous line of code presents a LatestNews view component that’s a packaged block of markup conceptually similar to partial views. The difference between a partial view and a view component is in the internal implementation. A partial view is a plain Razor template that optionally receives input data and incorporates that in the HTML template of which it’s made. A partial view isn’t expected to have any behavior of its own except for formatting and rendering.

A view component is a more sophisticated form of a partial view. A view component optionally receives plain input parameters that it typically uses to retrieve and process its data. Once available, data is incorporated in an embedded Razor template, much the way a partial view behaves. However, a view component is faster in implementation because it doesn’t go through the controller pipe-line the way child actions do. This means there’s no model binding and no action filters.

Overall, view components serve the noble purpose of helping to componentize the view so that it results from the composition of distinct and self-made widgets. This point is a double-edged sword. Having the view split into distinct and independent components seems a convenient way to organize the work and possibly make it more parallel by having different developers take care of the various parts. However, view components don’t speed up things in all cases. It all depends on what each component does internally. When discussing view components, and the Composition UI pattern underneath its vision, you’re often presented with the architecture of large service-oriented systems as an example, and that’s absolutely correct.

However, when you blindly use view components, and the pattern, in the context of a monolithic, compact system, you might end up with poorly optimized query logic. Each component might query for its data, producing repeat queries and unnecessary database traffic. View components can be employed in this scenario, but you should consider caching layers to avoid bad outcomes.

Passing Data to Views

There are three different, non-exclusive ways to pass data to a Razor view (in addition to DI via the @inject directive). You can use one of the two built-in dictionaries—ViewData or ViewBag—or you can use strongly typed view model classes. No difference exists between these approaches from a purely functional point of view, and even from a performance perspective the difference is negligible.

From the design perspective, though, I recommend the single channel view model approach. Any Razor view should have a single source of data and that’s the view model object. This approach demands that you pay close attention to the design of the base class for your view models, and that you avoid using dictionaries and even the @inject directive.

I realize that this approach sounds strict, but it comes from experience. Even calling DateTime.Now in the view can be dangerous, as it injects data into the view that might not be abstract enough for the application. DateTime.Now indicates the time of the server, not the time of the application. This is a problem for any application that depends on the time of the day for its operations. To avoid these kinds of traps, spend some time in the design of the view model classes to envision the common data you need to have in all views. Also, be sure to always pass data—any data—to the views via the model, avoiding dictionaries and DI as much as possible. While it’s faster to use dictionaries and DI, it won’t be faster later on when you have to refactor some domain functionality in a large application. DI in views is a cool piece of technology, but it should be used with more than just one grain of salt.

Wrapping Up

Views are the foundation of Web applications. In ASP.NET Core, views are the result of processing a template file—typically a Razor template file—that’s mixed up with data provided by a caller that most often is a controller method. In this article, I tried to compare the various approaches available to build views and pass data to them. Most ASP.NET Core literature emphasizes the role of tag helpers and view components, and even Razor Pages and DI, in views. While these are all compelling resources, it’s important not to use them blindly, as they have natural drawbacks that can produce some bad outcomes if you’re not careful.


Dino Esposito is the author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) and “Programming ASP.NET Core” (Microsoft Press, 2018). A Pluralsight author and developer advocate at JetBrains, Esposito shares his vision of software on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Steve Smith


Discuss this article in the MSDN Magazine forum