Templated Razor Delegates

What’s that? I’ll let the code do the speaking.


@{
  Func<dynamic, object> b = @<strong>@item</strong>;
}
<span>This sentence is @b("In Bold").</span>

That could come in handy if you have friends who'll jump on your case for using the bold tag instead of the strong tag because it’s "not semantic".

Note that the delegate that's generated is a Func<T, HelperResult>. Also, the @item parameter is a special magic parameter. These delegates are only allowed one such parameter, but the template can call into that parameter as many times as it needs.

The example I showed is pretty trivial. I know what you’re thinking. Why not use a helper? Show me an example where this is really useful. Ok, you got it!

Suppose I wrote this really cool HTML helper method for generating any kind of list.

public static class RazorExtensions {
    public static HelperResult List<T>(this IEnumerable<T> items, 
      Func<T, HelperResult> template) {
        return new HelperResult(writer => {
            foreach (var item in items) {
                template(item).WriteTo(writer);
            }
        });
    }
}

This List method accepts a templated Razor delegate, so we can call it like so.


@{
  var items = new[] { "one", "two", "three" };
}

<ul>
@items.List(@<li>@item)
</ul>

As I mentioned earlier, notice that the argument to this method, <span class="asp">@</span>&lt;li><span class="asp">@</span>item&lt;/li> is automatically converted into a Func&lt;dynamic, HelperResult> which is what our method requires.

Now this List method is very reusable. Let’s use it to generate a table of comic books.


@{
    var comics = new[] { 
        new ComicBook {Title = "Groo", Publisher = "Dark Horse Comics"},
        new ComicBook {Title = "Spiderman", Publisher = "Marvel"}
    };
}

<table>
@comics.List(
  @<tr>
    <td>@item.Title
    <td>@item.Publisher
  </tr>)
</table>

Creating reusable HTML components in ASP.NET MVC using Razor

Whilst working on a side project I started to notice I was using a lot of the same HTML to create floating boxes. Whilst these boxes looked the same, the content of them changed quite dramatically; for instance, some of the content boxes contained forms, others contained straight text, like so:

<div class="panel">
         <div class=panel-inner">
             <h2 class="panel-title">Panel Title</h2>
             <div class="panel-content">
                 /* Can I pass content to be rendered in here here? */
             </div>
         </div>
     </div>
</div>

 

As my side project progressed and grew, I found myself making more and more modifications to these HTML components so I started to look how I can encapsulate the component for greater flexibility and extensibility as my project progressed.

The solution I ended up with was creating a HTML extension modelled off of the way the the Html.BeginForm() extension works, by writing directly to the view's context and then returning an instance of IDisposable, with the call to disposing of the context writing the closing HTML statements of my component to the view context - essentially wrapping the contents passed into the HTML extension in my HTML component.

Below is an example of what the code looks like:

namespace System.Web.Mvc
{
    public static class HtmlHelperExtensions
    {
        public static HtmlPanelComponent PanelComponent(this HtmlHelper html, string title)
        {
            html.ViewContext.Writer.Write(
            "<div class=\"panel\">" +
            "<div class=\"panel-inner\">" +
            "<h2 class=\"panel-title\">" + title + "</h2>" +
            "<div class=\"panel-content\">"
            );

            return new HtmlPanelComponent(html.ViewContext);
        }
    }

    public class HtmlPanelComponent : IDisposable
    {
        private readonly ViewContext _viewContext;
        public HtmlPanelComponent(ViewContext viewContext)
        {
            _viewContext = viewContext;
        }
        public void Dispose()
        {
            _viewContext.Writer.Write(
            "</div>" +
            "</div>" +
            "</div>"
            );
        }
    }
}

Using this new HTML extension I'm now able to reuse my HTML component and fill the panel's content in with whatever I please, like so:

@using (Html.PanelComponent("Panel Title"))
{
    <p>Welcome back, please select from the following options</p>
    <a href="#">Profile</a>
    <a href="#">My Defails</a>
}

ASP.NET MVC 3: Razor’s @: and syntax

Razor minimizes the number of characters and keystrokes required when writing a view template, and enables a fast, fluid coding workflow. Unlike most template syntaxes, you do not need to interrupt your coding to explicitly denote the start and end of server blocks within your HTML. The Razor parser is smart enough to infer this from your code. This enables a compact and expressive syntax which is clean, fast and fun to type.

For example, the Razor snippet below can be used to iterate a list of products:

mvc_syntax_1

When run, it generates output like:

mvc_syntax_2

One of the techniques that Razor uses to implicitly identify when a code block ends is to look for tag/element content to denote the beginning of a content region.  For example, in the code snippet above Razor automatically treated the inner <li></li> block within our foreach loop as an HTML content block because it saw the opening <li> tag sequence and knew that it couldn’t be valid C#. 

This particular technique – using tags to identify content blocks within code – is one of the key ingredients that makes Razor so clean and productive with scenarios involving HTML creation.

Using @: to explicitly indicate the start of content

Not all content container blocks start with a tag element tag, though, and there are scenarios where the Razor parser can’t implicitly detect a content block.

Razor addresses this by enabling you to explicitly indicate the beginning of a line of content by using the @: character sequence within a code block.  The @: sequence indicates that the line of content that follows should be treated as a content block:

mvc_syntax_3

As a more practical example, the below snippet demonstrates how we could output a “(Out of Stock!)” message next to our product name if the product is out of stock:

mvc_syntax_4

Because I am not wrapping the (Out of Stock!) message in an HTML tag element, Razor can’t implicitly determine that the content within the @if block is the start of a content block.  We are using the @: character sequence to explicitly indicate that this line within our code block should be treated as content.

Using Code Nuggets within @: content blocks

In addition to outputting static content, you can also have code nuggets embedded within a content block that is initiated using a @: character sequence. 

For example, we have two @: sequences in the code snippet below:

mvc_syntax_5

Notice how within the second @: sequence we are emitting the number of units left within the content block (e.g. - “(Only 3 left!”). We are doing this by embedding a @p.UnitsInStock code nugget within the line of content.

Multiple Lines of Content

Razor makes it easy to have multiple lines of content wrapped in an HTML element.  For example, below the inner content of our @if container is wrapped in an HTML <p> element – which will cause Razor to treat it as content:

mvc_syntax_6

For scenarios where the multiple lines of content are not wrapped by an outer HTML element, you can use multiple @: sequences:

mvc_syntax_7

Alternatively, Razor also allows you to use a <text> element to explicitly identify content:

mvc_syntax_8

The <text> tag is an element that is treated specially by Razor. It causes Razor to interpret the inner contents of the <text> block as content, and to not render the containing <text> tag element (meaning only the inner contents of the <text> element will be rendered – the tag itself will not).  This makes it convenient when you want to render multi-line content blocks that are not wrapped by an HTML element. 

The <text> element can also optionally be used to denote single-lines of content, if you prefer it to the more concise @: sequence:

mvc_syntax_9

The above code will render the same output as the @: version we looked at earlier.  Razor will automatically omit the <text> wrapping element from the output and just render the content within it. 

Advertsing

125X125_06

TagCloud

MonthList

CommentList