郑州二七区做网站,松江工业区网站建设,优化门户网站建设,郑州搭建网站由于开发功能的需要,又懒得新建太多的class,所以ValueTuple是个比较好的偷懒方法,但是,由于WebApi需要返回序列化后的json,默认的序列化只能将ValueTuple定义的各个属性序列化成Item1...n但是微软还是良心的为序列化留下入口,编译器会在每个返回ValueTuple的函数或者属… 由于开发功能的需要,又懒得新建太多的class,所以ValueTuple是个比较好的偷懒方法,但是,由于WebApi需要返回序列化后的json,默认的序列化只能将ValueTuple定义的各个属性序列化成Item1...n 但是微软还是良心的为序列化留下入口,编译器会在每个返回ValueTuple的函数或者属性上,增加一个TupleElementNamesAttribute特性,该类的TransformNames就是存着所设置的属性的名称(强烈需要记住:是每个使用到ValueTuple的函数或者属性才会添加,而不是加在有使用ValueTuple的类上),比如 (string str1,string str2) 那么 TransformNames[str1,str2],那么现在有如下一个class public class AT1,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 ConcurrentDictionaryType,bool _cacheIsValueTuplenew ConcurrentDictionaryType, 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.GetCustomAttributeTupleElementNamesAttribute(); //获取定义在属性上的特性if (attr ! null) {//如果该属性是已经编译时有添加了TupleElementNamesAttribute特性的,,则不需要从method获取//这里主要是为了处理 (string str1,int int2) Prop3 这种情况property.Converter new ValueTupleConverter(attr, this.NamingStrategy);}else {//从输入的method获取,并且需要计算当前属性所属的泛型是在第几个,然后计算出在TupleElementNamesAttribute.Names中的偏移//这个主要是处理比如T2 Prop2 T2ValueTuple的这种情况var mAttr (TupleElementNamesAttribute)_methodInfo.ReturnTypeCustomAttributes.GetCustomAttributes(typeof(TupleElementNamesAttribute), true).FirstOrDefault(); //用来获取valueTuple的各个字段名称var basePropertyClass pi.DeclaringType.GetGenericTypeDefinition(); //属性定义的泛型基类 如 AT1,T2var 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的情况,因为有些比如 AT1,T2 在序列化的时候,并无法知道ValueTuple定义的属性名,这里添加忽略是为了跳过已定义过的JsonConverter//如有需要,可在这里多添加几个if (type is ResultReturnConverter){return null;}else{return type;}}}为了能兼容用于预先定义的ContractResolver,因此,先定义一个CompositeContractResolver,用于合并多个ContractResolver,可看可不看:/// summary/// 合并多个IContractResolver,,并只返回第一个返回非null的Contract,如果所有列表中的ContractResolver都返回null,则调用DefaultContractResolver返回默认的JsonContract/// /summarypublic class CompositeContractResolver : IContractResolver, IEnumerableIContractResolver{private readonly IListIContractResolver _contractResolvers new ListIContractResolver();private static DefaultContractResolver _defaultResolver new DefaultContractResolver();private ConcurrentDictionaryType, JsonContract _cacheContractResolversnew ConcurrentDictionaryType, JsonContract();/// summary/// 返回列表中第一个返回非null的Contract,如果所有列表中的ContractResolver都返回null,则调用DefaultContractResolver返回默认的JsonContract/// /summary/// param nametype/param/// returns/returnspublic 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 IEnumeratorIContractResolver GetEnumerator(){return _contractResolvers.GetEnumerator();}IEnumerator IEnumerable.GetEnumerator(){return GetEnumerator();}}接下来,就该定义OutputFormatter了public class ValueTupleOutputFormatter : TextOutputFormatter{private static ConcurrentDictionaryType, bool _canHandleType new ConcurrentDictionaryType, bool(); //缓存一个Type是否能处理,提高性能,不用每次都判断private static ConcurrentDictionaryMethodInfo, JsonSerializerSettings _cacheSettings new ConcurrentDictionaryMethodInfo, JsonSerializerSettings(); //用于缓存不同的函数的JsonSerializerSettings,各自定义,避免相互冲突private ActionValueTupleContractResolver _resolverConfigFunc null;/// summary/// /// /summary/// param nameresolverConfigFunc用于在注册Formatter的时候对ContractResolver进行配置修改,比如属性名的大小写之类的/parampublic ValueTupleOutputFormatter(ActionValueTupleContractResolver 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.GetCustomAttributeTupleElementNamesAttribute((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.GetMetadataControllerActionDescriptor(); //用来获取当前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序列化. 以上代码只能处理返回时,返回的类型为ValueTupleT1...n或者返回的类型中包含了ValueTupleT1....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.cshttps://github.com/kugarliyifan/Kugar.UI.Web/blob/master/Kugar.Core.Web.NetCore/Converters/ValueTupleConverter.cshttps://github.com/kugarliyifan/Kugar.UI.Web/blob/master/Kugar.Core.Web.NetCore/ValueTupleContractResolver.cs