asp.net core 使用newtonsoft完善序列化WebApi返回的ValueTuple
对MYSQL注入相关内容及部分Trick的归类小结
由于开发功用的须要,又懒得新建太多的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函数,建立出默许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 : 0); ; //盘算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 = 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(); } }
View Code
接下来,就该定义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(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(); }
注册完成后,用下面的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)>(("2222",222)); } public Test<(string Y1, string Y2), (string str1, string t2)> Test2() { return new Test< (string Y1, string Y2),(string str1, string t2)>(("111","22222"),("3333","44444") ); } }
View Code
总结一下,上面完成的道理是: 自定义一个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的序列化就没办法了
编写 Django 应用单元测试