ASP.NET MVC View Best Practices – Save time creating views with MvcContrib.FluentHtml

This isn’t strictly a best practice post, but it is now I am currently creating html in my ASP.NET MVC views.

It must be a good approach because Microsoft are implementing Templated Helpers to do what I’m showing you today in ASP.NET MVC 2. See Ben Scheirmans’ post A First Look at ASP.NET MVC 2 for more information.

In this post I want to show you two view implementations based on adapted versions of the Account Controller and LogOn view which are created whenever you do File > New > Project > ASP.NET MVC Web Application.

The slimmed down AccountController looks like this

using System;
using System.Web.Mvc;

namespace AspNetMvcViewBestPractices.Controllers
{    
    public class AccountController : Controller
    {
        public ActionResult LogOn()
        {
            return View();   
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult LogOn(string UserName, string Password, bool RememberMe)
        {
            if (!ValidateLogOn(UserName, Password))
            {
                return View();
            }

            return RedirectToAction("Index", "Product");
        }

        private bool ValidateLogOn(string userName, string password)
        {
            if (String.IsNullOrEmpty(userName))
            {
                ModelState.AddModelError("UserName", "You must specify a username.");
            }
            if (String.IsNullOrEmpty(password))
            {
                ModelState.AddModelError("Password", "You must specify a password.");
            }
            if (!(userName == "admin" && password == "password"))
            {
                ModelState.AddModelError("_FORM", "The username or password provided is incorrect.");
            }

            return ModelState.IsValid;
        }
    }
}

The LogOn view is shown below

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="loginContent" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Log On</h2>
 
    <%= Html.ValidationSummary("Login was unsuccessful. Please correct the errors and try again.") %>

    <% using (Html.BeginForm()) { %>
        <div>
            <fieldset>
                <legend>Account Information</legend>
                <p>
                    <label for="UserName">UserName</label>
                    <%= Html.TextBox("UserName") %>
                    <%= Html.ValidationMessage("UserName") %>
                </p>
                <p>
                    <label for="Password">Password</label>
                    <%= Html.Password("Password") %>
                    <%= Html.ValidationMessage("Password") %>
                </p>
                <p>
                    <%= Html.CheckBox("RememberMe") %> <label class="inline" for="RememberMe">Remember me?</label>
                </p>
                <p>
                    <input type="submit" value="Log On" />
                </p>
            </fieldset>
        </div>
    <% } %>
</asp:Content>

There are two problems I have this view. The first is that everything is tightly coupled using what are known as magic strings. For example if you change the line

<%= Html.TextBox("UserName") %>

to

<%= Html.TextBox("Username") %>

the UserName argument in the LogOn form post action no longer gets populated when the form is submitted. This is because the name for the id in the form and name in the method signature need to be the same.

The other is the amount of html you have to manually write. Sometimes Visual Studio can tell you if have made a typo in your html mark up, but not always. For example it can’t detect an error like this

<input type="sumbit" /> 

As I mentioned in my previous post unless you go through the laborious process of creating front end tests, the first time you will notice is when you view the website in a browser or one of your customers tell you.

How I would create the LogOn view

1. Create a LogOnForm data transfer object (DTO)

namespace AspNetMvcViewBestPractices.Models
{
    public class LogOnForm
    {
        public string UserName { get; set; }
        public string Password { get; set; }
        public bool RememberMe { get; set; }
    }
}

2. Change the signature of the Account controllers’ LogOn post action to take in the LogOnForm DTO. This model will be populated by the ASP.NET MVC model binders using the form values.

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult LogOn(LogOnForm logOnForm)
{
    if (!ValidateLogOn(logOnForm.UserName, logOnForm.Password))
    {
        return View(logOnForm);
    }

    return RedirectToAction("Index", "Product");
}      

3. Add a reference to the MvcContrib.FluentHtml assembly.

4. Change the view so it inherits from ‘MvcContrib.FluentHtml.ModelViewPage‘.

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="MvcContrib.FluentHtml.ModelViewPage<LogOnForm>" %>

5. Use the MvcContrib.FluentHtml assembly to create the html in the view

            <fieldset>
                <legend>Account Information</legend>
                <p>
                   <%=this.TextBox(m => m.UserName).Label("UserName")%>
                   <%=this.ValidationMessage(m => m.UserName)%>
                </p>
                <p>
                   <%=this.TextBox(m => m.Password).Label("Password")%>
                   <%=this.ValidationMessage(m => m.Password )%>
                </p>
                <p>
                    <%=this.CheckBox(c => c.RememberMe).LabelAfter(" Remember me?","inline")%>
                </p>
                <p>
                    <%=this.SubmitButton("Log On") %>
                </p>
            </fieldset>

Now there is less chance of any html typos in the view and we no longer have magic strings for the ids of the input fields. The MvcContrib.FluentHtml assembly can generate the ids from the property names in the view model for you automatically.

This means you no longer have to search for strings in views if you change the name of a property.

If you’re at all worried about the html generated by the MvcContrib.FluentHtml assembly don’t be, it’s as good if you had written it yourself.

For more information about the MvcContrib.FluentHtml assembly check out blog posts below:

What about ASP.NET MVC 2?

In ASP.NET MVC 2 the DisplayFor html helper can be used to create the html for the model like this

<%= Html.DisplayFor(m => m.Password) %>

What it can’t do (at the time of writing) is any chaining for things like labels e.g.

<%=this.TextBox(m => m.Password).Label("Password")%>

Jag Reehal’s Final Thought on ‘ASP.NET MVC View Best Practices – Save time creating views with MvcContrib.FluentHtml’

in this post I wanted to demonstrate the why at this moment in time I use the MvcContrib.FluentHtml assembly for creating html in my views.

It will definitely save you time and you will be more productive because you’re no longer going to write as much html plumbing code.

ASP.NET MVC View Best Practices – Keep logic out of your views

When it comes to views in ASP.NET MVC, you won’t be short of options how you decide how to create them.

One of the easiest mistakes is to implement logic in their views.

It’s no surprise developers such as Rob Conery have advised against having conditional code in ASP.NET MVC views. Before you know it a view that was simple and easy to understand becomes a complex monster intertwined with html and logic.

The code used in this post can be downloaded here.

The fact is ASP.NET MVC views which contain logic will ultimately end up being a nightmare to maintain and test.

Here is an example of what I mean. When displaying product information, our client wants to show to the number of units in stock. The background colour should reflect the stock level as shown below (along with the css class name)

Number In Stock Colour should be Css class name
0 Red stockLevelNone
1-20 Yellow stockLevelLow
Over 20 Green stockLevelHigh



This could be implemented by creating the view like this

<div class="stockLevel
   <%=Model.UnitsInStock == 0 ? " stockLevelNone" : String.Empty%>
   <%=Model.UnitsInStock > 0 && Model.UnitsInStock <= 20 ? " stockLevelLow" : String.Empty%>
   <%=Model.UnitsInStock > 20 ? " stockLevelHigh" : String.Empty%>
   ">
    <span>Number Of Units In Stock: <%=Model.UnitsInStock %></span>
</div>

Because there is logic in the ASP.NET MVC view, you have to go through the laborious process of creating front end tests or creating a product, setting the number in stock and then checking the correct class is used by viewing the page source for each condition. And if that wasn’t difficult enough, imagine doing that every time you do a release.

So what if I said there are alternative methods which could:

  • reduce the chance of typos in your html
  • clean up your view
  • be testable
  • be reused so you can follow the “Don’t Repeat Yourself (DRY)” principle which will save you so much time!

Option1 – Move the logic for choosing the css class into a helper method

The conditional logic in the ASP.NET MVC view can be replaced by a method which takes in the number of units in stock and returns the appropriate css class as shown below.

using System;

namespace AspNetMvcViewBestPractices.Helpers
{
    public static class ProductCssHelper
    {
        public const string STOCK_LEVEL_NONE_CSS_CLASS = "stockLevelNone";
        public const string STOCK_LEVEL_LOW_CSS_CLASS = "stockLevelLow";
        public const string STOCK_LEVEL_HIGH_CSS_CLASS = "stockLevelHigh";

        public static string GetCssClassForUnitsInStock(int unitsInStock)
        {
            if (unitsInStock == 0)
            {
                return STOCK_LEVEL_NONE_CSS_CLASS;
            }
            if (unitsInStock > 0 && unitsInStock <= 20)
            {
                return STOCK_LEVEL_LOW_CSS_CLASS;
            }
            if (unitsInStock > 20)
            {
                return STOCK_LEVEL_HIGH_CSS_CLASS;
            }
            return String.Empty;
        }
    }
}

In the view the number of units in stock is passed to the css helper method

<div class="stockLevel <%=ProductCssHelper.GetCssClassForUnitsInStock(Model.UnitsInStock)%>">        
    <span>Number Of Units In Stock: <%=Model.UnitsInStock %></span>
</div>

And the css helper method can now be unit tested as shown below

using System;
using AspNetMvcViewBestPractices.Helpers;
using NUnit.Framework;

namespace AspNetMvcViewBestPractices.Tests
{
    [TestFixture]
    public class ProductCssHelperTests
    {
        [Test]
        public void Will_Return_StockLevelNone_Css_Class_If_There_Are_No_Units_In_Stock()
        {
            // Arrange
            int unitsInStock = 0;

            // Act 
            string cssClass = ProductCssHelper.GetCssClassForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(ProductCssHelper.STOCK_LEVEL_NONE_CSS_CLASS,cssClass);
        }

        [Test]
        public void Will_Return_StockLevelLow_Css_Class_If_Units_In_Stock_Is_Between_One_And_Twenty(
            [Range(1, 20)] int unitsInStock)
        {
            // Arrange        

            // Act 
            string cssClass = ProductCssHelper.GetCssClassForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(ProductCssHelper.STOCK_LEVEL_LOW_CSS_CLASS, cssClass);
        }


        [Test]
        public void Will_Return_StockLevelHigh_Css_Class_If_Units_In_Stock_Is_Over_Twenty()
        {
            // Arrange        
            int unitsInStock = 21;

            // Act 
            string cssClass = ProductCssHelper.GetCssClassForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(ProductCssHelper.STOCK_LEVEL_HIGH_CSS_CLASS, cssClass);
        }

        [Test]
        public void Will_Return_EmptyString_If_Units_In_Stock_Is_Less_Then_Zero()
        {
            // Arrange        
            int unitsInStock = -1;

            // Act 
            string cssClass = ProductCssHelper.GetCssClassForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(String.Empty, cssClass);
        }
    }
}

Option 2 – Create a method to build the html for displaying the number of units in stock

I first saw this approach in Mike Hadlows’ Suteki Shop. Here an html helper method takes in the number of units in stock and returns an html string

using System;

namespace AspNetMvcViewBestPractices.Helpers
{
    public static class ProductHtmlHelper
    {
        public const string STOCK_LEVEL_NONE_CSS_CLASS = "stockLevelNone";
        public const string STOCK_LEVEL_LOW_CSS_CLASS = "stockLevelLow";
        public const string STOCK_LEVEL_HIGH_CSS_CLASS = "stockLevelHigh";
        public const string UNITS_IN_STOCK_HTML = @"<div class=""stockLevel {0}""><span>Number Of Units In Stock: {1}</span></div>";

        public static string GetHtmlForUnitsInStock(int unitsInStock)
        {
            if (unitsInStock == 0)
            {                
                return String.Format(UNITS_IN_STOCK_HTML,STOCK_LEVEL_NONE_CSS_CLASS,unitsInStock);
            }
            if (unitsInStock > 0 && unitsInStock <= 20)
            {
                return String.Format(UNITS_IN_STOCK_HTML, STOCK_LEVEL_LOW_CSS_CLASS, unitsInStock);
            }
            if (unitsInStock > 20)
            {
                return String.Format(UNITS_IN_STOCK_HTML, STOCK_LEVEL_HIGH_CSS_CLASS, unitsInStock);
            }
            return String.Format(UNITS_IN_STOCK_HTML, String.Empty, unitsInStock);
        }
    }
}

In the view a call is made to the html helper method passing the number of units in stock

<%=ProductHtmlHelper.GetHtmlForUnitsInStock(Model.UnitsInStock)%>

The html helper can be unit tested like this

using System;
using AspNetMvcViewBestPractices.Helpers;
using NUnit.Framework;

namespace AspNetMvcViewBestPractices.Tests
{
    [TestFixture]
    public class ProductHtmlHelperTests
    {
        [Test]
        public void Will_Return_StockLevelNone_Html_If_There_Are_No_Units_In_Stock()
        {
            // Arrange
            int unitsInStock = 0;
            string expectedResult = @"<div class=""stockLevel stockLevelNone""><span>Number Of Units In Stock: 0</span></div>";

            // Act 
            string html = ProductHtmlHelper.GetHtmlForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(expectedResult,html);
        }

        [Test]
        public void Will_Return_StockLevelLow_Html_If_Units_In_Stock_Is_Between_Zero_And_Twenty(
            [Range(1, 20)] int unitsInStock)
        {
            // Arrange        
            string expectedResult = String.Format(@"<div class=""stockLevel stockLevelLow""><span>Number Of Units In Stock: {0}</span></div>", unitsInStock);

            // Act             
            string html = ProductHtmlHelper.GetHtmlForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(expectedResult, html);
        }


        [Test]
        public void Will_Return_StockLevelHigh_Html_If_Units_In_Stock_Is_Over_Twenty()
        {
            // Arrange        
            int unitsInStock = 21;
            string expectedResult = @"<div class=""stockLevel stockLevelHigh""><span>Number Of Units In Stock: 21</span></div>";

            // Act 
            string html = ProductHtmlHelper.GetHtmlForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(expectedResult, html);
        }

        [Test]
        public void Will_Return_Html_With_NoStockLevelIndicator_If_Units_In_Stock_Is_Less_Then_Zero()
        {
            // Arrange        
            int unitsInStock = -1;
            string expectedResult = @"<div class=""stockLevel ""><span>Number Of Units In Stock: -1</span></div>";

            // Act             
            string html = ProductHtmlHelper.GetHtmlForUnitsInStock(unitsInStock);

            // Assert
            Assert.AreEqual(expectedResult, html);
        }
    }
}

Option 3 – Use a Product data transfer object with a css class name property

Instead of passing the product model into the view, we could map it into a data transfer object that has a property for the css class name. For this to work the controller needs to perform the mapping before returning a model to the view. I will be covering the why you should avoid using domain models directly into views in a future blog post.

The view model/data transfer object is shown below

namespace AspNetMvcViewBestPractices.Models
{
    public class ProductDto
    {
        public Product Product { get; set; }
        public string UnitsInStockCssClassName { get; set; }
    }
}

The mapping class looks like this

using AspNetMvcViewBestPractices.Helpers;
using AspNetMvcViewBestPractices.Models;

namespace AspNetMvcViewBestPractices.Mappers
{
    public static class ProductDtoMapper
    {
        public static ProductDto Map(Product product)
        {
            ProductDto productDto = new ProductDto();
            productDto.Product = product;
            productDto.UnitsInStockCssClassName =  ProductCssHelper.GetCssClassForUnitsInStock(product.UnitsInStock);
            return productDto;
        }
    }
}

And can be unit tested like this

using AspNetMvcViewBestPractices.Mappers;
using AspNetMvcViewBestPractices.Models;
using NUnit.Framework;

namespace AspNetMvcViewBestPractices.Tests
{
    [TestFixture]
    public class ProductDtoMapperTests
    {
        [Test]
        public void Can_Map_Product_to_ProductDto()
        {
            // Arrange
            Product product = new Product();
            product.UnitsInStock = 10;

            // Act 
            ProductDto productDto = ProductDtoMapper.Map(product);

            // Assert
            Assert.AreEqual(product, productDto.Product);
        }
    }
}

The controller uses the Product mapper as shown below

public ActionResult DisplayProductUsingMapper()
{
    Product product = new Product();
    Random random = new Random();
    product.UnitsInStock = random.Next(0, 30);

    ProductDto productDto = ProductDtoMapper.Map(product);
    return View(productDto);
}

Now the view does not need to make any method calls and only uses properties in the view model

<div class="stockLevel <%=Model.UnitsInStockCssClassName%>">        
    <span>Number Of Units In Stock: <%=Model.Product.UnitsInStock %></span>
</div>

Option 4 – When you need to show all css options in the view

I created this option after a comment I received. If you’re working with designers and/or want to show the css classes in the view this option will be to go for. Similar to first option we can use a css helper but this time we pass in the the number of units in stock and the css classes as shown below

using System;

namespace AspNetMvcViewBestPractices.Helpers
{
    public static class ProductCssHelper
    {
        public static string SelectCssClassForUnitsInStock(int unitsInStock, string stockLevelNone, string stockLevelLow, string stockLevelHigh)
        {
            if (unitsInStock == 0)
            {
                return stockLevelNone;
            }
            if (unitsInStock > 0 && unitsInStock <= 20)
            {
                return stockLevelLow;
            }
            if (unitsInStock > 20)
            {
                return stockLevelHigh;
            }
            return String.Empty;
        }
    }
}

In the view the number of units in stock and the css classes are passed to the css helper method

<div class="stockLevel <%=ProductCssHelper.SelectCssClassForUnitsInStock(Model.UnitsInStock,"stockLevelNone","stockLevelLow","stockLevelHigh")%>">        
    <span>Number Of Units In Stock: <%=Model.UnitsInStock %></span>
</div>

And can be unit tested like this

using System;
using AspNetMvcViewBestPractices.Helpers;
using NUnit.Framework;

namespace AspNetMvcViewBestPractices.Tests
{
    [TestFixture]
    public class ProductCssHelperTests
    {     
        [Test]
        public void SelectCssClassForUnitsInStock_Returns_First_Css_Class_If_There_Are_No_Units_In_Stock()
        {
            // Arrange
            int unitsInStock = 0;

            // Act 
            string cssClass = ProductCssHelper.SelectCssClassForUnitsInStock(unitsInStock,"a","b","c");

            // Assert
            Assert.AreEqual("a", cssClass);
        }

        [Test]
        public void SelectCssClassForUnitsInStock_Returns_Second_Css_Class_If_Units_In_Stock_Is_Between_One_And_Twenty(
            [Range(1, 20)] int unitsInStock)
        {
            // Arrange        

            // Act 
            string cssClass = ProductCssHelper.SelectCssClassForUnitsInStock(unitsInStock, "a", "b", "c");

            // Assert
            Assert.AreEqual("b", cssClass);
        }


        [Test]
        public void SelectCssClassForUnitsInStock_Returns_Third_Css_Class_If_Units_In_Stock_Is_Over_Twenty()
        {
            // Arrange        
            int unitsInStock = 21;

            // Act 
            string cssClass = ProductCssHelper.SelectCssClassForUnitsInStock(unitsInStock, "a", "b", "c");

            // Assert
            Assert.AreEqual("c", cssClass);
        }

        [Test]
        public void SelectCssClassForUnitsInStock_Returns_Empty_String_If_Units_In_Stock_Is_Less_Then_Zero()
        {
            // Arrange        
            int unitsInStock = -1;

            // Act 
            string cssClass = ProductCssHelper.SelectCssClassForUnitsInStock(unitsInStock, "a", "b", "c");

            // Assert
            Assert.AreEqual(String.Empty, cssClass);
        }
    }
}

Jag Reehal’s Final Thought on ‘ASP.NET MVC View Best Practices – Keep logic out of your views’

I like my views to contain html but not logic so I prefer options 1, 3 and 4. Because the html is hidden in option 2 finding out what method rendered what html could be like finding a needle in a haystack. In addition the the unit tests for the html helper method are difficult to maintain as you have to check the entire html string is correct.

If you’re working with a designer you should choose option 4.

ASP.NET MVC V2 and I met Scott Gu

Lets get one thing out the way, yes I’m a geek, maybe not a super geek but a geek nevertheless. For me Scott Guthrie is up there with my Barca heroes Xavi, Iniesta Messi, Henry, Puyol, Valdés, Busquets, Krkić etc.

Yesterday I went to Scott Gu’s demo of ASP.NET MVC 2 (using the preview 2 release) in London and got to meet Mr ASP.NET MVC ;)

Scott Gu and Jag Reehal

I’m not going to cover everything that’s coming in ASP.NET MVC 2 in any depth because Scott Gu and Ben Scheirman’ First Look at ASP.NET MVC 2 do a good job of that.

It’s interesting to see that Microsoft is taking rapid web development and templating so seriously. Using the new UI helpers you can create html for labels, inputs and validation with just one line of code in your view instead of three.

This will save you time and mean there is less chance of having any html typos. It will also make it easier to refactor because everything is strongly typed! If you wanted to do this now you could create an html helper extension.

What you can’t do now (not easily anyway) is just have the UI helpers create the label, input and validation for an entire model with one line of code. This will save you even more time and means you don’t have to manually edit html in views just because you have added new properties to your view model.

Amazing I think you will agree, however I can’t help thinking it’s not going to be testable and could potentially lead to inadvertently displaying something you didn’t want to show.

Scott also showed how to use System.ComponentModel.DataAnnotations for validation and the client side validation which uses the jQuery validation framework. If you want that functionality now checkout Steve Sanderson’s excellent xVal framework.

I asked Scott if there was a possibility of not having ‘magic strings’ in action links and the render partial methods. He said they were working on it, so hopefully we could see something in preview 3.

As for release dates, Scott Gu suggested ASP.NET MVC 2 should be ready early next year.

I have no doubt that ASP.NET MVC 2 will make it even better to develop web sites with.

If you want to see a recording of Scott Guthrie talking about ASP.NET MVC 2 it’s available on Channel 9.

Page 20 of 23« First...10...1819202122...Last »