Attribut maxlength eines Textfelds aus der DataAnnotations StringLength in Asp.Net MVC

80

Ich arbeite an einer MVC2-Anwendung und möchte die MaxLängenattribute der Texteingaben festlegen.

Ich habe das Attribut stringlength für das Model-Objekt bereits mithilfe von Datenanmerkungen definiert und es überprüft die Länge der eingegebenen Zeichenfolgen korrekt.

Ich möchte dieselbe Einstellung in meinen Ansichten nicht wiederholen, indem ich das Attribut für die maximale Länge manuell einstelle, wenn das Modell bereits über die Informationen verfügt. Gibt es eine Möglichkeit, dies zu tun?

Code-Schnipsel unten:

Aus dem Modell:

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

Aus der Sicht:

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

Was ich vermeiden möchte, ist:

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

Ich möchte diese Ausgabe erhalten:

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

Gibt es eine Möglichkeit, dies zu tun?

Pervez Choudhury
quelle
Es tut mir leid, ich weiß nicht, wofür Data Annonations gut ist. Ich meine, was ist, wenn sich die Längenkriterien ändern? Kann dies nicht dynamisch (zur Laufzeit) basierend auf einigen Metadaten gesteuert werden?
Shahkalpesh

Antworten:

51

Mir ist kein Weg bekannt, dies zu erreichen, ohne auf Reflexion zurückzugreifen. Sie könnten eine Hilfsmethode schreiben:

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

was Sie so verwenden könnten:

<%= Html.CustomTextBoxFor(model => model.Address1, new { @class = "text long" })%>
Darin Dimitrov
quelle
Ich erhalte den Fehler 1 'System.Web.Mvc.HtmlHelper <TModel>' enthält keine Definition für 'TextBoxFor' und keine Erweiterungsmethode 'TextBoxFor', die ein erstes Argument vom Typ 'System.Web.Mvc.HtmlHelper <TModel akzeptiert > 'könnte in dieser Zeile gefunden werden (fehlt eine using-Direktive oder eine Assemblyreferenz?): return htmlHelper.TextBoxFor <TModel> (Ausdruck, Attribute);
Sabbat
2
Ja, das habe ich bereits herausgefunden :) Ich habe jedoch ein anderes Problem: Meine Datenanmerkungen werden eher in MetaData-Klassen als im Modell selbst definiert. Das Spiegelbild nimmt sie nicht auf!
Sabbat
Danke für die Antwort. Ich habe Probleme mit Attributen, die _ enthalten. Sie werden nicht automatisch in - konvertiert. Kennen Sie einen anderen Weg als das manuelle Ersetzen des _ und das Auffüllen eines neuen RouteValueDictionary?.
Juan
Diese Antwort funktioniert nicht in Fällen, in denen das Modell die Eigenschaft ist. Zum Beispiel eine Editorvorlage für eine Zeichenfolge. Die Antwort von Dave Clemmer unten behandelt alle Fälle.
Michael Brandon Morris
59

Wenn Sie eine unauffällige Validierung verwenden, können Sie auch diese Clientseite behandeln:

$(document).ready(function ()
{
    $("input[data-val-length-max]").each(function ()
    {
        var $this = $(this);
        var data = $this.data();
        $this.attr("maxlength", data.valLengthMax);
    });
});
jrummell
quelle
Während Ihr Ansatz mir eine Bestätigung geben würde - ich war wirklich nach dem Einfügen des Attributs maxlength in die Eingabe, weil es den Benutzer daran hindern würde, mehr Zeichen in den Browser einzugeben, und unabhängig davon funktionieren würde, ob Javascript im Browser ausgeführt wird.
Pervez Choudhury
5
Genau das macht das. Es verwendet das Validierungsattribut für die maximale Datenlänge, um das Attribut für die maximale Eingabe festzulegen.
Jrummell
5
Ich war wirklich begeistert von der ersten Reflexionsantwort, aber dies scheint die gleichen Ergebnisse ohne komplexen Servercode zu erzielen. Gut gemacht. Sie sollten mehr Stimmen bekommen.
Brian White
1
+1 Tolle Idee für Ajaxed-Formulare. Ich stimme Brian White zu, dass diese Antwort mehr Stimmen verdient.
Waleed Eissa
1
Ich habe den Teil über das OP verpasst, das ohne Javascript validiert werden muss. Aber ich bin froh, dass dies anderen bei der Suche nach einer Javascript-Lösung geholfen hat.
Jrummell
20

Ich benutze den CustomModelMetaDataProvider, um dies zu erreichen

Schritt 1. Fügen Sie eine neue CustomModelMetadataProvider-Klasse hinzu

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

Schritt 2. Registrieren Sie in Global.asax den CustomModelMetadataProvider

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

Schritt 3. Fügen Sie in Views / Shared / EditorTemplates eine Teilansicht mit dem Namen String.ascx hinzu

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

Erledigt...

Bearbeiten. Schritt 3 kann hässlich werden, wenn Sie dem Textfeld weitere Inhalte hinzufügen möchten. In diesem Fall können Sie Folgendes tun:

<%@ 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
quelle
8

Wenn dies mit einer Metadatenklasse funktionieren soll, müssen Sie den folgenden Code verwenden. Ich weiß, dass es nicht schön ist, aber es erledigt den Job und verhindert, dass Sie Ihre Eigenschaften für die maximale Länge sowohl in der Entity-Klasse als auch in der Ansicht schreiben müssen:

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

Beispielklasse :

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

    /* ... */
  }
}
kompiliert
quelle
3

Während ich persönlich jrummels jquery fix liebe, ist hier ein anderer Ansatz, um eine einzige Quelle der Wahrheit in Ihrem Modell aufrechtzuerhalten ...

Nicht schön, aber ... hat bei mir gut funktioniert ...

Anstatt Eigenschaftsdekorationen zu verwenden, definiere ich einfach einige gut benannte öffentliche Konstanten in meiner Modellbibliothek / DLL und verweise sie dann in meiner Ansicht über die HtmlAttributes, z

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

Verwenden Sie dann in der Rasiereransichtsdatei im EditorFor ... ein HtmlAttirubte-Objekt in der Überladung, geben Sie die gewünschte Eigenschaft für die maximale Länge an und verweisen Sie auf die Konstante. Sie müssen die Konstante über einen vollständig qualifizierten Namespace-Pfad angeben. .. MyCompany.MyModel.MAX_ZIPCODE_LENGTH .. da es nicht direkt am Modell hängt, aber es funktioniert.

bkwdesign
quelle
2

Ich fand Darins auf Reflexion basierenden Ansatz besonders hilfreich. Ich fand, dass es etwas zuverlässiger war, die Metadaten ContainerTypeals Grundlage für das Abrufen der Eigenschaftsinformationen zu verwenden, da diese Methode in MVC-Editor- / Anzeigevorlagen aufgerufen werden kann (wobei es sich letztendlich um TModeleinen einfachen Typ wie z. B. handelt 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
quelle
1

Hier sind einige statische Methoden, mit denen Sie die StringLength oder ein anderes Attribut abrufen können.

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

}

Mit der statischen Methode ...

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

Oder verwenden Sie die optionale Erweiterungsmethode für eine Instanz ...

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

Oder verwenden Sie die vollständige statische Methode für jedes andere Attribut ...

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

Inspiriert von der Antwort hier ... https://stackoverflow.com/a/32501356/324479

Carter Medlin
quelle