Saturday, 16 February 2013

ASP.NET MVC - Methods in Razor Views (via @helper syntax)

Todays post is about a little-known but very useful feature that was added to the Razor view engine since ASP.NET MVC3. The feature that I'm refering to is the @helper syntax which lets you define reusable methods at the view level. Let's dive straight into an example to show off this feature.

Imagine you have a simple Book model in your ASP.NET MVC project:
public class Book
{
    public string Isbn { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
}
You may then have a controller called BookController with one HTTP GET action called Index. This action may read some books from some repository and pass a list of Book objects to the view. For the sake of brevity, I've hard-coded a couple of Book instances in the snippet below:
public class BookController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        var books = new List<Book>();

        books.Add(new Book {
            Isbn = "9780385660075",
            Title = "The Kite Runner",
            Author = "Khaled Hosseini"
        });

        books.Add(new Book {
            Isbn = "9780571219766",
            Title = "Self",
            Author = "Yann Martel"
        });

        return View(books);
    }
}
Initially, your Razor engine-based Index view may look something like:
@using System.Collections.Generic
@using MvcHelperMethods.Models

@model IList<Book>

<h2>Books Index</h2>

@foreach (var book in Model)
{
    <div>
        <span class="bookTitle">@book.Title</span>
        <span class="bookAuthor">by @book.Author</span>
        <span class="bookIsbn">ISBN: @book.Isbn</span>
    </div>
}
Now imagine that you have a new requirement to introduce another view in your application which shows books by individual authors. The requirement states that the books should be displayed exactly how they are on the Index view. In this scenario, you'll need to create another view that accepts a list of Book objects and renders them out. The only difference being that a different controller action would be used which returns books by a specific author. The view code would look very similar. This presents an opportunity to make use of the @helper syntax in Razor.

The @helper syntax lets you define a view-level method that consists of both C# and Razor syntax. This method can be placed in a specific view or outside of a specific view so that it can be reused across multiple views. We'll use the latter option and define an @helper method called RenderBook which accepts a single parameter of type Book and renders the book out using a mixture of Razor and html syntax.

To accomplish this in your ASP.NET MVC solution, ensure you have the App_Code folder. In Visual Studio 2012, you can add this folder by right-clicking your project then going to Add --> Add ASP.NET Folder --> App_Code. In your App_Code folder, you will now create a new MVC Razor View Page (a .cshtml file). Right click the App_Code folder and go to Add -> New Item. Select the option called "MVC View Page (Razor)", name your view page something sensible - like "ViewHelperMethods.cshtml" then click Add. If there is some pre-defined content in the new view, just remove it all. Now you can write your reusable @helper view method called RenderBook in the ViewHelperMethods.cshtml file:

@using MvcHelperMethods.Models

@helper RenderBook(Book book)
{
    <div>
        <span class="bookTitle">@book.Title</span>
        <span class="bookAuthor">by @book.Author</span>
        <span class="bookIsbn">ISBN: @book.Isbn</span>
    </div>
}
As you can see from the syntax, it's quite similar to the syntax of a standard C# method except you don't specify a return type and access modifier, instead, you use the keyword @helper. You can now refactor your original Index view so that it looks like:
@using System.Collections.Generic
@using MvcHelperMethods.Models

@model IList<Book>

<h2>Books Index</h2>

@foreach (var book in Model)
{
    @ViewHelperMethods.RenderBook(book)
}
Notice that we're now calling our reusable RenderBook method. You can now call the same RenderBook method from your other views, in our case, the view that displays books by specific authors. In summary, if you're working on a fairly large ASP.NET MVC application with many views, then this is a very useful feature to ensure your views are more maintainable.

Update: As pointed out by a reader of this post (on Google+), you also have the option to create a partial view for the Book model and reuse that. The example I gave above was quite simplistic and was aimed at introducing the reader to the @helper feature. I believe the advantage that helper methods give is that you can easily pass them additional parameters at the view level and those parameters can then control exactly how the smaller view is rendered out.