How to get metadata custom attributes?

牧云@^-^@ 提交于 2019-12-07 05:21:21

问题


I have a class that defines data annotations at class level. The meta data class has custom attributes associated with it, along with the usual DisplayName, DisplayFormat etc.

public class BaseMetaData
{
    [DisplayName("Id")]
    public object Id { get; set; }

    [DisplayName("Selected")]
    [ExportItem(Exclude = true)]
    public object Selected { get; set; }
}

[MetadataType(typeof(BaseMetaData))]
public class BaseViewModel
{
    public int Id { get; set; }
    public bool Selected { get; set; }

Given a type T, how can I retrieve the custom attributes from the meta data class? The attempt below would not work as the metadata properties are from the BaseViewModel rather than the BaseMetaData class.

Needs to work generically i.e. can't do typeof(BaseMetaData).GetProperty(e.PropertyName). Wondering if there is a way of getting the MetadataType from the class then it would make it possible.

var type = typeof (T);
var metaData = ModelMetadataProviders.Current.GetMetadataForType(null, type);

var propertMetaData = metaData.Properties
    .Where(e =>
    {
        var attribute = type.GetProperty(e.PropertyName)
            .GetCustomAttributes(typeof(ExportItemAttribute), false)
            .FirstOrDefault() as ExportItemAttribute;
        return attribute == null || !attribute.Exclude;
    })
    .ToList();

回答1:


Found a solution by using the type of MetadataTypeAttribute to get the custom attributes.

var type = typeof (T);
var metadataType = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true)
    .OfType<MetadataTypeAttribute>().FirstOrDefault();
var metaData = (metadataType != null)
    ? ModelMetadataProviders.Current.GetMetadataForType(null, metadataType.MetadataClassType)
    : ModelMetadataProviders.Current.GetMetadataForType(null, type);

var propertMetaData = metaData.Properties
    .Where(e =>
    {
        var attribute = metaData.ModelType.GetProperty(e.PropertyName)
            .GetCustomAttributes(typeof(ExportItemAttribute), false)
            .FirstOrDefault() as ExportItemAttribute;
        return attribute == null || !attribute.Exclude;
    })
    .ToList();



回答2:


Have you tried just doing this,

public class BaseViewModel
{
    [DisplayName("Id")]
    public int Id { get; set; }

    [DisplayName("Selected")]
    [ExportItem(Exclude = true)]
    public bool Selected { get; set; }
}

You can then use a variation of your code,

var type = typeof(T);    
var propertyMetaData = type.GetProperties()
    .Select(property => 
        property.GetCustomAttributes(typeof(ExportItemAttribute), false)
                .FirstOrDefault() as ExportItemAttribute)
    .Where(attribute => attribute == null || !attribute.Exclude)
    .ToList();



回答3:


I was looking for something similar, and looking at the class MetadataTypeAttribute, I realice that it stores the type of the Metdata class. Inside that class, you can have get/set properties, or just fields (get/set properties are defined in one partial class, like autogenerated models in MVC), so, I read the fields within that metadata class, then get the atributes for the field. Code is:

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

    namespace PruebaAtributos
    {
        // Podemos ver la definición de 'MetaDataType' en:
        // https://referencesource.microsoft.com/#System.ComponentModel.DataAnnotations/DataAnnotations/MetadataTypeAttribute.cs,fb9a5881152a1584,references
        [MetadataType(typeof(ProgramMetadata))]
        partial class Program
        {
            // Campos de la clase
            public int Id { get; set; }
            public string Nombre { get; set; }
            public string Puesto { get; set; }

            static void Main(string[] args)
            {
                Type t = typeof(Program);

                // Atributos de la clase
                Console.WriteLine("--- Atributos de clase: ");
                Attribute[] attrs = Attribute.GetCustomAttributes(t);
                foreach (Attribute at in attrs)
                {
                    Console.WriteLine(at.GetType().Name);
                    if (at is MetadataTypeAttribute mdt)
                    {
                        // Nos interesa la información que contiene 'MetadataType'
                        Console.WriteLine($"--- Campos de {mdt.GetType().Name}:");

                        // Obtenemos las propiedades de la clase asociada con metadata type
                        var fields = mdt.MetadataClassType.GetFields();
                        foreach (FieldInfo fi in fields)
                        {
                            // Y mostramos los atributos asociados a cada uno de sus campos
                            var cas = fi.GetCustomAttributes(); // ca = Custom Attributes
                            Console.WriteLine($"   {fi.Name}.");
                            Console.WriteLine($"      attributos: {string.Join(", ", cas.Select(a => a.GetType().Name))}");

                            // Ahora consultamos la propiedad que deseamos de cada atributo conocido:

                            // Para consultar un attributo específico:
                            //DisplayAttribute da = (DisplayAttribute)ca.FirstOrDefault(a => a.GetType() == typeof(DisplayAttribute));
                            //if (da != null)
                            //{
                            //    Console.WriteLine($"   {da.GetType().Name}: {da.Name}");
                            //}
                            string desc;
                            foreach (var fa in cas) // fa = Field Attribute
                            {
                                if (fa is ExportarAttribute exp)
                                {
                                    // Conocemos las propiedades específicas de este 
                                    desc = $"{exp.GetType().Name}.exportar: {exp.exportar}";
                                }
                                else if (fa is MostrarAUsuario mau)
                                {
                                    desc = $"{mau.GetType().Name}.mostrar: {mau.mostrar}";
                                }
                                else if (fa is DisplayAttribute da)
                                {
                                    desc = $"{da.GetType().Name}.Name: {da.Name}";
                                }
                                else
                                {
                                    desc = fa.GetType().Name;
                                }
                                Console.WriteLine($"      {desc}");
                            }
                        }
                    }
                }
            }
        }

        // Attributos personalizados
        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
        class MostrarAUsuario : Attribute
        {
            public readonly bool mostrar;
            public MostrarAUsuario(bool mostrar = true)
            {
                this.mostrar = mostrar;
            }
        };

        class ExportarAttribute : Attribute
        {
            public readonly bool exportar;
            public ExportarAttribute(bool exportar = true)
            {
                this.exportar = exportar;
            }
        }

        public class ProgramMetadata
        {
            // Display pertenece a MVC: System.ComponentModel.DataAnnotations
            [Display(Name = "Identificador"), MostrarAUsuario(false), Exportar(false), Phone]
            public int Id;
            [Display(Name = "Nombre completo"), MostrarAUsuario]
            public int Nombre;
            [Display(Name = "Puesto de trabajo"), Exportar]
            public int Puesto;
        }
    }

Result I see is:




回答4:


Based on the other answers, i'm successed to get the DisplayName attribute from MetadataType class in this way:

var metadataType = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true)
            .OfType<MetadataTypeAttribute>().FirstOrDefault();

        var metaData = (metadataType != null)
                ? ModelMetadataProviders.Current.GetMetadataForType(null, metadataType.MetadataClassType)
                : ModelMetadataProviders.Current.GetMetadataForType(null, type);

        List<string> propertyList = metaData.Properties.
            Select(x => x.DisplayName).ToList();



回答5:


I offer the following solution as an alternative. It works with any custom attribute (this example demonstrates StringLength) and it's fast. It's based on this method and this method above.

The MetaDataType attribute and type class:

[MetadataType(typeof(ImportSetMetaData))]
public partial class ImportSet
{
}

public class ImportSetMetaData
{
    [StringLength(maximumLength: 32)]
    public string Segment { get; set; }

The extension methods:

public static class Extension
{
    private static int? GetMaxLength<T>(Expression<Func<T, string>> propertyExpression)
    {
        int? result = GetPropertyAttributeValue<T, string, StringLengthAttribute, int?>
                (propertyExpression, attr => attr.MaximumLength);
        return result;
    }   

    public static int? GetMaxLength<T>(this T instance, Expression<Func<T, string>> propertyExpression)
    {
        return GetMaxLength<T>(propertyExpression);
    }

    private static TValue GetPropertyAttributeValue<T, TOut, TAttribute, TValue>
        (Expression<Func<T, TOut>> propertyExpression, Func<TAttribute, TValue> valueSelector) where TAttribute : Attribute
    {
        var expression = (MemberExpression)propertyExpression.Body;
        string mName = expression.Member.Name;
        Type type = typeof(T);
        MemberInfo member = type.GetMember(mName).FirstOrDefault();
        var attr = member.GetCustomAttribute<TAttribute>(inherit: true);
        if (attr != null)
        {
            return valueSelector(attr);
        }
        else
        {
            var mdTypeAttr = (MetadataTypeAttribute)type.GetCustomAttribute<MetadataTypeAttribute>(inherit: true);
            type = mdTypeAttr.MetadataClassType;
            member = type.GetMember(mName).FirstOrDefault();
            attr = member.GetCustomAttribute<TAttribute>(inherit: true);
            return (attr == null ? default(TValue) : valueSelector(attr));
        }
    }
}

The usage:

int n = ImportSet.GetMaxLength(x => x.Segment);


来源:https://stackoverflow.com/questions/23996564/how-to-get-metadata-custom-attributes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!