46

Good day!

I've such method to get [DisplayName] attribute value of a property (which is attached directly or using [MetadataType] attribute). I use it in rare cases where I need to get [DisplayName] in controller code.

public static class MetaDataHelper
{
    public static string GetDisplayName(Type dataType, string fieldName)
    {       
        // First look into attributes on a type and it's parents
        DisplayNameAttribute attr;
        attr = (DisplayNameAttribute)dataType.GetProperty(fieldName).GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();

        // Look for [MetadataType] attribute in type hierarchy
        // http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
        if (attr == null)
        {
            MetadataTypeAttribute metadataType = (MetadataTypeAttribute)dataType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
            if (metadataType != null)
            {
                var property = metadataType.MetadataClassType.GetProperty(fieldName);
                if (property != null)
                {
                    attr = (DisplayNameAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
                }
            }
        }
        return (attr != null) ? attr.DisplayName : String.Empty;
    }
}

It works, but it has two drawbacks:

  • It requires field name as string
  • It doesn't work if I want to get property of a property

Is it possible to overcome both problems using lambdas, something like we have in ASP.NET MVC:

Html.LabelFor(m => m.Property.Can.Be.Very.Complex.But.Strongly.Typed);  

Update

Here is an updated and checked version from BuildStarted solution. It is modified to use DisplayName attribute (you can modify back to Display attribute if you use it). And fixed minor bugs to get attribute of nested properties.

public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression)
{
    Type type = typeof(TModel);

    string propertyName = null;
    string[] properties = null;
    IEnumerable<string> propertyList;
    //unless it's a root property the expression NodeType will always be Convert
    switch (expression.Body.NodeType)
    {
        case ExpressionType.Convert:
        case ExpressionType.ConvertChecked:
            var ue = expression.Body as UnaryExpression;
            propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
            break;
        default:
            propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
            break;
    }

    //the propert name is what we're after
    propertyName = propertyList.Last();
    //list of properties - the last property name
    properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties

    foreach (string property in properties)
    {
        PropertyInfo propertyInfo = type.GetProperty(property);
        type = propertyInfo.PropertyType;
    }

    DisplayNameAttribute attr;
    attr = (DisplayNameAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();

    // Look for [MetadataType] attribute in type hierarchy
    // http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
    if (attr == null)
    {
        MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
        if (metadataType != null)
        {
            var property = metadataType.MetadataClassType.GetProperty(propertyName);
            if (property != null)
            {
                attr = (DisplayNameAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
            }
        }
    }
    return (attr != null) ? attr.DisplayName : String.Empty;
}
artvolk
  • 9,448
  • 11
  • 56
  • 85
  • Yes you can. Instead of getting a string parameter in your method, use an Expression> parameter. From there you can read the expression for the members ("property.Can.Be.Very.Complex.But.Strongly.Typed") and use reflection to get the attribute value. – Fabian Nicollier Mar 29 '11 at 14:54
  • Have a look at `ModelMetadata` and `ModelMetadata.FromLambdaExpression()`. These places have all the metadata you need. – Daniel A. White Mar 29 '11 at 14:49
  • Can I use it from controller or viewmodel classes? It seems to be a static method? – artvolk Mar 30 '11 at 10:23
  • Is there a reason you need it in your ViewModel? – Daniel A. White Mar 30 '11 at 11:51
  • Yep, I need to get field titles in my controller to construct some strings for legacy part of application, sorry, not in ViewModel. – artvolk Mar 30 '11 at 14:07

8 Answers8

47

There are two ways to do this:

Models.Test test = new Models.Test();
string DisplayName = test.GetDisplayName(t => t.Name);

string DisplayName = Helpers.GetDisplayName<Models.Test>(t => t.Name);

The first one works by virtue of writing a generic extension method to any TModel (which is all types). This means it will be available on any object and not just your model. Not really recommended but nice because of it's concise syntax.

The second method requires you to pass in the Type of the model it is - which you're already doing but as a parameter instead. This method is required to define type via Generics because Func expects it.

Here are the methods for you to check out.

Static extension method to all objects

public static string GetDisplayName<TModel, TProperty>(this TModel model, Expression<Func<TModel, TProperty>> expression) {

    Type type = typeof(TModel);

    MemberExpression memberExpression = (MemberExpression)expression.Body;
    string propertyName = ((memberExpression.Member is PropertyInfo) ? memberExpression.Member.Name : null);

    // First look into attributes on a type and it's parents
    DisplayAttribute attr;
    attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();

    // Look for [MetadataType] attribute in type hierarchy
    // http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
    if (attr == null) {
        MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
        if (metadataType != null) {
            var property = metadataType.MetadataClassType.GetProperty(propertyName);
            if (property != null) {
                attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
            }
        }
    }
    return (attr != null) ? attr.Name : String.Empty;


}

Signature for type specific method - same code as above just different call

public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) { }

The reason you can't just use Something.GetDisplayName(t => t.Name) on it's own is because in the Razor engine you're actually passing an instantiated object of HtmlHelper<TModel> which is why the first method requires an instantiated object - This is only required for the compiler to infer what types belong to which generic name.

Update with recursive properties

public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression) {

    Type type = typeof(TModel);

    string propertyName = null;
    string[] properties = null;
    IEnumerable<string> propertyList;
    //unless it's a root property the expression NodeType will always be Convert
    switch (expression.Body.NodeType) {
        case ExpressionType.Convert:
        case ExpressionType.ConvertChecked:
            var ue = expression.Body as UnaryExpression;
            propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
            break;
        default:
            propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
            break;
    }

    //the propert name is what we're after
    propertyName = propertyList.Last();
    //list of properties - the last property name
    properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties

    Expression expr = null;
    foreach (string property in properties) {
        PropertyInfo propertyInfo = type.GetProperty(property);
        expr = Expression.Property(expr, type.GetProperty(property));
        type = propertyInfo.PropertyType;
    }

    DisplayAttribute attr;
    attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();

    // Look for [MetadataType] attribute in type hierarchy
    // http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
    if (attr == null) {
        MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
        if (metadataType != null) {
            var property = metadataType.MetadataClassType.GetProperty(propertyName);
            if (property != null) {
                attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
            }
        }
    }
    return (attr != null) ? attr.Name : String.Empty;



}
Buildstarted
  • 26,529
  • 10
  • 84
  • 95
  • Thanks for the detailed answer, but I'm not fully understand what if I call it `Helpers.GetDisplayName(t => t.ComplexProperty.Name)`? It will still use type of `t` in reflection calls, but should use type of `ComplexProperty`? In this reflection call: `attr = (DisplayAttribute)type.GetProperty(propertyName)....` – artvolk Mar 30 '11 at 10:17
  • I've added support for recursive properties. `Helpers.GetDisplayName(t => t.ComplexProperty.Name)` this call should work properly now. Lemme know if it does - also, there's no error trapping or anything so you'll have to add your own error handling in case it breaks :) – Buildstarted Mar 30 '11 at 15:17
  • I'm also a bit late, but I've finally checked the code and put slightly modified version in my question. Thanks! – artvolk Jun 05 '12 at 09:36
33

Late to the game, but...

I created a helper method using ModelMetadata like @Daniel mentioned and I thought I'd share it:

public static string GetDisplayName<TModel, TProperty>(
      this TModel model
    , Expression<Func<TModel, TProperty>> expression)
{
    return ModelMetadata.FromLambdaExpression<TModel, TProperty>(
        expression,
        new ViewDataDictionary<TModel>(model)
        ).DisplayName;
}

Example Usage:

Models:

public class MySubObject
{
    [DisplayName("Sub-Awesome!")]
    public string Sub { get; set; }
}

public class MyObject
{
    [DisplayName("Awesome!")]
    public MySubObject Prop { get; set; }
}

Use:

HelperNamespace.GetDisplayName(Model, m => m.Prop) // "Awesome!"
HelperNamespace.GetDisplayName(Model, m => m.Prop.Sub) // "Sub-Awesome!"
JesseBuesking
  • 6,496
  • 4
  • 44
  • 89
  • The problem in my original question was slightly different. I need to get `[DisplayName]` value in controller code, where `ModelMetadata` is not accessible (and doesn't make sense). Your solution seems to be a HTML helper to use in the views. One small tip: I'd rather return `MvcHtmlString` instance. – artvolk Jun 05 '12 at 08:24
  • Ah, sorry about that. My solution works for pulling the `[DisplayName]` attribute off of a model from a view like you said. Well at least people coming to this question will have a solution for both scenarios now :p – JesseBuesking Jun 05 '12 at 12:13
  • One advantage of your solution when using in the views -- it will work with both `[Display]` and `[DisplayName]` attributes. – artvolk Jun 05 '12 at 13:07
  • Nice, I used it. But wrote mySubObjectInsatnce.GetDisplayName(m => m.Prop) – Per G Feb 13 '14 at 15:16
5

Just do this:

using System.ComponentModel;
using System.Linq;
using System.Reflection;

namespace yournamespace
{
    public static class ExtensionMethods
    {
        public static string GetDisplayName(this PropertyInfo prop)
        {
            if (prop.CustomAttributes == null || prop.CustomAttributes.Count() == 0)
                return prop.Name;

            var displayNameAttribute = prop.CustomAttributes.Where(x => x.AttributeType == typeof(DisplayNameAttribute)).FirstOrDefault();

            if (displayNameAttribute == null || displayNameAttribute.ConstructorArguments == null || displayNameAttribute.ConstructorArguments.Count == 0)
                return prop.Name;

            return displayNameAttribute.ConstructorArguments[0].Value.ToString() ?? prop.Name;
        }
    }
}

Example as requested:

var props = typeof(YourType).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead);

var propFriendlyNames = props.Select(x => x.GetDisplayName());
Joe
  • 1,295
  • 13
  • 16
  • Can you provide a short example how it should be used? – artvolk Dec 01 '15 at 09:24
  • Answer modified to include sample usage. Basically, the first line gets all of the public properties for a type using reflection, then the second will replace the prop name w/ a display name if one exists. – Joe Dec 01 '15 at 18:05
2

I totally agree with the solution BuildStarted provided. The only thing i would change is that the ExtensionsMethode does not support translations. To support this is simple minor change is needed. I would have put this in the comments but i don't have enough points to do so. Look for the last line in the method.

The Extension Method

public static string GetDisplayName<TModel, TProperty>(this TModel model, Expression<Func<TModel, TProperty>> expression)
{
        Type type = typeof(TModel);
        IEnumerable<string> propertyList;

        //unless it's a root property the expression NodeType will always be Convert
        switch (expression.Body.NodeType)
        {
            case ExpressionType.Convert:
            case ExpressionType.ConvertChecked:
                var ue = expression.Body as UnaryExpression;
                propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
                break;
            default:
                propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
                break;
        }

        //the propert name is what we're after
        string propertyName = propertyList.Last();
        //list of properties - the last property name
        string[] properties = propertyList.Take(propertyList.Count() - 1).ToArray();

        Expression expr = null;
        foreach (string property in properties)
        {
            PropertyInfo propertyInfo = type.GetProperty(property);
            expr = Expression.Property(expr, type.GetProperty(property));
            type = propertyInfo.PropertyType;
        }

        DisplayAttribute attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();

        // Look for [MetadataType] attribute in type hierarchy
        // http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
        if (attr == null)
        {
            MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
            if (metadataType != null)
            {
                var property = metadataType.MetadataClassType.GetProperty(propertyName);
                if (property != null)
                {
                    attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
                }
            }
        }
        //To support translations call attr.GetName() instead of attr.Name
        return (attr != null) ? attr.GetName() : String.Empty;
 }
Bosken85
  • 617
  • 6
  • 8
2

I found another nice code snippet here, and I have slightly modified it for 'DisplayName' purpose

    public static string GetDisplayName<TSource, TProperty>(Expression<Func<TSource, TProperty>> expression)
    {
        var attribute = Attribute.GetCustomAttribute(((MemberExpression)expression.Body).Member, typeof(DisplayNameAttribute)) as DisplayNameAttribute;

        if (attribute == null)
        {
            throw new ArgumentException($"Expression '{expression}' doesn't have DisplayAttribute");
        }

        return attribute.DisplayName;
    }

And usages

GetDisplayName<ModelName, string>(i => i.PropertyName)
Shuvo Amin
  • 631
  • 8
  • 14
1

I make a Little change is you are using resources to get the DisplayName

    public static string GetDisplayName<TModel>(Expression<Func<TModel, object>> expression)
  {

     string _ReturnValue = string.Empty;

     Type type = typeof(TModel);

     string propertyName = null;
     string[] properties = null;
     IEnumerable<string> propertyList;
     //unless it's a root property the expression NodeType will always be Convert
     switch (expression.Body.NodeType)
     {
        case ExpressionType.Convert:
        case ExpressionType.ConvertChecked:
           var ue = expression.Body as UnaryExpression;
           propertyList = (ue != null ? ue.Operand : null).ToString().Split(".".ToCharArray()).Skip(1); //don't use the root property
           break;
        default:
           propertyList = expression.Body.ToString().Split(".".ToCharArray()).Skip(1);
           break;
     }

     //the propert name is what we're after
     propertyName = propertyList.Last();
     //list of properties - the last property name
     properties = propertyList.Take(propertyList.Count() - 1).ToArray(); //grab all the parent properties

     Expression expr = null;
     foreach (string property in properties)
     {
        PropertyInfo propertyInfo = type.GetProperty(property);
        expr = Expression.Property(expr, type.GetProperty(property));
        type = propertyInfo.PropertyType;
     }

     DisplayAttribute attr;
     attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();

     // Look for [MetadataType] attribute in type hierarchy
     // http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
     if (attr == null)
     {
        MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
        if (metadataType != null)
        {
           var property = metadataType.MetadataClassType.GetProperty(propertyName);
           if (property != null)
           {
              attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
           }
        }
     }

     if (attr != null && attr.ResourceType != null)
        _ReturnValue = attr.ResourceType.GetProperty(attr.Name).GetValue(attr).ToString();
     else if (attr != null)
        _ReturnValue = attr.Name;

     return _ReturnValue;
  }

Happy Coding

Fernando
  • 27
  • 2
0

Another code snippet with code .Net uses itself to perform this

public static class WebModelExtensions
{
    public static string GetDisplayName<TModel, TProperty>(
      this HtmlHelper<TModel> html, 
      Expression<Func<TModel, TProperty>> expression)
    {
        // Taken from LabelExtensions
        var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);

        string displayName = metadata.DisplayName;
        if (displayName == null)
        {
            string propertyName = metadata.PropertyName;
            if (propertyName == null)
            {
                var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
                displayName = ((IEnumerable<string>) htmlFieldName.Split('.')).Last<string>();
            }
            else
                displayName = propertyName;
        }

        return displayName;
    }
}
// Usage
Html.GetDisplayName(model => model.Password)
Siarhei Kuchuk
  • 5,296
  • 1
  • 28
  • 31
0

@Buildstarted Answer works but i wanted to get the DisplayName by property name rather using linq expression so i did a little change that will save your time

public static string GetDisplayNameByMemberName<TModel>(string memberName)
    {
        Type type = typeof(TModel);
        string propertyName = memberName;

        DisplayAttribute attr;
        attr = (DisplayAttribute)type.GetProperty(propertyName).GetCustomAttributes(typeof(DisplayAttribute), true).SingleOrDefault();

        //Look for [MetadataType] attribute in type hierarchy
        //http://stackoverflow.com/questions/1910532/attribute-isdefined-doesnt-see-attributes-applied-with-metadatatype-class
        if (attr == null)
            {
                MetadataTypeAttribute metadataType = (MetadataTypeAttribute)type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).FirstOrDefault();
                if (metadataType != null)
                {
                    var property = metadataType.MetadataClassType.GetProperty(propertyName);
                    if (property != null)
                    {
                        attr = (DisplayAttribute)property.GetCustomAttributes(typeof(DisplayNameAttribute), true).SingleOrDefault();
                    }
                }
            }
        return (attr != null) ? attr.Name : String.Empty;
    }

I also wanted to get the localized value in the resources (.resx files) so :

string displayName = GeneralHelper.GetDisplayNameByMemberName<ViewModels.ProductVM>(memberName);
string displayNameTranslated = resourceManager.GetString(displayName, MultiLangHelper.CurrentCultureInfo);

This is a helper function i made to return an object of type "CultureInfo", create a culture info of some language or pass the current one

//MultiLangHelper.CurrentCultureInfo
return System.Threading.Thread.CurrentThread.CurrentCulture;
Adel Mourad
  • 1,351
  • 16
  • 13