Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

StringLength in ViewModel not setting maxlength on textbox

Here the property in my ViewModel:

[Display(Name = "Ext.")]
[MaxLength(6, ErrorMessage = "Must be a maximum of 6 characters")]
[StringLength(6)]
public string Extension { get; set; }

And in my View:

@Html.EditorFor(model => model.Extension)

And it renders:

<input class="text-box single-line" data-val="true" data-val-length="The field Ext. must be a string with a maximum length of 6." data-val-length-max="6" id="Extension" name="Extension" type="text" value="" />

Should this be setting the maxlength attribute on my textbox? If not, how can I do that with DataAttributes?

like image 585
Mike Cole Avatar asked Feb 06 '13 22:02

Mike Cole


2 Answers

I'd like the attribute that I set in the ViewModel to control this if possible.

ASP.NET MVC provides an extensible system for doing exactly this. Here is what you need to do:

  1. Implement a custom ModelMetadataProvider.
  2. Look for the StringLengthAttribute or the MaxLengthAttribute, extract the information and add it to the ModelMetadata.
  3. Provide a custom Editor template that makes use of the information.

Step 1: Implement a custom ModelMetadataProvider.

Create a class that derives from ModelMetadataProvider. Typically you would derive from the DataAnnotationsModelMetadataProvider as this provides some default functionality which means you only have to override a single method called CreateMetadata.

Step 2: Extract the information:

To get the information, you need to look for the attribute, extract the maximum length information and add it to the AdditionalValues dictionary of the ModelMetadata. The implementation would look something like this (this is the entire implementation):

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    protected override ModelMetadata CreateMetadata(
        IEnumerable<Attribute> attributes, 
        Type containerType, 
        Func<object> modelAccessor, 
        Type modelType, 
        string propertyName)
    {
        // Call the base class implementation to create the default metadata...
        var metadata = base.CreateMetadata(
            attributes, 
            containerType, 
            modelAccessor, 
            modelType, 
            propertyName);

        // Extract the stringLengthAttribute (you can do the same for the
        // MaxLengthAttribute if you want).
        var attr = attributes
            .OfType<StringLengthAttribute>()
            .First();

        // This could be getting called on a property that doesn't have the
        // attribute so make sure you check for null!
        if (attr != null)
        {
            metadata.AdditionalValues["maxLength"] = attr.MaximumLength;
        }

        return metadata;
    }
}

In order for ASP.NET MVC to use this you need to register it in the Application_Start method in Global.asax.

ModelMetadataProviders.Current = new CustomModelMetadataProvider();

Step 3: Create a custom editor template.

You now need to create a view that uses the information. Create a new view called String in the Views\Shared\ folder.

String.cshtml

@{
    object maxLength;
    if (!ViewData.ModelMetadata.AdditionalValues
            .TryGetValue("maxLength", out maxLength))
    {
        maxLength = 0;
    }

    var attributes = new RouteValueDictionary
    {
        {"class", "text-box single-line"},
        { "maxlength", (int)maxLength },
    };
}

@Html.TextBox("", ViewContext.ViewData.TemplateInfo.FormattedModelValue, attributes)

When you run your application you will get the following HTML output by calling @Html.EditorFor.

<input class="text-box single-line" id="Extension" maxlength="6" name="Extension" type="text" value="" />

If you want to know more about the model metadata provider system, Brad Wilson has a series of blog posts that detail how it works (these were written prior to the Razor view engine so some of the view Syntax is a bit funky but otherwise the information is sound).

like image 61
Benjamin Gale Avatar answered Sep 18 '22 16:09

Benjamin Gale


Essentially based on Brad's answer, wrapped in an extension on the Html helper using lambda syntax so you don't pollute your Razor views with reflection stuff:

using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;

public static class HtmlHelper
{
    public static int? MaxLength<TModel, TProperty>(
        this HtmlHelper<TModel> htmlHelper,
        Expression<Func<TModel, TProperty>> expression)
    {
        MemberExpression memberExpression = (MemberExpression)expression.Body;

        PropertyInfo property = typeof(TModel)
            .GetProperty(memberExpression.Member.Name);

        StringLengthAttribute attribute = (StringLengthAttribute)property
            .GetCustomAttributes(typeof(StringLengthAttribute), true)
            .FirstOrDefault();

        if (attribute != null)
        {
            return attribute.MaximumLength;
        }

        return null;
    }
}

Use it like such:

@Html.TextBoxFor(x => x.Name, new { maxlength = Html.MaxLength(x => x.Name) })

where x refers to your model.

If the StringLengthAttribute is not declared for the property, null will be returned and the maxlength attribute will be empty on the textbox element.

Remember to include using in your razor page so you can access the method.

@using HtmlHelper

You also need to use none null-able result for the method to overcome compile error.

like image 34
djule5 Avatar answered Sep 21 '22 16:09

djule5