maxlength attribute of a text box from the DataAnnotations StringLength in Asp.Net MVC

asp.net MvcValidation

asp.net Mvc Problem Overview


I am working on an MVC2 application and want to set the maxlength attributes of the text inputs.

I have already defined the stringlength attribute on the Model object using data annotations and it is validating the length of entered strings correctly.

I do not want to repeat the same setting in my views by setting the max length attribute manually when the model already has the information. Is there any way to do this?

Code snippets below:

From the Model:

[Required, StringLength(50)]
public string Address1 { get; set; }

From the View:

<%= Html.LabelFor(model => model.Address1) %>
<%= Html.TextBoxFor(model => model.Address1, new { @class = "text long" })%>
<%= Html.ValidationMessageFor(model => model.Address1) %>

What I want to avoid doing is:

<%= Html.TextBoxFor(model => model.Address1, new { @class = "text long", maxlength="50" })%>

I want to get this output:

<input type="text" name="Address1" maxlength="50" class="text long"/>

Is there any way to do this?

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

If you're using unobtrusive validation, you can handle this client side as well:

$(document).ready(function ()
{
    $("input[data-val-length-max]").each(function ()
    {
        var $this = $(this);
        var data = $this.data();
        $this.attr("maxlength", data.valLengthMax);
    });
});

Solution 2 - asp.net Mvc

I am not aware of any way to achieve this without resorting to reflection. You could write a helper method:

public static MvcHtmlString CustomTextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression, 
    object htmlAttributes
)
{
    var member = expression.Body as MemberExpression;
    var stringLength = member.Member
        .GetCustomAttributes(typeof(StringLengthAttribute), false)
        .FirstOrDefault() as StringLengthAttribute;

    var attributes = (IDictionary<string, object>)new RouteValueDictionary(htmlAttributes);
    if (stringLength != null)
    {
        attributes.Add("maxlength", stringLength.MaximumLength);
    }
    return htmlHelper.TextBoxFor(expression, attributes);
}

which you could use like this:

<%= Html.CustomTextBoxFor(model => model.Address1, new { @class = "text long" })%>

Solution 3 - asp.net Mvc

I use the CustomModelMetaDataProvider to achieve this

Step 1. Add New CustomModelMetadataProvider class

public class CustomModelMetadataProvider : DataAnnotationsModelMetadataProvider
{	
	protected override ModelMetadata CreateMetadata(
	    IEnumerable<Attribute> attributes,
	    Type containerType,
	    Func<object> modelAccessor,
	    Type modelType,
	    string propertyName)
	{
		ModelMetadata metadata = base.CreateMetadata(attributes,
		    containerType,
		    modelAccessor,
		    modelType,
		    propertyName);

        //Add MaximumLength to metadata.AdditionalValues collection
        var stringLengthAttribute = attributes.OfType<StringLengthAttribute>().FirstOrDefault();
        if (stringLengthAttribute != null)
            metadata.AdditionalValues.Add("MaxLength", stringLengthAttribute.MaximumLength);

		return metadata;
	}
}

Step 2. In Global.asax Register the CustomModelMetadataProvider

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    ModelMetadataProviders.Current = new CustomModelMetadataProvider();
}

Step 3. In Views/Shared/EditorTemplates Add a partial view called String.ascx

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%if (!ViewData.ModelMetadata.AdditionalValues.ContainsKey("MaxLength")) { %>
    <%: Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue,  new { @class = "text-box single-line" }) %>
<% } else {
    int maxLength = (int)ViewData.ModelMetadata.AdditionalValues["MaxLength"];
    %>
    <%: Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, new { @class = "text-box single-line", MaxLength = maxLength  })%>
<% } %>

Done...

Edit. The Step 3 can start to get ugly if you want to add more stuff to the textbox. If this is your case you can do the following:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%
    IDictionary<string, object> Attributes = new Dictionary<string, object>();
    if (ViewData.ModelMetadata.AdditionalValues.ContainsKey("MaxLength")) {
        Attributes.Add("MaxLength", (int)ViewData.ModelMetadata.AdditionalValues["MaxLength"]);
    }
    if (ViewData.ContainsKey("style")) {
        Attributes.Add("style", (string)ViewData["style"]);
    }
    if (ViewData.ContainsKey("title")) {
        Attributes.Add("title", (string)ViewData["title"]);
    }
%>
<%: Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue, Attributes)%>

Solution 4 - asp.net Mvc

If you want this to work with a metadata class you need to use the following code. I know its not pretty but it gets the job done and prevents you from having to write your maxlength properties in both the Entity class and the View:

public static MvcHtmlString TextBoxFor2<TModel, TProperty>
(
  this HtmlHelper<TModel> htmlHelper,
  Expression<Func<TModel, TProperty>> expression,
  object htmlAttributes = null
)
{
  var member = expression.Body as MemberExpression;
			
  MetadataTypeAttribute metadataTypeAttr = member.Member.ReflectedType
    .GetCustomAttributes(typeof(MetadataTypeAttribute), false)
    .FirstOrDefault() as MetadataTypeAttribute;

  IDictionary<string, object> htmlAttr = null;

  if(metadataTypeAttr != null)
  {
    var stringLength = metadataTypeAttr.MetadataClassType
      .GetProperty(member.Member.Name)
      .GetCustomAttributes(typeof(StringLengthAttribute), false)
      .FirstOrDefault() as StringLengthAttribute;

    if (stringLength != null)
    {
      htmlAttr = new RouteValueDictionary(htmlAttributes);
      htmlAttr.Add("maxlength", stringLength.MaximumLength);
    }				    				 
  }

  return htmlHelper.TextBoxFor(expression, htmlAttr);
}

Example class:

[MetadataType(typeof(Person.Metadata))]
public partial class Person
{
  public sealed class Metadata
  {

    [DisplayName("First Name")]
    [StringLength(30, ErrorMessage = "Field [First Name] cannot exceed 30 characters")]
    [Required(ErrorMessage = "Field [First Name] is required")]
    public object FirstName { get; set; }

    /* ... */
  }
}

Solution 5 - asp.net Mvc

While I'm personally loving jrummel's jquery fix, here's another approach to keeping a single-source-of-truth up in your model...

Not pretty, but.. has worked o.k. for me...

Instead of using property decorations, I just define some well-named public constants up in my model library/dll, and then reference them in my view via the HtmlAttributes, e.g.

Public Class MyModel

    Public Const MAX_ZIPCODE_LENGTH As Integer = 5

    Public Property Address1 As String

    Public Property Address2 As String

    <MaxLength(MAX_ZIPCODE_LENGTH)>
    Public Property ZipCode As String

    Public Property FavoriteColor As System.Drawing.Color

End Class

Then, in the razor view file, in the EditorFor... use an HtmlAttirubte object in the overload, supply the desired max-length property and referenece the constant.. you'll have to supply the constant via a fully qualied namespace path... MyCompany.MyModel.MAX_ZIPCODE_LENGTH.. as it won't be hanging right off the model, but, it works.

Solution 6 - asp.net Mvc

I found Darin's reflection based approach to be especially helpful. I found that it was a little more reliable to use the metadata ContainerType as the basis to get the property info, as this method can get called within mvc editor/display templates (where TModel ends up being a simple type such as string).

public static MvcHtmlString CustomTextBoxFor<TModel, TProperty>(
    this HtmlHelper<TModel> htmlHelper, 
    Expression<Func<TModel, TProperty>> expression, 
    object htmlAttributes
)
{
    var metadata = ModelMetadata.FromLambdaExpression( expression, new ViewDataDictionary<TModel>( htmlHelper.ViewDataContainer.ViewData ) );
    var stringLength = metadata.ContainerType.GetProperty(metadata.PropertyName)
        .GetCustomAttributes(typeof(StringLengthAttribute), false)
        .FirstOrDefault() as StringLengthAttribute;

    var attributes = (IDictionary<string, object>)new RouteValueDictionary(htmlAttributes);
    if (stringLength != null)
    {
        attributes.Add("maxlength", stringLength.MaximumLength);
    }
    return htmlHelper.TextBoxFor(expression, attributes);
}

Solution 7 - asp.net Mvc

Here are some static methods you can use to get the StringLength, or any other attribute.

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

public static class AttributeHelpers {

public static Int32 GetStringLength<T>(Expression<Func<T,string>> propertyExpression) {
	return GetPropertyAttributeValue<T,string,StringLengthAttribute,Int32>(propertyExpression,attr => attr.Length);
}

//Optional Extension method
public static Int32 GetStringLength<T>(this T instance,Expression<Func<T,string>> propertyExpression) {
	return GetStringLength<T>(propertyExpression);
}


//Required generic method to get any property attribute from any class
public static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>(Expression<Func<T,TOut>> propertyExpression,Func<TAttribute,TValue> valueSelector) where TAttribute : Attribute {
	var expression = (MemberExpression)propertyExpression.Body;
	var propertyInfo = (PropertyInfo)expression.Member;
	var attr = propertyInfo.GetCustomAttributes(typeof(TAttribute),true).FirstOrDefault() as TAttribute;

	if (attr==null) {
		throw new MissingMemberException(typeof(T).Name+"."+propertyInfo.Name,typeof(TAttribute).Name);
	}

	return valueSelector(attr);
}

}

Using the static method...

var length = AttributeHelpers.GetStringLength<User>(x => x.Address1);

Or using the optional extension method on an instance...

var player = new User();
var length = player.GetStringLength(x => x.Address1);

Or using the full static method for any other attribute...

var length = AttributeHelpers.GetPropertyAttributeValue<User,string,StringLengthAttribute,Int32>(prop => prop.Address1,attr => attr.MaximumLength);

Inspired by the answer here... https://stackoverflow.com/a/32501356/324479

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPervez ChoudhuryView Question on Stackoverflow
Solution 1 - asp.net MvcjrummellView Answer on Stackoverflow
Solution 2 - asp.net MvcDarin DimitrovView Answer on Stackoverflow
Solution 3 - asp.net MvcRandhirView Answer on Stackoverflow
Solution 4 - asp.net MvcdcompiledView Answer on Stackoverflow
Solution 5 - asp.net MvcbkwdesignView Answer on Stackoverflow
Solution 6 - asp.net MvcDave ClemmerView Answer on Stackoverflow
Solution 7 - asp.net MvcCarter MedlinView Answer on Stackoverflow