【问题标题】:Serialize Custom Attribute Values序列化自定义属性值
【发布时间】:2017-03-05 19:32:44
【问题描述】:

给定以下类:

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; }
}

还有这个自定义属性

[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; }
}

我想为每个字段创建一个List<UiGridColumn>,只包含提供的值(我不希望跳过的属性为空)。

是否可以创建一个List<UiGridColumn>,其中每个List 项目仅具有提供的值? (我担心这是不可能的,但我想我会问)如果是这样,怎么做?

如果不是,我的第二个偏好是这样的字符串数组:

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

我宁愿不遍历每个 propertyattributeargument 来手动构建所需的 JSON 字符串,但我无法找到一种简单的方法来做到这一点。

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;
}

有没有更好的方法来做到这一点?

【问题讨论】:

    标签: c#-4.0 json.net custom-attributes


    【解决方案1】:

    如果我正确理解您的问题,您想序列化应用于类属性的属性列表吗?

    如果是这样,您可以创建一个辅助方法来执行此操作:

    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);
    }
    

    然后像这样使用它:

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

    您最终会得到这个输出,它与您正在寻找的内容非常接近:

    [
      {
        "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"
      }
    ]
    

    您会注意到基类 Attribute 中的 TypeId 属性已包含在内,而且属性名称也不是驼峰式大小写。要解决这个问题,您需要使用自定义合同解析器:

    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;
        }
    }
    

    将解析器添加到辅助方法中的序列化设置中,您应该一切顺利:

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

    现在输出应该是这样的:

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

    演示小提琴:https://dotnetfiddle.net/2R5Zyi

    【讨论】:

    • 这几乎是完美的。 Visible 仅针对一个属性定义,但由于它是bool,所以它同时显示在这两个属性中。当我尝试使其成为nullable 时,我得到'Visible' is not a valid named attribute argument because it is not a valid attribute parameter type。有什么建议吗?
    • Visible设置为默认值false时,您可以在设置中使用DefaultValueHandling = DefaultValueHandling.Ignore来抑制Visible。但是,这也会为第一个对象抑制它,您已明确将其设置为 false。不幸的是,使用此解决方案,您不能同时拥有这两种方式 - 要么始终包含该值,要么仅在它与默认值不同时才包含它。
    • 很高兴知道。我可以跳过默认值。但是,我希望Visible 默认为true,所以我更改了默认值并添加了DefaultValueHandling 代码。它完全按预期工作。谢谢!
    • 哦,是的,去小熊队!
    • 太棒了,我很高兴你能够让它按照你想要的方式工作。去吧小熊们! :-)
    猜你喜欢
    • 2015-07-27
    • 2014-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多