Netcore webapi action swagger response返回参数使用匿名类型
问题:action中返回匿名对象时,swagger只能按强类型生成返回值描述
解决办法:使用roslyn在内存中动态执行代码,构建匿名对象,再从匿名对象解析构造多个临时类,向swagger返回临时类类型(直接使用匿名类会出现 重名问题)
效果:
Swaager在描述控制器的方法时,可以使用以下方法
<response code="400">如果id为空</response>
或
[ProducesResponseType(typeof(ResponseT<User>), 200)]
Openapi json如下
生成效果如下图
上述方法必须存在强类型user
代码
@@@code
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
[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
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
[ProducesResponseType(typeof(ResponseT<(int id,string name)>), 400)]