.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- ...
随机推荐
- Svelte入门——Web Components实现跨框架组件复用
Svelte 是构建 Web 应用程序的一种新方法,推出后一直不温不火,没有继Angular.React和VUE成为第四大框架,但也没有失去热度,无人问津.造成这种情况很重要的一个原因是,Svelte ...
- C#中使用protobuf-net进行序列化
前一篇文章我们看到使用Google.Protobuf有诸多不便(参考<如何在C#中使用Google.Protobuf工具>),这次我们来看看另一个工具的使用体验. 相关资料.链接: git ...
- bootstrap导入报错
- MySQL 在线开启&关闭GTID模式
MySQL 在线开启&关闭GTID模式 目录 MySQL 在线开启&关闭GTID模式 基本概述 在线开启GTID 1. 设置GTID校验ENFORCE_GTID_CONSISTENCY ...
- 洛谷 P7360 -「JZOI-1」红包(Min-Max 容斥+推式子)
洛谷题面传送门 hot tea. 首先注意到这个 \(\text{lcm}\) 特别棘手,并且这里的 \(k\) 大得离谱,我们也没办法直接枚举每个质因子的贡献来计算答案.不过考虑到如果我们把这里的 ...
- Codeforces 1097G - Vladislav and a Great Legend(第二类斯特林数+树上背包)
Codeforces 题目传送门 & 洛谷题目传送门 首先看到这题我的第一反应是:这题跟这题长得好像,不管三七二十一先把 \(k\) 次方展开成斯特林数的形式,\(f(X)^k=\sum\li ...
- MISA(在线)注释叶绿体基因组SSR
SSR (Simple Sequence Repeat),即简单重复序列,是一种以PCR技术为核心的DNA分子标记技术,也称为微卫星序列或者串联重复. 简单重复顾名思义就是以很短的序列为一个单元,比如 ...
- R语言与医学统计图形【6】低级绘图函数
R语言基础绘图系统 基础绘图包之低级绘图函数--定义坐标轴.图例.文本 低级绘图函数:本身不具备图形绘制能力,只是在已有图形基础上添加元素. 函数 功能 arrows 添加箭头 axis 坐标轴 bo ...
- dlang 安装
刷论坛看到TIOBE排行榜,排名靠前的基本是C.C++.java.python之类的语言,常用的R语言近几年排名一路走高,前20基本变化不大. 后面发现第二十九位居然有个叫做D的语言,看了下和C语法很 ...
- 拒绝恶意同构ssh登陆服务器脚本
#!/bin/bash #Deny specified IP access #IP:who is fail to login sever SECURE_LOG=/var/log/secure #通过s ...