@Html and beyond

I like the way MVC builds the content of my web-apps. I write plain HTML, and the @Html helper object will build all Html controls needed for in and output of the dynamic data. The nice thing is that the HtmlHelper object is strongly typed, all properties of the view’s model are available as lambda expressions.

<tr>

    <td>

        @Html.LabelFor(m => m.AanmeldingsKlacht)

    </td>

    <td>

        @Html.TextBoxFor(m => m.AanmeldingsKlacht)

    </td>

</tr>

 

Nice clean code.

Things are getting harder when you need more than the standards the HtmlHelper class gives you.

Extending the HtmlHelper

The way to extend the HtmlHelper is by using extension methods. An extension method extends an existing class without inheriting from it like in “classical” oop. Which extension methods are added to an object is ruled by the namespaces used in the code using the extended class.

The methods of the HtmlHelper class should return strings typed as MvcHtmlStrings. A standard string would be Html encoded by the view engine. An MvcHtmlString is literealy rendered into the view. The way to go is to extend the HtmlHelper class with methods which will build plain Html.

As an example of this is Purple, a very simple extension method which does demonstrate the principles and possibilities.

public static MvcHtmlString Purple<TModel, TProperty>(

this HtmlHelper<TModel> htmlHelper,

Expression<Func<TModel, TProperty>> expression)

{

    return new MvcHtmlString(

string.Format(“<span style=’background-color: blueviolet’>{0}</span>”,

                htmlHelper.EditorFor(expression)));

}

 

Note the following points

  • The return type is MvcHtmlString, as discussed above
  • The method is generic. <TModel> is the type of the model of the view where the method is used. <TProperty> is the type of the model’s property.
  • The method is static. The method’s class also has to be static
  • The first parameter this HtmHelper<TModel> hooks the the method into the HtmlHelper object
  • The second parameter expression describes the lambda of the property
  • The htmlHelper object itself, with its rich collection  of methods and properties is available. Here it builds an EditorFor the expression.
  • The result is embedded in a plain Html span with a  “nice”  background

Now when the view uses the class containing this method I can do this:

<tr>

    <td>

        @Html.LabelFor(m => m.HulpvraagInitiatief)

    </td>

    <td>

        @Html.Purple(m => m.HulpvraagInitiatief)

    </td>

</tr>

 

And now the textbox, or whatever other control is built, has a nice purple background. Of course this is not be the way to achieve that effect. But I do hope it clarifies the way the htmlhelper works.

A custom HtmlHelper

We now have a way to extend the HtmlHelper with new methods. These methods can have custom parameters. But what if I needed a custom HtmlHelper with some extra properties which are needed in many instances of many methods ? As a casus I will explore fiddling with the Html Id’s of the controls built.

In the default behavior all EditorFor controls built get an id which is equal to the name of the property. Which is great, when posting back the form TryUpdateModel will bind all posted values to an instance of the model. All of this goes horribly wrong when several instances of a view are rendered on one and the same page. Not just contents gets jumbled up, scripts will go haywire as well.

It is possible to give each control an unique Id by setting it’s HtmlAttributes. Some, like the Telerik suite, even have a method to assign a name. Assigning these Id’s is a hassle and he controller receiving the postdata will also have a lot of work to do to extract all posted values.

Here’s the plan:

  • Build an extended HtmlHelper which takes an Id to uniquify names
  • Give this class EditorFor methods which build controls with an unique name
  • Let the class create the postdata

The class will be generic, typed to the Model

public class MyHtmlbuilder<TModel>

{

    private readonly int _id;

    private readonly HtmlHelper<TModel> _htmlHelper;

 

    public MyHtmlbuilder(int id, HtmlHelper<TModel> htmlHelper)

    {

        _id = id;

        _htmlHelper = htmlHelper;

    }

}

 

In the constructor it receives the Id needed for unique names and the original HtmlHelper object.

To hook into the existing helper and model the builder is again created from an extension method.

 

public static MyHtmlbuilder<TModel> MyHtml<TModel>(this HtmlHelper<TModel> htmlHelper, int id)

{

    return new MyHtmlbuilder<TModel>(id, htmlHelper);

}

 

Having my helper in place I can give it methods to build Html. To create the unique names I need yet another extension method. This one is on the lambda expression to find the property name using reflection.

 

private static string NameBuilder<TModel, TProperty>(this Expression<Func<TModel, TProperty>> expression, int id)

{

    var me = expression.Body as MemberExpression;

    if (me == null)

    {

        throw new ArgumentException(“You must pass a lambda of the form: ‘(object) => object.Property’”);

    }

    return string.Format(“{0}{1}”, me.Member.Name, id);

}

 

The Html building methods are quite simple. Building a dropdownlist for a property:

 

public MvcHtmlString DropDownListFor<TProperty>(Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectListItems)

{

    var id = expression.NameBuilder(_id);

    return _htmlHelper.DropDownListFor(expression,

selectListItems,

new { id }) ;

}

The namebuilder method builds an unique name. The original htmlhelper does the real work. Using my name in the Html-attributes.

 

I can also build third party controls here. Here’s a Telerik datepicker:

 

public MvcHtmlString DatePickerFor(Expression<Func<TModel, DateTime>> expression)

{

    return

        new MvcHtmlString(_htmlHelper.Telerik()

                    .DatePickerFor(expression)

                    .OpenOnFocus(true)

                    .Name(expression.NameBuilder(_id)).ToHtmlString());

}

 

Note that Telerik has it’s own method for setting the name. It’s controls are extremely picky over unique names.

I leave it up to you to add the builder methods you need. Working with this kind of code is somewhat puzzling in the beginning, all the this’s and generics take some time to sink in. But when working with it it soon falls into place. It made me even more delighted over the powers of the C# language.

The helper can do more than building just Html. It’s also great for building script. Posting the form-data as they are will lead to all the funny names leaking back to the controller. It would be great to have the original simple names. To control this you have to post from script.

    $.post(‘/EersteLijnsTraject/Bewaren/?idTraject=@Model.Id’,

        {

            MyPostValue : $(‘#MyPostValue@(Model.Id)‘).val()

   }

        

 

Here a form variable named MyPostValue is posted with its value coming from a control named MyPostValueX. This works but is ugly code which is hard to write and maintain.

Here my custom helper comes to the rescue. It uses a class to build the postdata. The class takes the id in its constructor and uses a stringbuilder to build the script. The Add method adds a model member to the post. Using the pattern described above based on a lambda on the generic TModel type of the data.

public class PostDataBuilder<TModel>

{

    private readonly StringBuilder _sb = new StringBuilder();

    private readonly int _id;

 

    public PostDataBuilder(int id)

    {

        _id = id;

    }

 

    // Add form field to postdata

    public PostDataBuilder<TModel> Add<TProperty>(Expression<Func<TModel, TProperty>> expression)

    {

        var propName = expression.NameBuilder();

        // JQuery  i.e. : BeginDatum : $(‘#BeginDatum1′).val()

        _sb.Append(string.Format(“{0} : $(‘#{0}{1}’).val()”, propName, _id));

        // comma separated

        _sb.Append(‘,’);

        return this;

    }

    // Generate

    public HtmlString Render()

    {

        /*  Resulting in

            BeginDatum : $(‘#BeginDatum1′).val(),

            EindDatum : $(‘#EindDatum1′).val(),               

            DBC : $(‘#DBC1′).val()                

 

            */

        return new HtmlString(_sb.ToString(0, _sb.Length – 1));

    }

}

 

The Render methods builds the desired script snippet.

$.post(‘/EersteLijnsTraject/Bewaren/?idTraject=@Model.Id’,

    {

 

        @(myHtml.PostData().Add(m => m.DatumAanmelding)

                                    .Add(m => m.HulpvraagInitiatief)

                                    .Add(m => m.Aanmelding)

                                    .Add(m => m.AanmeldingsKlacht)

                                    .Add(m => m.DiagnoseSet)

                                    .Add(m => m.Werkzaamheden)

                                    .Add(m => m.Tests)

                                    .Add(m => m.BehandelMethodiek)

                                    .Add(m => m.MedicatieGebruik)

                                    .Render())

        },

        // return function

        function(data) {

 

 

This code is a lot better to read and maintain. Thanks to the lambdas. As an extra bonus posting this way does not require an html-form.

Wrapping up and winding down

Building a custom HtmlHelper is, when you understand how, no big job. Give these snippets of my experiments it should be no big problem to create your own.

It takes these steps to use your own custom helper in a view

  • Make sure the code is included the uses
  • Create a helper in the head of the page
  • Use its methods

Here’s a slightly stripped version of one of our pages using EposHtml, our custom HtmlHelper 

@model ELPTrajectModel

@using Epos4.Website.EposZilos.Helpers

@using Epos4.Website.EposZilos.Models

 

@{

    var eposHtml = Html.EposHtml(Model.Id);

}

 

<script type=”text/javascript”>

function Bewaar@(Model.Id)() {

    $.post(‘/EersteLijnsTraject/Bewaren/?idTraject=@Model.Id,

        {

 

            @(eposHtml.PostData().Add(m => m.DatumAanmelding)

                                     .Add(m => m.HulpvraagInitiatief)

                                     .Add(m => m.Aanmelding)

                                     .Add(m => m.AanmeldingsKlacht)

                                     .Add(m => m.DiagnoseSet)

                                     .Add(m => m.Werkzaamheden)

                                     .Add(m => m.Tests)

                                     .Add(m => m.BehandelMethodiek)

                                     .Add(m => m.MedicatieGebruik)

                                     .Render())

            },

            // return function

            function(data) {

                var grid = $(‘#EersteLijnsTrajecten’).data(‘tGrid’);

                grid.pageTo(grid.currentPage);

            });

    }

</script>

 

 

<table>

    <tr>

        <td>

            @Html.LabelFor(m => m.DatumAanmelding)

        </td>

        <td>

            @eposHtml.DatePickerFor(m => m.DatumAanmelding)

        </td>

    </tr>

…………………………….

    <tr>

        <td>

        </td>

        <td>

            <button type=”button” onclick=”Bewaar@(Model.Id)()”>

                Bewaar</button>

        </td>

    </tr>

</table>

 

You don’t want to see the pages before the custom Helper Glimlach  The way the name of the save method is “uniquified” might give a clue. Anyway, I hope to have inspired you to explore the HtmlHelper as well.

This entry was posted in Uncategorized. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • PeterGekko

    That’s a nice Haack. Thanks

    I could define my own view base class but the problem would be to get the models’s unique id into the helper. Which would require a base class for every model, or at least every model implementing some interface. I don’t need the added functionality in every view. I don’t even have an Id available in every view, the Id would have to be optional. This way I can use my helper, only there where needed, with any model.

  • http://twitter.com/thomasvm Thomas Van Machelen

    You can even take this one step further: configure Razor to use a custom base view class. And let that base class have an additional property “EposHtml”. That way you don’t need to explicitely instantiate an eposHtml variable.

    The entire process is similar to what is described here:
    http://haacked.com/archive/2011/02/21/changing-base-type-of-a-razor-view.aspx