27

On a control I am using multiple attribute properties:

[Browsable(false)]
[Bindable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Obsolete("", true)]
public new Boolean AllowDrop;

I am using those properties on a lot of the other control properties as well.

I am wondering if there is a way to reduce the amount of code to write each time.

It would be nice if I could combine multiple attributes like this:

[Hidden(true)]
public new Boolean AllowDrop;

Where the Hidden Property would include all the attributes above. So there is only 1 single line of code.

Maybe there is also a way to combine the attributes in a macro or something?

I am aware that there are other ways of hiding properties but I chose the way of using attributes.

Thanks

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Thomas
  • 677
  • 1
  • 5
  • 19
  • 1
    It's possible using type descriptors but it requires more code! You may find this post useful: [Set the default value of DesignerSerializationVisibility to hidden](http://stackoverflow.com/questions/36337249/set-the-default-value-of-designerserializationvisibility-to-hidden/36345209#36345209) – Reza Aghaei Jul 21 '16 at 11:39
  • @RezaAghaei Thanks for the link - that sounds promising. However VS doesn't compile the code. It complains that MyPropertyDescriptor doesn't implement the inherited abstract member "System.ComponentModel.PropertyDescriptor.GetValue(object)". – Thomas Jul 21 '16 at 11:57
  • 1
    Read the commented code in that class implementation ;) – Reza Aghaei Jul 21 '16 at 11:57
  • @RezaAghaei thanks :) – Thomas Jul 21 '16 at 12:12
  • @RezaAghaei Yea it is very useful. There is more code to write for the declaration of it. But once it is written one time i can use it in many controls. And that was exactly what i was looking for :) – Thomas Jul 21 '16 at 12:19

2 Answers2

29

It depends to the framework which is using the attribute.

Combining attributes can be meaningful in order to the context which uses and interprets attributes. For example for those contexts which use .Net Type Description mechanisms you can customize the type description which .Net returns to consumers.

It's possible to provide custom metadata for types using the standard .Net mechanism for that purpose, registering a custom type descriptor for your object.

The idea will work this way, you create a custom type descriptor for your type. In the custom type descriptor, you return custom property descriptors for the properties of your type and in the property descriptor, you return a custom set of attributes for the property.

The approach requires more code, but it's really interesting and shares some good idea about how to provide custom metadata for your types:

IMetedataAttribute Interface

The usage is providing an standard way to create MetaDataAttributes. Each attribute which implements this interface will be used as metadata and instead of the attribute, those one which it returns in Process method will be used:

public interface IMetadatAttribute
{
    Attribute[] Process();
}

Sample MetadataAttribute

It's a sample metadata attribute which returns some attribute instead when processing the attribute:

public class MySampleMetadataAttribute : Attribute, IMetadatAttribute
{
    public Attribute[] Process()
    {
        var attributes = new Attribute[]{ 
            new BrowsableAttribute(false),
            new EditorBrowsableAttribute(EditorBrowsableState.Never), 
            new BindableAttribute(false),
            new DesignerSerializationVisibilityAttribute(
                    DesignerSerializationVisibility.Hidden),
            new ObsoleteAttribute("", true)
        };
        return attributes;
    }
}

Property Descriptor

This class will be used by the custom type descriptor to provide a custom list of attributes for the property:

public class MyPropertyDescriptor : PropertyDescriptor
{
    PropertyDescriptor original;
    public MyPropertyDescriptor(PropertyDescriptor originalProperty)
        : base(originalProperty) { original = originalProperty;}
    public override AttributeCollection Attributes
    {
        get
        {
            var attributes = base.Attributes.Cast<Attribute>();
            var result = new List<Attribute>();
            foreach (var item in attributes)
            {
                if(item is IMetadatAttribute)
                {
                    var attrs = ((IMetadatAttribute)item).Process();
                    if(attrs !=null )
                    {
                        foreach (var a in attrs)
                            result.Add(a);
                    }
                }
                else
                    result.Add(item);
            }
            return new AttributeCollection(result.ToArray());
        }
    }
    // Implement other properties and methods simply using return original
    // The implementation is trivial like this one:
    // public override Type ComponentType
    // {
    //     get { return original.ComponentType; }
    // }
}

Type Descriptor

This is the type descriptor which provides a custom description for your type. In this example it uses custom property descriptors to provide custom attributes set for the properties of your class:

public class MyTypeDescriptor : CustomTypeDescriptor
{
    ICustomTypeDescriptor original;
    public MyTypeDescriptor(ICustomTypeDescriptor originalDescriptor)
        : base(originalDescriptor)
    {
        original = originalDescriptor;
    }
    public override PropertyDescriptorCollection GetProperties()
    {
        return this.GetProperties(new Attribute[] { });
    }
    public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
    {
        var properties = base.GetProperties(attributes).Cast<PropertyDescriptor>()
                             .Select(p => new MyPropertyDescriptor(p))
                             .ToArray();
        return new PropertyDescriptorCollection(properties);
    }
}

Typedescriptor Provider

This class will be used in the attribute above your type to introduce the custom type descriptor which we created as the metadata engine for the type:

public class MyTypeDescriptionProvider : TypeDescriptionProvider
{
    public MyTypeDescriptionProvider()
        : base(TypeDescriptor.GetProvider(typeof(object))) { }

    public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
                                                            object instance)
    {
       ICustomTypeDescriptor baseDescriptor = base.GetTypeDescriptor(objectType, instance);
       return new MyTypeDescriptor(baseDescriptor);
    }
}

Sample Class

Here is my sample class which its Name property is decorated using MySampleMetadataAttribute and the class itself is registered to use our custom type descriptor provider:

[TypeDescriptionProvider(typeof(MyTypeDescriptionProvider))]
public class MySampleClass
{
    public int Id { get; set; }
    [MySampleMetadataAttribue]
    [DisplayName("My Name")]
    public string Name { get; set; }
}

To see the result it's enough to create an instance of the class and see the result in PropertyGrid:

var o = new MySampleClass();
this.propertyGrid1.SelectedObject = o;

Some notes on answer

  • Probably it's not as simple as you expected for such task. But it's working.
  • It's a lengthy answer, but contains a complete working example of how you can apply type descriptors to your types to provide custom metadata.
  • The approach will not work for engines which use reflection instead of type description. But it's completely working with for example PropertyGrid control which works with type description.
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Would it be also possible to work in the opposite direction? So setting mentioned attributes (Browsable = false, Bindable = false, Obsolete = true, ...) as default and expose specific properties only? – Tommy Aug 27 '16 at 15:37
  • 1
    @Tommy Probably yes, it can be done. Take a look at this post [Set the default value of DesignerSerializationVisibility to hidden](http://stackoverflow.com/questions/36337249/set-the-default-value-of-designerserializationvisibility-to-hidden). In the answer I changed the default of `DesignerSerializationVisibility` attribute to be `Hidden`, so properties which doesn't have that attribute with `Visible` or `Content` attribute will not be serialized. You may find the liked post useful. – Reza Aghaei Aug 27 '16 at 15:41
  • 1
    @Tommy Yes, it's completely possible, I checked it. – Reza Aghaei Aug 27 '16 at 16:00
  • I see that one can add IsBrowserable to MyPropertyDescriptor. But how could you override the Obsolete property for example? – Tommy Aug 27 '16 at 16:13
  • @Tommy I approached the problem by overriding `Attributes` property. So You can add/remove any attribute which you want. For `BrowableAttribute` the result was what I expected. But about `ObsoleteAttribute` I don't know what's the expected result. – Reza Aghaei Aug 27 '16 at 16:21
  • All properties would be obsolete (= "", yes) by default. However there is no IsObsolete property to override in MyPropertyDescriptor. – Tommy Aug 27 '16 at 16:38
  • @Tommy Overriding `Attributes` property is the way you need to go. Let me know if you have any problem in implementation :) – Reza Aghaei Aug 27 '16 at 19:59
  • @RezaAghaei do you have any ideas with respect to my duplicate question here: https://stackoverflow.com/questions/54041759/simplify-attribute-decorator-on-methods-when-referencing-multiple-libraries? – Christopher Feb 18 '19 at 01:46
  • @Christopher I posted my comment under the question. – Reza Aghaei Feb 18 '19 at 06:11
  • Very good answer and explanation, I have a question, what is the equivalent of PropertyDescriptor for methods, to use it on MVC controller method? – Mustafa Magdy Jun 11 '19 at 07:12
  • @MustafaMagdy Could you elaborate your requirement? – Reza Aghaei Jun 13 '19 at 10:29
  • @RezaAghaei, The PropertyDescriptor is valid for Attributes that applied on class properties, is there an equivalent one to apply the same technique on MVC controller methods?, the same way AuthorizeAttribute for example works? – Mustafa Magdy Jun 23 '19 at 01:55
2

The best way for me to do this, is by using Metalama (modern rewrite of PostSharp for new .NET releases). It is absolutely the best framework for doing AOP in .NET from the same guys that did PostSharp. It is still in preview, but Metalama 1.0 will be released in a week or 2, and in next year, it will probably get most of features found in PostSharp... And it has a nice community on Slack and the authors of this Metalama framework are super supportive, they helped me with each question I had, and I had a lot of them already :D

And so this library is perfect for creating custom aspects, but could easily be used for this merging of attributes :) It will be even better then the approach above, because once you see transformed file (using Metalama Diff Preview - you gotta install Metalama extension to VS), then you will actually see all those original attributes there, in a transformed file :) And so this is how easily I will merge 3 attributes into 1 with Metalama: Metalama easy merging of attributes 1 (This would be for aspect attributes created by Metalama)

Or, for other attributes (from other libraries), that don't need to do the aspect work, it would by like this: (And this is probably what you want to use, not the first example...): enter image description here

Petrz147
  • 43
  • 5
  • 1
    We ask those who ask questions here to not post *images* of code but rather the code itself. This is the first time I have seen an image of code in an answer. You might want to fix that... – DevSolar Mar 01 '23 at 16:29