.net10 enum可视化显示到scalar中
由于.net 10 openapi 3.1的破坏性更新导致.net 9之前用的OpenApiArray等类型都不再可用,转而是使用JsonArray来替代,并使用JsonNodeExtension进行包装。
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;
namespace MyNameSpace;
public class EnumSchemaTransformer : IOpenApiSchemaTransformer
{
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
{
var type = context.JsonTypeInfo.Type;
var enumType = Nullable.GetUnderlyingType(type) ?? type;
if (!enumType.IsEnum) return Task.CompletedTask;
schema.Extensions ??= new Dictionary<string, IOpenApiExtension>();
// Add x-enum-varnames
var names = Enum.GetNames(enumType);
var namesArray = new JsonArray();
foreach (var name in names)
{
namesArray.Add(JsonValue.Create(name));
}
schema.Extensions["x-enum-varnames"] = new JsonNodeExtension(namesArray);
// Add x-enum-descriptions
var descriptions = EnumDescriptionProvider.GetDescriptions(enumType);
if (descriptions is not { Length: > 0 }) return Task.CompletedTask;
var descArray = new JsonArray();
foreach (var desc in descriptions)
{
descArray.Add(JsonValue.Create(desc));
}
schema.Extensions["x-enum-descriptions"] = new JsonNodeExtension(descArray);
// Add enum
var valueArray = new JsonArray();
foreach (var value in Enum.GetValues(enumType))
{
valueArray.Add(JsonValue.Create((int)value!));
}
schema.Extensions["enum"] = new JsonNodeExtension(valueArray);
return Task.CompletedTask;
}
}
其中的EnumDescriptionProvider.GetDescriptions(enumType)是使用源生成器生成的代码。以下为生成器的代码,支持从当前项目中读取枚举类型的xml注释或Description特性内容到一个字典中。源生成器的使用方式请见下一篇文章。
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
namespace MyNameSpace;
[Generator]
public class EnumDescriptionGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
#if DEBUG
//System.Diagnostics.Debugger.Launch();
#endif
var enumDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => s is EnumDeclarationSyntax,
transform: static (ctx, _) => GetEnumInfo(ctx))
.Where(static m => m is not null);
var compilationAndEnums = context.CompilationProvider.Combine(enumDeclarations.Collect());
context.RegisterSourceOutput(compilationAndEnums,
static (spc, source) => Execute(source.Left, source.Right!, spc));
}
private static EnumInfo? GetEnumInfo(GeneratorSyntaxContext context)
{
var enumDeclaration = (EnumDeclarationSyntax)context.Node;
if (context.SemanticModel.GetDeclaredSymbol(enumDeclaration) is not INamedTypeSymbol enumSymbol)
return null;
var members = new List<EnumMemberInfo>();
foreach (var member in enumSymbol.GetMembers().OfType<IFieldSymbol>())
{
if (member.ConstantValue == null) continue;
var description = member.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == "System.ComponentModel.DescriptionAttribute")
?.ConstructorArguments.FirstOrDefault().Value?.ToString();
if (description == null)
{
if (member.GetDocumentationCommentXml() is { Length: > 0 } xml)
{
var summaryStart = xml.IndexOf("<summary>");
if (summaryStart != -1)
{
var summaryEnd = xml.IndexOf("</summary>", summaryStart);
if (summaryEnd != -1)
{
description = xml.Substring(summaryStart + 9, summaryEnd - summaryStart - 9).Trim();
}
}
}
}
members.Add(new EnumMemberInfo(member.Name, description ?? member.Name));
}
return new EnumInfo(enumSymbol.ToDisplayString(), members);
}
private static void Execute(Compilation compilation, ImmutableArray<EnumInfo?> enums, SourceProductionContext context)
{
var validEnums = enums.Where(e => e != null).Select(e => e!).ToList();
if (validEnums.Count == 0) return;
var sb = new StringBuilder();
sb.AppendLine("// <auto-generated />");
sb.AppendLine("using System;");
sb.AppendLine("using System.Collections.Generic;");
sb.AppendLine("");
sb.AppendLine("namespace MyNameSpace;");
sb.AppendLine("");
sb.AppendLine("public static class EnumDescriptionProvider");
sb.AppendLine("{");
sb.AppendLine(" private static readonly Dictionary<Type, string[]> _descriptions = new Dictionary<Type, string[]>()");
sb.AppendLine(" {");
var distinctEnums = new Dictionary<string, EnumInfo>();
foreach (var info in validEnums)
{
if (!distinctEnums.ContainsKey(info.FullName))
{
distinctEnums[info.FullName] = info;
}
}
foreach (var info in distinctEnums.Values)
{
var descriptions = string.Join(", ", info.Members.Select(m => $"\"{m.Description.Replace("\"", "\\\"")}\""));
sb.AppendLine($" {{ typeof({info.FullName}), new string[] {{ {descriptions} }} }},");
}
sb.AppendLine(" };");
sb.AppendLine("");
sb.AppendLine(" public static string[] GetDescriptions(Type type)");
sb.AppendLine(" {");
sb.AppendLine(" return _descriptions.TryGetValue(type, out var descriptions) ? descriptions : Array.Empty<string>();");
sb.AppendLine(" }");
sb.AppendLine("}");
context.AddSource("EnumDescriptionProvider.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
}
private class EnumInfo
{
public string FullName { get; }
public List<EnumMemberInfo> Members { get; }
public EnumInfo(string fullName, List<EnumMemberInfo> members)
{
FullName = fullName;
Members = members;
}
}
private class EnumMemberInfo
{
public string Name { get; }
public string Description { get; }
public EnumMemberInfo(string name, string description)
{
Name = name;
Description = description;
}
}
}
生成出的代码大概如下:
// <auto-generated />
using System;
using System.Collections.Generic;
namespace VTrust.VideoMeeting.Web.Server;
public static class EnumDescriptionProvider
{
private static readonly Dictionary<Type, string[]> _descriptions = new Dictionary<Type, string[]>()
{
{ typeof(MyNameSpace.Dtos.JoinState), new string[] { "待加入", "已加入", "已离开" } },
};
public static string[] GetDescriptions(Type type)
{
return _descriptions.TryGetValue(type, out var descriptions) ? descriptions : Array.Empty<string>();
}
}
最后是EnumSchemaTransformer的使用方式。修改Program.cs中关于AddOpenApi()的部分为以下代码
builder.Services.AddOpenApi(options =>
{
options.AddDocumentTransformer((document, _, _) =>
{
document.Info.Title = "xxxx API";
document.Info.Version = "v1";
document.Info.Description = "XXXXXXXXXXX";
return Task.CompletedTask;
});
options.AddSchemaTransformer<EnumSchemaTransformer>();
});
最后在scalar中显示的效果如下


浙公网安备 33010602011771号