Подтвердить что ты не робот

Получить атрибут [DisplayName] свойства в строго типизированном виде

Добрый день!

У меня такой метод, чтобы получить значение атрибута [DisplayName] свойства (которое напрямую связано с атрибутом [MetadataType]). Я использую его в редких случаях, когда мне нужно получить [DisplayName] в коде контроллера.

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

        // Look for [MetadataType] attribute in type hierarchy
        // http://stackoverflow.com/info/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;
    }
}

Он работает, но имеет два недостатка:

  • Оно требует имени поля в виде строки
  • Это не работает, если я хочу получить свойство свойства

Можно ли обойти обе проблемы с помощью lambdas, что-то вроде ASP.NET MVC:

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

Обновление

Вот обновленная и проверенная версия из решения BuildStarted. Он модифицирован для использования атрибута DisplayName (вы можете изменить его обратно в атрибут Display, если используете его). И исправлены мелкие ошибки, чтобы получить атрибут вложенных свойств.

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 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/info/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;
}
4b9b3361

Ответ 1

Есть два способа сделать это:

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

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

Первый работает благодаря написанию общего метода расширения для любого TModel (который является всеми типами). Это означает, что он будет доступен для любого объекта, а не только для вашей модели. Не рекомендуется, но приятно из-за этого сжатого синтаксиса.

Второй метод требует, чтобы вы передали тип модели, которую вы уже делаете, но вместо этого в качестве параметра. Этот метод требуется для определения типа через Generics, потому что Func ожидает его.

Вот вам способы проверить.

Статический метод расширения для всех объектов

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


}

Подпись для типа специфического метода - тот же код, что и выше, только другой вызов

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

Причина, по которой вы не можете просто использовать Something.GetDisplayName(t => t.Name) в своей собственной, заключается в том, что в движке Razor вы фактически передаете экземпляр объекта HtmlHelper<TModel>, поэтому для первого метода требуется экземпляр объекта - это требуется только для компилятора, чтобы определить, какие типы принадлежат родовому имени.

Обновление с рекурсивными свойствами

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



}

Ответ 2

Поздно к игре, но...

Я создал вспомогательный метод, используя ModelMetadata, например, упомянутый @Daniel, и я подумал, что поделюсь им:

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

Пример использования:

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!"

Ответ 3

Просто сделайте следующее:

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

Пример по запросу:

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

var propFriendlyNames = props.Select(x => x.GetDisplayName());

Ответ 4

Я полностью согласен с решением BuildStarted. Единственное, что я бы изменил, это то, что ExtensionsMethode не поддерживает переводы. Для поддержки этого необходимы незначительные изменения. Я бы поместил это в комментарии, но у меня недостаточно очков для этого. Найдите последнюю строку в методе.

Метод расширения

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

        //unless it 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;
 }

Ответ 5

Я делаю небольшое изменение - вы используете ресурсы, чтобы получить 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 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;
  }

Счастливое кодирование

Ответ 6

Я нашел еще один хороший фрагмент кода здесь, и я немного изменил его для цели DisplayName

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

И обычаи

GetDisplayName<ModelName, string>(i => i.PropertyName)

Ответ 7

Другой фрагмент кода с кодом .Net использует себя для выполнения этого

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)