Monday, December 07, 2009

ASP.NET MVC 2 Beta EditorFor improvements

ASP.NET MVC 2 Beta has some great improvements.   One of these improvements is the Html.EditorFor extension.  Basically, this extension takes data annotations and types information from your model and uses those to generate editors.  You MUST use a strongly typed view to take advantage of this!

I am going to give a brief introduction, just to get to my enhancement - for more information see this article.

So, if you have this:

<%= Html.EditorFor( mymodel => mymodel.SomeField %>

If SomeField is a boolean, it will generate a checkbox.  If SomeField is a string, it will generate a textbox.  When it generates the editor it uses a new template system that you can change.  So, if you define a partial view called 'string.ascx' in an EditorTemplates folder, it will use that when it encounters a string that needs an editor.  You can also specify a [UIHint("myspecialui")] in the data model and it will look for myspecialui.ascx to generate the editor.

One of the recognized shortcomings of EditorFor is the lack of an additional method parameter to specify additional html options, such as the class.   So that means you have to go back to TextBoxFor, which does not use any of the new templates. 

There are several annotations you can put on your model, some annotations are for use in DisplayFor, EditorFor and ValidatorMessageFor extensions. What is interesting is if you have a [StringLength(20)] attribute on your model, this is used by the default ValidationMessageFor, but not by the default EditorFor template.  The MVC 2 beta does not set the maxlength attribute of the input box.   So, here is a new implementation of string editor for that does!

Create string.ascx in Views/Shared/EditorTemplates with the following code:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    Dictionary attributes = new Dictionary() {
                { "class", "text-box single-line " }
            };
    IEnumerable validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
    ModelClientValidationRule rule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault( m=> m.ValidationType=="stringLength");
    if (rule.ValidationParameters.ContainsKey("maximumLength"))
    {
        attributes.Add("maxlength", rule.ValidationParameters["maximumLength"]);
    }
%>
<%=Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, attributes)%>

Presto!  All strings on the site that use EditorFor extension will now have stringlength support in the input box.  Templates are a very powerful way to extend.  I am not sure the above was the best way to get to the StringLength data annotation - so, if anyone has any improvements let me know!

Edit 12/19: Latest version of above code:
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    Dictionary attributes = new Dictionary() {
                { "class", "text-box single-line " }
            };
    IEnumerable validators = ModelValidatorProviders.Providers.GetValidators(ViewData.ModelMetadata, ViewContext);
    ModelClientValidationRule rule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault(m => m.ValidationType == "stringLength");
    if (rule != null && rule.ValidationParameters.ContainsKey("maximumLength"))
    {
        attributes.Add("maxlength", rule.ValidationParameters["maximumLength"]);
    }
%>
<%=Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, attributes)%>
<%
    rule = validators.SelectMany(v => v.GetClientValidationRules()).FirstOrDefault(m => m.ValidationType == "required");
    if (rule != null)
    {
%>
*
<%}%> 

3 comments:

john said...

Hi, I can't seem to get this to work. Do you have a sample project?

Wayne Brantley said...

John,
I don't have a place to put downloads right now. Did you put [StringLength(20)] attribute on your datamodel member? Is the member a string?

If you did that, then just make sure the above template is in \views\shared\EditorTemplates\string.ascx

Also, I have edited the blog and posted the latest version of this template.

john said...

Thanks - I realized it was the fact I'm using ASP.NET 3.5 so this did not work

Dictionary

I just changed it to

Dictionary

and added the null check as you did in the updated version.

Thanks for a great blog!