maxlength pola tekstowego z DataAnnotations StringLength w Asp.Net MVC


80

Pracuję nad aplikacją MVC2 i chcę ustawić atrybuty maxlength danych wejściowych.

Zdefiniowałem już atrybut stringlength w obiekcie Model za pomocą adnotacji danych i poprawnie sprawdza poprawność długości wprowadzonych ciągów.

Nie chcę powtarzać tego samego ustawienia w moich widokach, ustawiając ręcznie atrybut maksymalnej długości, gdy model ma już informacje. Czy jest na to sposób?

Fragmenty kodu poniżej:

Z modelu:

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

Z widoku:

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

To, czego chcę uniknąć, to:

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

Chcę uzyskać ten wynik:

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

Czy jest na to sposób?


Przepraszam, nie wiem, do czego służą powiadomienia o danych? Mam na myśli, co jeśli zmieni się kryterium długości? Czy nie można tego sterować dynamicznie (w czasie wykonywania) na podstawie niektórych metadanych?
— shahkalpesh

Odpowiedzi:


51

Nie znam żadnego sposobu, aby to osiągnąć bez uciekania się do refleksji. Możesz napisać metodę pomocniczą:

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

którego możesz użyć w ten sposób:

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

Otrzymuję błąd 1 „System.Web.Mvc.HtmlHelper <TModel>” nie zawiera definicji dla „TextBoxFor” ani metody rozszerzenia „TextBoxFor” akceptującej pierwszy argument typu „System.Web.Mvc.HtmlHelper <TModel” > 'można znaleźć (brakuje ci dyrektywy using lub odwołania do zestawu?) w tym wierszu: return htmlHelper.TextBoxFor <TModel> (wyrażenie, atrybuty);
— sabbour

2
Tak, już to rozgryzłem :) Mam jednak inny problem, moje adnotacje danych są zdefiniowane w klasach MetaData, a nie w samym modelu. Odbicie ich nie podnosi!
— sabbour

Dziękuję za odpowiedź. Mam problemy z atrybutami zawierającymi _, nie są one automatycznie konwertowane na -. Czy znasz inny sposób niż ręczna zamiana _ i wypełnienie nowego RouteValueDictionary?
— LdN

Ta odpowiedź nie działa w przypadkach, w których model jest własnością. Na przykład szablon edytora dla ciągu. Poniższa odpowiedź Dave'a Clemmera dotyczy wszystkich spraw.
— Michael Brandon Morris

59

Jeśli używasz dyskretnej weryfikacji, możesz również obsłużyć tę stronę klienta:

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

Chociaż twoje podejście dałoby mi walidację - tak naprawdę po umieszczeniu atrybutu maxlength na wejściu, ponieważ uniemożliwiłoby to użytkownikowi umieszczenie większej liczby znaków w przeglądarce i działałoby niezależnie od tego, czy w przeglądarce działa JavaScript.
— Pervez Choudhury

5
Dokładnie to robi. Używa atrybutu walidacji maksymalnej długości danych, aby ustawić maksymalny atrybut wejściowy.
— jrummell

5
Byłem bardzo podekscytowany pierwszą odpowiedzią, ale wygląda na to, że daje te same wyniki bez skomplikowanego kodu serwera. Dobra robota. Powinieneś dostać więcej głosów.
— Brian White

1
+1 Świetny pomysł na formularze Ajaxed. Zgadzam się z Brianem White'em, że ta odpowiedź zasługuje na więcej głosów.
— Waleed Eissa,

1
Brakowało mi części dotyczącej OP wymagającego walidacji bez javascript. Ale cieszę się, że pomogło to innym szukającym rozwiązania javascript.
— jrummell,

20

Aby to osiągnąć, używam CustomModelMetaDataProvider

Krok 1. Dodaj nową klasę 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;
    }
}

Krok 2. W Global.asax zarejestruj CustomModelMetadataProvider

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

Krok 3. W Views / Shared / EditorTemplates Dodaj częściowy widok o nazwie 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  })%>
<% } %>

Gotowe...

Edytować. Krok 3 może zacząć być brzydki, jeśli chcesz dodać więcej rzeczy do pola tekstowego. W takim przypadku możesz wykonać następujące czynności:

<%@ 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)%>

8

Jeśli chcesz, aby to działało z klasą metadanych, musisz użyć następującego kodu. Wiem, że nie jest ładny, ale wykonuje swoją pracę i zapobiega konieczności pisania właściwości maxlength zarówno w klasie Entity, jak i widoku:

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

Przykładowe zajęcia :

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

    /* ... */
  }
}

3

Chociaż osobiście uwielbiam poprawkę jquery jrummela, oto inne podejście do utrzymywania jednego źródła prawdy w twoim modelu ...

Niezbyt ładne, ale ... dla mnie działa dobrze ...

Zamiast używać dekoracji właściwości, po prostu definiuję kilka dobrze nazwanych stałych publicznych w mojej bibliotece modelu / dll, a następnie odwołuję się do nich w moim widoku za pomocą HtmlAttributes, np.

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

Następnie w pliku widoku maszynki do golenia, w EditorFor ... użyj obiektu HtmlAttirubte w przeciążeniu, podaj żądaną właściwość max-length i odwołaj się do stałej. Będziesz musiał podać stałą za pośrednictwem w pełni kwalifikowanej ścieżki przestrzeni nazw. .. MyCompany.MyModel.MAX_ZIPCODE_LENGTH .. ponieważ nie będzie od razu zwisał z modelu, ale działa.


2

Uważam, że podejście Darina oparte na refleksji jest szczególnie pomocne. Zauważyłem, że trochę bardziej niezawodne było użycie metadanych ContainerTypejako podstawy do uzyskania informacji o właściwościach, ponieważ ta metoda może zostać wywołana w edytorze / szablonach wyświetlania mvc (gdzie TModelkończy się na prostym typie, takim jak 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);
}

1

Oto kilka metod statycznych, których można użyć, aby uzyskać StringLength lub dowolny inny atrybut.

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

}

Metoda statyczna ...

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

Lub używając opcjonalnej metody rozszerzenia dla instancji ...

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

Lub używając pełnej metody statycznej dla dowolnego innego atrybutu ...

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

Zainspirowany odpowiedzią tutaj ... https://stackoverflow.com/a/32501356/324479

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.