Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to read property data annotation value in .NET MVC

I Just starting out w/ ASP.NET MVC 3 and I am trying to render out the following HTML for the string properties on a ViewModel on the create/edit view.

<input id="PatientID" name="PatientID" placeholder="Patient ID" type="text" value="" maxlength="30" />

Each value ties back to the property on the ViewModel, id & name are the property name, placeholder is the Display attribute, value is the value of the property, and maxlength is the StringLength attribute.

Instead of typing out the above HTML w/ the correct values for each of my string properties I thought I would try to create an EditorTemplate by the name of SingleLineTextBox and use UIHint on my string properties or pass the name of the view when I call EditFor. So far so good, except I can't figure out how to get the maxlength value off the StringLength attribute.

Here is the code I have so far:

<input id="@ViewData.ModelMetadata.PropertyName" name="@ViewData.ModelMetadata.PropertyName" placeholder="@ViewData.ModelMetadata.DisplayName" type="text" value="@ViewData.Model" maxlength="??" />

As you can see, not sure how to set maxlength value. Anyone know how?

Also, am I going about this the best way? As I said before I could just write out the plain HTML myself for each property on the page. I've looked at using TextBoxFor it wasn't setting the maxlength and was adding a bunch of validation markup to the HTML output because of the StringLength attribute which I do not want. Another option I saw was extensions/helpers off the HTML class.

like image 375
Billy Avatar asked Dec 07 '22 18:12

Billy


1 Answers

A full code sample for tvanfosson's answer:

Model:

public class Product
{
    public int Id { get; set; }

    [MaxLength(200)]
    public string Name { get; set; }

EditorTemplates\String.cshtml

@model System.String
@{
    var metadata = ViewData.ModelMetadata;
    var prop = metadata.ContainerType.GetProperty(metadata.PropertyName);
    var attrs = prop.GetCustomAttributes(false);

    var maxLength = attrs.OfType<System.ComponentModel.DataAnnotations.MaxLengthAttribute>().FirstOrDefault();
}
<input [email protected]()@(metadata.IsRequired ? " required" : "")@(maxLength == null ? "" : " maxlength=" + maxLength.Length) />

HTML output:

<input id=Name maxlength=200 />

Ugly but it works. Now let's abstract it and clean it up a bit. Helper class:

public static class EditorTemplateHelper
{
    public static PropertyInfo GetPropertyInfo(ViewDataDictionary viewData)
    {
        var metadata = viewData.ModelMetadata;
        var prop = metadata.ContainerType.GetProperty(metadata.PropertyName);
        return prop;
    }

    public static object[] GetAttributes(ViewDataDictionary viewData)
    {
        var prop = GetPropertyInfo(viewData);
        var attrs = prop.GetCustomAttributes(false);
        return attrs;
    }

    public static string GenerateAttributeHtml(ViewDataDictionary viewData, IEnumerable<Delegate> attributeTemplates)
    {
        var attributeMap = attributeTemplates.ToDictionary(t => t.Method.GetParameters()[0].ParameterType, t => t);
        var attrs = GetAttributes(viewData);

        var htmlAttrs = attrs.Where(a => attributeMap.ContainsKey(a.GetType()))
            .Select(a => attributeMap[a.GetType()].DynamicInvoke(a));

        string s = String.Join(" ", htmlAttrs);
        return s;
    }
}

Editor Template:

@model System.String
@using System.ComponentModel.DataAnnotations;
@using Brass9.Web.Mvc.EditorTemplateHelpers;
@{
    var metadata = ViewData.ModelMetadata;

    var attrs = EditorTemplateHelper.GenerateAttributes(ViewData, new Delegate[] {
        new Func<StringLengthAttribute, string>(len => "maxlength=" + len.MaximumLength),
        new Func<MaxLengthAttribute, string>(max => "maxlength=" + max.Length)
    });

    if (metadata.IsRequired)
    {
        attrs.Add("required");
    }

    string attrsHtml = String.Join(" ", attrs);
}
<input type=text [email protected]() @attrsHtml />

So you pass in an array of Delegates, and for each entry use a Func<AttributeTypeGoesHere, string>, and then return whatever HTML string you wanted for each attribute.

This actually decouples well - you can map only the attributes you care about, you can map different sets for different parts of the same HTML, and the final usage (like @attrsHtml) doesn't harm readability of the template.

like image 199
Chris Moschini Avatar answered Feb 03 '23 13:02

Chris Moschini