asp.net core 使用newtonsoft完美序列化WebApi返回的ValueTuple

由于开发功能的需要,又懒得新建太多的class,所以ValueTuple是个比较好的偷懒方法,但是,由于WebApi需要返回序列化后的json,默认的序列化只能将ValueTuple定义的各个属性序列化成Item1...n

    但是微软还是良心的为序列化留下入口,编译器会在每个返回ValueTuple<>的函数或者属性上,增加一个TupleElementNamesAttribute特性,该类的TransformNames就是存着所设置的属性的名称(强烈需要记住:是每个使用到ValueTuple的函数或者属性才会添加,而不是加在有使用ValueTuple的类上),比如 (string str1,string str2) 那么 TransformNames=["str1","str2"],那么现在有如下一个class

  public class A<T1,T2>{public T1 Prop1{set;get;}public T2 Prop2{set;get;}public (string str5,int int2) Prop3{set;get;}}

  经过测试,如下一个函数

  public A<(string str1,string str2),(string str3,string str4)> testApi(){}

  这样一个函数testApi 的会加上 TupleElementNamesAttribute 特性,,TransformNames=["str1","str2","str3","str4","str5","int2"],注意了,,这里只会添加一个TupleElementNamesAttribute特性,然后把A里所有的名字按定义的顺序包含进去.

  然后我们需要定义一个JsonConverter,用来专门针对一个函数或一个属性的返回值进行了序列化

public class ValueTupleConverter : JsonConverter{private string[] _tupleNames = null;private NamingStrategy _strategy = null;//也可以直接在这里传入特性public ValueTupleConverter(TupleElementNamesAttribute tupleNames, NamingStrategy strategy = null) {_tupleNames = tupleNames.TransformNames.ToArrayEx();_strategy = strategy;}//这里在构造函数里把需要序列化的属性或函数返回类型的names传进来public ValueTupleConverter(string[] tupleNames, NamingStrategy strategy = null)  {_tupleNames = tupleNames;_strategy = strategy;}public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer){if (value != null && value is ITuple v){writer.WriteStartObject();for (int i = 0; i < v.Length; i++){var pname = _tupleNames[i];//根据规则,设置属性名writer.WritePropertyName(_strategy?.GetPropertyName(pname, true) ?? pname);if (v[i] == null){writer.WriteNull();}else{serializer.Serialize(writer, v[i]);}}writer.WriteEndObject();}}public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer){//只需要实现序列化,,不需要反序列化,因为只管输出,所以,这个写不写无所谓throw new NotImplementedException();  }public override bool CanConvert(Type objectType){return objectType.IsValueTuple();}}

  接下来说说实现的原理:

     1.newtonsoft.json的组件里,有一个ContactResolver类,用于对不同的类的解析,类库中自带的DefaultContractResolver默认定义了将类解析成各个JsonProperty,利用这个类,可用于将ValueTuple的定义的名字当做属性,返回给序列化器

     2.asp.net core的Formatter,可以对Action输出的对象进行格式化,一般用于比如json的格式化器或者xml格式化器的定义,利用格式化器,在Action最后输出的时候,配合ContractResolver进行序列化

  下面的实现中,很多地方需要判断是否为ValueTuple,为了节省代码,因此,先写一个Helper:

public static class ValueTupleHelper{private static ConcurrentDictionary<Type,bool> _cacheIsValueTuple=new ConcurrentDictionary<Type, bool>();public static bool IsValueTuple(this Type type){return _cacheIsValueTuple.GetOrAdd(type, x => x.IsValueType && x.IsGenericType &&(x.FullName.StartsWith("System.ValueTuple") || x.FullName?.StartsWith("System.ValueTuple`") == true));}}

 那么开始来定义一个ContractResolver,实现的原理请看注释

public class CustomContractResolver : DefaultContractResolver{private MethodInfo _methodInfo = null;private IContractResolver _parentResolver = null;public CustomContractResolver(MethodInfo methodInfo, IContractResolver? parentContractResolver = null){_methodInfo = methodInfo;_parentResolver = parentContractResolver;}public override JsonContract ResolveContract(Type type){if (!type.GetProperties().Where(x => x.CanRead && x.PropertyType.IsValueTuple()).Any())  //如果Type类中不包含可读的ValueTuple类型的属性,则调用预定义的Resolver处理,当前Resolver只处理包含ValueTuple的类{return _parentResolver?.ResolveContract(type);}var rc = base.ResolveContract(type);return rc;}public MethodInfo Method => _methodInfo;protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization){//CreateProperty函数的结果,不需要额外加缓存,因为每个Method的返回Type,只会调用一次JsonProperty property = base.CreateProperty(member, memberSerialization);  //先调用默认的CreateProperty函数,创建出默认JsonPropertyvar pi = member as PropertyInfo;if (property.PropertyType.IsValueTuple()){var attr = pi.GetCustomAttribute<TupleElementNamesAttribute>();  //获取定义在属性上的特性if (attr != null)  {//如果该属性是已经编译时有添加了TupleElementNamesAttribute特性的,,则不需要从method获取//这里主要是为了处理 (string str1,int int2) Prop3 这种情况property.Converter = new ValueTupleConverter(attr, this.NamingStrategy);}else {//从输入的method获取,并且需要计算当前属性所属的泛型是在第几个,然后计算出在TupleElementNamesAttribute.Names中的偏移//这个主要是处理比如T2 Prop2 T2=ValueTuple的这种情况var mAttr = (TupleElementNamesAttribute)_methodInfo.ReturnTypeCustomAttributes.GetCustomAttributes(typeof(TupleElementNamesAttribute), true).FirstOrDefault(); //用来获取valueTuple的各个字段名称var basePropertyClass = pi.DeclaringType.GetGenericTypeDefinition(); //属性定义的泛型基类 如 A<T1,T2>var basePropertyType = basePropertyClass.GetProperty(pi.Name)!.PropertyType; //获取基类属性的返回类型 就是T1 ,比如获取在A<(string str1,string str2),(string str3,string str4)> 中 Prop1 返回的类型是对应基类中的T1还是T2var index = basePropertyType.GenericParameterPosition;//获取属性所在的序号,用于计算 mAttr.Names中的偏移量var skipNamesCount = (pi.DeclaringType as TypeInfo).GenericTypeArguments.Take(index).Sum(x => x.IsValueTuple() ? x.GenericTypeArguments.Length : 0); ;  //计算TupleElementNamesAttribute.TransformNames中当前类的偏移量var names = mAttr.TransformNames.Skip(skipNamesCount).Take(pi.PropertyType.GenericTypeArguments.Length).ToArrayEx(); //获取当前类的所有nameproperty.Converter = new ValueTupleConverter(names, this.NamingStrategy);  //传入converter}property.GetIsSpecified = x => true;property.ItemConverter = property.Converter;  //传入converterproperty.ShouldSerialize = x => true;property.HasMemberAttribute = false;}return property;}protected override JsonConverter? ResolveContractConverter(Type objectType) //该函数可用于返回特定类型类型的JsonConverter{var type = base.ResolveContractConverter(objectType);//这里主要是为了忽略一些在class上定义了JsonConverter的情况,因为有些比如 A<T1,T2> 在序列化的时候,并无法知道ValueTuple定义的属性名,这里添加忽略是为了跳过已定义过的JsonConverter//如有需要,可在这里多添加几个if (type is ResultReturnConverter){return null;}else{return type;}}}

 为了能兼容用于预先定义的ContractResolver,因此,先定义一个CompositeContractResolver,用于合并多个ContractResolver,可看可不看:

/// <summary>/// 合并多个IContractResolver,,并只返回第一个返回非null的Contract,如果所有列表中的ContractResolver都返回null,则调用DefaultContractResolver返回默认的JsonContract/// </summary>public class CompositeContractResolver : IContractResolver, IEnumerable<IContractResolver>{private readonly IList<IContractResolver> _contractResolvers = new List<IContractResolver>();private static DefaultContractResolver _defaultResolver = new DefaultContractResolver();private ConcurrentDictionary<Type, JsonContract> _cacheContractResolvers=new ConcurrentDictionary<Type, JsonContract>();/// <summary>/// 返回列表中第一个返回非null的Contract,如果所有列表中的ContractResolver都返回null,则调用DefaultContractResolver返回默认的JsonContract/// </summary>/// <param name="type"></param>/// <returns></returns>public JsonContract ResolveContract(Type type){return _cacheContractResolvers.GetOrAdd(type, m =>{for (int i = 0; i < _contractResolvers.Count; i++){var contact = _contractResolvers[i].ResolveContract(type);if (contact != null){return contact;}}return _defaultResolver.ResolveContract(type);});}public void Add(IContractResolver contractResolver){if (contractResolver == null) return;_contractResolvers.Add(contractResolver);}public IEnumerator<IContractResolver> GetEnumerator(){return _contractResolvers.GetEnumerator();}IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}}

 接下来,就该定义OutputFormatter了

public class ValueTupleOutputFormatter : TextOutputFormatter{private static ConcurrentDictionary<Type, bool> _canHandleType = new ConcurrentDictionary<Type, bool>();  //缓存一个Type是否能处理,提高性能,不用每次都判断private static ConcurrentDictionary<MethodInfo, JsonSerializerSettings> _cacheSettings = new ConcurrentDictionary<MethodInfo, JsonSerializerSettings>(); //用于缓存不同的函数的JsonSerializerSettings,各自定义,避免相互冲突private Action<ValueTupleContractResolver> _resolverConfigFunc = null;/// <summary>/// /// </summary>/// <param name="resolverConfigFunc">用于在注册Formatter的时候对ContractResolver进行配置修改,比如属性名的大小写之类的</param>public ValueTupleOutputFormatter(Action<ValueTupleContractResolver> resolverConfigFunc = null){SupportedMediaTypes.Add("application/json");SupportedMediaTypes.Add("text/json");SupportedEncodings.Add(Encoding.UTF8);SupportedEncodings.Add(Encoding.Unicode);_resolverConfigFunc = resolverConfigFunc;}protected override bool CanWriteType(Type type){return _canHandleType.GetOrAdd(type, t =>{return type.GetProperties()  //判断该类是否包含有ValueTuple的属性.Where(x => x.CanRead && (CustomAttributeExtensions.GetCustomAttribute<TupleElementNamesAttribute>((MemberInfo) x) != null || x.PropertyType.IsValueTuple())).Any();});}public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding){var acce = (IActionContextAccessor)context.HttpContext.RequestServices.GetService(typeof(IActionContextAccessor));#if NETCOREAPP2_1var ac = acce.ActionContext.ActionDescriptor as ControllerActionDescriptor;
#endif
#if NETCOREAPP3_0var endpoint = acce.ActionContext.HttpContext.GetEndpoint();var ac = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>();   //用来获取当前Action对应的函数信息
#endifvar settings = _cacheSettings.GetOrAdd(ac.MethodInfo, m =>  //这里主要是为了配置settings,每个methodinfo对应一个自己的settings,当然也就是每个MethodInfo一个CustomContractResolver,防止相互冲突{var orgSettings = JsonConvert.DefaultSettings?.Invoke();  //获取默认的JsonSettingsvar tmp = orgSettings != null ? cloneSettings(orgSettings) : new JsonSerializerSettings();  //如果不存在默认的,则new一个,如果已存在,则clone一个新的var resolver = new ValueTupleContractResolver(m, tmp.ContractResolver is CompositeContractResolver ? null : tmp.ContractResolver); //创建自定义ContractResolver,传入函数信息_resolverConfigFunc?.Invoke(resolver);  //调用配置函数if (tmp.ContractResolver != null)  //如果已定义过ContractResolver,则使用CompositeContractResolver进行合并{if (tmp.ContractResolver is CompositeContractResolver c)  //如果定义的是CompositeContractResolver,则直接插入到最前{c.Insert(0, resolver);}else{tmp.ContractResolver = new CompositeContractResolver(){resolver,tmp.ContractResolver};}}else{tmp.ContractResolver = new CompositeContractResolver(){resolver};}return tmp;});var json = JsonConvert.SerializeObject(context.Object, Formatting.None, settings);  //调用序列化器进行序列化await context.HttpContext.Response.Body.WriteAsync(selectedEncoding.GetBytes(json));}private JsonSerializerSettings cloneSettings(JsonSerializerSettings settings){var tmp = new JsonSerializerSettings();var properties = settings.GetType().GetProperties();foreach (var property in properties){var pvalue = property.GetValue(settings);if (pvalue is ICloneable p2){property.SetValue(tmp, p2.Clone());}else{property.SetValue(tmp, pvalue);}}return tmp;}}

  到此,该定义的类都定义完了,下面是注册方法:在Start.cs中:

public void ConfigureServices(IServiceCollection services){services.AddControllersWithViews(opt =>{opt.OutputFormatters.Insert(0,new ValueTupleOutFormatter(x =>{x.NamingStrategy= new CamelCaseNamingStrategy(true,true);  //这里主要是为了演示对CustomContractResolver的配置,设置了所有属性首字母小写}));}).AddNewtonsoftJson();}

  总结一下,上面实现的原理是: 自定义一个OutputFormatter,在WriteResponseBodyAsync中,可以获取到当前的Action对应的MethodInfo,然后利用编译器在所有返回ValueTuple的地方,都加了TupleElementNamesAttribute的功能,获取到使用时定义的ValueTuple各个Item的名字,再利用ContractResolver的CreateProperty功能,将定义的各个Item转换为对应的name.然后使用newtonsoft的序列化器,进行json序列化.

  以上代码只能处理返回时,返回的类型为ValueTuple<T1...n>或者返回的类型中包含了ValueTuple<T1....n>的属性,但是对于函数内,不用于返回的,则无法处理,比如

  public object Test2(){var s=  new Test< (string Y1, string Y2),(string str1, string t2)>(("111","22222"),("3333","44444") );JsonConvert.SerializeObject(s);return null;}

  这种情况的变量s的序列化就没办法了

 

部分代码地址:

https://github.com/kugarliyifan/Kugar.UI.Web/blob/master/Kugar.Core.Web.NetCore/Formatters/ValueTupleOutputFormatter.cs

https://github.com/kugarliyifan/Kugar.UI.Web/blob/master/Kugar.Core.Web.NetCore/Converters/ValueTupleConverter.cs

https://github.com/kugarliyifan/Kugar.UI.Web/blob/master/Kugar.Core.Web.NetCore/ValueTupleContractResolver.cs

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高级数据结构---优先队列

高级数据结构—优先队列 原理&#xff1a;参考趣学数据结构 代码&#xff1a; #include <stdio.h> #include <stdlib.h> int r[] { -1,1,4,590,4,2,8,7,5,89,67,5,2,1,67,86,54 };//存储要排序的数,第一个元素不存储元素赋值为-1 int length sizeof(r) / size…

[蓝桥杯2017初赛]贪吃蛇长度-模拟(水题)

题目描述 小明在爷爷的私人收藏馆里找到一台老式电脑。居然没有图形界面&#xff0c;只能用控制台编程。 经过小明的一阵摸索&#xff0c;神奇地设计出了控制台上的贪食蛇游戏。 如下图&#xff0c;是游戏时画面截图。 其中&#xff0c;H表示蛇头&#xff0c;T表示蛇尾。#表示…

蓝屏(BSOD)转储设置,看本文就够了!

前言 我们在内核转储&#xff0c;开抓啦&#xff01;这篇文章里介绍了一个关键的系统设置。设置好后可以让系统在蓝屏&#xff08;Blue Screen of Death&#xff0c;简称 BSOD&#xff09;的时候自动保存转储文件。当时只是简单的介绍了设置步骤&#xff0c;本文力求详细的介绍…

算法---字符串顺序平移

算法—字符串顺序平移 原理&#xff1a;矩阵的转置思想 代码&#xff1a; #include <stdio.h> #include <stdlib.h> void swap(char *a,int i, int j) {//交换二个变量的值char temp a[i];a[i] a[j];a[j] temp; } void invert(char *a,int s, int e) {//对称…

delphi中的函数传参如何传枚举参数_Python基础笔记Day05函数

Python函数 function函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。函数能提高应用的模块性&#xff0c;和代码的重复利用率。Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0c;这被…

[蓝桥杯2018初赛]日志统计-双指针

代码如下&#xff1a; #include <iostream> #include <algorithm> using namespace std; typedef pair<int, int>PII; #define x first #define y second const int N 100010; bool st[N]; int n, d, k; PII a[N]; int cnt[N];int main() {cin >> n …

不要错过这轮疫情的“洗牌”机会

大家好&#xff0c;我是Z哥。今天和大家随便聊聊天。这次的疫情对我们所有人影响都很大&#xff0c;除了让你心怀忐忑的过了个春节之外&#xff0c;呆在家的时间对很多人来说也是格外的长。这样的突发事件&#xff0c;除了能看出不同公司之间应对突发状况的能力差异之外&#x…

算法---查找倒数第k个链表的值

算法—查找倒数第k个链表的值 代码&#xff1a; link.h #pragma once #define elemType int #include<stdlib.h> typedef struct link {elemType data;struct link * next; }link; bool initLink(link* &Link) {//初始化链表Link (link*)malloc(sizeof(link));Li…

mysql boolean_产品操作MySQL第7篇 – 运算符 – IS NULL

本资料为产品岗位作为日常工作参考&#xff0c;语言口语化At 2019/4/27 By David.Yang介绍什么是IS NULL IS NULL作为一种运算符&#xff0c;用来对数据表中的NULL值数据进行过滤。语法target IS NULLtarget值为NULL&#xff0c;则表达式返回TRUE&#xff0c;否则返回FALSE。MY…

[蓝桥杯2016初赛]交换瓶子

题目描述 有N个瓶子&#xff0c;编号 1 ~ N&#xff0c;放在架子上。 比如有5个瓶子&#xff1a;2 1 3 5 4&#xff0c;要求每次拿起2个瓶子&#xff0c;交换它们的位置。 经过若干次后&#xff0c;使得瓶子的序号为&#xff1a;1 2 3 4 5 对于这么简单的情况&#xff0c;显然…

微软开源Scalar,提升操作巨型Git仓库的速度

Git 属于分布式版本控制系统&#xff0c;默认情况下&#xff0c;每个 Git 仓库都具有整个历史记录的完整文件副本。即便是中等规模的开发团队也会产生数千个提交&#xff0c;每个月向仓库添加几百兆的数据。而随着仓库的占用空间增加&#xff0c;Git 难以管理所有数据&#xff…

word List 41

word List41 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

b树与b+树的区别_面试必考:B树、B树、B+树、B*树图文详解

B树B树又叫做二叉搜索树&#xff0c;倒状的树形结构。如下图所示特点&#xff1a;所有的非子夜节点最多拥有两个子节点树(左子树和右子树)。所有结点存储一个关键字。节点的左右儿子&#xff0c;左边是比该节点小的&#xff0c;右边是比该节点大的。缺点&#xff1a;因为二叉搜…

[蓝桥杯2016初赛]煤球数目-找规律

题目描述 有一堆煤球&#xff0c;堆成三角棱锥形。具体&#xff1a; 第一层放1个&#xff0c; 第二层3个&#xff08;排列成三角形&#xff09;&#xff0c; 第三层6个&#xff08;排列成三角形&#xff09;&#xff0c; 第四层10个&#xff08;排列成三角形&#xff09;&…

.NET Core开发实战(第6课:作用域与对象释放行为)--学习笔记(上)

06 | 作用域与对象释放行为作用域主要由 IServiceScope 这个接口来承载对于实现 IDisposable 类的实例的对象&#xff0c;容器会负责对其生命周期进行管理&#xff0c;使用完毕之后&#xff0c;他会释放这些对象实现 IDisposable 接口类型的释放&#xff1a;1、容器只会负责由其…

python调试_Python调试坑

写python的人一般都不太看重调试&#xff0c; 因为这是一门解释性语言&#xff0c; 在哪出错会立马在解释器里提示出来。但是&#xff0c; 以上观点只适用于小的脚本或者项目。因为我在接触目前项目之前&#xff0c; 我一直都是这么做的。 对于一个大的项目来说&#xff0c; 调…

[蓝桥杯]字符串对比-模拟

题目描述 给定两个仅由大写字母或小写字母组成的字符串(长度介于1到10之间)&#xff0c;它们之间的关系是以下4中情况之一&#xff1a;   1&#xff1a;两个字符串长度不等。比如 Beijing 和 Hebei   2&#xff1a;两个字符串不仅长度相等&#xff0c;而且相应位置上的字符…

word List 42

word List 42 如果存在什么问题&#xff0c;欢迎批评指正&#xff01;谢谢&#xff01;

利用AppMetrics对Web进行监控教程

一、基础准备1. 安装依赖这里可以通过nuget或使用命令行进行安装&#xff0c;具体需要安装的类库如下&#xff08;注意版本&#xff09;&#xff1a;Install-Package App.Metrics.AspNetCore.Mvc -Version 2.0.0由于我们需要兼容Prometheus进行监控&#xff0c;所以我们还需要安…

matlab贝叶斯优化工具箱_经济学人的神器——BEAR(贝叶斯估计、分析和回归工具包)...

武林至尊&#xff0c;宝刀屠龙&#xff0c;号令天下&#xff0c;莫敢不从&#xff0c;倚天不出&#xff0c;谁与争锋。——金庸《倚天屠龙记》Bayesian Estimation, Analysis and Regression(简写为BEAR) Toolbox是欧央行(ECB)发布的一个综合 (面板)VAR工具箱&#xff0c;通过图…