How to add an attribute to a property at runtime
Asked Answered
O

5

89
//Get PropertyDescriptor object for the given property name
var propDesc = TypeDescriptor.GetProperties(typeof(T))[propName];

//Get FillAttributes methodinfo delegate
var methodInfo = propDesc.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public |
                                                      BindingFlags.NonPublic)
    .FirstOrDefault(m => m.IsFamily || m.IsPublic && m.Name == "FillAttributes");

//Create Validation attribute
var attribute = new RequiredAttribute();
var  attributes= new ValidationAttribute[]{attribute};

//Invoke FillAttribute method
methodInfo.Invoke(propDesc, new object[] { attributes });

Hi I am trying to add Validation attribute at runtime using the above code. However I am getting the below exception:

Collection was of a fixed size

Obligor answered 2/2, 2013 at 16:9 Comment(0)
M
244

Don't let someone tell you that you can't do it. You can run for president if you want :-)

For your convenience, this is a fully working example

public class SomeAttribute : Attribute
{
    public SomeAttribute(string value)
    {
        this.Value = value;
    }

    public string Value { get; set; }
}

public class SomeClass
{
    public string Value = "Test";
}

[TestMethod]
public void CanAddAttribute()
{
    var type = typeof(SomeClass);

    var aName = new System.Reflection.AssemblyName("SomeNamespace");
    var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run);
    var mb = ab.DefineDynamicModule(aName.Name);
    var tb = mb.DefineType(type.Name + "Proxy", System.Reflection.TypeAttributes.Public, type);

    var attrCtorParams = new Type[] { typeof(string) };
    var attrCtorInfo = typeof(SomeAttribute).GetConstructor(attrCtorParams);
    var attrBuilder = new CustomAttributeBuilder(attrCtorInfo, new object[] { "Some Value" });
    tb.SetCustomAttribute(attrBuilder);

    var newType = tb.CreateType();
    var instance = (SomeClass)Activator.CreateInstance(newType);

    Assert.AreEqual("Test", instance.Value);
    var attr = (SomeAttribute)instance.GetType()
        .GetCustomAttributes(typeof(SomeAttribute), false)
        .SingleOrDefault();
    Assert.IsNotNull(attr);
    Assert.AreEqual(attr.Value, "Some Value");
}
Madelina answered 25/6, 2014 at 15:40 Comment(14)
Thumbs up for the motivational speak!Arboretum
Hmm, seems we can't use this technique for enum members. I get an error saying the parent class is sealed...Embroideress
@Jerther, the code creates a proxy class which inherits the base class at runtime. So yes, this does not work for sealed classes.Reichsmark
This adds an attribute to the class instead of a property of the class, right?Sanctimonious
@Sanctimonious That's true but the concept will be the same. Property will have to be virtual for this to work and you will neeed to emit some IL code. This question should help you out.Reichsmark
The code being a lill abstruse, in your example, what's being added to what now?Doing
@JeromeJ The code dynamically creates a new class that inherits from SomeClass adds the class level attribute SomeAttribute, creates an instance and verifies the result. Just like you could do statically with [SomeAttribute("Some Value")] public class SomeClassProxy : SomeClass { }; var instance = new SomeClassProxy();Reichsmark
A bit late here but the solution was quoted in another answer. So, instance variable is in fact an instance of a class which inherits from the original. And everything that uses reflection (and this is how custom attributes are used) will see no attributes applied to typeof(SomeClass) or instance.GetType(), right?Wrest
@AlexanderChristov Yes and no. While SomeClass isn't modified instance.GetType() will be SomeClassProxy and this will most likely work for code that takes a type or object as an argument for attribute querying. However, a hard coded typeof(SomeClass).GetCustomAttributes(...) won't work.Reichsmark
@Jurgen: Yes, in some case this might work, I do agree. However, this is a huge deviation from normal coding practices (shortest distance to target), this is why I would reconsider the need to dynamically inject an attribute. This is an excellent example of a suggestion to rethink and reformulate the problem - .NET is almost perfect in this respect.Wrest
@AlexanderChristov Obviously, you can't just change an existing class without recompiling. If you have a class A from an assembly that is out of your control and you have a method B out of your control that expects some attribute metadata, this is a working example. Proxy classes are quite common pattern, i.e. EntityFramework does this to implement lazy loading.Reichsmark
in .NET 6 this didn't work for me when I was trying to use reflection AppDomain.CurrentDomain.GetAssemblies() - I got an assembly not found and apparently it's not supported in .NET Core - anyone got the same?Osmosis
I just tested it with .NET core 6 on windows an linux and AppDomain.CurrentDomain.GetAssemblies() returns a result. Even more, the whole code runs fine (except AppDomain.CurrentDomain.DefineDynamicAssembly has to be replaced with System.Reflection.Emit.AssemblyBuilder.DefineDynamicAssembly. So no issue here.Reichsmark
Seems like the downvoted answer is actually the answer then. This is creating a new class, not taking the existing class and adding an attribute to a property.Protein
T
10

The top answer is awesome. Recently, a library has been developed, that abstracts away all of that complexity, and gives you something as simple as this:

var attributeType = typeof(CustomAAttribute);
var attributeParams = new object[] { "Jon Snow" };
var typeExtender = new TypeExtender("ClassA");
typeExtender.AddProperty("IsAdded", typeof(bool), attributeType, attributeParams);

To work with. details of how to install and use the library can be found here

Disclaimer: I developed this library and I've been using it for a lot of projects and it works like magic

Timmi answered 18/12, 2020 at 15:14 Comment(2)
but you can't add attribute to Existing property, can you ?Garget
It depends on your use case. You can extend the class with TypeExtender, then add a property with the same name and signature (property hiding), and then attach the attribute.Timmi
H
5

Use FastDeepCloner which I developed.

public class test{
    public string Name{ get; set; }
}

var prop = DeepCloner.GetFastDeepClonerProperties(typeof(test)).First();
prop.Attributes.Add(new JsonIgnoreAttribute());
// now test and se if exist 
prop = DeepCloner.GetFastDeepClonerProperties(typeof(test)).First();
bool containAttr = prop.ContainAttribute<JsonIgnoreAttribute>()
// or 
JsonIgnoreAttribute myAttr = prop.GetCustomAttribute<JsonIgnoreAttribute>();
Heyes answered 16/9, 2017 at 17:4 Comment(1)
How would I do the deep clone and add an attribute to the class instead of the properties?Frowst
C
0

It is not working because the FillAttributes method expects a parameter of the IList type and you are passing an array. Below is the implementation of MemberDescriptor.FillAttributes:

protected virtual void FillAttributes(IList attributeList) { 
    if (originalAttributes != null) {
        foreach (Attribute attr in originalAttributes) {
            attributeList.Add(attr);
        } 
    }
}

As you can see, FillAttributes just fills the attributeList parameter with all attributes of your property. And to make your code work, change the var attributes= new ValidationAttribute[]{attribute}; line with:

var attributes = new ArrayList { attribute };

This code has nothing with adding attributes to property of type at runtime. This is "adding attribute to the PropertyDescriptor" extracted from type and has no sense unless you are trying to build a type at runtime which is based on an already existing type.

Canonicity answered 2/2, 2013 at 18:59 Comment(0)
T
-15

It is not possible to add Attributes in run-time. Attributes are static and cannot be added or removed.

Similar questions:

Tag answered 2/2, 2013 at 17:5 Comment(1)
Of course it's possible with Refelection in C#Typology

© 2022 - 2024 — McMap. All rights reserved.