Can I use the IMetadataAware attribute multiple times in the same field?

I have fields that different people should see in different names.

For example, suppose I have the following user types:

public enum UserType {Expert, Normal, Guest} 

I applied the IMetadataAware attribute:

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

The idea is that I can override other values ​​as needed, but I will not return to the default values ​​when I do not. For example:

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

The problem is that when I have several attributes of the same type, DataAnnotationsModelMetadataProvider only works OnMetadataCreated for the first. In the above example, Address can only be displayed as "Address" or "ADR" - other attributes are never executed.

If I try to use different attributes - DisplayForUserType , DisplayForUserType2 , DisplayForUserType3 , everything works as expected.

Am I doing something wrong here?

+7
source share
2 answers

I know I'm a little late for this party, but I was looking for the answer to the same question and could not find it anywhere on the Internet. In the end, I did it myself.

Short answer: yes, you can have several attributes of the same type that implement the IMetadataAware interface in a single field / property. You just need to remember redefining the TypeId class of the Attribute class when expanding it and replacing it with something that will give you a unique object for each instance of each derived attribute.

If you do not override the TypeId property of the derived attribute, then all attributes of this type are treated the same, since the default implementation returns the attribute's runtime type as an identifier.

So now we have to work as follows:

 [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; } } 
+10
source

You are not mistaken, but any attribute that implements IMetadataAware is applied by AssociatedMetadataProvider (and any derived type) after the creation of metadata. To override the default behavior, you can implement a custom ModelMetadataProvider .

Here is another alternative quick fix:

Remove the IMetadataAware interface from the DisplayForUserType class.

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

Define a new IMetadataAware attribute that will apply display logic of the UserType type, as shown below:

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

And the model will be:

 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; } } 
+1
source

All Articles