TextBoxFor(u => u.Name) – Unleash the power

Introduction


Three weeks ago, Jeremy Miller and Chad Myers laid down their MVC approach. From the comments it’s clear that many of their goals resonated with fellow developers. Since I happened to be starting a large-scale application using MVC, I was very interested in learning more. The videos, from their presentation, detailed their customized MVC stack, but didn’t really provide us with anything to hit the ground running (i.e. working code).Chad has stated that he’s working on some code, but I figured I’d share my implementation of something he and Jeremy talked about: the TextBoxFor control. Now I don’t know exactly how they implemented their version, and mine just provides the necessary basis to get started, but hopefully it’ll help people bake some of this goodness within their own code.


Base ViewPage


The first thing to do is create our own base ViewPage which our views will inherit from. Since this code relies on passing strongly-typed objects to our view (remember, “no slinging around HashTable’s, IDictionary’s, or magic strings”), we inherit from ViewPage<T> instead of ViewPage:

namespace CodeBetter.Web.Components
{
public class ApplicationView<T> : ViewPage<T> where T : class
{
}
}

For those not familiar with generic constraints, the where T : class states that T must be a reference type (this is actually a constraint in the base ViewPage<T> which must be repeated when we inherit from it). This simple code actually has a nasty (and stupid) side effect. To use a generic within the ASP.NET @Page directive we need to use CLR notation (because the ASP.NET directives are language agnostic). In other words, if we want to use a User object in our thunderdome system, we need to declare our view as:

<%@ Page Language=”C#” Inherits=”CodeBetter.Web.Components.ApplicationView`1[[CodeBetter.User,CodeBetter]]” … %>

We can’t use <XXX>, but instead have to use the CLR `[[XXX,YYY]] notation. This quickly gets out of hand if you’re model is a generic type itself. The simplest solution is to use a codebehind (yuck!) file for your view:

login.aspx:
<%@ Page Language=”C#” Inherits=”CodeBetter.Web.Views.Login” … %>

login.aspx.cs:
public class Login : ApplicationView<User>
{
}


Expressions


Next we’re going to add the method definition for our TextBoxFor method:

protected internal MemberTextBox TextBoxFor(Expression<Func<T, string>> action)
{

}


We’re going to implement MemberTextBox shortly (named because it’ll server as a TextBox for a class member). First though we need to get over that crazy parameter. Expression<Func<T, string>>, what the func? I’ve discussed Func<T, X> and their usefulness before (you should read that blog post, good stuff!) it’s simply a generic delegate that expects a type T as a parameter and returns type X. So in our example it’s expecting type T (which is the type being passed as our ViewData.Model) and returns a string. As for the expressions (and I’m by no means an expert on Expressions, hopefully someone will write a good follow up to explain it better), they are a new features in .NET 3.5 used to implement LINQ (as such you’ll find them in the System.Linq.Expressions namespace). When you supply a delegate as an Expression, the code doesn’t actually execute. Instead it gets turned into a tree of objects which we can use to dynamically build our textbox. Still confused, look at it this way. If we do this within our view without using an Expression:

<% User user = ViewData.Model %>

<%= TextBoxFor(u => u.Name) %>

Guess what’ll get passed into our TextBoxFor method? The user’s actual name (because the code will be executed as you’d normally expect). We need more than that – we need the expression itself so we can build a consistent naming (name=”u.Name”) as well as type information. You might be wondering where u comes from? This is the variable that gets passed into our function of type T, which we said would be a user. You need to pass an actual instance, which is why you must render your view via:

return view(new User());

Taking it further


Now that we have that ground-work out of the way, we can focus on actually implementing this thing.

protected internal MemberTextBox TextBoxFor(Expression<Func<T, string>> action)
{
var expression = (MemberExpression)action.Body;
var function = action.Compile();
string value = function(ViewData.Model);
return new MemberTextBox(expression.ToString(), value, expression.Member);
}

This code looks more complicated than it is. First we get the body of our actual expression as a MemberExpression (if you call TextBoxFor and supply a method, like u => u.Save(), you’ll get a cast exception). Next we compile the expression (you can more or less think of this as compiling your code within visual studio) which gives us a function. Remember our function definition, Func<T, string>, so we know it expects a T (in this particular case that’s a User, whatever it is, ViewData.Model will always be of type T) and returns a string. We execute the actual function and get the result. We now have everything we need to build our textbox.


MemberTextBox


Now we can build an initial version of our MemberTextBox

public class MemberTextBox
{
private readonly Dictionary<string, string> _attributes;

public MemberTextBox(string name, string value, MemberInfo member)
{
_attributes = new Dictionary<string, string>(4);
_attributes["type"] = “text”;
var maxlengthAttribute = member.GetCustomAttributes(typeof(MaximumLengthAttribute), true);
if (maxlengthAttribute != null && maxlengthAttribute.Length == 1)
{
_attributes["maxlength"] = ((MaximumLengthAttribute)maxlengthAttribute[0]).Length.ToString();
}
if (!string.IsNullOrEmpty(value))
{
_attributes["value"] = value;
}
}
public MemberTextBox AsPassword()
{
_attributes["type"] = “password”;
return this;
}
public MemberTextBox WithClass(string cssClass)
{
_attributes["class"] = cssClass;
return this;
}
public override string ToString()
{
var sb = new StringBuilder(75);
sb.Append(“<input “);
foreach(var attribute in _attributes)
{
sb.Append(attribute.Key);
sb.Append(“=\”");
sb.Append(HttpUtility.HtmlEncode(attribute.Value));
sb.Append(“\” “);
}
return sb.Append(“/>”).ToString();
}
}


This code is much more straight-forward and really provides everything we need as a basis to move on to a more advanced implementation. We’re basically building up a hashtable of names and values which then gets dumped as an HTML tag via the ToString override. Do notice that within the constructor we query our property to see if it has a MaximumLengthAttribute. This is a custom attribute that we use here at work (you should be able to easily find libraries that provide attribute-based validations online). I left this in so that you could see how the TextBoxFor can be used to really write powerful and domain-driven code with minimal effort (you’ll need to take it out to get this code to compile, plus fix all the other little mistakes I doubtlessly made!).


Improvement 1


If you’ve understood everything so far, you might be wondering why I limited my expression to returning a string. I think we can agree that it doesn’t make sense to allow any object (how do you display a “UserGroup” within a textbox?), but surely we should, at the very least, also allow numbers. In order to keep things nice and clean, and really show-off the power of this approach, we’ll actually create an overloaded TextBoxFor method:

protected internal MemberTextBox TextBoxFor(Expression<Func<T, int>> action)
{
var expression = (MemberExpression)action.Body;
var function = action.Compile();
int value = function(ViewData.Model);
return new MemberTextBox(expression.ToString(), value, expression.Member);
}

Which also requires an overloaded constructor within MemberTextBox (which accepts an int instead of a string):

public MemberTextBox(string name, int value, MemberInfo member) : this(name, value.ToString(), member)
{
_attributes["rel"] = “numeric”;
}

As you can see, whenever we create a textbox for an integer, we’ll be adding a rel=”numeric” attribute to our tag. Why? Now we can actually use a JavaScript library and automatically apply a numeric mask to any inputs with a rel=”numeric” (and it just so happens that I wrote an article for DotNetSlackers just the other week that accomplished this using jQuery). So, with very little effort, our code will automatically detect that our type is an int and apply a numeric mask to the appropriate textboxes.


Improvement 2


The last thing we’ll look at is cleaning up our code. Specifically, we’ll introduce a base MemberControl that’ll provide much of the core functionality, allowing us to easily create a CheckBoxFor and so on. First though, let’s clean up our ApplicationView a little (and while we’re at it, let’s add the CheckBoxFor method):

protected internal MemberTextBox TextBoxFor(Expression<Func<T, int>> action)
{
var expression = (MemberExpression)action.Body;
return new MemberTextBox(expression.ToString(), GetValue(action), expression.Member);
}
protected internal MemberTextBox TextBoxFor(Expression<Func<T, string>> action)
{
var expression = (MemberExpression)action.Body;
return new MemberTextBox(expression.ToString(), GetValue(action), expression.Member);
}
protected internal MemberCheckBox CheckBoxFor(Expression<Func<T, bool>> action)
{
var expression = (MemberExpression)action.Body;
return new MemberCheckBox(expression.ToString(), GetValue(action), expression.Member);
}
private V GetValue<V>(Expression<Func<T, V>> action)
{
var function = action.Compile();
return function(ViewData.Model);
}

There are only two interesting things here. First we’ve introduced a GetValue method to remove some of the repetition found in each of our methods. Secondly, as you probably expect, our CheckBoxFor expects members that return Booleans.


Now we create our base MemberControl class:

public abstract class MemberControl<T>
{
private readonly Dictionary<string, string> _attributes;
private readonly MemberInfo _member;
protected internal abstract string TagName { get; }
protected MemberInfo Member{ get { return _member; } }
protected internal Dictionary<string, string> Attributes
{
get { return _attributes; }
}

protected MemberControl(string name, MemberInfo member)
{
_attributes = new Dictionary<string, string>(4);
_member = member;
Add(“name”, name);
}

public T WithClass(string className)
{
Add(“class”, className);
return This();
}
public T WithId(string id)
{
Add(“id”, id);
return This();
}

protected internal abstract T This();
protected void Add(string key, string value)
{
_attributes[key] = value;
}
public override string ToString()
{
var sb = new StringBuilder(75);
sb.Append(“<”);
sb.Append(TagName);
sb.Append(” “);
foreach(var attribute in _attributes)
{
sb.Append(attribute.Key);
sb.Append(“=\”");
sb.Append(HttpUtility.HtmlEncode(attribute.Value));
sb.Append(“\” “);
}
return sb.Append(“/>”).ToString();
}
}


This is largely what we saw in our previous MemberTextBox code so should be pretty familiar. Now we’ll look at our simplified MemberTextBox:

public class MemberTextBox : MemberControl<MemberTextBox>
{
protected internal override string TagName
{
get { return “input”; }
}
public MemberTextBox(string name, int value, MemberInfo member) : this(name, value.ToString(), member)
{
Add(“rel”, “numeric”);
}
public MemberTextBox(string name, string value, MemberInfo member) : base(name, member)
{
Add(“type”, “text”);
var attribute = member.GetCustomAttributes(typeof(MaximumLengthAttribute), true);
if (attribute != null && attribute.Length == 1)
{
Add(“maxlength”, ((MaximumLengthAttribute)attribute[0]).Length.ToString());
}
if (!string.IsNullOrEmpty(value))
{
Add(“value”, value);
}
}
protected internal override MemberTextBox This()
{
return this;
}
public MemberTextBox AsPassword()
{
Add(“type”, “password”);
return this;
}
}

And, while we’re at it, take a look at the MemberCheckBox class:

public class MemberCheckBox : MemberControl<MemberCheckBox>
{
protected internal override string TagName
{
get { return “input”; }
}
public MemberCheckBox(string name, bool value, MemberInfo member) : base(name, member)
{
Add(“type”, “checkbox”);
if (value)
{
Add(“checked”, “checked”);
}
}
protected internal override MemberCheckBox This()
{
return this;
}
}

Conclusion


So that certainly is a lot of code. And it is relatively complicated. However, I think the numeric textbox example and integration with our validation attributes really showcases how simple it is to write powerful UIs.

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

20 Responses to TextBoxFor(u => u.Name) – Unleash the power

  1. karl says:

    David:
    The actual code is heavily unit tested. The code here was heavily modified for the masses and to provoke discussions. Had I provided a download like to an assembly, I’d also have included unit tests. The code isn’t particularly difficult to test – it isn’t coupled to anything – input an expression output a string. As others have mentioned, there’s obviously some work being done by Chad and Jeremy and friends to get something more official out there (see above comments) – I suspect you’ll see unit tests out of that effort.

  2. David Kemp says:

    Where are the unit tests? I can see people copying this code, tweaking it, and having no tests around it.
    I’m sure you drove out this code from tests, so where are the tests you used to develop it?

  3. Daniel says:

    Cool, but at some point, this becomes ASP.NET DynamicData. For example, why not:

    Html.ControlFor(u => u.Name)

    Where ControlFor looks at the type and decides the best UI control for it. But, if you do that, you may as well gen up the whole form that way:

    Html.FormFor(u)

    Where it reflects the properties and calls ControlFor for each. But if you do that, you may as well gen up UI for your whole domain. At which point, you’ve rewritten DynamicData…

  4. karl says:

    Thanks Phil. Pretty new to using expressions, didn’t run into any problems so stuck with that :)

  5. Haacked says:

    Why use MemberExpression and not Expression? Try adding a property of type System.Nullable to your model.

  6. Tim Scott says:

    I also attended the “Opinionated” presentation, and I have been working on something similar. Basically, I’ve got this:

    public class ConventionViewPage : ViewPage, IViewModelContainer where T : class
    {
    public T ViewModel
    {
    get { return ViewData.Model; }
    }
    }

    …along with some brothers, ConventionUserControl and ConventionMasterPage. Then…

    public static class ViewModelContainerExtensions
    {
    public static TextBox TextBox(this IViewModelContainer view, Expression> expression) where T : class
    {
    return new TextBox(GetMemberExpression(expression)).Value(GetValueFor(view.ViewModel, expression));
    }

    Used like this…

    < %=this.TextBox(x => x.Name).Class(“required”).Label(“Name:”).Title(“Enter your name”)%>

    IViewModelContainer implements IViewDataContainer. It provides a common interface for all things that contain a strongly typed view model and so the extension methods apply to view, user control or master. Most importantly, no reflection is needed.

    For view pages without strongly typed model I have ViewDataContainerExtensions which returns the same chain-able objects as does ViewModelContainerExtensions, but instead of taking an expression they take the name as a string. There is no need of a custom view page for these because they extend IViewDataContainer, which the basic page, user control and master all implement.

    I have stopped short of the custom attribute magic. It’s way cool, but my view objects are DTOs, not domain entities. I might add this later. My HTML objects have the member expression and so this could easily be added.

    By the way, thanks to Chad for some feedback he gave me on an ealier version, although he may want to deny it. :)

  7. Jeff Handley says:

    Karl,

    Yeah — what you have it definitely much beyond what I had done — but the idea is the same. Instead of having extension methods that return strings, create objects and override ToString() to return the Html. I don’t know that TagBuilder was there when I created my stuff, so I had rolled my own centralized way of rendering objects as HTML tags.

    In MVC, I still want to have Custom Controls that are extensible and flexible. The Html Helpers are tough to extend to slightly change the behavior of the existing functionality I thought.

    -Jeff

  8. karl says:

    @LaptopHeaven:
    The DoveTail guys might have beat me to the code, but I predictably beat them to the documentation.

  9. Colin Jack says:

    Really cool stuff, great work and we’ll definitely be giving this approach a go very soon.

  10. karl says:

    The strongly-typed HTML helper is certainly a welcome addition. Will look into it. The fact that we’re even having this discussion really speaks well for the flexibility of the framework (which of course brings up the interesting conversation of flexibility vs opinionated).

    Same with the TagBuilder – although for simple cases like this, object-base rendering is often more of a mess than simply building a string. I’ll look into it though, maybe its really lightweight.

  11. You might want to look at the class: System.Web.Mvc.TagBuilder

    It’s used by the built-in HtmlHelper extensions and makes it really easy to valid HTML tags rather than doing it all yourself.

  12. Torkel says:

    Very nice stuff, I and I like Scott’s idea of generic types helpers for specific views.

  13. Chad Myers says:

    So what we did is to prefer more Extension methods on the view pages:

    http://code.google.com/p/opinionatedmvc/source/browse/trunk/src/OpinionatedMVC.Core/Html/HtmlExtensions.cs

    We use light-weight “Expression Builder” (Fluent API/Method Chaining, builder) objects and then add common functionality via extension methods to avoid having a “Huge Base Class” problem.

    The base class:
    http://code.google.com/p/opinionatedmvc/source/browse/trunk/src/OpinionatedMVC.Core/Html/Expressions/HtmlExpressionBase.cs

    A simplistic data-bound textbox expression implementation:

    http://code.google.com/p/opinionatedmvc/source/browse/trunk/src/OpinionatedMVC.Core/Html/Expressions/TextBoxExpression.cs

    Then, we add extra juice to all Html Expressions for common stuff:

    http://code.google.com/p/opinionatedmvc/source/browse/trunk/src/OpinionatedMVC.Core/Html/Expressions/HtmlExpressionExtensions.cs

    Eventually, that enables us to do things like this:

    < %= this.TextBoxFor(m=>m.GreetingText)
    .AsMultiline()
    .Required()
    .Width(300) %>

  14. ScottGu says:

    An alternative you might consider is creating a HtmlHelper class that derives from HtmlHelper:

    public class HtmlHelper : HtmlHelper
    {
    public HtmlHelper(ViewContext viewContext, IViewDataContainer viewDataContainer)
    : base(viewContext, viewDataContainer)
    {

    }
    }

    You could then have your custom ViewPage class hide the built-in Html property and expose HtmlHelper instead:

    public class MyCustomViewPage : ViewPage where TModel : class
    {
    public new HtmlHelper
    Html {
    get
    {
    return new HtmlHelper
    (base.Html.ViewContext, base.Html.ViewDataContainer);
    }
    }
    }

    The advantage of this is that you can now implement strongly-typed viewhelper methods as extension methods to HtmlHelper – where the TModel type comes from the ViewPage declaration.

    For example, you could implement a TextBoxFor() helper method that enabled:

    < %= Html.TextBoxFor(p=>p.Name) %>

    Using an extension method like so:

    public static class StronglyTypedHtmlHelper
    {
    public static string TextBoxFor(this HtmlHelper htmlHelper, Expression> action)
    {
    var expression = (MemberExpression)action.Body;
    var function = action.Compile();

    TModel model = (TModel) htmlHelper.ViewData.Model;

    string value = function(model);

    return ““;
    }
    }

    You will get full VS 2008 intellisense for extension methods like this within the .aspx editor. Because they are extension methods, you can implement them in separate libraries (as opposed to having to bake them into your base class). This will also naturally “group” your html helpers in one place under the Html. property (making them a little more discoverable).

    Because HtmlHelper derives from HtmlHelper, you will be able to see and use all existing html helper extensions built-into ASP.NET MVC, as well as all strongly typed ones you add. If you want to hide the built-in late-bound extension methods you can do this by simply commenting out the System.Web.Mvc.Html namespace from web.config (since the built-in helpers are implemented as extension methods – if you nuke their namespace they will disappear).

    The above solution today requires you to add HtmlHelper and a custom ViewPage to enable this scenario. We are considering adding these types built-into ASP.NET MVC 1.0 – which would enable you to write strongly-typed Html.TextBoxFor(p=>p.Name) extension methods out of the box really easily.

    Hope this helps,

    Scott

  15. Hi Karl,

    Take a look at this:

    http://akuaproject.googlecode.com/svn/trunk/framework/trunk/src/Web.MVC/

    its part of my own “glue” framework which handles the MVC part, there you can find helpers for all html control types. It is implemented based on Chad’s and Jeremry’s ideas.

    Its a first version implementation and needs more cleanup, but it works for now.

    Cheers!

    Vladan

  16. Chad Myers says:

    @LaptopHeaven:

    Drats! I’ve been outed…

    *ahem* I mean, I have no idea what you’re talking about. Surely you are mistaken.

  17. LaptopHeaven says:

    I found they have posted code to google:

    http://code.google.com/p/opinionatedmvc/

  18. Morten Lyhr says:

    Great stuff.

    I and think the level of abstraction is just right.

    One of the main reasons i like monorail and ASP MCV is that the markup is clean. I can read the markup and i know how the final result will be,

    If the markup builder (TextboxFor, Html etc) get to abstract, you will loose the transparency.

    I wonder how long before someone introduces a markup builder like:

    RenderMarkupFor(someModel)

    with nothing else in the view.

  19. karl says:

    Jeff:
    Cool stuff. Though I do think what I have here is an evolution beyond what you did. This approach really makes the domain king. Without any extra code in your markup, TextBoxFor(u => u.Name) can be generate pretty complex html – validation rules, javascript, whatever. We actually have it generate labels, so that:

    TextBoxFor(u => u.Name); automatically creates:

    (it looks up “User:Name” in a dictionary used for localization).

    We also have it so you can present a list based on an enum:

    RadioListFor(u => u.AccountType).Exclude(AccountType.Uknown).TemplatedWith(“

    {0} {1}

    “)

    will automagically generate:

    Guest
    Normal
    Admin

    and check the right one if an existing User is supplied.

  20. Jeff Handley says:

    Karl,

    This is great stuff. It looks a lot like some stuff I blogged about awhile back.

    http://blog.jeffhandley.com/archive/2008/03/08/custom-controls-everywhere-and-asp.net-mvc-part-2.aspx

    While I got good feedback from some folks on my blog, Phil Haack and Rob Conery weren’t sold. I’d be curious to see what kind of feedback you get.

    Jeff Handley