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)]

@@#

posted @ 2021-04-16 16:21  秦秋随  阅读(1049)  评论(0编辑  收藏  举报