AI智能
改变未来

.NET Core工程应用系列(2) 实现可配置Attribute的Json序列化方案


背景

在这篇文章中,我们实现了基于自定义Attribute的审计日志数据对象属性过滤,但是在实际项目的应用中遇到了一点麻烦。需要进行审计的对象属性中会包含其他类对象,而我们之前的实现是没办法处理这种类属性对象内部的Attribute的。另外,属性值为

null

的会抛异常。

但是Newtonsoft自带的

JsonConverter.SerializeObject

方法实际上是能够处理这些情况的,给类属性对象所属的类中某个属性添加的Attribute能够正常被处理。同时我们也希望这个Attribute仅仅在这种情况下被应用,项目中的其地方序列化忽略这个Attribute。

骚年,继续我们的填坑之旅。

解决方案

思路

首先既然原框架中的

JsonConverter.SerializeObject

能够做到序列化类对象属性时处理另一个类中的诸如

JsonIgnore

的Attribute,那这篇文章中我们重写

AuditDataProvider

中的

Serialize

方法时,就不能自定义序列化的操作,而是借用

JsonConverter.SerializeObject

方法,然后想办法通过配置项来实现定制化的Attribute处理。

核心代码

打开Newtonsoft.Json的源代码进行查看,跟踪

JsonConverter.SerializeObject

方法:

SerializeObject静态方法

public static string SerializeObject(object value, Type type, JsonSerializerSettings settings){// 接收调用方法时传入的JsonSerializerSettings对象并构造JsonSerializer对象JsonSerializer jsonSerializer = JsonSerializer.CreateDefault(settings);// 调用序列化对象操作return SerializeObjectInternal(value, type, jsonSerializer);}

先看

CreateDefault

方法:

public static JsonSerializer CreateDefault(JsonSerializerSettings settings){JsonSerializer serializer = CreateDefault();if (settings != null){ApplySerializerSettings(serializer, settings);}return serializer;}private static void ApplySerializerSettings(JsonSerializer serializer, JsonSerializerSettings settings){// ... 省略若干代码if (settings.ContractResolver != null){// 如果指定了ContractResolver,则使用我们指定的,否则使用默认的Resolverserializer.ContractResolver = settings.ContractResolver;}// ... 省略若干代码}

SerializeObjectInternal方法

追踪方法

SerializeObjectInternal

到深层,可以看到在内部调用的序列化逻辑是根据当前遇到的节点类型分别实现了不同的

WriteJson

方法,以

KeyValueConverter

WriteJson

方法为例:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){ReflectionObject reflectionObject = ReflectionObjectPerType.Get(value.GetType());// 使用ContractResolver对象进行后面的序列化,其实看到这里就可以了,我们大致可以推断出来具体解析一个对象// 的工作,是由这个ContractResolver对象来定义的。DefaultContractResolver resolver = serializer.ContractResolver as DefaultContractResolver;writer.WriteStartObject();writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(KeyName) : KeyName);serializer.Serialize(writer, reflectionObject.GetValue(value, KeyName), reflectionObject.GetType(KeyName));writer.WritePropertyName((resolver != null) ? resolver.GetResolvedPropertyName(ValueName) : ValueName);serializer.Serialize(writer, reflectionObject.GetValue(value, ValueName), reflectionObject.GetType(ValueName));writer.WriteEndObject();}

DefaultContractResolver

查看官方实现的一个

CamelCasePropertyNamesContractResolver

类,继承自

DefaultContractResolver

类。我们发现在基类中有这样一个虚方法:

protected virtual IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)

这个方法说明了我们可以通过实现该方法来定义要获取当前对象中的哪些属性。解决方案已经很明显了,我们继承该基类,实现自己的

CreateProperties

方法,在

CreateProperties

方法中通过Attribute过滤需要序列化的属性集合返回即可。

代码实现

通过分析,我们推测使用自定义的

ContractResolver

,在内部判断属性上的Attribute值,来返回过滤后的对象属性集合就能实现我们想要的功能。

添加自定义ContractResolver,重写CreateProperties方法

public class MyContractResolver<T> : DefaultContractResolver where T : Attribute{private readonly Type _attributeToIgnore;public MyContractResolver(){_attributeToIgnore = typeof(T);}protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization){// 过滤出那些没有Ignore掉的属性集合var list =  type.GetProperties().Where(x => x.GetCustomAttributes().All(a => a.GetType() != _attributeToIgnore)).Select(p => new JsonProperty(){PropertyName = p.Name,PropertyType = p.PropertyType,Readable = true,Writable = true,ValueProvider = base.CreateMemberValueProvider(p)}).ToList();return list;}}

使用自定义ContractResolver

修改

CustomFileDataProvider

中的

Serialize

方法:

public override object Serialize<T>(T value){if (value == null){return null;}// 传入自定义的MyContractResolver对象并指定需要忽略的Attribute类型var js = JsonConvert.SerializeObject(value, new JsonSerializerSettings{ContractResolver = new MyContractResolver<UnAuditableAttribute>()});return JToken.FromObject(js);}

测试结果

首先我们修改对象

Order

,让它包含一个类对象属性:

public class OrderBase{[UnAuditable]public string Name { get; set; }}public class Order : OrderBase{public Guid Id { get; set; }[UnAuditable]public string CustomerName { get; set; }public int TotalAmount { get; set; }public DateTime OrderTime { get; set; }public Product Product { get; set; }public Order(string name, Guid id, string customerName, int totalAmount, DateTime orderTime, Product product){Id = id;CustomerName = customerName;TotalAmount = totalAmount;OrderTime = orderTime;Product = product;Name = name;}public void UpdateOrderAmount(int newOrderAmount){TotalAmount = newOrderAmount;}public void UpdateName(string name){CustomerName = name;}}public class Product{[UnAuditable]public string ProductName { get; set; }public int ProductPrice { get; set; }public Guid ProductId { get; set; }}

修改

Main

方法:

static void Main(string[] args){ConfigureAudit();var order = new Order("BaseName", Guid.NewGuid(), "Jone Doe", 100, DateTime.UtcNow, new Product{ProductId = Guid.NewGuid(),ProductName = "Some Product Name",ProductPrice = 30});using (var scope = AuditScope.Create("Order::Update", () => order)){order.UpdateOrderAmount(200);order.UpdateName(null);// optionalscope.Comment("this is a test for update order.");}}

运行程序,查看记录的审计日志:

$ cat Order::Update_637409019020799770.json{"EventType": "Order::Update","Environment": {"UserName": "yu.li1","MachineName": "Yus-MacBook-Pro","DomainName": "Yus-MacBook-Pro","CallingMethodName": "TryCustomAuditNet.Program.Main()","AssemblyName": "TryCustomAuditNet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null","Culture": ""},"Target": {"Type": "Order","Old": "{\\"Id\\":\\"7f5f26af-fc09-45cf-9f2f-349d6a2a962c\\",\\"TotalAmount\\":100,\\"OrderTime\\":\\"2020-11-13T14:05:01.684625Z\\",\\"Product\\":{\\"ProductPrice\\":30,\\"ProductId\\":\\"7f3e2c16-fe20-43cc-ae18-594ddcb77ca9\\"}}","New": "{\\"Id\\":\\"7f5f26af-fc09-45cf-9f2f-349d6a2a962c\\",\\"TotalAmount\\":200,\\"OrderTime\\":\\"2020-11-13T14:05:01.684625Z\\",\\"Product\\":{\\"ProductPrice\\":30,\\"ProductId\\":\\"7f3e2c16-fe20-43cc-ae18-594ddcb77ca9\\"}}"},"Comments": ["this is a test for update order."],"StartDate": "2020-11-13T14:05:01.694775Z","EndDate": "2020-11-13T14:05:02.075199Z","Duration": 380}

那几个添加了

UnAuditable

Attribute的属性已经不在我们的日志中了,收工,回家过周末。

总结

本文对应的代码在这里。

赞(0) 打赏
未经允许不得转载:爱站程序员基地 » .NET Core工程应用系列(2) 实现可配置Attribute的Json序列化方案