1

Given the following class:

public class PayrollReport
{
    [UiGridColumn(Name = "fullName",Visible = false,Width = "90")]
    public string FullName { get; set; }
    [UiGridColumn(Name = "weekStart", CellFilter = "date")]
    public DateTime WeekStart { get; set; }
}

And this custom attribute

[AttributeUsage(AttributeTargets.All)]
public class UiGridColumn : Attribute
{
    public string CellFilter { get; set; }
    public string DisplayName { get; set; }
    public string Name { get; set; }
    public bool Visible { get; set; }
    public string Width { get; set; }
}

I want to create a List<UiGridColumn> for each field with only the provided values (I don't want a null for the skipped properties).

Is it possible to create a List<UiGridColumn> where each List item has only the provided values? (I fear this isn't possible, but thought I would ask) If so, how?

If not, my second preference would be a string array like this:

[{"name":"fullName","visible":false,"width":"90"},{"name":"weekStart","cellFilter":"date"}]

I would prefer to not loop through each property and attribute and argument to manually build the desired JSON string, but I haven't been able to find an easy way to do it otherwise.

public List<Object> GetUiGridColumnDef(string className)
{
    Assembly assembly = typeof(DynamicReportService).Assembly;
    var type = assembly.GetType(className);
    var properties = type.GetProperties();

    var columnDefs = new List<object>();
    foreach (var property in properties)
    {
        var column = new Dictionary<string, Object>();
        var attributes = property.CustomAttributes;
        foreach (var attribute in attributes)
        {
            if (attribute.AttributeType.Name != typeof(UiGridColumn).Name || attribute.NamedArguments == null)
                    continue;
            foreach (var argument in attribute.NamedArguments)
            {
                column.Add(argument.MemberName, argument.TypedValue.Value);
            }
        }
        columnDefs.Add(column);
    }
    return columnDefs;
}

Is there a better way to do this?

1 Answer 1

3

If I understand your question correctly, you want to serialize the list of attributes applied to the properties on a class?

If so, you can make a helper method to do it like this:

public static string SerializeAppliedPropertyAttributes<T>(Type targetClass) where T : Attribute
{
    var attributes = targetClass.GetProperties()
                                .SelectMany(p => p.GetCustomAttributes<T>())
                                .ToList();

    JsonSerializerSettings settings = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        Formatting = Formatting.Indented
    };

    return JsonConvert.SerializeObject(attributes, settings);
}

Then use it like this:

string json = SerializeAppliedPropertyAttributes<UiGridColumn>(typeof(PayrollReport));

You will end up with this output, which is pretty close to what you're looking for:

[
  {
    "Name": "fullName",
    "Visible": false,
    "Width": "90",
    "TypeId": "UiGridColumn, JsonTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
  },
  {
    "CellFilter": "date",
    "Name": "weekStart",
    "Visible": false,
    "TypeId": "UiGridColumn, JsonTest, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
  }
]

You'll notice the TypeId property from the base Attribute class is included, and also the property names are not camel cased. To fix that, you'll need to use a custom contract resolver:

public class SuppressAttributeTypeIdResolver : CamelCasePropertyNamesContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty prop = base.CreateProperty(member, memberSerialization);
        if (member.DeclaringType == typeof(Attribute) && member.Name == "TypeId")
        {
            prop.ShouldSerialize = obj => false;
        }
        return prop;
    }
}

Add the resolver to the serialization settings in the helper method and you should be good to go:

    JsonSerializerSettings settings = new JsonSerializerSettings
    {
        NullValueHandling = NullValueHandling.Ignore,
        ContractResolver = new SuppressAttributeTypeIdResolver(),
        Formatting = Formatting.Indented
    };

Now the output should look like this:

[
  {
    "name": "fullName",
    "visible": false,
    "width": "90"
  },
  {
    "cellFilter": "date",
    "name": "weekStart",
    "visible": false
  }
]

Demo fiddle: https://dotnetfiddle.net/2R5Zyi

Sign up to request clarification or add additional context in comments.

5 Comments

This is almost perfect. Visible was defined for only one property, but it shows in both because it is a bool. When I try to make it nullable, I get 'Visible' is not a valid named attribute argument because it is not a valid attribute parameter type. Suggestions?
You can use DefaultValueHandling = DefaultValueHandling.Ignore in the settings to suppress Visible when it is set to its default value of false. However that will also suppress it for the first object, where you had explicitly set it to false. Unfortunately with this solution you can't have it both ways-- either you always include the value, or you only include it when it's different from the default.
Good to know. I am ok with the default values getting skipped. However, I want Visible to be true by default, so I changed the default value and added the DefaultValueHandling code. It works exactly as expected. Thanks!
Oh yeah, Go Cubs!
Excellent, I'm glad you were able to get it working the way you want. Go Cubs! :-)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.