Tuesday, July 14, 2015

Ten Tricks for Razor Views

1. Code Blocks and Variables

In Razor you can create blocks of code nearly anywhere using @{ }. A block of code doesn't emit anything into the output, but you can use the block to manipulate a model, declare variables, and set local properties on a view. Be extremely careful about having too much code in a view, however, because it's not the best place for logic.
@{
    var message = "Hello, World!";
}
 
@message
 
@for (var i = 0; i < 10; i++)
{
    
        @message
    </div>
}
In the above code you can see the declaration for a string variable named message.  We can use the message variable throughout the rest of the view.
If you are wondering why it is legal to use the message variable outside the block defined by { and } (which would be illegal in a .cs file), then you should know that a Razor code block doesn't define an actual C# statement block. You can see this if you look at the source code generated from the Razor template. An easy way to see the code is to intentionally add a compiler error in the view, run the app, then click the "Show Complete Compilation Source" link that appears in the resulting error page. Another approach, if you want to see the real source code,  is to output or inspect the value of Assembly.GetExecutingAssembly().CodeBase. The result will be the temporary folder with all the generated code for the application.
For the last sample, the generated code looks something like this:
public class TheView : WebViewPage<dynamic>
{
    public override void Execute()
    {
        var message = "Hello, World!";
        Write(message);
        for (var i = 0; i < 10; i++)
        {
            WriteLiteral("
"
);
            Write(message);
            WriteLiteral("
"
);
        }
    }
}
I simplified the code, but now it is easy to see the true scope of the message variable.

2. Explicit Expressions

Razor makes it easy to transition between C# code and HTML, but there are a few edge cases. One edge case is when you need to have literal text in front of a code expression (which outputs a value). For example, adding a "PN" prefix using literal text in front an expression to output a part number.
PN@Model.PartNumber
In this case, Razor is a little too smart and thinks the code represents an email address, so what you'll see on the screen is PN@Model.PartNumber. To solve the problem we need to use an explicit code expression with parentheses around the code. 
PN@(Model.PartNumber)
This will produce the desired output, like "PN10400".

3. Mixing Code and Text

Another edge case is transitioning from C# code into literal text. Razor does this seamlessly when the text is markup and starts with a <.
@foreach(var item in Model)
{
    <div>@item</div>
}
However, Razor won't make the transition if you want to output plain text.
@foreach(var item in Model)
{
    Item: @item
}
In this case, Razor thinks "Item:" is part of your C# code, so you'll have a compiler error. The easiest solution to this edge case is to use @: to switch from C# into text.
@foreach(var item in Model)
{
    @:Item: @item
}

4. Comments

Comments in Razor can start with @* and end with *@. Nothing inside a comment will execute or appear in the output, not even HTML markup.
@* 
  You won't see any of this 
  @foreach(var item in Model) { 
    @:Item: @item 
  } 
*@

5. Using @ and _ with HTML Helpers

Not necessarily a Razor trick, but when using an HTML helper you'll often want to add additional attributes into an HTML element. Adding a class attribute for style or a data- attribute for scripting are quite popular. Fortunately, many HTML helpers allow you to pass along an anonymously typed object as an "htmlAttributes" parameter. The helpers will pick the properties out of the object, and add these properties as attributes in the HTML. This all sounds easy until you realize class is a reserved keyword in C# (so it causes problems if you try to use it as a property name), and the – character is illegal in a property name.
The following code then, has two syntax errors.
@Html.ActionLink("Help", "Help", null,
    htmlAttributes: new { class="special", data-ajax="true"})
You can fix one problem by using data_ajax instead of data-ajax, because the MVC runtime will turn underscores into dashes when it picks out the property names. The other problem you can fix with the @ sign, which in C# you can use to preface a reserved and the C# compiler will teat the word as a symbol. In other words, the following code ...
@Html.ActionLink("Help", "Help", null,
    htmlAttributes: new { @class="special", data_ajax="true"})
... will give you the output you desire ...
<a class="special" data-ajax="true" href="/Home/Help">Help</a>

6. Removing the WebForms View Engine

If Razor is the only view engine you use in an ASP.NET MVC application, then it makes sense to remove the Web Forms view engine and save some cycles. Otherwise, the MVC runtime will go looking for .aspx and .ascx files when it tries to locate a view or template. A small savings, but just 2 lines of code required during application startup.
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new RazorViewEngine());

7. @helpers

A @helper in Razor ultimately defines a function you can invoke from anywhere inside the view. The function can output literal text and can contain code blocks and code expressions. Again I'll remind you that heavy amounts of code in a view can lead to grief, but the following example shows how you could use a helper to produce the text "You have 3 octopuses in your collection" (when Model.Count == 3).
You have
   @PluralPick(Model.Count, "octopus", "octopuses")
in your collection.
 
@helper  PluralPick(int amount, string singular, string plural) {
    <span>
        @amount @(amount == 1 ? singular : plural)
    </span>
}

8. App_Code Helpers

If you want to use the PluralPick helper in more than one view, you can define the helper in a view living in the App_Code folder.
The App_Code folder is a magic folder in ASP.NET. The runtime will parse and compile views you place in this folder, but they won't be normal views derived from WebViewPage that you can execute to produce HTML. Instead, views here derive from HelperPage and will expose any @helper functions defined inside as public, static members.
In other words, if I create an App_Code folder in an MVC project, and create a Helper.cshtml view inside, I can add the PluralPick helper to the new view. The runtime will use the view to create a class named Helper, so I can now use PluralPick from any view in the application like this:
You have
   @Helper.PluralPick(Model.Count, "outopus", "octopuses")
in your collection.
Of course you can also build an HTML Helper extension method to make a helper globally available. The advantage to an extension method is that the method builds when you build the project, is easy to unit test, and is easy to package into a class library to share across multiple projects. The downside to an extension method is that extension methods have to work hard to create and manipulate HTML. Views in the App_Code folder, like any Razor view by default, will not compile until runtime, are not reachable from a unit test project, and can only be shared by copying the file to another project, but they do make it easy to intermingle HTML and C# code.
There is a way to avoid some of the App_Code downsides, however.

9. Building Views and Eagerly Generating Code From Razor

It is possible to build Razor views when building a project. Flipping on this switch will slow down a build, of course, but also will let you catch errors in Razor files before runtime. You might want to only build views during a Release mode build.
Just building the views doesn't package the view code into your assembly, however. For this, the Razor Generator project is available. The Razor Generator will force the Razor code generation to happen before compilation (similar to T4 templates), so you can include the generated code in your assembly.
For helpers, this means you can have helpers available for testing, or even create a shared library of helpers built from Razor views, and the helper views do not need to live in the App_Code folder.
The Razor Generator project page has all the details, but the current process looks like this:
- Install the Razor Generator extension (and restart VS).
- Set the Helper.cshtml file's "Custom Tool" property to RazorGenerator (and move the file out of App_Code).
- After saving, you should see Helper.generated.cs appear as a file behind Helper.cshtml.
Now the helper code is available at build time, and you could write tests for the trickier helpers.
var result = Helper.PluralPick(1, "octopus", "octopods");
var html = result.ToHtmlString();
 
Assert.True(html.Contains("octopus"));

10. Templated Razor Delegates

Another approach to building helper type code is to take advantage of the fact that a chunk of Razor syntax will convert to a Func. Instead of calling to a helper from Razor code, a templated delegate will let you do the inverse and pass Razor code to other methods. Since this post is getting rather long, I'm going to punt now and pass you along toPhil's post on the topic, which is excellent, like Phil himself.


No comments: