Click here to Skip to main content
Click here to Skip to main content

Simplifying HTML generation in code using Razor templates

, 20 Sep 2012
Rate this:
Please Sign up or sign in to vote.
In this article we are going to see how we can create HTML templates in Razor views and pass them to HTML helpers.

HTML Generation

ASP.NET MVC simplifies constructing HTML in code using the TagBuilder class. The built-in HTML helpers uses the TagBuilder class to generate textboxes, checkboxes, and other HTML stuff. Generating HTML from code is not flexible because every time we need to alter the HTML we have to go for recompilation.

The built-in ValidationSummary HTML helper displays the validation errors as an unordered list. In some cases we need to customize the way in which the errors are being displayed, say as a table instead of list. The ValidationSummary method creates the list inside the code and we can't customize it. All we could do is create our own custom helper to display the errors as a table.

It would be nice if we could pass the HTML structure or template that controls the way in which the validation errors are being displayed to the user to the helper from the view. In this article we are going to see how we can create HTML templates in Razor views and pass them to HTML helpers.

Templated Razor Delegates

Razor delegates are an excellent ways to create templates that could be easily passed to a helper from views. Phil Haack has thrown some initial thoughts on this subject right here.

Razor delegates are nothing but they are templates build by C# and HTML.

A simple Razor delegate looks like this,

Func<dynamic, HelperResult> variable = @@item;
Listing 1. A simple Razor delegate

Let's see the right-hand side first. The markup starts with the "@" character and ends with ";". The @item parameter represents the passed argument to the delegate/template.

The markup represents a Func delegate that is represented in the left-hand side. The first parameter of the delegate represents the type of the parameter passed to the template and the second parameter represents the type returned by the Razor markup, which is HelperResult.

Before diving deep into templates I would like to tell more about HelperResult.

HelperResult

HelperResult is the type returned from declarative HTML helpers. Not only that, we can return HelperResult from code as well. The HelperResult class implements IHtmlString, which represents a HTML-encoded string that should not be encoded again.

Let's see a simple HTML helper that returns the server time as HelperResult.

public static HelperResult ServerTime(this HtmlHelper htmlHelper)
{
	return new HelperResult
	(
		writer => 
		{
			writer.Write(String.Format("{0}", DateTime.Now.ToShortTimeString()));
		}
	);						
}
Listing 2. HTML helper that returns server time as HelperResult

The HelperResult class has a constructor that takes Action<TextWriter> as parameter. In the above helper we are writing the server time to the writer in the action that is passed to the HelperResult's constructor.

What happens if the helper just returns a plain string instead of HelperResult?

public static string ServerTime(this HtmlHelper htmlHelper)
{
	return String.Format("<i>{0}</i>", DateTime.Now.ToShortTimeString());
}
Listing 3. HTML helper that returns server time as string

The Razor engine HTML encodes the output and this is how the time is displayed to the user.

We can rewrite the above HTML helper into a declarative one as below,

@helper ServerTime() {
	<i>@DateTime.Now.ToShortTimeString()</i>
}
Listing 4. Declarative HTML helper

As I said earlier, this also returns a HelperResult as output. Like HelperResult there is one more type that implements IHtmlString which is MvcHtmlString.

Complex Razor Delegates

In listing 1. we saw a simple Razor template. We can also create complex templates like the below one that spans multiple lines.

Func<Product[], HelperResult> tableTpl = @<table>
	@foreach(var p in @item)
	{
		<tr>
			<td>@p.Name</td>
			<td>@p.Price $</td>
		</tr>
	}				
</table>;
Listing 5. Complex Razor delegate

One important thing to point out in the above template is I've strongly typed the input parameter type of Func delegate to Product[]. The advantage I'm going to get is intellisense.

We can pass only one argument to the Razor template and it can be accessed through the @item parameter.

Razor delegates as templates

Let's see how we can pass the Razor delegate as template to the HTML helper we saw in listing 2. The advantage of passing a template to the helper is we can display the server time in different styles. Here is our modified helper.

public static HelperResult ServerTime(this HtmlHelper htmlHelper, 
	Func<string, HelperResult> template)
{
	return template(DateTime.Now.ToShortTimeString()); 
}
Listing 6. HTML helper that accepts Razor delegate as template

The parameter Func<Name, HelperResult> represents our template. Inside the method, all we doing is calling the template passing the server time as input parameter. The template returns HelperResult as output which is returned from the method.

Here are some of the examples of how we can pass different templates to the helper to display the time in different styles.

@HTML.ServerTime(@@item)

@HTML.ServerTime(@@item)
Listing 7. Passing templates to HTML helper

Displaying validation errors using templates

As you know, the built-in ValidationSummary helper can display both the property and model validation errors. The ValidationSummary helper displays the errors as an unordered list(<ul>). As default the ValidationSummary displays both the property and model errors but we can filter out the property errors by passing true to excludePropertyErrors.

The following is the typical HTML generated by the ValidationSummary helper.

<div class="validation-summary-errors" data-valmsg-summary="true">
	<ul>
		<li>Duplicate Order.</li>
		<li>Invalid Product Code.</li>
	</ul>
</div>
Listing 8. HTML rendered by ValidationSummary

The default implementation of the ValidationSummary is sufficient in most of the cases but it creates some inconvenience when we try to display only the model errors. The issue is even though there are no model errors it still renders the below HTML and this may creates some CSS issues.

<div class="validation-summary-errors">
	<ul>
		<li style="display: none;" />
	</ul>
</div>
Listing 9. HTML rendered by ValidationSummary when there are no model errors

Custom ValidationSummary helper

In this section we are going to create a simple HTML helper that displays only the model validation errors in any format that is decided by the passed template. Let's call our custom helper as ModelValidationSummary and the implementation is shown below.

public static HelperResult ModelValidationSummary(this HtmlHelper 
       htmlHelper, Func<string[], HelperResult> tpl)
{
	ModelState ms;
	htmlHelper.ViewData.ModelState.TryGetValue(htmlHelper.ViewData.TemplateInfo
              .HtmlFieldPrefix, out ms);

	if (ms != null && ms.Errors != null && ms.Errors.Count > 0)
		return tpl(ms.Errors.Select(p => p.ErrorMessage).ToArray());

	return null;
} 
Listing 10. Custom ModelValidationSummary that takes a delegate/template as input parameter

The code is very simple. We are reading the model errors from the ModelState and passing it to the template as a string array.

Here are some examples of how we can pass different templates to our custom ModelValidationSummary helper to display the model validation errors in different formats.

@HTML.ModelValidationSummary(@<ol class="val-container">
@foreach(var error in @item)
{
	<li>@error</li>
}
</ol>) 
Listing 11. Passing template that displays validation errors as an ordered list.
@HTML.ModelValidationSummary(@<table>
	@foreach(var error in @item)
	{
		<tr>
			<td class="error-icon"></td>
			<td class="error">@error</td>
		</tr>
	}
</table>) 
Listing 12. Passing template that displays validation errors in a table.

Creating global templates in application

Mostly applications prefers to display the validation errors in a common format throughout the pages. In those cases we don't like to create the template in every view and pass to our ModelValidationSummary helper instead we would like to store it in a global variable that could be accessible anywhere from views.

One way we can create global templates is by creating a view in App_Code folder and writing the templates as declarative HTML helpers. Unfortunately, storing templates as public static variables in functions won't work and we have to rely on declarative helpers. Let's create a view in the App_Code folder with the name Templates.cshtml that contains the global validation template as a HTML helper.

@helper ValidationTemplate(string[] errors)
{
	<ol class="val-container">
	@foreach(var error in errors)
	{
		<li>@error</li>
	}
	</ol>
}
Listing 13. Creating global templates as declarative HTML helpers.

Now from the views we can do something like this:

@HTML.ModelValidationSummary(Templates.ValidationTemplate) 
Listing 14. Calling ModelValidationSummary by passing HTML helper as template

That's cool! We can pass the HTML helper just like a delegate to our ModelValidationSummary method and the result is same! 

Accessing templates from code

How about accessing global templates in the ModelValidationSummary helper? Let's modify the ModelValidationSummary helper such that if we don't passes any template it uses the default (global) one else uses the passed one. The problem is we can't just directly access the declarative HTML helper from code, fortunately, using the David Eboo's RazorGenerator extension tool we can easily create re-usable classes from views.

RazorGenerator

RazorGenerator is an extension tool for Visual Studio and we can install it by going to Tools > Extension Manager and search for RazorGenerator.

RazorGenerator extension

Installing RazorGenerator extension

After installing RazorGenerator we have to restart Visual Studio. The next step is we have to create class for the Templates.cshtml file. For that, we have to go to the properties of the file and set Build Action to None and Custom Tool to RazorGenerator.

Setting Custom Tool for Razor file

Setting Custom Tool for Razor file

On saving, Templates.generated.cs file will be created that contains the equivalent C# class for the declarative HTML helper ValidationTemplate we saw in listing 13. To access the generated class from code we have to set the Build Action to Compile for the Templates.generated.cs file.

Now we can access the declarative HTML helper ValidationTemplate from the code and this is our modified ModelValidationSummary helper.

public static HelperResult ModelValidationSummary(this HtmlHelper
    htmlHelper, Func<string[], HelperResult> tpl = null)
{
	ModelState ms;
	htmlHelper.ViewData.ModelState.TryGetValue(htmlHelper.ViewData.TemplateInfo
            .HtmlFieldPrefix, out ms);

	if (ms != null && ms.Errors != null && ms.Errors.Count > 0)
	{
		var errors = ms.Errors.Select(p => p.ErrorMessage).ToArray();

		if (tpl == null)
			return App_Code.Templates.ValidationTemplate(errors);

		return tpl(errors);
	}

	return null;
} 
Listing 15. Modified ModelValidationSummary helper

Hope you enjoyed this article. Glad to hear comments from you.

 Download Sample 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

After2050
Software Developer Trigent Software Private Limited
India India
I'm a software developer from south tip of India. I spent most of the time in learning new technologies. I've a keen interest in client-side technologies especially JavaScript and admire it is the most beautiful language ever seen.
 
I like sharing my knowledge and written some non-popular articles. I believe in quality and standards but blames myself for lagging them.
 
I believe in small things and they makes me happy!
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 PinmemberLee Humphries14-Jan-13 12:27 
GeneralMy vote of 5 PinmvpKanasz Robert12-Sep-12 3:16 
GeneralRe: My vote of 5 PinmemberAfter205012-Sep-12 4:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140902.1 | Last Updated 20 Sep 2012
Article Copyright 2012 by After2050
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid