Allow a custom Attribute only on specific type

28,801

Solution 1

No, you can't, basically. You can limit it to struct vs class vs interface, that is about it. Plus: you can't add attributes to types outside your code anyway (except for via TypeDescriptor, which isn't the same).

Solution 2

You can run this unit test to check it.

First, declare validation attribute PropertyType:

  [AttributeUsage(AttributeTargets.Class)]
    // [JetBrains.Annotations.BaseTypeRequired(typeof(Attribute))] uncomment if you use JetBrains.Annotations
    public class PropertyTypeAttribute : Attribute
    {
        public Type[] Types { get; private set; }

        public PropertyTypeAttribute(params Type[] types)
        {
            Types = types;
        }
    }

Create unit test:

 [TestClass]
    public class TestPropertyType 
    {
        public static Type GetNullableUnderlying(Type nullableType)
        {
            return Nullable.GetUnderlyingType(nullableType) ?? nullableType;
        }

        [TestMethod]
        public void Test_PropertyType()
        {
            var allTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes());
            var allPropertyInfos = allTypes.SelectMany(a => a.GetProperties()).ToArray();

            foreach (var propertyInfo in allPropertyInfos)
            {
                var propertyType = GetNullableUnderlying(propertyInfo.PropertyType);
                foreach (var attribute in propertyInfo.GetCustomAttributes(true))
                {
                    var attributes = attribute.GetType().GetCustomAttributes(true).OfType<PropertyTypeAttribute>();
                    foreach (var propertyTypeAttr in attributes)
                        if (!propertyTypeAttr.Types.Contains(propertyType))
                            throw new Exception(string.Format(
                                "Property '{0}.{1}' has invalid type: '{2}'. Allowed types for attribute '{3}': {4}",
                                propertyInfo.DeclaringType,
                                propertyInfo.Name,
                                propertyInfo.PropertyType,
                                attribute.GetType(),
                                string.Join(",", propertyTypeAttr.Types.Select(x => "'" + x.ToString() + "'"))));
                }
            }
        }
    }

Your attribute, for example allow only decimal property types:

 [AttributeUsage(AttributeTargets.Property)]
    [PropertyType(typeof(decimal))]
    public class PriceAttribute : Attribute
    {

    }

Example model:

public class TestModel  
{
    [Price]
    public decimal Price1 { get; set; } // ok

    [Price]
    public double Price2 { get; set; } // error
}

Solution 3

You could write code yourself to enforce correct use of your attribute class, but that's as much as you can do.

Solution 4

The code below will return an error if the attribute was placed on a property/field that is not List of string.

The line if (!(value is List<string> list)) may be a C#6 or 7 feature.

[AttributeUsage(AttributeTargets.Property |
                AttributeTargets.Field, AllowMultiple = false)]
public sealed class RequiredStringListAttribute : ValidationAttribute
{
    protected override ValidationResult IsValid(object value, ValidationContext context)
    {
        if (!(value is List<string> list))
            return new ValidationResult($"The required attrribute must be of type List<string>");

        bool valid = false;
        foreach (var item in list)
        {
            if (!string.IsNullOrWhiteSpace(item))
                valid = true;
        }

        return valid
            ? ValidationResult.Success
            : new ValidationResult($"This field is required"); ;
    }

}
Share:
28,801

Related videos on Youtube

gdoron is supporting Monica
Author by

gdoron is supporting Monica

Doron Grinzaig

Updated on July 09, 2022

Comments

  • gdoron is supporting Monica
    gdoron is supporting Monica almost 2 years

    Is there a way to force the compiler to restrict the usage of a custom attribute to be used only on specific property types like int, short, string (all the primitive types)?
    similar to the AttributeUsageAttribute's ValidOn-AttributeTargets enumeration.

    • Admin
      Admin over 12 years
      No, this isn't possible. The most you could do is write a unit test that uses reflection and validates its usage. But nothing in the compiler will do this.
    • Marc Gravell
      Marc Gravell over 12 years
      also; you can't add attributes to classes outside your control anyway - so you can't add attributes to int or string. Do you mean "only to properties that are int or string" ? if so, the answer is still "no" ;p
    • gdoron is supporting Monica
      gdoron is supporting Monica over 12 years
      @MarcGravell ofcourse I ment int, string properties and not changing the int class itself, But I'll edit. thanks for the answer.
    • Mike Nakis
      Mike Nakis almost 11 years
      Some good, workable answers have been given on this duplicate which was asked just 15 days later: stackoverflow.com/questions/8574275/…
  • Marc Gravell
    Marc Gravell over 12 years
    side note: an attribute has no access to it's own context, so any check here would have to be in the reflection code that queries for the attribute
  • Admin
    Admin over 12 years
    I wrote a unit test (NUnit) once that used Cecil to verify my "allowable" attribute usages.
  • derHugo
    derHugo about 5 years
    what is a ValidationAttribute?
  • Ian
    Ian about 5 years
    This forces validation. works in conjunction with the ModelState
  • derHugo
    derHugo about 5 years
    hm I don't have such a type (Unity 2019 with .Net 4.6)
  • Dominic Jonas
    Dominic Jonas almost 5 years
    @derHugo add Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Compone‌​ntModel.DataAnnotati‌​ons.dll and namespace using System.ComponentModel.DataAnnotations;