Tim Scott's Blog

November 24, 2008

MvcFluentHtml – Fluent HTML Interface For MS MVC

Filed under: C#, FluentHtml, MS MVC — Tags: , — Tim Scott @ 6:45 pm

UPDATE: MvcFluentHtml has been moved to MvcContrib where it lives in its own assembly, MvcContrib.FluentHtml.

A few weeks ago I attended a presentation at the KaizenConf by Jeremy Miller and Chad Myers in which they showed their “opinionated” approach to MS MVC.  One of the things that caught my attention was the fluent interface they had created for HTML generation.   So I went home and started working on this for my own application.

I have put my work in progress here: MvcFluentHtml.

Incidentally, since I started working on MvcFluentHtml, Chad and Jeremy have put out some bits of their code as opinionatedmvc.  And Karl Seguin, who also saw their presentation, has created some fluent HTML stuff which he shows in this blog post.

So what problem does MvcFluentHtml solve?  Can’t I be happy with the HtmlHelpers that come with the framework?

I have come to truly hate the overloading approach taken by the out-of-the-box Html helpers.  Methods with long signatures are hard to read, and it takes investigation to see what’s happening.  What’s worse, you must worry about problems with overload resolution, especially when some parameters are typed as object.  As a result, HtmlHelper is not easily extensible.  It’s hard to bend it to do new things without breaking existing functionality.  We saw an example of this when Beta1 was released with breaking changes.  With a fluent interface, it’s much easier to extend with new behaviors while leaving existing behaviors closed.

So how does it work?  Let’s start with some examples of how you might use it in your views:

<%=this.TextBox(x => x.FirstName).Class("required").Label("First Name:")%>
<%=this.Select(x => x.ClientId).Options((SelectList)ViewData["clients"]).Label("Client:")%>
<%=this.MultiSelect(x => x.UserId).Options(ViewModel.Users)%>
<%=this.CheckBox("enabled").LabelAfter("Enabled").Title("Click to enable.").Styles(vertical_align => "middle")%>

As you can see in this example, the helpers are methods of the view itself, not of some helper class.  You will also notice two different kinds of methods.  One kind takes the name as a string which is meant to evaluate to an object in the ViewData dictionary.  These methods are extensions of IViewDataContainer, and as such they can be used in any type of view (ViewPage, ViewPage<T>, ViewUserControl, ViewUserControl<T>, ViewMasterPage, ViewMasterPage<T>).   The other kind takes a lambda expression that refers to a member of ViewData.Model.  This kind of method extends a new interface that is defined in MvcFluentHtml MvcContrib.FluentHtml:

public interface IViewModelContainer<T> where T : class
{
    T ViewModel { get; }
    IEnumerable<IMemberBehavior> MemberBehaviors { get; }
}

Both kinds of methods set the value (value attribute, inner text, etc.) from ViewData.  Both kinds set the name of form elements so that most binders will pick up the value on post.

So what must you do before you can use these methods in your views?  To use the methods that extend IViewDataContainer, nothing.  To use the methods that extend IViewModelContainer<T>, you have some options:

  1. Derive your strongly-typed views from ModelViewPage<T>, ModelViewUserControl<T> or ModelViewMasterPage<T> which are bits of MvcFluentHtml.
  2. Implement IViewModelContainer<T> directly on your views.
  3. Define your own base view classes that implement IViewModelContainer<T>.

There is a fourth option.  MvcFluentHtml contains a set of classes, ConventionModelViewPage<T>, ConventionModelViewUserControl<T> and ConventionModelViewMasterPage<T>.  These classes apply some default behaviors.  These behaviors operate on certain model attributes, which are also part of MvcFluentHtml MvcContrib.

So given this model:

public class Person
{
    [Required]
    [MaxLength(200)]
    public string Name { get; set; }
}

And this in the view:

<%this.TextBox(x => x.Name)%>

The resulting HTML would be something like:

<input type="text" maxlength="200" id="Name" name="Name" class="required" value="Pete"/>

These attributes are found in a separate assembly, MvcFluentHtml.Attributes MvcContrib.ModelAttributes.  So in case your models are in a separate assembly from your MVC web UI, you don’t have to reference the UI specific bits in your model assembly.

But what if you want different behaviors and different attributes?  You can create custom behaviors by implementing IMemberBehavior.

public interface IMemberBehavior
{
     void Execute(IMemberElement element);
}

For example:

public class MyMemberBehavior1 : IMemberBehavior
{
    public void Execute(IMemberElement element)
    {
        var helper = new MemberBehaviorHelper<RequiredAttribute>();
        var attribute = helper.GetAttribute(element);
        if (attribute  != null)
        {
            element.SetAttr("class", "req");
        }
    }
}

Then create your own “opinionated” views something like this:

public class MyOpinionatedModelViewPage<T> : ModelViewPage<T> where T : class
{
     public MyOpinionatedModelViewPage() : base(new MyMemberBehavior1(), new MyMemberBehavior2()) { }
}

Or using your favorite IoC container:

public class MyOpinionatedModelViewPage<T> : ModelViewPage<T> where T : class
{
     public MyOpinionatedModelViewPage(IMyMemberBehavior1Marker myBehavior1, IMyMemberBehavior2Marker myBehavior1) : base(myMemberBehavior1, myMemberBehavior1) { }
     public MyOpinionatedModelViewPage() : base(IoC.Resolve<IMyMemberBehavior1Marker>(), IoC.Resolve<IMyMemberBehavior2Marker>()) { }

}

One final point.  The goal of MvcFluentHtml was to leave the opinions to you.  We did this by allowing you to define your own behaviors.  However, it is not without opinions regarding practices. For example the Select object does not have any “first option” functionality. That’s because in my opinion adding options to selects is not a view concern. Also, while we allow you to set inline styles we do not expose any methods that abstract styling (e.g., Width, TextAlign).  That’s because we want to encourage semantic HTML and leave the styling to CSS.  For the same reason we do not render any layout tags (e.g. <br/>).  Also we generally don’t try to abstract the language of HTML (e.g., we use “Select” instead of “DropDownList”).  In my opinion, MvcFluentHtml is more of a helper library than an abstraction over HTML, and moving developers away from understanding what’s going on with the HTML is not a good thing.

I heartily welcome critique, especially if you want to contribute! Areas that I know need some work are: radio button, radio button list, checkbox list, performance tweaks, XML comments, and CI.

About these ads

32 Comments »

  1. This is VERY interesting.
    I was giving a presentation yesterday about ASP.NET MVC (http://weblogs.asp.net/meligy/archive/2008/11/30/slides-for-dotnetwork-9th-applying-ddd-on-asp-net-mvc-part-1-asp-net-mvc.aspx). While working on demos and such, that was my big wonder: why I cannot just write ‘.’ and have another method with something related and such. Fluent interfaces are so cool :).

    I wonder if you would like to add it to the MVCContrib though (http://www.codeplex.com/MVCContrib). Sounds like the right place to look for it as to have less dependencies to have to every MVC website.

    Comment by Mohamed Meligy — November 30, 2008 @ 9:43 am

  2. Mohamed, MvcContrib has a class, FormHelper, which extends HtmlHelper. It was developed to deal with limitations of HtmlHelper in the early previews. At some point (Preview 5, I think) they decided to deprecate FormHelper because HtmlHelper had mostly caught up. Although MvcFluentHtml takes a different approach, it largely overlaps FormHelper functionality. The underlying element classes indeed have similar names and function. So I was concerned that adding a new HTML helper feature to MvcContrib could create some confusion. That’s mostly why I decided to make MvcFluentHtml a separate project. Another reason was, some people who might want to use MvcFluentHtml might not want to use all the goodies in MvcContrib.

    That said, I use MvcContrib, so it would be no issue for me personally to roll it in to MvcContrib. One option might be to make it part of MvcContrib, but as a separate assembly. NHibernate Contrib, for example consists of several independent assemblies. I’m not sure if the MvcContrib guys are receptive to that approach. This is all to say, I am receptive to the idea if it makes sense.

    Comment by Tim Scott — November 30, 2008 @ 7:28 pm

  3. I encourage you to submit a patch to the MvcContrib project. AFAIK we don’t have this functionality yet, even though there was the aformentioned FormHelper from way back when.

    I’m totally okay with having different/competing APIs as long as they are supported and work nicely with other aspects of MvcContrib. For example, I just committed some fluent route testing helpers, but you might just want to use the basic test extensions rather than the fluent API. Its about choice.

    Comment by Ben Scheirman — December 2, 2008 @ 3:07 pm

  4. Why go to all this trouble to be able to pretend you’re not writing HTML? What makes this better than just writing the HTML itself? You’re not abstracting anything, you’re just making it less efficient and harder to read and comprehend. If your view engine is making it hard to write HTML, maybe you need a new view engine?

    Comment by Mark — December 11, 2008 @ 9:09 pm

  5. Mark, How is it better? Here are a few ways:

    1) When using strong typed views it ensures correct model binding in both directions without error prone “magic strings” or hand coded HTML. Intellisense further speeds coding and prevents errors.

    2) How would you hand code this <%=this.MultiSelect(x => x.UserId).Options(ViewModel.Users)%>? It would surely involve some very ugly iteration, which might even be mistaken for classic ASP.

    3) If you use the behaviors feature, you can eliminate repetition of business rules in your UI which are more properly expressed in your domain.

    4) I respectfully disagree that it’s less efficient or harder to read than pure HTML.

    HTML helpers are widely used with MVC frameworks. I would guess that the vast majority of MS MVC users are using the built-in HtmlHelper to create views. I contend that this is a huge improvement over that.

    Comment by Tim Scott — December 11, 2008 @ 9:40 pm

  6. It seems to me that you’re attempting to compensate for failings in your view engine. In my opinion, this has lead to mixing your business logic with your model, and has come dangerously close to mixing your view logic with your model.

    While I can understand the benefits from the point of view that you need to work with the regular ASP.NET view engine (which is, after all, just “classic ASP” with a different scripting language, when you take out the codebehind class), I think you could possibly go further by coming at the problem from a different angle.

    Just my opinion.

    Comment by Mark — December 13, 2008 @ 4:30 pm

  7. Mark, I’m curious what the different angle looks like. Do you use MS MVC with a different view engine? If so, what is your approach to template authoring? No helpers? If so, how you you handle things like dynamically populating select options?

    Comment by Tim Scott — December 13, 2008 @ 5:53 pm

  8. I’ve used MS MVC with a few different view engines, including ones I’ve written myself. I wouldn’t say that currently there exists what I would consider the perfect view engine, but what I can say is that if it existed, there wouldn’t be blocks of c# code embedded in the templates.

    Comment by Mark — December 14, 2008 @ 7:40 am

  9. One thing we’ve taken to is overlapping our HTML generation with Castle Validation…so that instead of having a “Required” attribute twice, one for validation and one for generation, our expression-based generator keys into those Castle attributes. If you’re validating Dates, it adds a special CSS class that jQuery can key into.

    One other direction we went is making the generation a little smarter, so that I can give it one expression “InputFor(c => c.State)”, and the underlying “generator” selector would recognize that State is an enumeration type, and automatically choose SELECT, with OPTIONs for each enum value.

    That helped us create conventions around our inputs, so that every Date validated input shows up exactly the same etc.

    Comment by Jimmy Bogard — December 23, 2008 @ 4:08 am

  10. @Jimmy,

    With FluentHtml you could easily create a behavior that uses Castle Validation attributes to govern HTML generation. You could also check for DateTime type and add a CSS class. Jeremey Skinner has done some interesting work to integrate with his own (non attribite based) validation framework.

    http://www.jeremyskinner.co.uk/2008/12/13/integrating-fluentvalidation-with-mvccontribs-fluent-html-helpers/

    Very interesting about your InputFor. We have Options<TEnum>() for specifying enum values as the options. You have taken it a step further in a very cool way.

    However, I have started feeling some discomfort with either approach. I soon realized that we needed an overload, Options<TEnum>(string firstOptionText). Then someone asked why not generalize it to FirstOptionText(string text). At this point I started to question this whole direction. It shifts responsibility for setting options to the view. If I expect a view to have a select box with the 50 states, plus a null option, I would like to verify that in my controller tests. Perhaps I’m being too picky, but my sense it to guard against convenience over SOC and testability.

    Comment by Tim Scott — December 23, 2008 @ 4:00 pm

  11. [...] on an instance of Foo, which leads me to use the “this” keyword.  Which is why you see examples like these that make prodigious use of the “this” [...]

    Pingback by Extension methods and a plea for mixins - Jimmy Bogard - — January 3, 2009 @ 3:35 am

  12. I really like this approach, but
    x.UserId).Options(ViewModel.Users)%> should be something like x.UserId).Options(ViewModel.Users)%>

    Comment by Lauri — January 4, 2009 @ 1:31 pm

  13. Hi,
    i have downloaded binaries of MvcContrib, but the is no “MvcContrib.FluentHtml.dll” file.
    Please, where i can donwload FluentHtml binaries?
    Thnx.

    Comment by Feryt — January 20, 2009 @ 8:26 pm

    • The latest release of MvcContrib predates FluentHtml, so it’s not in any binary download. You must get the source and build it. There is a file “ClickToBuild.bat” in the trunk which puts the binaries in the “build” folder.

      Comment by Tim Scott — January 20, 2009 @ 8:34 pm

  14. @Tim
    Hi Tim.
    Ok i have downloaded source files from SVN but build fails(unit tests dont pass) :

    [nunit2] application/pdf
    [nunit2]
    [nunit2] Tests run: 1140, Failures: 6, Not run: 1, Time: 10.699 seconds
    [nunit2]
    [nunit2] Failures:
    [nunit2] 1) MvcContrib.UnitTests.FluentHtml.LiteralTests.literal_renders_with_inner_text_formatted : Expected string length 9 but was 14. Strings differ at index 2.
    [nunit2] Expected: “$1 234,50″
    [nunit2] But was: “$1 234,50″
    [nunit2] ————-^
    [nunit2]
    [nunit2] at MvcContrib.UnitTests.FluentHtml.LiteralTests.literal_renders_w
    ith_inner_text_formatted() in d:\Coding\Projects\Asp.Net MVC\MVCContrib\src\MVCC
    ontrib.UnitTests\FluentHtml\LiteralTests.cs:line 34
    [nunit2]

    …. total of 6 unit test fails …

    It seems it’s a problem when you have windows locales set to different language than English.

    F.

    Comment by Feryt — January 20, 2009 @ 8:43 pm

  15. When you set windows locales to “English” all tests pass.
    It is not good for people outside english-enabled countries :)
    Bye.

    Comment by Feryt — January 20, 2009 @ 8:45 pm

  16. This was reported, and I thought it had been fixed. I will look into it. In any case, it’s an error in the test, not in the code under test.

    Comment by Tim Scott — January 20, 2009 @ 8:47 pm

  17. Hmm, in my comment with [nunit2] error are both values the same, but in fact the buils output is :

    Expected: “$1 234,50″
    But was: “$1234,50″

    There is some kind of unicode characted inside “” chars, i thing this blog engine hides it…

    Comment by Feryt — January 20, 2009 @ 8:50 pm

  18. Damn, i dont know how to post full text, ok let it be.
    I have finally build code, thnx.

    Comment by Feryt — January 20, 2009 @ 8:52 pm

  19. [...] Javascript With MvcContrib.FluentHtml Filed under: Uncategorized — Tim Scott @ 3:27 am MvcContrib.FluentHtml provides a library of HTML helper methods that use expressions with strongly typed views to [...]

    Pingback by Eliminate Magic Strings In Javascript With MvcContrib.FluentHtml « Lunablog — February 5, 2009 @ 3:27 am

  20. Hi,

    You mention using ctor dependency injection to pass down the behaviors to the base view page class. Can you point me to any source explaining how to set this up. It is easy for controllers, but for views I can’t find the extension point. I am seing the locator interface IViewEngine, and implementors using the BuildManager.CreateInstanceFromVirtualPath method. Also I don’t want to call the container from within the default constructor in the base view page class. I want injection to work independent of which IoC container is being used.

    Regards
    Maxild

    Comment by Morten Maxild — February 16, 2009 @ 10:41 pm

  21. Maxild,

    I guess I lied. In my example you would have to call your container in the c-tor like IoC.Resolve<IMyMemberBehavior1Marker>(). I suppose there is some way to do it with a custom view engine, but I think you would still have to call the container. If you find a better way let us know.

    Comment by Tim Scott — February 17, 2009 @ 8:34 pm

  22. [...] MvcFluentHtml – Fluent HTML Interface For MS MVC [...]

    Pingback by ASP.NET MVC View Best Practices – Save time creating views with MvcContrib.FluentHtml | Arrange Act Assert — October 19, 2009 @ 7:34 am

  23. i think your custom ModelViewPage got to have a 0 argument constructor. that’s because the generated class will inherit from your csutom ModelViewPage but doesn’t call the right constructor.. there for you can’t use DI in this case. Read more at http://stackoverflow.com/questions/1769442/dependency-injection-into-custom-viewpage-generates-wierd-error

    Comment by Carl Hörberg — November 20, 2009 @ 10:11 am

    • Carl, You are correct. Instead you would use DI something like this:

      public MyOpinionatedModelViewPage() : base(IoC.Resolve<IMyMemberBehavior1Marker>(), IoC.Resolve<IMyMemberBehavior2Marker>()) { }

      Comment by Tim Scott — November 20, 2009 @ 3:04 pm

  24. [...] Fluent HTML – Tim Scott [...]

    Pingback by Dev Links « Blogosphere — December 27, 2009 @ 1:19 am

  25. For those of you who wish to hook up client side validation (I’m using Jquery) to FluentHtml I have developed a little hack see below.

    Essentially the problem is MVC fails to hook up the dataannotations meaning Html.EnableClientValidation() does not output correctly, I found this using Jquery validation in conjunction with MicrosoftMvcJQueryValidation.js

    Hope it helps… If you have anything cleaner please let me know

    public class ViewPage : ModelViewPage where T : class
    {

    public ViyoloViewPage() : base() {

    behaviors.Add(new JavascriptClientBehavior(this));

    }
    }

    ///
    /// Behaviour adds the appropriate bits so Html.EnableClientValidation() outputs correctly
    ///
    public class JavascriptClientBehavior : IMemberBehavior where T : class
    {
    ViyoloViewPage _page;
    public JavascriptClientBehavior(ViyoloViewPage page) {
    _page = page;

    }

    public void Execute(IMemberElement element)
    {

    var formContext = _page.ViewContext.FormContext;
    var helper = new MemberBehaviorHelper();
    var attribute = helper.GetAttribute(element);
    if (attribute != null)
    {
    var fvmd = new FieldValidationMetadata() { FieldName = element.ForMember.Member.Name, ReplaceValidationMessageContents = true, ValidationMessageId = element.ForMember.Member.Name + “_validationMessage” };
    fvmd.ValidationRules.Add(new ModelClientValidationRequiredRule(attribute.ErrorMessage));
    formContext.FieldValidators.Add(new KeyValuePair(element.ForMember.Member.Name,fvmd));

    }

    }
    }

    Comment by acr — June 30, 2010 @ 1:00 pm

  26. acr,

    Interesting. I have not used the new MVC 2 validation stuff yet. I suggest that you post this on the MvcContrib Google discussion group. There might be something from this that we can add to MvcContrib.

    Comment by Tim Scott — July 2, 2010 @ 6:57 pm

  27. Thanks for this Tim.
    I will endeavor to do a full write up over at Google in the next days.

    Btw I quite like your additions:

    var attributes = element.ForMember.Member.GetCustomAttributes(typeof(ValidationAttribute), true);
    And the extension method. Bit tidier…
    I did not quite figure out why the MVC framework does not pick out the HtmlFluent controls and do this automatically. I did see something in the HTMLhelpers… That I started to re-engineer in the core HtmlFluent lib. I thought perhaps, we could add this functionality a bit further up the stack somewhere so it ‘just works’ straight out of the box. It ended up turning very ugly maybe you have some thoughts on that? The best option I think at the mo is to package the behaviour with Fluent so the world doesn’t waste the hours.

    Anyhow, as a side note I have been working on extending HtmlFluent so that controls encapsulate Visible / Readonly / Editable features out of the box. These features are useful when you wish to apply security features eg user 1 has readonly access on a view or can only see certain elements. So far Ive done it through some fairly messy behaviours and extensions. I’ll post that over at google so you guys can take a look and take the necessary ‘p!ss’.

    Cheers acr

    Comment by acr — July 5, 2010 @ 8:07 am

  28. [...] than MVC 2 (and in some ways older than ASP.NET 2.0): in the MVC space this to-do has the title “Fluent HTML.” Tim Scott writes: I have come to truly hate the overloading approach taken by the [...]

    Pingback by the rasx() context » Blog Archive » ASP.NET MVC 3 Preview 1 is Released and Catching Up with MVC 2 — August 5, 2010 @ 3:34 pm

  29. Hi, thanks for the great work !! some pictures could be helpfull!!

    thanks again

    Comment by james — October 7, 2010 @ 10:24 am

  30. My mistake, they were blocked by our proxy.

    Sorry!

    Comment by james — October 7, 2010 @ 10:34 am


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

The Shocking Blue Green Theme. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: