由于开发功能的需要,又懒得新建太多的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 = ; 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函数,创建出默认JsonProperty var 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还是T2
var index = basePropertyType.GenericParameterPosition;//获取属性所在的序号,用于计算 mAttr.Names中的偏移量
var skipNamesCount = (pi.DeclaringType as TypeInfo).GenericTypeArguments
.Take(index)
.Sum(x => x.IsValueTuple() ? x.GenericTypeArguments.Length : ); ; //计算TupleElementNamesAttribute.TransformNames中当前类的偏移量
var names = mAttr.TransformNames
.Skip(skipNamesCount)
.Take(pi.PropertyType.GenericTypeArguments.Length)
.ToArrayEx(); //获取当前类的所有name
property.Converter = new ValueTupleConverter(names, this.NamingStrategy); //传入converter
} property.GetIsSpecified = x => true;
property.ItemConverter = property.Converter; //传入converter
property.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 = ; 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_1
var ac = acce.ActionContext.ActionDescriptor as ControllerActionDescriptor;
#endif
#if NETCOREAPP3_0
var endpoint = acce.ActionContext.HttpContext.GetEndpoint();
var ac = endpoint.Metadata.GetMetadata<ControllerActionDescriptor>(); //用来获取当前Action对应的函数信息
#endif
var settings = _cacheSettings.GetOrAdd(ac.MethodInfo, m => //这里主要是为了配置settings,每个methodinfo对应一个自己的settings,当然也就是每个MethodInfo一个CustomContractResolver,防止相互冲突
{
var orgSettings = JsonConvert.DefaultSettings?.Invoke(); //获取默认的JsonSettings
var 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(, 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(,new ValueTupleOutFormatter(x =>
{
x.NamingStrategy= new CamelCaseNamingStrategy(true,true); //这里主要是为了演示对CustomContractResolver的配置,设置了所有属性首字母小写
}));
}).AddNewtonsoftJson();
}

  注册完成后,用下面的Action可测试:

    public class ApiTestController : ControllerBase
{
[FromBodyJson()]
public IActionResult test1(List<(string productid,int qty)> details)
{ return Content("success");
} public ResultReturn<(string str1, int int3)> Test()
{
return new SuccessResultReturn<(string str1, int int3)>(("",));
} public Test<(string Y1, string Y2), (string str1, string t2)> Test2()
{
return new Test< (string Y1, string Y2),(string str1, string t2)>(("",""),("","") );
}
}

  

  总结一下,上面实现的原理是: 自定义一个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)>(("",""),("","") );
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

最新文章

  1. MySQL主从复制
  2. Support for Xpm library: no问题
  3. WebService到底是什?
  4. hdu 2102 BFS
  5. supersocket+controller+action
  6. linux常见问题集锦
  7. Java的ResultSet中rs.next()含义
  8. Android 讯飞语音之语音合成(在线有声朗读)
  9. 安装oracle客户端(navicat for oracle)
  10. Unity Debug类
  11. (原创)用Java实现链表结构对象:单向无环链表
  12. DevExpress winform 友好皮肤
  13. win openssl 生成证书
  14. 浅谈工作单元 在整个 ABP 框架当中的应用
  15. Spring 学习笔记一
  16. 手把手教你实现Confluence6.7.1安装与破解
  17. JAVA实现具有迭代器的线性表(顺序表)
  18. web前端基础知识!
  19. 移植到windows下的iconv
  20. linux中线程池【转】

热门文章

  1. vue 项目使用JSbrideg.js与app通信
  2. TCP 协议详解
  3. 使用 git 将代码推送到多个仓库
  4. win10内存泄漏怎么办
  5. Redis 分布式锁的正确实现方式( Java 版 )
  6. 《C# 爬虫 破境之道》:第二境 爬虫应用 — 第一节:HTTP协议数据采集
  7. 异数OS 织梦师-云(五)-- 容器服务化,绿色拯救未来。
  8. 异数OS TCP协议栈测试(三)--长连接篇
  9. 使用FileZilla Pro S3协议访问七牛云对象存储
  10. 文艺平衡树(区间splay)