Atributo maxlength de un cuadro de texto de DataAnnotations StringLength en Asp.Net MVC

80

Estoy trabajando en una aplicación MVC2 y quiero establecer los atributos de longitud máxima de las entradas de texto.

Ya definí el atributo stringlength en el objeto Model usando anotaciones de datos y está validando la longitud de las cadenas ingresadas correctamente.

No quiero repetir la misma configuración en mis vistas estableciendo el atributo de longitud máxima manualmente cuando el modelo ya tiene la información. ¿Hay alguna forma de hacer esto?

Fragmentos de código a continuación:

Desde el modelo:

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

Desde la vista:

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

Lo que quiero evitar hacer es:

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

Quiero obtener esta salida:

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

¿Hay alguna forma de hacer esto?

Pervez Choudhury
fuente
Lo siento, no sé para qué sirven las Anotaciones de datos. Quiero decir, ¿y si cambia el criterio de longitud? ¿No se puede impulsar dinámicamente (en tiempo de ejecución), basándose en algunos metadatos?
shahkalpesh

Respuestas:

51

No conozco ninguna forma de lograrlo sin recurrir a la reflexión. Podrías escribir un método auxiliar:

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);
}

que podrías usar así:

<%= Html.CustomTextBoxFor(model => model.Address1, new { @class = "text long" })%>
Darin Dimitrov
fuente
Recibo el Error 1 'System.Web.Mvc.HtmlHelper <TModel>' no contiene una definición para 'TextBoxFor' y ningún método de extensión 'TextBoxFor' acepta un primer argumento de tipo 'System.Web.Mvc.HtmlHelper <TModel > 'podría encontrarse (¿le falta una directiva using o una referencia de ensamblado?) en esta línea: return htmlHelper.TextBoxFor <TModel> (expresión, atributos);
sábado
2
Sí, ya me di cuenta :) Sin embargo, tengo otro problema, mis anotaciones de datos están definidas en clases de metadatos en lugar del modelo en sí. ¡El reflejo no los capta!
sábado
Gracias por la respuesta. Tengo problemas con los atributos que contienen _, no se convierten automáticamente en -. ¿Conoce una forma diferente de reemplazar el _ manualmente y completar un nuevo RouteValueDictionary ?.
LdN
Esta respuesta no funciona para los casos en los que el modelo es la propiedad. Por ejemplo, una plantilla de editor para una cadena. La respuesta de Dave Clemmer a continuación maneja todos los casos.
Michael Brandon Morris
59

Si está utilizando una validación discreta, también puede manejar este lado del cliente:

$(document).ready(function ()
{
    $("input[data-val-length-max]").each(function ()
    {
        var $this = $(this);
        var data = $this.data();
        $this.attr("maxlength", data.valLengthMax);
    });
});
jrummell
fuente
Si bien su enfoque me daría validación, realmente quería poner el atributo maxlength en la entrada porque evitaría que el usuario ingresara más caracteres en el navegador y funcionaría independientemente de si javascript se estaba ejecutando en el navegador.
Pervez Choudhury
5
Eso es exactamente lo que hace esto. Utiliza el atributo de validación de longitud máxima de datos para establecer el atributo maxlenth de entrada.
jrummell
5
Estaba realmente emocionado con la primera respuesta de reflexión, pero parece lograr los mismos resultados sin ningún código de servidor complejo. Buen trabajo. Deberías conseguir más votos.
Brian White
1
+1 Gran idea para formas Ajaxed. Estoy de acuerdo con Brian White en que esta respuesta merece más votos.
Waleed Eissa
1
Me perdí la parte sobre el OP que necesita validación sin javascript. Pero me alegro de que esto haya ayudado a otros a buscar una solución de JavaScript.
jrummell
20

Utilizo CustomModelMetaDataProvider para lograr esto

Paso 1. Agregar nueva clase CustomModelMetadataProvider

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;
    }
}

Paso 2. En Global.asax, registre CustomModelMetadataProvider

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

Paso 3. En Vistas / Shared / EditorTemplates, agregue una vista parcial llamada 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  })%>
<% } %>

Hecho...

Editar. El Paso 3 puede comenzar a ponerse feo si desea agregar más cosas al cuadro de texto. Si este es tu caso puedes hacer lo siguiente:

<%@ 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)%>
Randhir
fuente
8

Si desea que esto funcione con una clase de metadatos, debe usar el siguiente código. Sé que no es bonito, pero hace el trabajo y evita que tengas que escribir tus propiedades de longitud máxima tanto en la clase Entidad como en la Vista:

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);
}

Clase de ejemplo :

[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; }

    /* ... */
  }
}
dcompilado
fuente
3

Si bien personalmente me encanta la solución jquery de jrummel, aquí hay otro enfoque para mantener una única fuente de verdad en su modelo ...

No es bonito, pero ... me ha funcionado bien ...

En lugar de usar decoraciones de propiedad, solo defino algunas constantes públicas bien nombradas en mi biblioteca de modelos / dll, y luego las hago referencia en mi vista a través de HtmlAttributes, por ejemplo

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

Luego, en el archivo de vista de maquinilla de afeitar, en EditorFor ... use un objeto HtmlAttirubte en la sobrecarga, proporcione la propiedad de longitud máxima deseada y haga referencia a la constante ... tendrá que proporcionar la constante a través de una ruta de espacio de nombres completamente calificada. .. MyCompany.MyModel.MAX_ZIPCODE_LENGTH .. ya que no estará colgando directamente del modelo, pero funciona.

bkwdesign
fuente
2

Encontré que el enfoque basado en la reflexión de Darin es especialmente útil. Descubrí que era un poco más confiable usar los metadatos ContainerTypecomo base para obtener la información de la propiedad, ya que este método puede ser llamado dentro del editor de mvc / plantillas de visualización (donde TModeltermina siendo un tipo simple como 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);
}
Dave Clemmer
fuente
1

A continuación se muestran algunos métodos estáticos que puede utilizar para obtener StringLength o cualquier otro atributo.

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);
}

}

Usando el método estático ...

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

O usando el método de extensión opcional en una instancia ...

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

O usando el método estático completo para cualquier otro atributo ...

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

Inspirado por la respuesta aquí ... https://stackoverflow.com/a/32501356/324479

Carter Medlin
fuente