Netcore webapi action swagger response返回参数使用匿名类型
问题:action中返回匿名对象时,swagger只能按强类型生成返回值描述
解决办法:使用roslyn在内存中动态执行代码,构建匿名对象,再从匿名对象解析构造多个临时类,向swagger返回临时类类型(直接使用匿名类会出现 重名问题)
效果:
Swaager在描述控制器的方法时,可以使用以下方法
<response code="400">如果id为空</response>
或
[ProducesResponseType(typeof(ResponseT<User>), 200)]
Openapi json如下
生成效果如下图
上述方法必须存在强类型user
代码
@@@code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CSharp; namespace Q.DevExtreme.Tpl.Attributes { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true , Inherited = true )] public class ProducesResponseAnonymousTypeAttribute : Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute { public ProducesResponseAnonymousTypeAttribute( string typeJson, int statusCode) : base (getAnonymousType(typeJson), statusCode) { //TODO:内部实际 还是强类型,不支持多个匿名类型引用 } static long seq = DateTime.Now.Millisecond; /// <summary> /// 匿名类型转类型 /// </summary> /// <param name="t"></param> /// <returns></returns> public static void AnonymousType2Type(System.Text.StringBuilder sb, Type type) { List<(Type, string , bool )> list = new List<(Type, string , bool )>(); foreach ( var m in type.GetProperties()) { //判断是否为数组 bool isCollection = m.PropertyType.GetInterface( "ICollection" ) != null ; string arrayFlag = isCollection ? "[]" : "" ; if (m.PropertyType.Name.Contains( "AnonymousType" , StringComparison.OrdinalIgnoreCase)) { //创建子类,记录名称 string tempClassName = $ "temp_{System.Threading.Interlocked.Increment(ref seq)}" ; list.Add((m.PropertyType, tempClassName, isCollection)); sb.AppendLine($ "public {tempClassName}{arrayFlag} {m.Name} {{get;set;}}" ); } else sb.AppendLine($ "public {m.PropertyType.Name}{arrayFlag} {m.Name} {{get;set;}}" ); } sb.AppendLine( "}" ); foreach ( var x in list) { if (x.Item3) { sb.AppendLine($ "class {x.Item2} {{" ); AnonymousType2Type(sb, x.Item1.GetElementType()); } else { sb.AppendLine($ "class {x.Item2} {{" ); AnonymousType2Type(sb, x.Item1); } } } /// <summary> /// 从字符中创建匿名类型 /// </summary> /// <param name="typeJson"></param> /// <returns></returns> static Type createAnonymousType( string typeJson) { var code = @" public class Result { public System.Type GetType5( ){ var t=#T#; return t.GetType(); } } " .Replace("#T# ", typeJson.Replace(" ' ", @" "" ")); List<PortableExecutableReference> refList = new List<PortableExecutableReference>(); refList.Add(MetadataReference.CreateFromFile( typeof ( object ).Assembly.Location)); var tree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create( "test" ) .WithOptions( new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) .AddReferences(refList.ToArray()).AddSyntaxTrees(tree); using ( var stream = new MemoryStream()) { var emitResult = compilation.Emit(stream); if (emitResult.Success) { stream.Seek(0, SeekOrigin.Begin); var assembly = Assembly.Load(stream.ToArray()); var typeResult = assembly.GetType( "Result" ); var m = Activator.CreateInstance(typeResult); var Type = m.GetType(); var Type2 = typeResult.GetMethod( "GetType5" ).Invoke(m, null ) as Type; return Type2; } } return null ; } /// <summary> /// 从字符串创建临时类型 /// </summary> /// <param name="typeJson">匿名类型描述串 /// </param> /// <example>new {id=1,name=""}</example> /// <remarks>swagger不支持多个相同的匿名类型</remarks> static System.Type getAnonymousType( string typeJson) { // var x = Newtonsoft.Json.JsonConvert.DeserializeAnonymousType("{}", new { Code = 0, Msg = "", Data = new[] { new { Id = 1, Name = "" } } }); Type anonymousType = createAnonymousType(typeJson); var x = Newtonsoft.Json.JsonConvert.DeserializeObject( "{}" , anonymousType); string tempClassName = $ "temp_{System.Threading.Interlocked.Increment(ref seq)}" ; System.Text.StringBuilder sbType = new System.Text.StringBuilder(@$" using System; class {tempClassName} {{ "); AnonymousType2Type(sbType, x.GetType()); // Console.WriteLine(sbType.ToString()); var code = sbType.ToString(); List<PortableExecutableReference> refList = new List<PortableExecutableReference>(); refList.Add(MetadataReference.CreateFromFile( typeof ( object ).Assembly.Location)); // refList.Add(MetadataReference.CreateFromFile(typeof(Newtonsoft.Json.JsonConvert).Assembly.Location)); // refList.Add(MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location)); // refList.Add(MetadataReference.CreateFromFile(System.IO.Path.Combine(System.IO.Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll"))); var tree = CSharpSyntaxTree.ParseText(code); var compilation = CSharpCompilation.Create( "test" ) .WithOptions( new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) .AddReferences(refList.ToArray()).AddSyntaxTrees(tree); using ( var stream = new MemoryStream()) { var emitResult = compilation.Emit(stream); if (emitResult.Success) { stream.Seek(0, SeekOrigin.Begin); var assembly = Assembly.Load(stream.ToArray()); var typeResult = assembly.GetType(tempClassName); return typeResult; } } return null ; } } } |
使用
@@@code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | [ProducesResponseAnonymousType( @"new {Code=0,Msg='',Data=new []{new {id=0,name=''}}}" , 200)] public object DeptGet() { return new { Code = 0, Msg = string .Empty, Data = _context.Group.Select(r => new { id = r. ID, name = r. Name }).ToList() }; } |
另一种借助元组实现的方法
花了大量时间获取属性名没有结果,泛型传递时的方法获取 不到TupleElementNamesAttribute,只能走动态方法获取 返回值 ,再利用TupleElementNamesAttribute获取 名称
@@@code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | public class ResponseT<T> { //有效 [JsonProperty("haaa")] public int Code { get ; set ; } // 无效 [JsonConverter(typeof(Q.DevExtreme.Tpl.Json.IPAddressConverter))] public string Msg { get ; set ; } public T Data { get ; set ; } public T[] GetData() { //泛型,丢失名称信息,没有TupleElementNamesAttribute属性 return new [] { Data }; } public ( int a, string b) GetData2() { //可以使用下列方法获得名称 //Type t = typeof(C); //MethodInfo method = t.GetMethod(nameof(C.M)); //var attr = method.ReturnParameter.GetCustomAttribute<TupleElementNamesAttribute>(); //string[] names = attr.TransformNames; return (1, "2" ); } } |
使用
@@@code
1 | [ProducesResponseType( typeof (ResponseT<( int id, string name)>), 400)] |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南