Devhammer's Den


Jul 8, 2011

Tweaking Add Item Templates for Better Script Performance

[This is the second in a 3-part series. Part 1, "Make Script Performance Automatic with Custom Templates in Visual Studio 2010", can be found here.]

In part 1 of this series, I showed you how you can improve the script performance of your websites by using Visual Studio's built-in support for exporting templates to create a new MVC3 web site template that relocates <script> references and blocks to the end of the page, where they will not interfere with the loading of the main visual elements of your site.

In this second part, I will show you how you can customize the T4 templates used to create new items in an MVC3 project, so that when you add new views to your project, they also have the scripts located at the end of the page. That way both the Master page / Layout for your original project AND any views you add have the scripts in the best location for performance. As a reminder, if your scripts dynamically add content to the DOM, you may want to leave those scripts in the <head> section of the page, since locating them at the bottom of the page may impact the page visibly during rendering.

Background

As I noted in part 1, there's substantial evidence that putting most scripts at the bottom of your page will improve the rendering performance of the page. This is because many browsers treat external script references differently from other content, and may end up serializing the download of other parts of your page (images, etc.) while they are downloading the script libraries.

While creating your own custom project templates gets you part of the way towards making this improvement automatic, you'll find that as soon as you add a new Create or Edit view, you're right back into script at the top land. Here's an example of a new Edit view from one of my MVC3 projects:

   1:  @model CodeFirst.Models.Album
   2:   
   3:  @{
   4:      ViewBag.Title = "Edit";
   5:  }
   6:   
   7:  <h2>Edit</h2>
   8:   
   9:  <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
  10:  <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
  11:   
  12:  @using (Html.BeginForm()) {
  13:      @Html.ValidationSummary(true)
  14:      <fieldset>
  15:          <legend>Album</legend>
  16:   
  17:          @Html.HiddenFor(model => model.ID)
  18:   
  19:          <div class="editor-label">
  20:              @Html.LabelFor(model => model.Name)
  21:          </div>
  22:          <div class="editor-field">
  23:              @Html.EditorFor(model => model.Name)
  24:              @Html.ValidationMessageFor(model => model.Name)
  25:          </div>
  26:   
  27:          <p>
  28:              <input type="submit" value="Save" />
  29:          </p>
  30:      </fieldset>
  31:  }
  32:   
  33:  <div>
  34:      @Html.ActionLink("Back to List", "Index")
  35:  </div>

 

Lines 9 and 10 above are our problem, since they put scripts back at the top, where we don't want them. We don't want to lose the scripts, of course, because they provide support for MVC3's unobtrusive validation, which is a very useful feature, but we want them to appear at the bottom of the page.

A Quick Aside on T4

No, T4 isn't the Terminator model that appeared in the unreleased 4th installment of the movie series of the same name. It's a templating engine used by Visual Studio for many of the new item templates in various projects, including MVC3. A full explanation of T4 is a bit beyond the scope of this post, but just understand that it's a very powerful way of specifying how various templates are created in a dynamic way at the time they're created, and jump into showing you how they work in the case of the MVC3 Views. If you want to dig deeper on T4, here's a nice list of tutorials for T4 templating. There's also a deeper treatment of T4 specifically in the context of ASP.NET MVC.

Prerequisite

As a starting point for this tutorial, you'll need an MVC3 project that has a model with at least one validation rule…in my examples below, I'm using a simple example using Entity Framework Code First, MVC Scaffolding, and a Person model with only an Id and Name properties, with the Name property being required. Here's what my model code looks like:

   1:  public class Person
   2:  {
   3:      public int Id { get; set; }
   4:      [Required]
   5:      public string Name { get; set; }
   6:  }

For help getting started with Scaffolding and EF Code First, check out Steve Sanderson's MIX video on scaffolding. He's also got a blog series on the topic, but it refers to the MvcScaffolding NuGet package, which is no longer available (the scaffolding functionality is built into MVC 3 once you install the MVC3 Tools Update). For the remainder of the tutorial, I'm assuming you have a project with a valid model in place so we can create strongly-typed views with validation rules.

Tutorial – Customizing T4 View Templates

To get started on modifying our T4 template, let's take a look at the relevant section of the Edit.tt template for an MVC3 Razor view (which by default are located at C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML for C#):

   1:  <#
   2:  if (mvcHost.ReferenceScriptLibraries) {
   3:  #>
   4:  <#
   5:      if (!mvcHost.IsContentPage) {
   6:  #>
   7:  <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
   8:  <#
   9:      }
  10:  #>
  11:  <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
  12:  <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
  13:   
  14:  <#
  15:  }
  16:  #>
  17:  @using (Html.BeginForm()) {

 

I've included line 17 to show where we are in the template file, which is fairly extensive. The "<#" and "#>" tokens denote T4 template language constructs. On line 2, the template will check the value of ReferenceScriptLibraries, which will be true if you check the related checkbox in the Add View dialog, shown below:

AddViewDialog

Line 5 checks to see whether we're in a content page (if we are, then jQuery itself will be referenced by the Layout page for this page, so we don't need to include it), and lines 11 and 12 insert the script references required for jQuery unobtrusive validation. It's lines 1-16 that we ultimately want to have at the bottom of our page when we create a new view.

A Choice to Make

Yogi Berra once famously said, "when you come to a fork in the road, you take it!" Our particular road forks here between creating a custom T4 template that's local to our project, and permanently editing the one in the above directory, which will change the template for ALL projects on the machine. A third option exists, which is to make a copy of the template in its original location, which is what I'll illustrate here. If you want to see an example of adding the .tt files directly to your project, check out Scott Hanselman's post on the topic. Let's get started:

  1. Open up Windows Explorer (since we'll be working in the Program Files area, it may save you some prompts if you open Windows Explorer as an administrator), navigate to your Visual Studio install directory, and drill down to the \Common7\IDE\ItemTemplates\CSharp\Web\MVC 3\CodeTemplates\AddView\CSHTML folder.
  2. Copy the Edit.tt template (Ctrl+C) and paste it back into the same folder, which will create a copy named Edit – Copy.tt. Rename this to Edit_SB.tt (for Scripts at the Bottom).
  3. Open Visual Studio 2010 as an administrator (right-click the program icon and select "Run as administrator"), so we'll be able to save our edits to the template. Select Open > File from the File menu, and navigate to the location of the new Edit_SB.tt file, and click Open.

    OpenFile
  4. Find the section of the template matching lines 1-16 above, and cut those lines to the clipboard. The selection should begin and end with the "<#" and "#>" constructs.
  5. Locate the following section of code, and paste the cut lines directly above it (text wrapped for formatting on the page):
       1:  <#
       2:  // The following code closes the asp:Content tag used in the case of a master page and the body and 
       3:  // html tags in the case of a regular view page
       4:  #>

    the finished code should look like this:
       1:  <#
       2:  if (mvcHost.ReferenceScriptLibraries) {
       3:  #>
       4:  <#
       5:      if (!mvcHost.IsContentPage) {
       6:  #>
       7:  <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
       8:  <#
       9:      }
      10:  #>
      11:  <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
      12:  <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
      13:   
      14:  <#
      15:  }
      16:  #>
      17:  <#
      18:  // The following code closes the asp:Content tag used in the case of a master page and the body and 
      19:  // html tags in the case of a regular view page
      20:  #>

  6. Save the .tt file.
  7. Next, open a new instance of Visual Studio 2010, and either create a new ASP.NET MVC3 project and add a simple model with validation for the template to use, or open an existing project containing a valid model class with validation rules (we need a model to test that our client validation scripts still work). If you've not worked with validation in ASP.NET MVC3, there's a good tutorial here. You may also want to check out Brad Wilson's post on unobtrusive validation in ASP.NET MVC3.
  8. In your project, drill down into the Views folder and find the folder matching the name of your model, right-click it, and select Add > View. Choose to create a strongly-typed view for your model, and then select the Edit_SB template, which should now appear in the Scaffold template list, and then click Add (note that if you already have an Edit view, you may want to rename it first):

    AddViewDialog2
  9. Run the application, and navigate to the edit page, and try to submit the page with a validation error. The result should indicate everything's working, right?

    image

    Not so fast! What we're seeing here is a problem caused by the fact that our script reference to the jQuery library is in our Layout page, several lines below the call to RenderBody where the markup from our view will appear. So what are we to do? It wouldn't make sense to add the jQuery reference in our view, but it needs to appear before the reference to the jQuery validation scripts.

Sections to the Rescue!

Thankfully, the Razor view engine provides a simple means of solving this problem, called sections. We can add a section to our Layout page as a placeholder for any script references that rely on jQuery, and provide the references from our view. Follow these steps to implement this solution (note that if you're using the Web Forms view engine, you can use a ContentPlaceHolder for a similar solution):

  1. Open _Layout.cshtml (or whatever your layout file is for your MVC project), and add the following code immediately after the jQuery script reference (line 3 below is the new code):

       1:      <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.6.1.min.js" 
       2:          type="text/javascript"></script>
       3:      @RenderSection("ScriptRefs", false)

    If you created a project template using the instructions from Part 1 of this series, you may want to update the template with the call to @RenderSection.
  2. Now open the T4 template for the Edit view (the one we edited in step 5 above), and modify the validation script references to wrap them in a section as follows:

       1:  @section ScriptRefs {
       2:  <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" 
       3:      type="text/javascript"></script>
       4:  <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" 
       5:      type="text/javascript"></script>
       6:  }

  3. Next, delete the Edit view, and recreate it per step 8 above. Run the project, navigate to the edit view for your model, and attempt to submit an invalid value. You should see a screen that looks like the following:

    Result
  4. Right-click the page, and select View Source. You should see that there are data- attributes in our input fields for validation, and the jQuery and jQuery validation script references appear correctly at the bottom of the page, just before the closing <body> tag.

T4 Templates are a powerful mechanism for creating new items in your projects, and I've only skimmed the surface here of what they can do. T4 can also be very useful in conjunction with the MVC Scaffolding NuGet package, but note the caveat below about the technique I've shown in this post.

IMPORTANT

One important caveat to note. Because in the examples above we created a copy of the Edit.tt template file, rather than modifying the Edit template itself, when you use the new scaffolding feature in MVC3 to create your controller and all your views at once based on a given model, it will use the default Edit.tt template rather than your custom template (Edit_DB.tt in the examples above). If you want the scaffolding engine to pick up your changes, you can modify Edit.tt directly rather than making a copy. You can also refer to Steve Sanderson's blog for more information on T4 customization in the context of scaffolding.

DevNugget Available

For those of you who learn better from video tutorials, I've published a screencast of this tutorial as part of my DevNuggets show on Channel 9.

Additional Resources

Tags: MVC, Tutorials, JavaScript, Templates, T4

Comments powered by Disqus

Visitors

Disclaimer

The views expressed on this weblog are mine and do not necessarily reflect the views of my employer.
All postings are provided "AS IS" with no warranties, and confer no rights.

Unless otherwise noted, all code provided in this blog is copyright © G. Andrew Duthie, and licensed under the Microsoft Limited Public License (Ms-LPL). All rights reserved.



worldmaps