You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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扩展枚举定义,但遇到两个问题:

  1. 生成了重复的枚举Schema(比如WorldStatus和WorldStatus2)
  2. 接口端点引用的是不带扩展的原始错误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);
    }
});

效果验证

  1. 生成的openapi.json中只会存在一个WorldStatus Schema,包含枚举值、名称和描述扩展
  2. 所有接口端点引用的都是这个正确的Schema
  3. 执行npx openapi-typescript命令后,生成的TypeScript代码会包含带名称的枚举定义,例如:
export enum WorldStatus {
  Active = 0,
  Maintenance = 1,
  Full = 2,
  Ended = 3,
}

内容的提问来源于stack exchange,提问作者Fritz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 22:27:04