.NET 9 OpenAPI枚举Schema生成:如何移除重复的默认Schema?
问题描述
在ASP.NET Core 9项目中,我需要通过OpenAPI生成正确的枚举定义,以便用以下命令生成TypeScript schema:
npx openapi-typescript https://localhost:7088/openapi/v1.json -o ./apiSchema.ts --enum
.NET 9默认的OpenAPI实现仅会将枚举生成为整数类型,我尝试用Schema Transformer扩展枚举定义,但遇到两个问题:
- 生成了重复的枚举Schema(比如
WorldStatus和WorldStatus2) - 接口端点引用的是不带扩展的原始错误Schema
我的Schema Transformer代码如下:
var enumCache = new Dictionary<Type, OpenApiSchema>(); options.AddSchemaTransformer(async delegate( OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken ct) { if (context?.JsonPropertyInfo?.PropertyType.IsEnum == true) { if (enumCache.TryGetValue(context.JsonPropertyInfo.PropertyType, out var enumSchema)) { Console.WriteLine($"prop: {context?.JsonPropertyInfo?.Name}: enumSchema:{enumSchema} - from cache"); schema = enumSchema; return; } var enumNames = Enum.GetNames(context.JsonPropertyInfo.PropertyType); var enumValues = Enum.GetValues(context.JsonPropertyInfo.PropertyType); var enumDesc = enumValues.Cast<object>().Select( (value, index) => { // get description attribute via reflection var attr = context.JsonPropertyInfo.PropertyType.GetField(value.ToString())? .GetCustomAttribute<DescriptionAttribute>(); return new { Value = (int) value, Name = enumNames[index], Description = attr?.Description }; }); var openApiValueArray = new OpenApiArray(); var openApiNameArray = new OpenApiArray(); var openApiDescArray = new OpenApiArray(); foreach (var item in enumDesc) { openApiValueArray.Add(new OpenApiInteger(item.Value)); openApiNameArray.Add(new OpenApiString(item.Name)); openApiDescArray.Add(new OpenApiString(item.Description)); } schema.Extensions.Add("x-enum-varnames", openApiNameArray); schema.Extensions.Add("x-enum-descriptions", openApiDescArray); schema.Extensions.Add("enum", openApiValueArray); enumCache[context.JsonPropertyInfo.PropertyType] = schema; return; } return; });
生成的重复Schema示例:
"WorldStatus": { "type": "integer", "x-enum-varnames": [ "Active", "Maintenance", "Full", "Ended" ], "x-enum-descriptions": [ null, null, null, "game over" ], "enum": [ 0, 1, 2, 3 ] }, "WorldStatus2": { "type": "integer" }
测试用的枚举定义:
public enum WorldStatus { Active, Maintenance, Full, [Description("game over")] Ended }
我想知道:如何移除默认的原始Schema?有没有比逐个检查接口的OperationTransformer更简便的方法?
解决方案
问题根源
重复Schema的原因是:.NET默认会先为枚举创建一个基础的整数Schema(不带扩展),之后你的Schema Transformer又生成了一个带扩展的新Schema,两者并存导致重复。而且你用局部缓存的方式没有关联到全局的Schema仓库,导致接口引用的还是原始的基础Schema。
修正后的实现
直接操作OpenAPI的全局Schema仓库,确保枚举类型只生成一个Schema,并覆盖默认的基础Schema,同时为其添加所需的扩展:
options.AddSchemaTransformer(async (schema, context, ct) => { if (context?.JsonPropertyInfo?.PropertyType.IsEnum != true) return; var enumType = context.JsonPropertyInfo.PropertyType; // 获取枚举的Schema名称(和.NET默认生成的名称保持一致) var schemaName = context.SchemaRepository.GetOrAddSchemaName(enumType); // 检查全局Schema中是否已存在该枚举的Schema,不存在则创建 if (!context.SchemaRepository.Schemas.TryGetValue(schemaName, out var enumSchema)) { enumSchema = new OpenApiSchema { Type = "integer" }; context.SchemaRepository.Schemas[schemaName] = enumSchema; } // 仅当Schema未添加过扩展时才处理(避免重复执行) if (!enumSchema.Extensions.ContainsKey("x-enum-varnames")) { var enumNames = Enum.GetNames(enumType); var enumValues = Enum.GetValues(enumType).Cast<object>(); var enumItems = enumValues.Zip(enumNames, (value, name) => new { Value = (int)value, Name = name, Description = enumType.GetField(name)?.GetCustomAttribute<DescriptionAttribute>()?.Description }); // 构建枚举相关的OpenAPI数组 var enumValueArray = new OpenApiArray(enumItems.Select(i => new OpenApiInteger(i.Value))); var enumNameArray = new OpenApiArray(enumItems.Select(i => new OpenApiString(i.Name))); var enumDescArray = new OpenApiArray(enumItems.Select(i => string.IsNullOrEmpty(i.Description) ? null : new OpenApiString(i.Description))); // 添加openapi-typescript需要的扩展字段 enumSchema.Extensions.Add("x-enum-varnames", enumNameArray); enumSchema.Extensions.Add("x-enum-descriptions", enumDescArray); enumSchema.Enum = enumValueArray; } // 返回全局唯一的枚举Schema,确保所有引用都指向它 return enumSchema; });
额外清理:移除残留的重复Schema
如果仍然存在带数字后缀的重复Schema(比如WorldStatus2),可以在OpenAPI文档生成完成后,清理全局Schema集合:
options.AddOpenApiDocumentFilter((doc, context) => { // 找出所有枚举类型对应的Schema名称 var enumSchemaNames = context.TypeDescriptions .Where(td => td.Type.IsEnum) .Select(td => context.SchemaRepository.GetOrAddSchemaName(td.Type)) .ToHashSet(); // 移除所有不是枚举类型的重复Schema(名称带数字后缀的) var schemasToRemove = doc.Components.Schemas .Where(kv => !enumSchemaNames.Contains(kv.Key) && enumSchemaNames.Any(esn => kv.Key.StartsWith(esn) && kv.Key.Length > esn.Length)) .Select(kv => kv.Key) .ToList(); foreach (var schemaKey in schemasToRemove) { doc.Components.Schemas.Remove(schemaKey); } });
效果验证
- 生成的
openapi.json中只会存在一个WorldStatusSchema,包含枚举值、名称和描述扩展 - 所有接口端点引用的都是这个正确的Schema
- 执行
npx openapi-typescript命令后,生成的TypeScript代码会包含带名称的枚举定义,例如:
export enum WorldStatus { Active = 0, Maintenance = 1, Full = 2, Ended = 3, }
内容的提问来源于stack exchange,提问作者Fritz
相关产品推荐
相关产品推荐

