0

This is a minor inconvenience, but it ends up generating a lot of boiler plate code. I'm using multiple libraries (ServiceStack.Net, Json.Net, the DataContractSerializer, etc), and to coerce all possible interpreters to serialize/deserialize my objects correctly, I end up with lots of property definitions that look like this:

private const string select_comparisons = "select-comparisons";
[DataMember(Name = select_comparisons, EmitDefaultValue = true)]
[ApiMember(Name = select_comparisons)]
[JsonProperty(select_comparisons, DefaultValueHandling = DefaultValueHandling.Populate, NullValueHandling = NullValueHandling.Include)]
public bool SelectComparisons { get; set; }

Which is annoying. It would be preferable to compose something like this:

[MyAttributeToRuleThemAll("select-comparisons", EmitDefaultValue = true, IgnoreNulls = true)]
public bool SelectComparisons { get; set; }

Where MyAttributeToRuleThemAll looks something like this:

public class MyAttributeToRuleThemAll : Attribute, DataMember, ApiMember, JsonProperty 
{
    //insert attribute-specific logic in the constructor
}

I realize the proposed solution isn't possible as you can't inherit in this way, but it seems like there should be some way to simplify common attributes into a single re-usable component.

Update:

I attempted to use the answer from the referenced duplicate using the following code.

[TypeDescriptionProvider(typeof(SerializableTypeDescriptionProvider))]
public class SerializableTest
{
    [Serializable("nu-id", DefaultValue = 2)]
    public int Id { get; set; }

    [Serializable("nu-key", DefaultValue = "2")]
    public string Key { get; set; }

}

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

public enum DefaultValueOptions
{
    Include,
    Exclude,
    IncludeAndPopulate
}

public class SerializableAttribute : Attribute, IMetadatAttribute
{
    public DefaultValueHandling DefaultValueHandling { get; set; }
    public DefaultValueOptions DefaultValueOptions { get; set; }
    public bool EmitDefaultValue { get; set; }
    public bool IsRequired { get; set; }
    public bool ExcludeInSchema { get; set; }
    public bool AllowMultiple { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public string DataType { get; set; }
    public string Format { get; set; }
    public string ParameterType { get; set; }
    public string Route { get; set; }
    public string Verb { get; set; }
    public int Order { get; set; }
    public object DefaultValue { get; set; }

    public SerializableAttribute()
    {
        this.DefaultValueHandling = DefaultValueHandling.Include;
    }

    public SerializableAttribute(string name) : this()
    {
        Name = name;
    }

    public SerializableAttribute(string name, object defaultValue, bool emitDefaultValue, string description) : this(name)
    {
        DefaultValue = defaultValue;
        EmitDefaultValue = emitDefaultValue;
        Description = description;
    }

    public Attribute[] Process()
    {
        var attributes = new Attribute[]{
            new DataMemberAttribute() {
                EmitDefaultValue = EmitDefaultValue,
                IsRequired = IsRequired,
                Name = Name,
                Order = Order },
            new ApiMemberAttribute() {
                Name = Name,
                Description = Description,
                ExcludeInSchema = ExcludeInSchema,
                IsRequired = IsRequired,
                AllowMultiple = AllowMultiple,
                DataType = DataType,
                Format = Format,
                ParameterType = ParameterType,
                Route = Route,
                Verb = Verb
            },
            new JsonPropertyAttribute(Name) {
                DefaultValueHandling = DefaultValueHandling,
                PropertyName = Name,
                NullValueHandling = NullValueHandling.Ignore,
                ObjectCreationHandling = ObjectCreationHandling.Reuse
            },
            new DefaultValueAttribute(DefaultValue) {}
        };
        return attributes;
    }
}

public class SerializableDescriptor : PropertyDescriptor
{
    PropertyDescriptor original;
    public SerializableDescriptor(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());
        }
    }

    public override Type ComponentType => original.ComponentType;
    public override bool IsReadOnly => original.IsReadOnly;
    public override Type PropertyType => original.PropertyType;

    public override bool CanResetValue(object component) => original.CanResetValue(component);

    public override object GetValue(object component)
    {
        return original.GetValue(component);
    }
    public override void ResetValue(object component) => original.ResetValue(component);
    public override void SetValue(object component, object value) => original.SetValue(component, value);
    public override bool ShouldSerializeValue(object component) => original.ShouldSerializeValue(component);
}

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

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

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

Which you can run doing something like:

void Main()
{
    var nuTest = new SerializableTest();
    var jsonOut = JsonConvert.SerializeObject(nuTest).Dump(); //or insert whatever ToJson/ToString logic here that you want
    //expecting: { "nu-id": 2, "nu-key": "2" }
    //getting: { "Id": 0, "Key": "null" }
}

While this compiles and executes without error, the attributes specified are not returned as expected. Manually injecting the [JsonProperty("nu-name")] attribute works as expected. I'm assuming this Attribute approach is generated at compile time and not at runtime?

Christopher
  • 1,723
  • 1
  • 18
  • 31
  • What if at some future day you have a member that has only 2 attributes? Or another one which you didn´t handle at all so far? You´d have to create a proxy for every possible combinations of your attributes, which seems pretty cumbersome to me, doesn´t it? – MakePeaceGreatAgain Jan 04 '19 at 15:27
  • While that is true, in this particular case the scope is pretty well defined. I have a limited number of possible combinations required, and a single proxy is easier to maintain and control vs trying to manage every place in the code where combinations of these attributes exist. Factoring the most common combinatory uses out into an encapsulated class would make maintenance and refactorings easier in my opinion. – Christopher Feb 04 '19 at 15:47
  • @HimBromBeere I updated my question to include an example based on your answer to https://stackoverflow.com/questions/38503146/combining-multiple-attributes-to-a-single-attribute. – Christopher Feb 04 '19 at 15:59
  • As far as I see you cannot use this solution to combine attributes because for example swagger uses [reflection](https://github.com/ststeiger/CoinBaseSharp/blob/master/ServiceStack.Text/ReflectionExtensions.cs) for finding attributes. This solution is applicable on frameworks which use `TypeDescriptor`. – Reza Aghaei Feb 18 '19 at 06:11

0 Answers0