Hi Sascha,
Sorry, it looks like the TypeDescriptor provided by Microsoft doesn't completely implement the needed TypeDescriptor overrides. Specifically, it should override GetProperties() *and* GetProperties(Attribute[]). But it currently only implements the latter.
You can work around this by extending the classes they provide. In addition, you can have it automatically check the validation before (or after) the user sets the value via the PropertyGrid.
The code is as follows:
/// <summary>
/// Represents an extension of AssociatedMetadataTypeTypeDescriptionProvider that wraps the
/// TypeDescriptor returned with a custom TypeDescriptor.
/// </summary>
public class MyAssociatedMetadataTypeTypeDescriptionProvider : AssociatedMetadataTypeTypeDescriptionProvider {
public MyAssociatedMetadataTypeTypeDescriptionProvider(Type type)
: base(type) {
// No-op
}
public MyAssociatedMetadataTypeTypeDescriptionProvider(Type type, Type associatedMetadataType)
: base(type, associatedMetadataType) {
// No-op
}
private ICustomTypeDescriptor Descriptor {
get;
set;
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) {
if (null == this.Descriptor)
this.Descriptor = new MyCustomTypeDescriptor(base.GetTypeDescriptor(null, null));
return this.Descriptor;
}
}
/// <summary>
/// Represents a custom TypeDescriptor that wraps the real TypeDescriptor, but overrides
/// both GetProperties() *and* GetProperties(Attribute[]).
/// </summary>
public class MyCustomTypeDescriptor : CustomTypeDescriptor {
public MyCustomTypeDescriptor(ICustomTypeDescriptor wrappedTypeDescriptor) {
this.WrappedTypeDescriptor = wrappedTypeDescriptor;
}
private ICustomTypeDescriptor WrappedTypeDescriptor {
get; set;
}
public override AttributeCollection GetAttributes() {
return this.WrappedTypeDescriptor.GetAttributes();
}
public override PropertyDescriptorCollection GetProperties() {
PropertyDescriptorCollection properties = this.WrappedTypeDescriptor.GetProperties();
List<PropertyDescriptor> list = new List<PropertyDescriptor>();
foreach (PropertyDescriptor descriptor in properties)
list.Add(new MyPropertyDescriptor(descriptor));
return new PropertyDescriptorCollection(list.ToArray(), true);
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) {
return this.GetProperties();
}
}
/// <summary>
/// Represents a custom PropertyDescriptor that wraps the real PropertyDescriptor, and enforces
/// any ValidationAttributes defined on the associated property.
/// </summary>
public class MyPropertyDescriptor : PropertyDescriptor {
public MyPropertyDescriptor(PropertyDescriptor wrappedPropertyDescriptor)
:base (wrappedPropertyDescriptor){
this.WrappedPropertyDescriptor = wrappedPropertyDescriptor;
}
private PropertyDescriptor WrappedPropertyDescriptor {
get;
set;
}
public override void AddValueChanged(object component, EventHandler handler) {
this.WrappedPropertyDescriptor.AddValueChanged(component, handler);
}
public override bool CanResetValue(object component) {
return this.WrappedPropertyDescriptor.CanResetValue(component);
}
public override Type ComponentType {
get {
return this.WrappedPropertyDescriptor.ComponentType;
}
}
public override bool IsReadOnly {
get {
return this.WrappedPropertyDescriptor.IsReadOnly;
}
}
public override object GetValue(object component) {
return this.WrappedPropertyDescriptor.GetValue(component);
}
public override Type PropertyType {
get {
return this.WrappedPropertyDescriptor.PropertyType;
}
}
public override void RemoveValueChanged(object component, EventHandler handler) {
this.WrappedPropertyDescriptor.RemoveValueChanged(component, handler);
}
public override void ResetValue(object component) {
this.WrappedPropertyDescriptor.ResetValue(component);
}
public override void SetValue(object component, object value) {
List<Attribute> attributes = new List<Attribute>();
this.FillAttributes(attributes);
foreach (Attribute attribute in attributes) {
ValidationAttribute validationAttribute = attribute as ValidationAttribute;
if (null == validationAttribute)
continue;
if (!validationAttribute.IsValid(value))
throw new ValidationException(validationAttribute.ErrorMessage, validationAttribute, component);
}
this.WrappedPropertyDescriptor.SetValue(component, value);
}
public override bool ShouldSerializeValue(object component) {
return this.WrappedPropertyDescriptor.ShouldSerializeValue(component);
}
public override bool SupportsChangeEvents {
get {
return this.WrappedPropertyDescriptor.SupportsChangeEvents;
}
}
}
You would again apply this via a static constructor like so:
static Booking() {
TypeDescriptor.AddProvider(new MyAssociatedMetadataTypeTypeDescriptionProvider(typeof(Booking)), typeof(Booking));
}
If you do not want the validation to be automatically applied, you can remove MyPropertyDescriptor and update MyCustomTypeDescriptor.GetProperties() to simply return the wrapped properties.
Finally, if you want the value of the underlying object to be set before the validation is applied, then you would need to move "this.WrappedPropertyDescriptor.SetValue(component, value)" to the top of the SetValue method in MyPropertyDescriptor.
Hope this helps.