Tuesday, March 23, 2010

Using FubuMVC.UI in asp.net MVC : Getting started

If you are not fortunate enough to be able to use FubuMVC but are fortunate enough to be using asp.net MVC, you may find yourself unendingly vexed by the suckyness of the ui helpers in asp.net mvc ( heretofor referred to as mvc ).  mvccontrib has some good stuff with the uibuilders, but I find the FubuMVC.UI more to my taste.  In this post or series of posts I will show how I am using the FubuMVC.UI in my MVC app.  I am using Structuremap as my DI container so there may be a few differences if you are not.  And to that not, I got a big leg up from RyanOhs post Using FubuMVC’s Html Conventions in Microsoft MVC which seems to be dead now but may someday return so I linked to it anyway.  He’s using Windsor as his DI container.  Now on to the meat.

One of the central ideas in the FubuMVC.UI is that there are three kinds of elements were interested in.  Inputs, Labels, and Displays.  Inputs being any kind of element that allows you submit information, Labels being, well, labes and Displays being elements that display information.  Readonly information.  Labels are the easiest.  Once you know how you like to display your labels you can specify that concvention and largely be done with it.  Displays too can be pretty easy too but can get more complicated depending on what you want to display.  Inputs are the proverbial money shot.  That is where most of the cool stuff happens.

One of the first things you’ll need to do is create a set of conventions.  The idea is that you say ok whenever I specify a label ( e.g. Html.LabelFor(x=>x.FirstName) ) render it like this.  So for my convention I have

Labels.Always.BuildBy(req => new HtmlTag("label").Attr("for", req.Accessor.Name).Text(req.Accessor.FieldName.ToSeperateWordsFromPascalCase()));

So for labels always build using a “label” tag, set an attribute “for” equal to the name of the property and set the text of the label to the Property name and ( using a little extension method I build ) separate the words of the Property.  So my property was FIrstName.  it will render <label for=”FirstName” >First Name</label> . 

Similarly I do something for the display.

The fun happens with the InputFor.  To keep this simple I will show the very least that you will need for Input. 

Editors.IfPropertyIs<bool>().BuildBy(TagActionExpression.BuildCheckbox);

Editors.Always.BuildBy(TagActionExpression.BuildTextbox);

Here if you do something like this Html.InputFor(x=>x.IsReallyCool) you will get a checkbox since IsReallyCool is a bool.  and if you do basically anything else you will get a textbox.

So the final very bare minimum HtmlConvention file would like this

public class FlywheelHtmlConventions : HtmlConventionRegistry
    {
        public FlywheelHtmlConventions()
        {
            Editors.IfPropertyIs<bool>().BuildBy(TagActionExpression.BuildCheckbox);
                      Editors.Always.BuildBy(TagActionExpression.BuildTextbox);
            Displays.Always.BuildBy(req => new HtmlTag("span").Text(req.StringValue()));
            Labels.Always.BuildBy(req => new HtmlTag("label").Attr("for", req.Accessor.Name).Text(req.Accessor.FieldName.ToSeperateWordsFromPascalCase()));
        }

This is won’t take you very far but we can go over some more interesting conventions later.

Next

You will also need some extension methods that you will access the FubuMVC.UI with from the view.  Now here I chose to write the extension methods off the HtmlHelper despite the fact that it’s a big bloated pos.  I did this for a very important reason.  If you use partials you will need to be able to get information off the model from the view that called the partial from with in the partial.  So inside the partial your calling InputFor(blah).  You will know that blah is on the BlahViewModel, but you wont know that the BlahBlahBlahViewModel called that partial unless you dig around in the HtmlHelper.  More on this later. probably.

So here are the three extension methods you will need and a little helper method.

public static class FubuUIHtmlExtensions
   {
       private static ITagGenerator<T> GetGenerator<T>(HtmlHelper<T> helper, Expression<Func<T, object>> expression) where T : class
       {
           TagGenerator<T> generator = ServiceLocator.Current.GetInstance<ITagGenerator<T>>() as TagGenerator<T>;
           generator.Model = helper.ViewData.Model;
                     return generator;
       }

       public static HtmlTag InputFor<T>(this HtmlHelper<T> helper, Expression<Func<T, object>> expression) where T : class
       {
           ITagGenerator<T> generator = GetGenerator<T>(helper, expression);
           return generator.InputFor(expression);
       }
       public static HtmlTag LabelFor<T>(this HtmlHelper<T> helper, Expression<Func<T, object>> expression) where T : class
       {
           ITagGenerator<T> generator = GetGenerator<T>(helper, expression);
           HtmlTag tag = generator.LabelFor(expression);
           return tag;
       }

       public static HtmlTag DisplayFor<T>(this HtmlHelper<T> helper, Expression<Func<T, object>> expression) where T : class
       {
           ITagGenerator<T> generator = GetGenerator<T>(helper, expression);
           return generator.DisplayFor(expression);
       }

}

basically each of these is getting a TagGenerator and calling either input,label, or display on it.

The Generator method gets a TagGenator from the servicelocator( container ).  You then pass the instance of the viewmodel into the TagGenerator. Essentially the TagGenerator will read you conventions decide what to render and do so using the data in the Model.

The last step is to register some stuff with your container. 

First in your ContainerRegistry you should do as follows ( for structure map.  of windsor check out ryanohs post )

For<HtmlConventionRegistry>().Add<MYHtmlConventions>();
            For<IServiceLocator>().Singleton().Use(new StructureMapServiceLocator());
            For(typeof(ITagGenerator<>)).Use(typeof(TagGenerator<>));
            For<TagProfileLibrary>().Singleton();

Then after you call bootstrapper for Structuremap in the application.start you should call

            ServiceLocator.SetLocatorProvider(() => new StructureMapServiceLocator());

            var library = ObjectFactory.Container.GetInstance<TagProfileLibrary>();
            var conventions = ObjectFactory.Container.GetAllInstances<HtmlConventionRegistry>();
            conventions.Each(library.ImportRegistry);

Here we set up the ServiceLocatorProvider. then we get an instance of the TagProfilerLiberary and an instance of our HtmlConventions and call Library.ImportRegistry on each of the conventions.

Now in our view we can do <%= Html.LabelFor(x=>x.FirstName) %>  <%= Html.InputFor(x=>x.FirstName) %> and get

<label for=”FirstName”>First Name</label><input type=”text” name=”FirstName” value=”Cannibal” ></input>

There.  I made it through.  This looks like total ass in my editor and I may need to figure out how to display code better and redo this post.  But in any case in future posts I will go over more useful conventions and some advanced stuff.

No comments: