Thursday, December 5, 2013

Html.RatingFor: Extending the MVC HtmlHelper

When working on a web application, I was in the need to add a rating for a product. That rating will be between 1 and 5 and will be always an int. So my model has a property like public int Rating {get;set;}. I decided to add 5 radio buttons, and each will hold the corresponding rating value.

But then (as always happen) the requirement changed. We didn't want to have only 1 rating property, but 5. So adding 5 radios for each was something that I didn't want to happen

In order to solve this problem, I created an extension method for the HtmlHelper class that we normally use in our MVC applications. As you may notice, in the method I created all the logic for adding the set of radio buttons needed for the rating process.


public static MvcHtmlString RatingFor(this HtmlHelper htmlHelper, Expression> expression, int from, int to, object htmlAttributes = null)
	{
		var builder = new StringBuilder();
 
		var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
 
		var model = metadata.Model;
		var name = ExpressionHelper.GetExpressionText(expression);
 
		var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
 
		var fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
 
		int direction = 1;
		if (from > to)
			direction = -1;
 
		for (var i = from; direction == 1 ? i <= to : i >= to; i += direction)
		{
			var tagBuilder = new TagBuilder("input");
			tagBuilder.MergeAttributes(attributes);
			tagBuilder.MergeAttribute("type", "radio");
			tagBuilder.MergeAttribute("name", fullName, true);
			tagBuilder.MergeAttribute("value", i.ToString(CultureInfo.InvariantCulture));
			//If model has a value we need to select it
			if (model != null && model.Equals(i))
			{
				tagBuilder.MergeAttribute("checked", "checked");
			}
			tagBuilder.GenerateId(fullName);
 
 
			ModelState modelState;
			if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
			{
				if (modelState.Errors.Count > 0)
				{
					tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
				}
			}
 
			tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
 
			builder.AppendLine(tagBuilder.ToString(TagRenderMode.SelfClosing));
		}
 
 
		return MvcHtmlString.Create(builder.ToString());
	}


One important part of this code is
if (model != null && model.Equals(i))
{
 tagBuilder.MergeAttribute("checked", "checked");
}

where we assign the value of the property if it is already set. This is useful when you use this method on an Edit process.

Now on your view, instead of having to create all that radio buttons manually, you can have something like this
@Html.RatingFor(model => model.Rating, 1, 5)
in order to add a rating from 1 to 5.

Hopefully you will find this useful. If you have created another useful helper, it would be nice if you share it with the community :)

 

Copyright @ 2013 A learning journey.