How to add property-level Attribute to the TypeDescriptor at runtime?
Unlike others have suggested, it's quite possible, and also not that hard. For example, you want to add some new attributes to some properties, which you can select at runtime based on some criteria.
There're two helper classes we'll need to implement this.
First goes PropertyOverridingTypeDescriptor
, it allows us to supply our own property descriptors for some properties, while keeping others intact:
public class PropertyOverridingTypeDescriptor : CustomTypeDescriptor
{
private readonly Dictionary<string, PropertyDescriptor> overridePds = new Dictionary<string, PropertyDescriptor>();
public PropertyOverridingTypeDescriptor(ICustomTypeDescriptor parent)
: base(parent)
{ }
public void OverrideProperty(PropertyDescriptor pd)
{
overridePds[pd.Name] = pd;
}
public override object GetPropertyOwner(PropertyDescriptor pd)
{
object o = base.GetPropertyOwner(pd);
if (o == null)
{
return this;
}
return o;
}
public PropertyDescriptorCollection GetPropertiesImpl(PropertyDescriptorCollection pdc)
{
List<PropertyDescriptor> pdl = new List<PropertyDescriptor>(pdc.Count+1);
foreach (PropertyDescriptor pd in pdc)
{
if (overridePds.ContainsKey(pd.Name))
{
pdl.Add(overridePds[pd.Name]);
}
else
{
pdl.Add(pd);
}
}
PropertyDescriptorCollection ret = new PropertyDescriptorCollection(pdl.ToArray());
return ret;
}
public override PropertyDescriptorCollection GetProperties()
{
return GetPropertiesImpl(base.GetProperties());
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
return GetPropertiesImpl(base.GetProperties(attributes));
}
}
Few remarks:
- Constructor takes
ICustomTypeDescriptor
, no worries here, we can get one for any type or it's instance with theTypeDescriptor.GetProvider(_settings).GetTypeDescriptor(_settings)
where _settings can be eitherType
orobject
of that type. OverrideProperty
does just what we need, more on it later.
The other class we need is the TypeDescriptionProvider
that will return our custom type descriptor instead of the default one. Here it is:
public class TypeDescriptorOverridingProvider : TypeDescriptionProvider
{
private readonly ICustomTypeDescriptor ctd;
public TypeDescriptorOverridingProvider(ICustomTypeDescriptor ctd)
{
this.ctd = ctd;
}
public override ICustomTypeDescriptor GetTypeDescriptor (Type objectType, object instance)
{
return ctd;
}
}
Fairly simple: you just supply the type descriptor instance on construction and here you go.
And finally, processing code. For example, we want all properties ending with ConnectionString
in our object (or type) _settings
to be editable with the System.Web.UI.Design.ConnectionStringEditor
. To achieve that, we can use this code:
// prepare our property overriding type descriptor
PropertyOverridingTypeDescriptor ctd = new PropertyOverridingTypeDescriptor(TypeDescriptor.GetProvider(_settings).GetTypeDescriptor(_settings));
// iterate through properies in the supplied object/type
foreach (PropertyDescriptor pd in TypeDescriptor.GetProperties(_settings))
{
// for every property that complies to our criteria
if (pd.Name.EndsWith("ConnectionString"))
{
// we first construct the custom PropertyDescriptor with the TypeDescriptor's
// built-in capabilities
PropertyDescriptor pd2 =
TypeDescriptor.CreateProperty(
_settings.GetType(), // or just _settings, if it's already a type
pd, // base property descriptor to which we want to add attributes
// The PropertyDescriptor which we'll get will just wrap that
// base one returning attributes we need.
new EditorAttribute( // the attribute in question
typeof (System.Web.UI.Design.ConnectionStringEditor),
typeof (System.Drawing.Design.UITypeEditor)
)
// this method really can take as many attributes as you like,
// not just one
);
// and then we tell our new PropertyOverridingTypeDescriptor to override that property
ctd.OverrideProperty(pd2);
}
}
// then we add new descriptor provider that will return our descriptor instead of default
TypeDescriptor.AddProvider(new TypeDescriptorOverridingProvider(ctd), _settings);
That's it, now all properties ending with ConnectionString
will be editable through ConnectionStringEditor
.
As you can see, we just override some functionality of the default implementation every time, so the system should be fairly stable and behave as expected.
Add property-level attributes dynamically using TypeDescriptor for PropertyGrid
Actually It is possible, but I have to admit it is rather obscure and poorly documented. The key is to derive your own custom TypeDescriptorProvider from System.ComponentModel.TypeDescriptionProvider. Then you can return your own TypeDescriptor descendant.
I did this to fix a rather nasty limitation regarding ViewModels and metadata in ASP.NET MVC, but you can just as well use it to insert your own extra metadata.
Add display attribute to a variable on runtime
Very simple example, it can be build and in PropertyGrid you will see something.
You need read about ICustomTypeDescriptor and PropertyDescriptor.
On Form Load:
propertyGrid1.SelectedObject = new MyType(new[] { "Property1", "Property2" });
Types:
public class MyType : ICustomTypeDescriptor
{
private string[] _properties;
public MyType(string[] properties)
{
_properties = properties;
}
public AttributeCollection GetAttributes()
{
return null;
}
public string GetClassName()
{
return nameof(MyType);
}
public string GetComponentName()
{
throw new NotImplementedException();
}
public TypeConverter GetConverter()
{
return null;
}
public EventDescriptor GetDefaultEvent()
{
throw new NotImplementedException();
}
public PropertyDescriptor GetDefaultProperty()
{
return null;
}
public object GetEditor(Type editorBaseType)
{
return null;
}
public EventDescriptorCollection GetEvents()
{
throw new NotImplementedException();
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
throw new NotImplementedException();
}
public PropertyDescriptorCollection GetProperties()
{
return GetProperties(null);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var props = new PropertyDescriptor[_properties.Length];
for (int i = 0; i < _properties.Length; i++)
props[i] = new CustomPropertyDescriptor(_properties[i],
new Attribute[]
{
new DisplayNameAttribute(@"Displ Value " + i),
new CategoryAttribute("Category" + i%2)
});
return new PropertyDescriptorCollection(props);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
}
public class CustomPropertyDescriptor : PropertyDescriptor
{
public CustomPropertyDescriptor(string name, Attribute[] attrs) : base(name, attrs)
{
}
public override bool CanResetValue(object component)
{
return true;
}
public override object GetValue(object component)
{
return "1";
}
public override void ResetValue(object component)
{
throw new NotImplementedException();
}
public override void SetValue(object component, object value)
{
throw new NotImplementedException();
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
public override Type ComponentType { get; }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return typeof (string); } }
}
How can I add CustomAttribute to all object properties dynamically?
I'm having some trouble understanding your question, but let me attempt to clarify.
I have an object with multiple attributes
...
it will erase all CustomAttributes
In C# parlance, class members of the format <access> <type> <name> { get; set; }
are referred to as "properties" rather than "attributes." "Attributes," on the other hand, are the C# implementation of annotations, such as the custom attributes to which you are referring.
That said, I currently understand you to mean you have an automatically generated class with multiple properties. You would like each of these properties to have their own custom attributes, but if you edit the class they are removed the next time it is generated, and you cannot get the class generator to include custom attributes.
It might be helpful to know more of the context of your class. How is it being generated, for example? If it is an Entity Framework class, the following SO question may provide some insight:
Add data annotations to a class generated by entity framework. In general, is (or can you make) the generated class partial
? If so, then you can still follow the approach in the above question's answer, viz. make your own partial class implementation that provides the properties' custom attributes.
For example, if your generated class looks (or can be made to look) like this:
/// <auto-generated />
public partial class MyClass
{
public int Num1 { get; set; }
public int Num2 { get; set; }
public string Str1 { get; set; }
public string Str2 { get; set; }
}
You could write the other part of the partial class with the custom annotations, like this:
/// human generated
public partial class MyClass
{
[Submit]
public int Num1 { get; set; }
[Submit]
public int Num2 { get; set; }
[Submit]
public string Str1 { get; set; }
[Submit]
public string Str2 { get; set; }
}
Again, without knowing more about your situation, I am not certain if this provides you the information you need, but I hope it at least gives you a starting point.
Edit
If the class is not partial, you might consider wrapping your generated class with a class whose wrapping properties use the custom attribute. For example,
/// human generated
public class MyClassWrapper
{
private readonly MyClass wrapped;
public MyClassWrapper(MyClass wrapped)
{
this.wrapped = wrapped;
}
[Submit]
public int Num1 { get => this.wrapped.Num1; set => this.wrapped.Num1 = value; }
[Submit]
public int Num2 { get => this.wrapped.Num2; set => this.wrapped.Num2 = value; }
[Submit]
public string Str1 { get => this.wrapped.Str1; set => this.wrapped.Str1 = value; }
[Submit]
public string Str2 { get => this.wrapped.Str2; set => this.wrapped.Str2 = value; }
}
Edit 2
If you would rather have a more dynamic solution, at the cost of some design and runtime complexity, you might consider this SO question: How to add property-level Attribute to the TypeDescriptor at runtime?. It seems to address a similar concern --
Really, it's for MS's Application Settings that generates code, so you can't extend it in any way property-wise.
I won't duplicate Gman's explanation entirely here, but essentially this approach consists of
- Get the type (
MyClass
) or an instance of the typemyObject
- Use
TypeDescriptor.GetProvider(MyClass
/myObject).GetTypeDescriptor(MyClass
/myObject)
to get the type or object's baselineICustomTypeDescriptor
- Construct his
PropertyOverridingTypeDescriptor
with this baseline descriptor - Iterate through
MyClass
/myObject
's properties' definitions withTypeDescriptor.GetProperties(MyClass
/myObject)
. UseTypeDescriptor.CreateProperty
to create a new property definition based on the current property's definition, that adds the custom attributeEditorAttribute
(or in your caseSubmitAttribute
), and use thePropertyOverridingTypeDescriptor
constructed in 3. to use the new property definition. - Construct his
TypeDescriptorOverridingProvider
with thePropertyOverridingTypeDescriptor
constructed in 3. - Apply the new property definitions to
MyClass
/myObject
withTypeDescriptor.AddProvider
How to add custom Visible property that doesn't hide the control to PropertyGrid?
The basic idea here is to have a shadow property which doesn't belong to the original object, but is showing in the PropertyGrid. Such a property can belong to the proxy class itself.
The following proxy class will hide the original Visible
property, however it shows a Visible
property which you can change but will not change the visibility of the original object:
Here's the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
public class CustomObjectWrapper : CustomTypeDescriptor
{
public object WrappedObject { get; private set; }
public List<string> BrowsableProperties { get; private set; }
public CustomObjectWrapper(object o, List<string> pList)
: base(TypeDescriptor.GetProvider(o).GetTypeDescriptor(o))
{
WrappedObject = o;
BrowsableProperties = pList;
}
public override PropertyDescriptorCollection GetProperties()
{
return this.GetProperties(new Attribute[] { });
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
.Where(p => p.Name != "Visible")
.Where(p => BrowsableProperties.Contains(p.Name))
.Select(p => TypeDescriptor.CreateProperty(
WrappedObject.GetType(),
p,
p.Attributes.Cast<Attribute>().ToArray()))
.ToList();
if (BrowsableProperties.Contains("Visible"))
{
var p = TypeDescriptor.GetProperties(this, true)["Visible"];
properties.Add(TypeDescriptor.CreateProperty(
this.GetType(), p, new[] { BrowsableAttribute.Yes }));
}
return new PropertyDescriptorCollection(properties.ToArray());
}
public bool Visible { get; set; }
public override object GetPropertyOwner(PropertyDescriptor pd)
{
if (pd == null)
return base.GetPropertyOwner(pd);
else if (pd.Name == "Visible")
return this;
else
return WrappedObject;
}
}
Related Topics
Why Gettype Returns System.Int32 Instead of Nullable<Int32>
Accessing UI Controls in Task.Run with Async/Await on Winforms
How to Query for an Event Log Details with a Given Event Id
Outofmemoryexception on Declaration of Large Array
JSONvalueproviderfactory Throws "Request Too Large"
C# Controlling a Transaction Across Multiple Databases
Setting the Initial Directory of an Savefiledialog
Route Parameter with Slash "/" in Url
Create an Application That Will Expire After a Trial Period
How to Compare Two Images Using Byte Arrays
No Access to the Session Information Through Signalr Hub. Is My Design Is Wrong
Take Screenshot of Multiple Desktops of All Visible Applications and Forms
Encoding Trouble with Httpwebresponse
What Is the Correct Way to Cancel an Async Operation That Doesn't Accept a Cancellationtoken
Get the Decimal Part from a Double