ASP.NET MVC Preview: Using The MVC UI Helpers

For the last month or so I've been working on the MVC UI Helpers (aka the MVC Toolkit) that will help developers work with HTML in their Views. These helper methods are primarily Extension Methods, and are designed to encapsulate UI code in much the same way that Server Controls do with Web Forms.

For the last month or so I’ve been working on the MVC UI Helpers (aka the MVC Toolkit) that will help developers work with HTML in their Views. These helper methods are primarily Extension Methods, and are designed to encapsulate UI code in much the same way that Server Controls do with Web Forms.

 

It’s All Extension Methods Baby
When you create a View using ASP.NET MVC, you set it to inherit from System.Web.Mvc.ViewPage<>, where the passed-in type is a data class shared between the Controller and ViewPage (read more on this here). At that point it’s all up the UI (the HTML View), and we haven’t forgotten you :) . To that end we’ve created a set of Extension Methods, currently called the “MVC Tookit”, that you can use to push the data from your controller to your screen, wrapped in some nice HTML controls.

The UI Helper library (currently called System.Web.Mvc.Toolkit) extends the HtmlHelper class, which hangs from ViewPage.Html. When you download the MVC bits (later this week), in there will be a binary file which you can add to your project in order to use the new UI bits (this stuff is slated to be combined with the core MVC bits in the next drop). To use it, you simply reference the Toolkit dll, and then in your Web.Config, add a reference to the namespace:

<pages>
    …
    <namespaces>
        <add namespace=”System.Web.Mvc”/>
    </namespaces>
</pages>

Now you’re ready to rock!

 

Enough Chatter – Get To The Code!
Accessing the helpers from your view is pretty straightforward once you have the namespace available to your ViewPage:

1

As you can see here, there are an abundance of methods for you to choose from – I’ll go over each one here, and show some examples of each. You asked for more code… you got it!

First, however, you might be asking:

If I can’t use server controls, and there’s no PostBack, why then can’t I just use plain old HTML?

… and the answer is that you can. It’s up to you as to what you think is more maintainable and easier to work with.

For the samples below, I’ll be working with a sample Forums application for context.

 

Anonymous Hash (Queue Midnight Run Soundtrack)
When looking over the Toolkit code you’ll see a lot of “object htmlAttributes” as an argument in the method signatures. We have a nice hashing system we’re using which allows you to pass in things we haven’t thought of in terms of tag attributes. This system works on the concept of Anonymous Types – the ability to declare, as needed, an object type:

object o=new{name="Rob", type="quack"}

We have an Extension Method (which you can use too!) called “ToAttributeList()”, which will transform this object into a set of attrbute values:

//This evaluates to "name=\"Rob\" type=\"quack\" "string myList=o.ToAttributeList()

So with all of the methods below, you can use htmlAttributes to send in whatever extra tags you like using the Anonymous Type declaration.

Note: if your tag is a reserved work, like “class”, use the standard “_” reserved word prefix: “_class”

 

User Controls
The first helper method I want to show off is the RenderUserControl() method. This method allows you to dynamically call/render a UserControl to a page as a string. These controls can be simple standalones, or they can share the ViewData with the controller. So to render a basic UserControl, we use:

<%=Html.RenderUserControl("~/UserControls/ForumList.ascx")%>

This will output the rendered control to the page, right where I put the call. But what if there are properties set on the ForumList? Like ForumGroupID for instance (which tells the ForumList.ascx which forums to display)? We can handle that too by using an Anonymous Type, sent in as a Hash, which will apply each setting to the UserControl:

<%=Html.RenderUserControl(“~/UserControls/ForumList.ascx”, new{GroupID=2})%>

If the UserControl is typed (ViewUserControl<MyControllerData>) then the ViewData from the controller will be passed down to the control.

To take it one step farther, let’s pretend we’re building a Forums application and we want to create a skinning engine which applies certain styles (or “skins”) to our application based on a setting somewhere and for this we’ve decided to leverage UserControls.

Normally we’d have to jump through some hoops (small ones) with the BuildManager and some reflection to get this to work properly (note that that since we’re not using Server Controls, we also can’t use the ASP.NET Skinning Engine).

With MVC we simply setup a Themes folder, and inside of that put our Theme directories. We can then define a set of user controls for each “skin” or Theme, and then call them as needed, based on the current theme. But how do we know what that theme is? And how do we call it?

Since we don’t want all this logic in our UI (deciding on which theme and how to call it), we create a Helper Class that will decide for us what theme we’re using. Helpers are essentially static classes, designed to contain the UI logic that otherwise clutters up your UI. Think of these as UI utilities.

Since Theme logic is application-wide, I’m going to create an AppHelper class (as opposed to a helper specific to my controller), and put it in my Helpers folder, using the Global namespace. In here I can now put shared View Logic:

apphelper

And now I can create a method to help me decide which themed control to use. I’ll call it GetThemedControl():

/// <summary>/// Gets the root of the site we're working with/// </summary>/// <returns></returns>public static string GetSiteRoot() {        string appPath = System.Web.HttpContext.Current.Request.ApplicationPath;    if (appPath == string.Empty)        appPath = "/";    return appPath;}/// <summary>/// Gets a user control based on the current theme/// </summary>/// <param name="controlName">The name of the control</param>/// <returns></returns>public static string GetThemedControl(string controlName) {    string appPath = GetSiteRoot();    if (!controlName.EndsWith(".ascx"))        controlName += ".ascx";        //all themes are kept in ~/Themes/ThemeNAME    string result = appPath+"Themes/" + GetTheme() + "/" + controlName;    return result;}

One thing you’ll notice here is that I can no longer call “Page.ResolveClientUrl()” because, well, we have no Page! I’ll show you below how you can get around that with our LinkExtensions class.

The GetTheme() method above can do something as simple as check the Web.Config’s appSettings, or it can shoot to the DB to see which theme the user likes – this parts up to you.

So using GetThemedControl(), I can now rewrite the call to be:

<%=Html.RenderUserControl(AppHelper.GetThemedControl("ForumList"),new {GroupID=2})%>

Nice and tight :) .

 

Forms
Working with forms takes on a whole new meaning with ASP.NET MVC since we are no longer bound to one form per page. For those of you who have used Web Forms almost explicitly, this might come as a bit of a Strange New Thing.

You can work with many forms on a page – there is no limit – and often it’s nice to be able to split things up like this. Each form submits itself to a Controller/Action, and that action is responsible for doing whatever needs done.

The toolkit allows you to create forms in a lot of ways, including the most transparent way (using our Url helper):

<form action="<%=Url.Action(new{controller="Home", action="Index"})%> method=post>

If all of the “new{thing=1}” code is freaking you out – this is new to C# 3.5 and it’s anonymous typing. Basically when you see this, know that in the background we’re setting properties based on what you send in. This is typical in dynamic languages like Ruby, and you’ll see this a lot in the code below (specifically when creating HTML attributes for HTML tags).

The <form> tag above works fine, but it’s a tad verbose. It’s also worth mentioning at this point that

All you know and think about URLs and ASP.NET is out the window. URLs no longer route to a physical file – they are (essentially) Remote Procedure Calls. Given that, you generally don’t want to “hard-code” URLs into a page since it makes your site pretty brittle. Use ViewPage.Url to resolve the URL for you.

You can set routes to your controllers as you see fit, and it’s very customizable. You’re no longer constrained to folder structure so keep that in mind as you work with MVC – you may decide to change your routes as you develop your application – this might very well break your links if you don’t use our helpers (or some of your own).

You can also use a nice, type-safe way to create a form (this was Phil’s brain child):

<%using(Html.Form<HomeController>(action=>action.Index()))}%>...<%}%>

This is a nice way to create a form control since you can work with intellisense, and have type-safety associated with your form tag. It’s also nice because you get a compiler error if you don’t close off your Form tag :) .

If you don’t like all that code, you can also do this:

<%using(Html.Form("Home","Index"))%>...<%}%>

If you want to use GET, you can (for either methods):

<%using(Html.Form("Home","Index"), FormExtensions.FormMethod.get)%>...<%}%>

To submit the form, you can use:

<%=Html.Submit()%>

or

<%=Html.Submit("Save")%>

or use an image

<%=Html.SubmitImage("~/Images/SaveButton.gif")%>

 

Using Form Data In Your Controller
Once you post your form data back, we have a helper that you can use to gather it from Request.Form. There are two such methods:

Object.UpdateFrom(Request.Form)

and

Controller.ReadFromRequest(string key)

UpdateFrom basically introspects your object and then tries to match your object’s properties to the keys passed in from Request.Form – in other words “auto-binding” it. So if you’re working with the Northwind.Product class (using any object type – it doesn’t matter), you could use:

Product p=new Product();p.UpdateFrom(Request.Form);

This will bind your object to the values passed-in on the form. If there is a type-conversion error (or other error), we wait until the end of the method, and then throw a PopulateTypeException. This exception gathers all the exceptions into a collection for you to evaluate in your code. The important thing here is that if there is an error – the binding doesn’t stop, it continues to “do it’s best” and then lets you know there was a problem.

Currently we support 3 naming conventions (i.e. the name for your controls in your HTML page) that UpdateFrom() uses to “sniff out” the passed-in form values for binding:

  • Property Name (“ProductName”)
  • Object.PropertyName(“Product.ProductName”)
  • Object_PropertyName(“Product_PropertyName”)

Controller.ReadFromRequest() is another extension method that you can call in your controller to pull a value that has been passed in using HTTP GET or POST (or the QueryString):

string myName=this.ReadFromRequest("name");

 

Select, ListBox, CheckBoxList, RadioButtonList
We very much wanted to create an experience with these methods much like you had with their Web Form counterparts.

Each control works in much the same way: you specify a name, a data source, selected values, etc. The dataSource, currently, can be:

  • IEnumerable
  • DataSet
  • DataTable
  • IQueryable (Linq)
  • IDataReader

For CheckBoxList and ListBox, the selectedValues parameter is also IEnumerable.

Samples:

<%
//Sample Datastring [] songs=new string[]{"Robot Rock (Daft Punk)","Stairway to Heaven (Zeppeling)",     "New Slang (Shins)"};string [] movies=new string[]{"Tron","Big Trouble In Little China",     "Say Anything"};string [] zodiac =new string[]{"Aries", "Taurus","Gemini","Cancer","Leo",    "Virgo","Libra","Scorpio","Sag","Capricorn","Aquarius","Haack"};%>Sign:<br /><%=Html.Select("myZodiac",zodiac) %><br />Sign (select Haacked):<br /><%=Html.Select("myZodiac",zodiac,"Haacked") %><br />        Favorite Movie:<br /><%=Html.CheckBoxList("favMovie",movies).ToFormattedList("<li>{0}</li>") %><br />Favorite Movie, List :<br /><%=Html.ListBox("favMovie",movies,new string[]{"Say Anything"}) %><br />Favorite Movie, List, Long, Mult (select "Say Anything" and "Tron")i:<br /><%=Html.ListBox("favMovie",movies,20,true,new string[]{"Say Anything", "Tron"}) %><br />Favorite Songs (Select Shins):<br /><%=Html.CheckBoxList("favSongs",songs,new string[]{"New Slang (Shins)"})    .ToFormattedList("<li>{0}</li>") %><br />Favorite Songs:<br /><%=Html.CheckBoxList("favSongs", songs).ToFormattedList("<li>{0}</li>")%><br />Favorite Songs, Radio:<br /><%=Html.RadioButtonList("favSongs", songs,"Robot Rock (Daft Punk)")    .ToFormattedList("<li>{0}</li>")%><br />

One super-groovy thing to note here is how CheckBoxList() calls “ToFormattedList()”. This is another Extension Method that hangs off of “IEnumerable” and allows you to enumerate the values to a list.

CheckBoxList and RadioButtonList return String[] – not strings! Each string is a single <checkbox> or <radio> tag.

The reason we did it this way was to allow you as much freedom as possible in terms of laying out your page, but if you don’t want to write a foreach loop each time, we included the ToFormattedList() method to make things easier.

 

TextBox, TextArea, Password, Hiddens
These methods work in the same way as above. Here are some samples:

Name:<br /><%=Html.TextBox("txtName",20) %><br />Name, with maxlength:<br /><%=Html.TextBox("txtName","My Value",20,20) %><br />Thoughts:<br /><%=Html.TextArea("txtBlob","Lorem Ipsum la la la") %><br />Thoughts, with maxlength, rows, cols:<br /><%=Html.TextArea("txtBlob","Lorem Ipsum la la la",20,10,40) %><br />Password<br /><%=Html.Password("myPassword",50)%><br />
With size, value<%=Html.Password("myPassword",50,"My Password")%><br />
With size, value, style attribute<%=Html.Password("myPassword", 50,"",new { style = "width:100px" })%><br />

Links, Buttons, and Navigation
We augmented the Url.Action() method to include a nice, type-safe way of navigating between controllers and actions on your site:

<%=Html.ActionLink<HomeController>(x=>x.Index(),"Home, using Action<T>") %>

Notice that in the linkText argument (second position, after the Lambda) that I’m outputting <T>? It’s worth mentioning that we scrub the settable text bits using Html.Encode (another extension method).

You can also navigate between controllers using Button<>():

<%=Html.Button<HomeController>(x=>x.Index(),"cmdNav2","Home") %>

You can also navigate around the internets using NavigateButton():

<%=Html.NavigateButton("cmdNav","GoTo MSN","http://www.msn.com") %>

If you have some custom javascript to run, you can do that with Button():

<%=Html.Button("cmdJS","Click Me","DoTheClickThang()")%>

If you like using Images as buttons, you can do that too, using ImageButton():

<%=Html.Button("cmdJS","~/Images/Clicky.png","DoTheClickThang()")%>

Note: with this method we make sure the cursor hand appears when hovering to denote a clickable element.

 

Images, ResolveUrl, and MailTo
We really want to make things as easy as can be for you with the new MVC Toolkit. So we’ve also thrown in an Image method:

A nice picture of a wave:<%=Html.Image("~/Images/bigwave.jpg") %><br />Set the name and width<%=Html.Image("~/Images/bigwave.jpg", "myWave", new { width = "30px" })%><br />

Since you’re ViewPage no longer has access to Page proper, we’ve included a method to help you resolve URLs on your page:

<%=Html.ResolveUrl("~/HALP!")%>

If you want to embed an email link, you can do so using MailTo():

Simple Email:<%=Html.MailTo("robcon@microsoft.com","Email Me!") %>With Subject and Body<%=Html.MailTo("robcon@microsoft.com","Email Me!","Sending you a note"    ,"Hey - wanted to say hi!") %>

MailTo() contains all the overloads needed to send subject, body, cc, bcc, to the client’s email program.

 

Summary
Long, long post – but I thought it was worth it to get as much out there as possible. The CTP will be landing very soon, and with it will be the MVC Toolkit. We’ve tried to pack as much UI help in there as possible – and please know that these things will likely change as we keep building and tweaking.

As always – would love to hear your thoughts!

PS: YES, there is lots of room for SubSonic here, and I’ve already started putting together the UI “Sugar” that we’re going to offer on top of MVC. Look for that in the next post :) .