由于开发功能的需要,又懒得新建太多的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

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

  1. Asp.Net Core Mvc上Json序列化首字母大小写的问题

    在mvc中返回Json格式数据,用Return Json()时,序列化后的Json串首字母全变成了小写. 第一种解决方案 用 return Json(new { code = 0, msg = &qu ...

  2. ASP.NET Core 中的 ORM 之 Entity Framework

    目录 EF Core 简介 使用 EF Core(Code First) EF Core 中的一些常用知识点 实体建模 实体关系 种子数据 并发管理 执行 SQL 语句和存储过程 延迟加载和预先加载 ...

  3. 使用ASP.NET Core开始使用gRPC客户端和服务器

    目录 介绍 GRPC Protobuf文件 背景 先决条件 使用代码 第1步--创建gRPC服务应用程序 gRPC项目结构 构建并运行应用程序 第2步:创建客户端控制台.NET核心应用程序 添加必需的 ...

  4. ASP.NET Core: 全新的ASP.NET !

    背景 最新版本的 ASP.NET 叫做 ASP.NET Core (也被称为 ASP.NET 5)   它颠覆了过去的 ASP.NET. 什么是 ASP.NET Core? ASP.NET Core ...

  5. Visual Studio 2015/2017 与ASP.NET CORE 联合创建具有SPA模式的Angular2模板

    虽然注册博客园很久,但是一直没有什么可写的,真心感觉好尴尬了,这次终于找到了一点可以写,有点小兴奋和小害羞呢. 进入主题,前端SPA模式越来越受到欢迎,Core 也开始被很多企业提上日程,但是因为这个 ...

  6. ASP.NET Core: 全新的ASP.NET

    背景 最新版本的 ASP.NET 叫做 ASP.NET Core (也被称为 ASP.NET 5)   它颠覆了过去的 ASP.NET. 什么是 ASP.NET Core? ASP.NET Core ...

  7. ASP.NET Core 3.1系列(30)——Newtonsoft.Json实现JSON的序列化和反序列化

    1.前言 在早期版本的ASP.NET Core项目中,Newtonsoft.Json的使用率非常高.虽然微软当前主推System.Text.Json来处理JSON的序列化和反序列化,但Newtonso ...

  8. 【源码解读】Vue与ASP.NET Core WebAPI的集成

    在前面博文[Vue]Vue 与 ASP.NET Core WebAPI 的集成中,介绍了集成原理:在中间件管道中注册SPA终端中间件,整个注册过程中,终端中间件会调用node,执行npm start命 ...

  9. ASP.NET Core WebApi构建API接口服务实战演练

    一.ASP.NET Core WebApi课程介绍 人生苦短,我用.NET Core!提到Api接口,一般会想到以前用到的WebService和WCF服务,这三个技术都是用来创建服务接口,只不过Web ...

最新文章

  1. 33 个 2017 年必须了解的 iOS/swift 开源库第三方库
  2. Eclipse基本使用
  3. ospf 环回口的路由条目_【网络工程师配置篇】——OSPF汇总配置!
  4. Java提高—对象克隆(复制)/对象属性拷贝
  5. 我的Java后端书架
  6. 互联网晚报 | 12月14日 星期二 | “植发第一股”雍禾医疗登陆港交所;商汤科技将延迟上市;“拍照搜题”等作业APP暂时下线...
  7. 网络安全课程学习内容
  8. Java InputStream转换为String
  9. 【Dubbo源码阅读系列】服务暴露之本地暴露
  10. 如何实现混合线性模型?
  11. 将VMware与SoftICE基于网络的远程调试功能相结合
  12. APP在后台启动Activity
  13. 高通modem启动过程_高通8953启动流程【转】
  14. sql server关系代数练习--进阶
  15. windows-sys9 :windows 系统官方下载网址
  16. fnd_profile.value的用法
  17. 用户上传用户头像至服务器
  18. Soul源码总结-01-15
  19. 【面试题】深复制与浅复制的区别
  20. JAVA编写的纯色背景图片去除底色变成透明背景图片的工具

热门文章

  1. 心得9--jsp设计模版
  2. 【转】Asp.Net中Excel操作权限的问题
  3. 男人别让爱你的女孩流泪
  4. Teams Bot开发系列:初识Bot
  5. chrome 悬停大图插件_Google Chrome浏览器的悬停卡:我不想要的我最喜欢的新东西
  6. 99. Recover Binary Search Tree
  7. ACM题解系列之一:刘汝佳:《算法竞赛入门经典》(第2版)
  8. Mysql安装及自动化部署脚本方案
  9. GPG key retrieval failed: [Errno 14]
  10. hdu 1879 继续畅通工程 (最小生成树)