.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,则使用我们指定的,否则使用默认的Resolver
serializer.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);
// optional
scope.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
}
那几个添加了UnAuditableAttribute的属性已经不在我们的日志中了,收工,回家过周末。
总结
本文对应的代码在这里。
.NET Core工程应用系列(2) 实现可配置Attribute的Json序列化方案的更多相关文章
- .NET Core工程应用系列(1) 定制化Audit.NET实现自定义AuditTarget
需求背景 最近在项目上需要增加对用户操作进行审计日志记录的功能,调研了一圈,在.net core生态里,用的最多的是Audit.NET.浏览完这个库的文档后,觉得大致能满足我们的诉求,于是建立一个控制 ...
- 小解系列-自关联对象.Net MVC中 json序列化循环引用问题
自关联对象在实际开发中用的还是比较多,例如常见的树形菜单.本文是自己实际的一个小测试,可以解决循环引用对象的json序列化问题,文笔不好请多见谅,如有错误请指出,希望有更好的解决方案,一起进步. 构造 ...
- 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之三 —— 配置
==== 目录 ==== 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之一 —— 开篇 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之二 —— ...
- 《ASP.NET Core 高性能系列》关于.NET Core的配置信息的若干事项
1.配置文件的相关闲话 Core自身对于配置文件不是必须品,但由上文分析可知ASP.NET Core默认采用appsettings.json作为配置文件,关于配置信息的优先等级 命令行>环境变量 ...
- Net core学习系列(九)——Net Core配置
一.简介 NET Core为我们提供了一套用于配置的API,它为程序提供了运行时从文件.命令行参数.环境变量等读取配置的方法.配置都是键值对的形式,并且支持嵌套,.NET Core还内建了从配置反序列 ...
- spring cloud+dotnet core搭建微服务架构:配置中心(四)
前言 我们项目中有很多需要配置的地方,最常见的就是各种服务URL地址,这些地址针对不同的运行环境还不一样,不管和打包还是部署都麻烦,需要非常的小心.一般配置都是存储到配置文件里面,不管多小的配置变动, ...
- spring cloud+dotnet core搭建微服务架构:配置中心续(五)
前言 上一章最后讲了,更新配置以后需要重启客户端才能生效,这在实际的场景中是不可取的.由于目前Steeltoe配置的重载只能由客户端发起,没有实现处理程序侦听服务器更改事件,所以还没办法实现彻底实现这 ...
- EntityFramework Core 学习系列(一)Creating Model
EntityFramework Core 学习系列(一)Creating Model Getting Started 使用Command Line 来添加 Package dotnet add pa ...
- 13.翻译系列:Code-First方式配置多对多关系【EF 6 Code-First系列】
原文链接:https://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code- ...
随机推荐
- [loj539]旅游路线
考虑枚举加油的位置,当确定某次在第$i$个位置加油后,且下一次到$j$加油,那么$i$到$j$必然会选择不超过$c_{i}$条边且最长的路径,记作$d_{i,j}$ 如果能求出$d_{i,j}$,再设 ...
- 跟着老猫来搞GO-内建容器Map
前期回顾 在上面的文章中,老猫和大家分享了GO语言中比较重要的两种数据结构,一种是数组,另外一种是基于数组的slice.本篇文章想要继续和大家分享剩下的容器以及字符字符串的处理. MAP map的定义 ...
- 跟着老猫来搞GO,"面向对象"
前言 之前和大家分享了容器以及相关的基础语法,以及函数,相信如果大家有接触过C++或者java的朋友都晓得面向对象,其实在GO语言中也存在面向对象,但是还是比较简单的,下面我们来看一下GO语言的&qu ...
- 走向深蓝:那些 Linshey 不会的算法
网络流 树论: Algorithm Round-1 Round-2 Algorithm Round-1 Round-2 点分治 \(\checkmark\) 边分治 \(\checkmark\) 动态 ...
- Vue 中 $on $once $off $emit 详细分析,以及使用
vue的 $on,$emit,$off,$once Api 中的解释: $on(eventName:string|Array, callback) 监听事件 监听当前实例上的自定义事件.事件可以由 v ...
- STL的equal_range()
equal_range()根据键值,返回一对迭代器的pair对象. 如果该键值在容器中存在,则pair对象中的第一个迭代器指向该键关联的第一个实例,第二个迭代器指向该键关联的最后一个实例的下一位置. ...
- R语言与医学统计图形-【13】ggplot2几何对象之盒形图
ggplot2绘图系统--几何对象之盒形图 参数: geom_boxplot(mapping = , #lower,middle,upper,x,ymax,ymin必须(有默认) #alpha/col ...
- STM32驱动直流电机的程序与电路设计(IR2110S自举电路+H桥+高级定时器和死区PWM)
https://blog.csdn.net/geek_monkey/article/details/82079435
- 对Javascript中的对象Object改变内存及其变量改变的图解
Object 存储变量时,变量属性的内存改变图解 左边: 对象的内存 中间:变量属性的内存 右边:属性值的内存 [图一]创建一个对象,存obj1 变量--里面存age 属性和属性值--12. ...
- 取gridview中textbox的值【C#】
<asp:GridView ID="gridView" runat="server" OnRowCommand="gridView_RowCom ...