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?

Pervez Choudhury
źródło
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" })%>
Darin Dimitrov
źródło
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);
    });
});
jrummell
źródło
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)%>
Randhir
źródło
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; }

    /* ... */
  }
}
dcompiled
źródło
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.

bkwdesign
źródło
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);
}
Dave Clemmer
źródło
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

Carter Medlin
źródło