March 2018

Volume 33 Number 3

[ASP.NET]

Use Razor to Generate HTML for Templates in a Single-Page App

By Nick Harrison

Single-Page Application (SPA) apps are very popular and for good reason. Users expect Web apps to be fast, engaging and work on every device from smartphones to the widest-screen desktops. Beyond that, they need to be secure, visually engaging and do something useful. That’s a lot to ask for from a Web app, but really this is only the starting point.

As users started expecting more from Web applications, there was an explosion of client-side frameworks to “help” by providing a client-side implementation of the Model-View-ViewModel (MVVM) pattern. It can seem like there’s a new framework every week. Some have proven to be helpful, others not so much. But they all implement the design pattern differently, adding new features or solving recurring problems in their own ways. Each framework takes a different approach toward the design pattern, with a unique syntax for templates and introducing custom concepts like factories, modules and observables. The result is a sharp learning curve that can frustrate developers trying to stay current as new frameworks go in and out of vogue. Anything we can do to ease the learning curve is a good thing.

Having client-side frameworks implement the MVVM pattern and ASP.NET implement the MVC pattern on the server has led to some confusion. How do you blend server-side MVC with client-­side MVVM? For many, the answer is simple: They don’t blend. The common approach is to build static HTML pages or fragments and serve them up to the client with minimal server-side processing. In this scenario, Web API controllers may often replace the processing that would have been handled by the MVC Controller in the past.

As Figure 1 shows, there are minimal requirements for the Web Server and a full-fledged server for the Web API, and both servers need to be accessible from outside the firewall.

Typical Application Layout
Figure 1 Typical Application Layout

This arrangement provides good separation of concerns, and many applications have been written following this pattern, but as a developer, you’re leaving a lot on the table. ASP.NET has a lot more to offer and its implementation of MVC provides many features that are still relevant, even when one of the client-side frameworks does much of the heavy lifting. In this article, I’ll focus on one of these features, the Razor View Engine.

Razor in the SPA

The Razor View Engine is a marvel when it comes to simplifying generation of HTML markup. With just a couple of tweaks, the generated markup is easily customized to fit the template expectations for whatever framework is used on the client. I’ll highlight some examples with Angular and Knockout, but these techniques will work regardless of the framework you employ. If you make calls back to the server to provide templates for your application, you can now use Razor to do the heavy lifting generating the HTML.

There’s a lot to like here. EditorTemplates and DisplayTemplates are still your friends, as are scaffolding templates. You can still inject partial views, and the fluid flow of the Razor syntax is still there to use to your advantage. Beyond the views you can also use the MVC controller, which can add processing power to speed things up or add an extra layer of security. If this isn’t needed, the controllers could be a simple pass through to the view.

To demonstrate how this can be used to your benefit, I’ll step through the creation of some data entry screens for a time-tracking application, showing how Razor can help expedite the creation of HTML suitable for use by either Angular or Knockout.

As Figure 2 shows, not much changes from the typical layout. I’ve included an option showing that the MVC application could interact with this database. This isn’t necessary at this stage, but if it simplifies your processing, it is available. In general, the flow will look like:

  • Retrieve a full page from the MVC application, including all style sheet and JavaScript refer­ences and minimal content.
  • Once the page has been fully loaded in the browser and the framework initialized, the framework can call the MVC server to request a template as a Partial View.
  • This template is generated with Razor, typically without any associated data.
  • At the same time, the framework makes a call to the API to get the data that will be bound to the template received from the MVC site.
  • Once the user makes any required edits, the Framework makes calls to the Web API server to update the back-end database.

Simple Flow Adding in Razor
Figure 2 Simple Flow Adding in Razor

This workflow repeats as needed when a new template is requested. If the framework allows you to specify a URL for a template, MVC can serve up the template. While I will show examples generating views suitable for binding in Angular and Knockout, keep in mind that these are hardly the only options.

Setting up a Solution

From a setup perspective, you need at least three projects. One for the Web API, another for the MVC application and, finally, a common project to host code common between these two projects. For purposes of this article, the common project will host the ViewModels so they can be used in both the Web and Web API. The initial project will be structured as shown in Figure 3.

Initial Project Structure
Figure 3 Initial Project Structure

In the real world, you’ll only support one client-side framework. For this article, it simplifies matters to have two MVC projects, one for each of the frameworks to be demonstrated. You may encounter something similar if you need to support a Web application, a customized mobile application, a SharePoint application, a desktop application or any other scenario that’s not easily rendered from a common UI project. Regardless, only the logic embedded in the UI project will have to be repeated as you support multiple front ends.

Bringing in Data

In practice, your data would be stored in a database, proba­bly using an object relational mapper (ORM) such as Entity Framework. Here, I’ll sidestep issues of data persistence to focus on the front end. I’ll rely on the Web API controllers to return hardcoded values in the Get actions and I’ll run these samples in a perfect world where every API call returns successful. Adding appropriate error handling will be left as an exercise for you, the intrepid reader.

For this example I’ll use a single View Model decorated with attributes from the System.ComponentModel.DataAnnotations namespace, as shown in Figure 4.

Figure 4 Simple TimeEntryViewModel

namespace TimeTracking.Common.ViewModels
{
  public class TimeEntryViewModel
  {
    [Key]
    public int Id { get; set; }
    [Required (ErrorMessage ="All time entries must always be entered")]
    [DateRangeValidator (ErrorMessage ="Date is outside of expected range",
      MinimalDateOffset =-5, MaximumDateOffset =0)]
    public DateTime StartTime { get; set; }
    [DateRangeValidator(ErrorMessage = "Date is outside of expected range",
      MinimalDateOffset = -5, MaximumDateOffset = 0)]
    public DateTime EndTime { get; set; }
    [StringLength (128, ErrorMessage =
      "Task name should be less than 128 characters")]
    [Required (ErrorMessage ="All time entries should be associated with a task")]
    public string Task { get; set; }
    public string Comment { get; set; }
  }
}

The DateRangeValidator attribute doesn’t come from the DataAnnotations namespace. This isn’t a standard validation attribute, but Figure 5 shows how easily a new validator can be created. Once applied, it behaves just like the standard Validators.

Figure 5 Custom Validator

public class DateRangeValidator : ValidationAttribute
{
  public int MinimalDateOffset { get; set; }
  public int MaximumDateOffset { get; set; }
  protected override ValidationResult IsValid(object value,
    ValidationContext validationContext)
  {
    if (!(value is DateTime))
      return new ValidationResult("Inputted value is not a date");
    var date = (DateTime)value;
    if ((date >= DateTime.Today.AddDays(MinimalDateOffset)) &&
      date <=  DateTime.Today.AddDays(MaximumDateOffset))
      return ValidationResult.Success;
    return new ValidationResult(ErrorMessage);
  }
}

Anytime the model is validated, all validators will run, including any custom validators. Views created with Razor can easily incorporate these validations client-side, and these validators are automatically evaluated on the server by the Model Binder. Validating user input is key to having a more secure system.

API Controllers

Now that I have a View Model, I’m ready to generate a controller. I’ll use the built-in scaffolding to stub out the controller. This will create methods for the standard verb-based actions (Get, Post, Put, Delete). For purposes of this article, I’m not worried about the details for these Actions. I’m only interested in verifying that I have the endpoints that will be called from the client-side framework.

Creating a View

Next, I turn my attention to the View that the built-in scaffolding produces from the View Model.

In the TimeTracking.Web project, I’ll add a new Controller and name it TimeEntryController and create it as an Empty Controller. In this Controller, I create the Edit action by adding this code:

public ActionResult Edit()
{
  return PartialView(new TimeEntryViewModel());
}

From inside this method, I’ll right-click and select “Add View.” In the popup, I’ll specify that I want an Edit template, selecting the TimeEntryViewModel template.

In addition to specifying the model, I make sure to specify creation of a partial view. I want the generated view to include only the markup defined in the generated view. This HTML fragment will be injected into the existing page on the client. A sampling of the markup generated by the scaffolding is shown in Figure 6.

Figure 6 Razor Markup for the Edit View

@model TimeTracking.Common.ViewModels.TimeEntryViewModel
@using (Html.BeginForm())
{
  @Html.AntiForgeryToken()
  <div class="form-horizontal">
    <h4>TimeEntryViewModel</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.Id)
    <div class="form-group">
      @Html.LabelFor(model => model.StartTime, htmlAttributes:
        new { @class = "control-label col-md-2" })
      <div class="col-md-10">
        @Html.EditorFor(model => model.StartTime,
          new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.StartTime, "",
          new { @class = "text-danger" })
      </div>
    </div>
    ...
  </div>
}
<div>
  @Html.ActionLink("Back to List", "Index")
</div>

There are a few key things to note with this generated view, which out of the box generates a responsive, bootstrap-based UI. These include:

  • The form group is repeated for each property in the View Model.
  • The Id property is hidden because it’s marked with the Key attribute identifying it as a key that’s not to be modified.
  • Each input field has associated validation message placeholders that can be used if unobtrusive validation is enabled.
  • All labels are added using the LabelFor helper, which interprets metadata on the property to determine the appropriate label. The DisplayAttribute can be used to give a better name, as well as handle localization.
  • Each input control is added using the EditorFor helper, which interprets the metadata on the property to determine the appropriate editor.

The metadata is evaluated at run time. This means that you can add attributes to your model after generating the view and these attributes will be used to determine the validators, labels and editors, as appropriate.

Because the scaffolding is a one-time code generation, it’s OK to edit the generated markup, as that’s generally expected.

Binding to Knockout

To make the generated markup work with Knockout, I need to add a couple of attributes to the generated markup. I won’t delve deeply into the internal workings of Knockout except to note that binding uses a data-bind attribute. The binding declaration specifies the type of binding and then the property to be used. I’ll need to add a data-bind attribute to the input controls. Looking back at the generated markup, you see how the class attribute is added. Following the same process, I can modify the EditorFor function, as shown in the code here:

@Html.EditorFor(model => model.StartTime,
  new { htmlAttributes = new { @class = "form-control",
         data_bind = "text: StartTime" } })

Using the markup generated out of the box from the scaffolding, this is the only change needed to add Knockout binding.

Binding to Angular

Data binding with Angular is similar. I can add either an ng-model attribute or a data-ng-model attribute. The data-ng-model attribute will keep the markup HTML5-compliant, but the ng-bind is still commonly used. In either case, the value for the attribute is simply the name of the property to bind. To support binding to an Angular controller, I’ll modify the EditorFor function, using this code:

@Html.EditorFor(model => model.StartTime,
  new { htmlAttributes = new { @class     = "form-control",
                                  data_ng-model = "StartTime" } })

There are a couple of more minor tweaks that come into play for defining the application and controller. Please refer to the sample code for the full working example to see these changes in context.

You can adopt a similar technique to make the generated markup work with whatever MVVM framework you’re using.

Changing the Scaffolding

Because scaffolding uses T4 to generate the output, I can change what gets generated to prevent having to edit every View generated. The templates used are stored under the installation for Visual Studio. For Visual Studio 2017, you’ll find them here:

C:\Program Files (x86)\Microsoft Visual Studio 14.0\
  Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcView

You could edit these templates directly, but this would affect every project you work on from your computer, and anyone else working on the project would not benefit from the edits made to the templates. Instead, you should add the T4 templates to the project and have the local copy override the standard implementation.

Just copy the templates you want to a folder called “Code Templates” in the root folder of your project. You’ll find templates for both C# and Visual Basic. The language is reflected in the file name. You only need the templates for one language. When you invoke scaffolding from Visual Studio, it will first look in the CodeTemplates folder for a template to use. If a template isn’t found there, the scaffolding engine will then look under the Visual Studio installation.

T4 is a powerful tool for generating text in general, not just code. Learning it is a large topic on its own, but don’t worry if you’re not already familiar with T4. These are very minor tweaks to the templates. You’ll have no need to delve deeply into the internals to understand how T4 works its magic, but you will need to download an extension to add support for editing T4 templates in Visual Studio. The Tangible T4 Editor (bit.ly/2Flqiqt) and Devart T4 Editor (bit.ly/2qTO4GU) both offer excellent community versions of their T4 Editors for editing T4 templates that provide Syntax Highlighting, making it easier to separate code that drives the template from code being created by the template.

When you open the Edit.cs.t4 file, you’ll find blocks of code to control the template and blocks of code that is the markup. Much of the code driving the template is conditional processing for handling special cases such as supporting partial pages and handling special types of properties such as foreign keys, enums and Booleans. Figure 7 shows a sample of the template in Visual Studio when using an appropriate extension. The code driving the template is hidden in the collapsed sections, making it easier to see what the output will be.

The Visual Studio Edit T4 Template
Figure 7 The Visual Studio Edit T4 Template

Fortunately, you don’t have to trace through these conditionals. Instead, look for the generated markup that you want to change. In this case, I care about six different statements. They’re extracted for your reference in Figure 8.

Figure 8 Original Code for Generating the Editors

@Html.DropDownList("<#= property.PropertyName #>", null,
  htmlAttributes: new { @class = "form-control" })
@Html.DropDownList("<#= property.PropertyName #>", String.Empty)
@Html.EditorFor(model => model.<#= property.PropertyName #>)
@Html.EnumDropDownListFor(model => model.<#= property.PropertyName #>,
  htmlAttributes: new { @class = "form-control" })
@Html.EditorFor(model => model.<#= property.PropertyName #>,
  new { htmlAttributes = new { @class = "form-control" } })
@Html.EditorFor(model => model.<#= property.PropertyName #>)

Once you make the updates to support your specific client-side framework, all new views generated with the template will support your framework automatically.

Client-Side Validation

When I examined the view that was generated, I skipped over the ValidationMessageFor function calls that are made for each property. These calls produce placeholders to display any validation messages created when client-side validations are evaluated. These validations are based on the Validation attributes added to the model. All that’s needed to enable these client-side validations is to add references to the jquery validation scripts:

@Scripts.Render("~/bundles/jqueryval")

The jqueryval bundle is defined in the BundleConfig class from the App_Start folder.

If you try to submit the form without entering any data, the required field validators will trigger to prevent the submission.

If you prefer a different validation strategy client-side, such as bootstrap form validation (bit.ly/2CZmRqR), you can easily modify the T4 template to not output the ValidationMessageFor calls. And if you don’t use the native validation approach, you won’t need to reference the jqueryval bundle, because it will no longer be needed.

Editor Templates

Because I specify the input control by calling the EditorFor html helper, I’m not explicitly specifying what the input control should be. Instead, I leave it to the MVC framework to use the most appropriate input control for the property specified, based on data type or attributes such as UIHint. I can also directly influence the editor selection by explicitly creating an EditorTemplate. This allows me to control at a global level how input of specific types will be treated.

The default editor for a DateTime property is a textbox. Not the most ideal editor, but I can change that.

I’ll add a partial view called DateTime.cshtml to the folder \Views\Shared\EditorTemplates. The markup added to this file will be used as the editor for any property of type DateTime. I add the markup shown in Figure 9 to the partial view and add the following code to the bottom of the Layout.cshtml:

<script>
  $(document).ready(function () {
    $(".date").datetimepicker();
  });
</script>
Figure 9 Markup for a DateTime Editor
@model DateTime?
<div class="container">
  <div class="row">
    <div class='col-md-10'>
      <div class="form-group ">
        <div class='input-group date'>
         <span class="input-group-addon">
           <span class="glyphicon glyphicon-calendar"></span>
         </span>
         @Html.TextBox("", (Model.HasValue ?
           Model.Value.ToShortDateString() :
           string.Empty), new
           {
             @class = "form-control"
           })
        </div>
      </div>
    </div>
  </div>
</div>

Once these code elements are added, I end up with a pretty nice Date and Time editor to help edit a DateTime property. Figure 10 shows this new editor in action.

An Activated Date Picker
Figure 10 An Activated Date Picker

Wrapping Up

As you have seen, Razor can do a lot to simplify and streamline the creation of views in any client-side MVVM framework you want to use. You have the freedom to support application styles and conventions, and features such as scaffolding and EditorTemplates help ensure consistency across your application. They also enable built-in support for validations based on attributes added to your view model, making your application more secure.

Take a second look at ASP.NET MVC and you’ll find many areas that are still relevant and useful, even as the landscape of Web applications continues to change.


Nick Harrison is a software consultant living in Columbia, S.C., with his loving wife Tracy and daughter. He’s been developing full stack using .NET to create business solutions since 2002. Contact him on Twitter: @Neh123us, where he also announces his blog posts, published works and speaking engagements.

Thanks to the following technical expert for reviewing this article: Lide Winburn (Softdocks Inc.)
Lide Winburn is the cloud architect at Softdocs, Inc., where he helps schools go paperless. He is also a beer league hockey player and avid movie-goer with his family.


Discuss this article in the MSDN Magazine forum