attribut maxlength d'une zone de texte de DataAnnotations StringLength dans Asp.Net MVC
Je travaille sur une application MVC2 et je veux définir les attributs maxlength des entrées de texte.
J'ai déjà défini l'attribut stringlength sur l'objet Model en utilisant des annotations de données et il valide correctement la longueur des chaînes saisies.
Je ne veux pas répéter le même paramètre dans mes vues en définissant manuellement l'attribut max length lorsque le modèle contient déjà les informations. Est-il possible de faire cela?
Extraits de Code ci-dessous:
Du Modèle:
[Required, StringLength(50)]
public string Address1 { get; set; }
De la vue:
<%= Html.LabelFor(model => model.Address1) %>
<%= Html.TextBoxFor(model => model.Address1, new { @class = "text long" })%>
<%= Html.ValidationMessageFor(model => model.Address1) %>
Ce que je veux éviter de faire est:
<%= Html.TextBoxFor(model => model.Address1, new { @class = "text long", maxlength="50" })%>
Je veux obtenir cette sortie:
<input type="text" name="Address1" maxlength="50" class="text long"/>
Y a-t-il un moyen de le faire?
7 réponses
Je ne connais aucun moyen d'y parvenir sans avoir recours à la réflexion. Vous pouvez écrire une méthode d'aide:
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 vous pourriez utiliser comme ceci:
<%= Html.CustomTextBoxFor(model => model.Address1, new { @class = "text long" })%>
Si vous utilisez une validation discrète, vous pouvez également gérer ce côté client:
$(document).ready(function ()
{
$("input[data-val-length-max]").each(function ()
{
var $this = $(this);
var data = $this.data();
$this.attr("maxlength", data.valLengthMax);
});
});
J'utilise le CustomModelMetaDataProvider pour y parvenir
Étape 1. Ajouter une nouvelle classe 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;
}
}
Étape 2. Mondiale.ASAX enregistre le CustomModelMetadataProvider
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelMetadataProviders.Current = new CustomModelMetadataProvider();
}
Étape 3. Dans Views / Shared / EditorTemplates, ajoutez une vue partielle appelée 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 })%>
<% } %>
Fait...
Modifier. L'Étape 3 peut commencer à devenir moche si vous voulez ajouter plus de choses à la zone de texte. Si c'est votre cas, vous pouvez faire ce qui suit:
<%@ 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)%>
Si vous voulez que cela fonctionne avec une classe de métadonnées, vous devez utiliser le code suivant. Je sais que ce n'est pas joli mais cela fait le travail et vous empêche d'écrire vos propriétés maxlength dans la classe Entity et la 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);
}
Exemple de classe:
[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; }
/* ... */
}
}
Alors que j'aime personnellement le correctif jquery de jrummel, voici une autre approche pour garder une seule source de vérité dans votre modèle...
Pas jolie, mais.. a travaillé.o.k. pour moi...
Au lieu d'utiliser des décorations de propriétés, je viens de définir des constantes publiques bien nommées dans ma bibliothèque de modèles / dll, puis de les référencer à mon avis via htmlattributes, par exemple
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
Ensuite, dans le fichier Razor view, dans EditorFor... utiliser un objet HtmlAttirubte dans la surcharge, fournissez la propriété max-length désirée et référenece la constante.. vous devrez fournir la constante via un chemin d'espace de noms entièrement qualié... MyCompany.Monmodèle.MAX_ZIPCODE_LENGTH.. comme il ne sera pas suspendu à droite du modèle, mais il fonctionne.
J'ai trouvé L'approche basée sur la réflexion de Darin particulièrement utile. J'ai trouvé qu'il était un peu plus fiable d'utiliser les métadonnées ContainerType
comme base pour obtenir les informations de propriété, car cette méthode peut être appelée dans les modèles MVC editor / display (où TModel
finit par être un type simple tel que 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);
}
Voici quelques méthodes statiques que vous pouvez utiliser pour obtenir le StringLength, ou tout autre attribut.
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);
}
}
En utilisant la méthode statique...
var length = AttributeHelpers.GetStringLength<User>(x => x.Address1);
Ou en utilisant la méthode d'extension facultative sur une instance...
var player = new User();
var length = player.GetStringLength(x => x.Address1);
Ou en utilisant la méthode statique complète pour tout autre attribut...
var length = AttributeHelpers.GetPropertyAttributeValue<User,string,StringLengthAttribute,Int32>(prop => prop.Address1,attr => attr.MaximumLength);
Inspiré par la réponse ici... https://stackoverflow.com/a/32501356/324479