我可以在同一个字段上多次使用IMetadataAware属性吗?

时间:2013-03-05 09:48:09

标签: c# asp.net-mvc-3 data-annotations modelmetadata

我有不同的人应该看到不同名称的字段。

例如,假设我有以下用户类型:

public enum UserType {Expert, Normal, Guest}

我实施了IMetadataAware属性:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DisplayForUserTypeAttribute : Attribute, IMetadataAware
{
    private readonly UserType _userType;

    public DisplayForUserTypeAttribute(UserType userType)
    {
        _userType = userType;
    }

    public string Name { get; set; }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        if (CurrentContext.UserType != _userType)
            return;
        metadata.DisplayName = Name;
    }
}

我的想法是,我可以根据需要覆盖其他值,但是当我不这样做时,可以使用默认值。例如:

public class Model
{
    [Display(Name = "Age")]
    [DisplayForUserType(UserType.Guest, Name = "Age (in years, round down)")]
    public string Age { get; set; }

    [Display(Name = "Address")]
    [DisplayForUserType(UserType.Expert, Name = "ADR")]
    [DisplayForUserType(UserType.Normal, Name = "The Address")]
    [DisplayForUserType(UserType.Guest, Name = "This is an Address")]
    public string Address { get; set; }
}

问题在于,当我有多个相同类型的属性时,DataAnnotationsModelMetadataProvider仅为第一个属性运行OnMetadataCreated
在上面的示例中,Address只能显示为“地址”或“ADR” - 其他属性永远不会执行。

如果我尝试使用不同的属性 - DisplayForUserTypeDisplayForUserType2DisplayForUserType3,一切都按预期工作。

我在这里做错了吗?

2 个答案:

答案 0 :(得分:10)

我知道我对这个派对来说有点迟了但是我正在寻找同样问题的答案而无法在网上找到它。最后我自己解决了。

简短的回答是,您可以拥有在同一字段/属性上实现IMetadataAware接口的多个相同类型的属性。您只需要记住在扩展它时覆盖Attribute类的TypeId并将其替换为为每个派生属性的实例提供唯一对象的东西。

如果不覆盖派生属性的TypeId属性,那么该类型的所有属性都将被视为相同,因为默认实现将属性的运行时类型作为id返回。

因此,以下内容现在可以按预期工作:

[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DisplayForUserTypeAttribute : Attribute, IMetadataAware
{
    private readonly UserType _userType;

    public DisplayForUserType(UserType userType)
    {
        _userType = userType;
    }

    public override object TypeId
    {
        get
        {
            return this;
        }
    }

    public string Name { get; set; }

    public void OnMetadataCreated(ModelMetadata metadata)
    {
        if (CurrentContext.UserType != _userType)
            return;
        metadata.DisplayName = Name;
    }
}

答案 1 :(得分:1)

您的实现没有错,但是在创建元数据之后,AssociatedMetadataProvider(以及任何派生类型)会应用任何实现IMetadataAware的属性。 要覆盖默认行为,您可以实现自定义ModelMetadataProvider

这是另一种替代的快速解决方案:

IMetadataAware类中删除界面DisplayForUserType

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
    public class DisplayForUserTypeAttribute : Attribute//, IMetadataAware
    {
        //your existing code...
    }

定义一个新的IMetadataAware属性,该属性将按UserType应用显示逻辑,如下所示:

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class ApplyDisplayForUserTypeAttribute : Attribute, IMetadataAware
    {
        private readonly string _property;
        public ApplyDisplayForUserTypeAttribute(string property)
        {
            this._property = property;
        }

        public void OnMetadataCreated(ModelMetadata metadata)
        {
            var attribues = GetCustomAttributes(metadata.ContainerType
                                                        .GetProperty(this._property), typeof(DisplayForUserTypeAttribute))
                                                        .OfType<DisplayForUserTypeAttribute>().ToArray();
            foreach (var displayForUserTypeAttribute in attribues)
            {
                displayForUserTypeAttribute.OnMetadataCreated(metadata);
            }
        }
    }

模型将是:

public class Model
    {
        [Display(Name = "Age")]
        [DisplayForUserType(UserType.Guest, Name = "Age (in years, round down)")]
        [ApplyDisplayForUserType("Age")]
        public string Age { get; set; }

        [Display(Name = "Address")]
        [DisplayForUserType(UserType.Expert, Name = "ADR Expert")]
        [DisplayForUserType(UserType.Normal, Name = "The Address Normal")]
        [DisplayForUserType(UserType.Guest, Name = "This is an Address (Guest)")]
        [ApplyDisplayForUserType("Address")]
        public string Address { get; set; }
    }