Issues

MVC Helpers and how they can work in Umbraco

I’m sure it goes against all good advice to introduce an article by listing what it is not about. Oh well - bad start - but I need a reminder to stay on topic!

his article is not:

  • A discussion on whether you should or shouldn’t use strongly typed models in Umbraco
  • A review of the different approaches for strongly typed models in Umbraco (there are quite a few)
  • An opinion on whether hand-crafted models are preferable to auto-generated ones

If you would like to read about the above then just skip to the bottom where I’ve included a couple of links to get you started!

Please note that in the rest of this article I use the abbreviation “uSTM” to represent the phrase “Strongly Typed Models in Umbraco”.  This should save me some keystrokes (a comment prompted by a recent visit to Scott Hanselman’s http://keysleft.com!)

So what am I writing about?

If you already use uSTM - or are just about to start - then the aim of this article is to get you to consider replacing the following line:

@Model.MyProperty

With:

@Html.DisplayFor(model => model.MyProperty)

Yes really, that’s it!  But before I start explaining why these few extra keystrokes will save you time when building your Umbraco website, I think we could benefit from some terminology clarification...

What is an MVC Helper?

To be pedantic, I should call them MVC HTML helpers.  They are just methods that return a string (likely to be HTML but could be plain text) to be rendered in the browser.  They are not new: I found a post from Microsoft in 2008 stating that the first release of MVC included some standard ones such as:

  • Html.ActionLink()
  • Html.DropDownList()
  • Html.TextBox()

For example:

@Html.TextBox("MyTextBoxName")

Renders as:

<input id="MyTextBoxName" name="MyTextBoxName" type="text" value="">

In MVC 2 Microsoft introduced strongly-typed HTML helpers, using lambda expressions to reference the model passed into a view.   MVC is all about the naming conventions, and so we were given Html.HelperNameFor() methods such as:

  • Html.DropDownListFor()
  • Html.CheckBoxFor()
  • Html.TextBoxFor

For example, with a model whose property SomeText is currently 'ABCDE':

@Html.TextBoxFor(model => model.SomeText)

Would render as:

<input id="SomeText" name="SomeText" type="text" value="ABCDE">

MVC determined what the name of the textbox should be.

MVC 2 also introduced some generic helpers (generic is just my word to mean not control-specific; Scott Gu just referred to them as "other" helpers!).  Fast-forward to the current day and in MVC 4 (still the default MVC version for Umbraco 7.2.x) the generic helpers include:

  • Html.DisplayFor()
  • Html.LabelFor()
  • Html.EditorFor()
  • Html.ValidationMessageFor()

The first one, Html.DisplayFor(), is what I want to look at in detail.  The other three, being concerned with the editing of data, are beyond the scope of this article but the GitHub repo referenced at the end contains examples of how they can be helpful to us in Umbraco too.

What does Html.DisplayFor() do?

As Microsoft have now open-sourced .Net, I could simply say start here and have a good wander round.  But that seems a bit lazy (although it is an interesting task which I've had to try a few times)!  Instead I'll start with an example using a document type that describes an Event such as a workshop.  

M is for … Model (EventPage.cs)

public class EventPage : BasePage
{
   public EventPage(IPublishedContent content) : base(content)
   {
   }

   public string Title { get; set; }
   public DateTime? EventDate { get; set; }
   public bool MembersOnly { get; set; }
   public string Introduction { get; set; }
}

V is for … View (EventPage.cshtml)

@inherits UmbracoViewPage<Umbraco.Site.Models.EventPage>
<h3>@Html.DisplayFor(model => model.Title)</h3>
<p><i>@Html.DisplayFor(model => model.EventDate)</i></p>
<p>
    Is this event for members only?
    @Html.DisplayFor(model => model.MembersOnly)
</p>
<p>@Html.DisplayFor(model => model.Introduction)</p>

C is for … Controller (EventPageController.cs)

public class EventPageController : RenderMvcController
{
   public override ActionResult Index(RenderModel model)
   {
      model = new RenderModel(model.Content.As());
      return base.Index(model);
   }
}

The controller is using the awesome Ditto package – as written about by James South in another Skrift article.  But I promised not to get distracted by that side of uSTM…

The output that renders in the browser looks like:

 The DisplayFor method has generated different HTML mark-up dependent upon the type of the model property being rendered:

  • The Boolean property has rendered as a disabled checkbox (yes, really!)
  • There is the odd other exception, but most data types are just rendered using .ToString()

We can change this behaviour by creating our own display templates.  Display templates are just partial razor views saved in the ~/Views/Shared/DisplayTemplates folder.

So first let's get rid of that horrible checkbox.  To do this we need to create a new script in the ~/Views/Shared/DisplayTemplates folder, with the name Boolean.cshtml (the name of the script needs to match the underlying data type).  

~/Views/Shared/DisplayTemplates/Boolean.cshtml

@model bool?       
@{
    var display = Model.GetValueOrDefault() ? "Yes" : "No";
}
@display

Refresh the browser (no compilation required) and now the output in the browser is:

So to clarify what that means…  By adding that one file called Boolean.cshtml in the DisplayTemplates folder, every instance in our site of @Html.DisplayFor(x => x.Property) - where Property has the type Boolean or nullable Boolean - will now be showing Yes or No instead of the checkbox.  

Hurray: site-wide consistency achieved with minimal effort!

What about localisation?

Well, hmmm, not so obvious that one (as far as I am aware).  We would get around that by using images or perhaps icon fonts in the display template, such as:

@model bool?      
@{
    var display = Model.GetValueOrDefault() ? "<i class='fa fa-tick'></i>" 
             : "<i class='fa fa-times'></i>";
}
@Html.Raw(display)

What about wanting a different template to be used for a specific property?

Let's consider our EventDate property.  Imagine that the vast majority of DateTime properties in our website need to display the date part, not the time.  We would create a DateTime display template to do just that:

~/Views/Shared/DisplayTemplates/DateTime.cshtml

@model DateTime?
@if (!Model.HasValue) { return; }
@Model.Value.ToShortDateString()

No localisation issues there then.  Now when we refresh the page we get:

The DateTime property is now showing only the date part.  So that's great for the vast majority of our DateTime properties.  But it's a fair expectation that our specific EventDate property should really show the time as well.  The solution to this is to use UIHint attributes.

UIHint Attributes for specific use cases

First let's create another version of the DateTime display template which includes the time.  As this template is only to be used in certain instances we can choose whatever name we like for the .cshtml (but it still must reside in the DisplayTemplates folder).  I'm going to call it "DateTimeBoth":

~/Views/Shared/DisplayTemplates/DateTimeBoth.cshtml

@model DateTime?
@if (!Model.HasValue) { return; }
@Model.Value.ToShortDateString() @Model.Value.ToString("HH:mm")

Then we need to add the System.ComponentModel.DataAnnotations.UIHint attribute to the property in our model class specifying the name of the template to use, i.e.:

[UIHint("DateTimeBoth")]
public DateTime? EventDate { get; set; }

We've made a change that requires compilation so after a build and a refresh, the browser now shows:

Great, we're now showing the time too.  Let's prove that this DateTimeBoth template is always used for this property by building a page with the list of events (again I'm side-stepping the uSTM magic that populates our child Events collection with EventPage items):

EventListing.cshtml

@foreach (var item in Model.Events)
{
    <p>
        <a href="@item.Url">@Html.DisplayFor(model => item.Name)</a>
        <br/><i>@Html.DisplayFor(model => item.EventDate)</i>
    </p>
}

So here we have a different instance of the DisplayFor method used with our EventDate property, which also shows the time (i.e. is also using the DateTimeBoth template) as follows:

In case you are wondering… if there were no DateTimeBoth.cshtml in the DisplayTemplates folder, MVC would revert to using the default template for the DateTime data type.

But what if the client then decides that, despite all promises to the contrary, they actually don't want to show the time of the event when it is being featured on the home page, they only want the date.  The solution to this is to specify the name of the display template in the DisplayFor method directly.

Specifying the template name for the exceptions to the rule

The Html.DisplayFor() method can take an extra parameter to indicate the template name to use.  To meet the new requirements, the DisplayFor call on the home page needs a second parameter of "DateTime" ensuring, in this instance, the correct template is used:

@Html.DisplayFor(model => item.EventDate, "DateTime")

Display templates recap

The MVC built-in display template is used for the data type, unless:

  • There is an appropriately named DataType.cstml in the DisplayTemplates folder
  • The property being rendered has a valid UIHint attribute set
  • The 'templateName' parameter has been set in the DisplayFor method call itself

Other benefits of using Html helpers

If you have all your properties being rendered in views using Html.DisplayFor() then I hope you can see how you can easily enforce site-wide consistency without losing flexibility to handle exceptions.  There are other benefits too though.

They are Null-friendly

Html helpers play nicely with nulls without you having to worry.  Consider what would happen with:

@Model.RelatedArticle.Name

If the RelatedArticle property was null, and you hadn't manually trapped this, the view would raise an exception.  Replace the line above with:

@Html.DisplayFor(model => model.RelatedArticle.Name)

This view won't raise an exception as the html helper doesn't fall over, it just returns the empty string.  That's what I mean by 'null-friendly'!

They work with custom types

When using uSTM it is likely you're going to have an Image or Media class, used in lots of places.  Usually we would put something like this into our views for each occurrence:

@if (Model.Image != null) {
    <img src='@Model.Image.Url' />
}

If we create:

~/Views/Shared/DisplayTemplates/Image.cshtml:

@if (Model.Image != null) {
    <img src='@Model.Image.Url' />
}@model Umbraco.Site.Models.Image                   
@if (Model == null || Model.Bytes == 0) { return; }
<img src="@Model.Url" alt="@Model.AltText" />

We can then replace the 'if' clause in our view with a single Html.DisplayFor() call to achieve the same result.

You can roll your own

Let's say we want to display a heading of "This month: X events".  If there is only 1 event in the current month, the page should of course show the singular unit (i.e. 'event' not 'events').   

You could use:

<h2>This month: @String.Format("{0} {1}", Model.Events, Model.Events == 1 ? "event" : "events")</h2>

But we could also create a custom helper, DisplayNumberWithUnitFor, in a static class in our website:

public static class HtmlHelpers
{
    public static MvcHtmlString DisplayNumberWithUnitFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, string unit)
    {
        var unitPlural = unit + "s";
        var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
        if (metadata.Model == null || String.IsNullOrEmpty(metadata.Model.ToString())) return new MvcHtmlString("");




        var value = Convert.ToDecimal(metadata.Model);
        return new MvcHtmlString(String.Format("{0} {1}", value, (value==1) ? unit : unitPlural));
    }
}

Which would allow us to simplify the line in the view to:

<h2>This month: @Html.DisplayNumberWithUnitFor(model => model.Events, "event")</h2>

Perhaps overkill if you only have one such occurrence, but if you have several the custom helper will definitely start saving time.

Github Repo

At a recent umbLondon meetup one of the regulars said "if there isn't a Github repo, then it isn't a proper presentation", and he had a point!  I now assume the same to be true for written articles.  But, being a Mercurial girl (anyone else also now humming Madonna?), the git-way is not obvious for me, yet!  But I've persevered and hopefully by the time this goes to press you should find a decent number of working examples of everything discussed here, and maybe even more such as:

  • Using the EditorFor, LabelFor templates
  • Creating your own Html helper methods
  • Custom code templates for scaffolding your models

The views in the repo should have two versions of each template so you can compare @Umbraco.Field code alongside @Html.DisplayFor.  It also proves that you can mix and match in your views if you really want or need to.

Repo URL: https://github.com/LottePitcher/UmbracoMvcHelpers

In Conclusion

I know that not everyone loves uSTM, or having to use Visual Studio.  We've been using these techniques in pure ASP.Net MVC applications for several years and are very appreciative of all the hard work that has gone into the various uSTM solutions that allow us to apply them to Umbraco too.  

I've been a bit surprised that when people list the pros of using uSTM, the html helpers don't get much of a look in.  Feel free to tweet me the reasons for why that might be.

And finally … big thanks to the Skrift team for giving me a reason to write sentences that end in full stops instead of semi-colons.  Oh, and of course to you for reading to the end!

Useful Links

Lotte Pitcher

Lotte is a .NET / Umbraco developer, Microsoft MVP and passionate advocate for being an active member of a healthy, inclusive tech community. She is part of the Developer Relations team at Umbraco and runs PAM Internet, a micro-agency in London. Outside of work, Lotte is most likely to be found on a padel court, strumming a ukulele, or being a Candid Contributions podcast host.

comments powered by Disqus