Custom Html helper that can browse DataAnnotations
Asked Answered
P

2

11

Say I have a model like this

public class User
{
    [Required]
    [StringLength(14, ErrorMessage = "Can only be 14 characters long")]
    public string UserName;

}

I want to create a Html helper like this:

@Html.ValidatableEditorFor(m => m.UserName)

so that it spits out a text field with the correct format for jQuery Vaidation plugin to be able to validate, like this:

   <input type="text" class="required" maxlength="14" />

From my research, it seems that there is no way to iterate over all the data annotations in a MetaDataModel so that I can check to see which one's are applicable to jQuery Validation.

How I envision it working in pseudo code:

    var tag = new TagBuilder("input");
    tag.mergeAttribute("type", "text");
    foreach(var attribute in metadata.attributes)
    {
       CheckForValidatableAttribute(attribute, tag);
    }

...
    private void CheckForValidatableAttribute(DataAnnotation attribute, TagBuilder tag)
    {
        switch(attribute.type)
       {
          case Required:
             tag.addClass("required");
             break;
          case StringLength
             tag.mergeAttribute("maxlength", attribute.value)
             break;
       }
    }

How could I go about achieving a helper like this? I want it to work on data annotations so that I don't have to duplicate the validation literals.

For instance, the current Html helpers like TextEditorFor do append validatable attributes to their output fields. How does it do this, and how can I make my own implementation?

Cheers

Pathway answered 30/4, 2012 at 10:13 Comment(0)
T
6

You may use this simple condition:

if(attribute.Type is ValidationAttribute)
{
   string className = attribute.Type.Name.Replace("Attribute", "").ToLower();
}

UPDATE

Define an Html helper:

public static MvcHtmlString ValidationEditorFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, 
        Expression<Func<TModel, TProperty>> expression)
{
    ....
}

Create this helper method:

private static string GetPropertyNameFromExpression<TModel, TProperty>(HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
    MemberExpression memberExpression = expression.Body as MemberExpression;
    if (memberExpression == null)
        throw new InvalidOperationException("Not a memberExpression");

    if (!(memberExpression.Member is PropertyInfo))
        throw new InvalidOperationException("Not a property");

    return memberExpression.Member.Name;
}

Now use this in ValidationEditorFor:

var propertyName = GetPropertyNameFromExpression(htmlHelper, expression);
var propertyType = typeof(TModel).GetProperties().Where(x=>x.Name == propertyName).First().PropertyType;
var attributes = propertyType.GetCustomAttributes(true).OfType<ValidationAttribute>();

Now you can check the attributes.... rest is easy.

Thomasenathomasin answered 30/4, 2012 at 10:24 Comment(2)
But there is no MetaDataModel.Attributes collection. How can I get a list of all the attributes that any particular model may have?Pathway
I altered the ValidationEditorFor code so that it grabs the ValidationAttribute's from the property. As it was, it was looking for the attributes on the Type of the Property (string, int, whatever). Code is working great now thanks! :)Pathway
P
2

Slightly altered and extracted into a helper.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Payntbrush.Infrastructure.Web.Mvc
{
    public static class ReflectionHelper
    {
        public static IEnumerable<ValidationAttribute> GetAttributes<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
        {
            Type type = typeof(TModel);
            var prop = type.GetProperty(GetPropertyNameFromExpression(expression));
            return prop.GetCustomAttributes(true).OfType<ValidationAttribute>();
        }


        private static string GetPropertyNameFromExpression<TModel, TProperty>(Expression<Func<TModel, TProperty>> expression)
        {
            var memberExpression = expression.Body as MemberExpression;
            if (memberExpression == null)
                throw new InvalidOperationException("Not a memberExpression");

            if (!(memberExpression.Member is PropertyInfo))
                throw new InvalidOperationException("Not a property");

            return memberExpression.Member.Name;
        }
    }
}
Pathway answered 30/4, 2012 at 11:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.