Blogs
📆 2025-12-22 19:34

OpenApi自定义转换器

微软官方的OpenApi一言难尽,如果你想使用它,本文将介绍如何自定义OpenApi转换器来解决一些常见问题。

Warning

在现有阶段,仍然建议使用Swashbuckle.AspNetCore,微软官方的OpenApi目前仍然不够成熟,很多功能都不完善。

枚举类型处理

默认情况下,OpenApi会将枚举类型转换为整型,其字段和描述信息会丢失,我们将使用Extension来添加这些信息。

/// <summary>
/// Transformer for Microsoft.AspNetCore.OpenApi Schema
/// </summary>
public sealed class OpenApiSchemaTransformer : IOpenApiSchemaTransformer
{
    public Task TransformAsync(
        OpenApiSchema schema,
        OpenApiSchemaTransformerContext context,
        CancellationToken cancellationToken
    )
    {
        var type = context.JsonTypeInfo.Type;
        AddEnumExtension(schema, type);
        return Task.CompletedTask;
    }
    private static void AddEnumExtension(OpenApiSchema schema, Type type)
    {
        if (!type.IsEnum)
        {
            return;
        }
        schema.Extensions ??= new Dictionary<string, IOpenApiExtension>();
        var enumItems = new List<EnumItem>();
        foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
        {
            var raw = field.GetRawConstantValue();
            if (raw is null)
            {
                continue;
            }
            var value = Convert.ToInt32(raw);
            string? description = null;
            var desAttr = field.GetCustomAttribute<DescriptionAttribute>();
            if (desAttr is not null && !string.IsNullOrWhiteSpace(desAttr.Description))
            {
                description = desAttr.Description;
            }
            enumItems.Add(new EnumItem(field.Name, value, description));
        }
        if (schema.Enum is null || schema.Enum.Count == 0)
        {
            schema.Enum = [];
            foreach (var item in enumItems)
            {
                schema.Enum.Add(JsonValue.Create(item.Value));
            }
        }
        schema.Extensions["x-enumData"] = new EnumDataExtension(enumItems);
    }
    private sealed record EnumItem(string Name, int Value, string? Description);
    /// <summary>
    /// 自定义扩展输出器
    /// </summary>
    private sealed class EnumDataExtension(IReadOnlyList<EnumItem> items) : IOpenApiExtension
    {
        public void Write(IOpenApiWriter writer, OpenApiSpecVersion specVersion)
        {
            WriteInternal(writer);
        }
        private void WriteInternal(IOpenApiWriter writer)
        {
            writer.WriteStartArray();
            foreach (var item in items)
            {
                writer.WriteStartObject();
                writer.WritePropertyName("name");
                writer.WriteValue(item.Name);
                writer.WritePropertyName("value");
                writer.WriteValue(item.Value);
                if (!string.IsNullOrWhiteSpace(item.Description))
                {
                    writer.WritePropertyName("description");
                    writer.WriteValue(item.Description);
                }
                writer.WriteEndObject();
            }
            writer.WriteEndArray();
        }
    }
}

自定义OperationId

OperationId很重要,它提供了接口的唯一标识,可用在生成客户端代码时使用。

/// <summary>
/// Transformer for Microsoft.AspNetCore.OpenApi operations.
/// </summary>
public class OpenApiOperationTransformer : IOpenApiOperationTransformer
{
    public Task TransformAsync(
        OpenApiOperation operation,
        OpenApiOperationTransformerContext context,
        CancellationToken cancellationToken
    )
    {
        if (string.IsNullOrEmpty(operation.OperationId))
        {
            var actionDescriptor = context.Description.ActionDescriptor;
            var controller = actionDescriptor.RouteValues.TryGetValue("controller", out var c)
                ? c
                : "UnknownController";
            var action = actionDescriptor.RouteValues.TryGetValue("action", out var a)
                ? a
                : context.Description.RelativePath;
            operation.OperationId = $"{controller}_{action}";
        }
        return Task.CompletedTask;
    }
}

Tip

以上示例使用controller和action名称来生成OperationId,从编码角度,你是可以定义多个action名称相同的方法,但不建议这样做,对于接口,建议每个接口具有唯一的action名称。