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

Swashbuckle.AspNetCore嵌套模型自定义SchemaIds致Schema解析失败

问题描述

我正在使用Swashbuckle.AspNetCore 6.4.0,项目采用垂直切片架构,所有嵌套模型类都命名为Command。因为Swagger默认用模型名称生成Schema导致命名冲突,所以我在Swagger生成选项中添加了以下配置:

c.CustomSchemaIds(x => x.FullName);

但添加后Schema功能失效,端点能显示但无Schema内容,页面顶部出现以下解析错误:

Resolver error at paths./api/agents/{aggregateId}/enrolments.post.requestBody.content.application/json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/MyProject.Features.EnrolAgent+Command does not exist in document
Resolver error at paths./api/agents/{aggregateId}/enrolments.post.requestBody.content.text/json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/MyProject.Features.EnrolAgent+Command does not exist in document
Resolver error at paths./api/agents/{aggregateId}/enrolments.post.requestBody.content.application/*+json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/MyProject.Features.EnrolAgent+Command does not exist in document
Resolver error at paths./api/clients.post.requestBody.content.application/json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/MyProject.Features.CreateClient+Command does not exist in document
Resolver error at paths./api/clients.post.requestBody.content.text/json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/MyProject.Features.CreateClient+Command does not exist in document
Resolver error at paths./api/clients.post.requestBody.content.application/*+json.schema.$ref
Could not resolve reference: Could not resolve pointer: /components/schemas/MyProject.Features.CreateClient+Command does not exist in document
MyProject.Features.EnrolAgent+Command does not exist in document

如何让嵌套模型生成带有XML注释的正确Schema?


业务代码示例

public class EnrolAgent
{
    private static readonly ILogger Logger = LoggerFactory.CreateLogger<EnrolAgent>();

    public class Command
        : ICommand
    {
        /// <summary>
        /// 可选的消息ID,如果为空会在服务端生成
        /// </summary>
        /// <example>00000000-0000-0000-0000-000000000000</example>
        public Guid MessageId { get; init; }
        
        /// <summary>
        /// 代理ID
        /// </summary>
        /// <example>00000000-0000-0000-0000-000000000000</example>
        public Guid AggregateId { get; init; }
        
        /// <summary>
        /// ISO 639-1标准支持的语言代码
        /// </summary>
        /// <example>["00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002"]</example>
        public IEnumerable<Guid> BrandIds { get; init; } = Enumerable.Empty<Guid>();

        public override string ToString()
        {
            return this.DisplayInfo();
        }
    }
    
    public class Handler
        : ICommandHandler<Command>
    {
        private readonly IDomainEventsConsumer _domainEventsConsumer;
        private readonly IDateTimeFactory _dateTimeFactory;
        private readonly IAggregateRepository<AgentAggregate> _agentRepository;

        public Handler(
            IDomainEventsConsumer domainEventsConsumer,
            IDateTimeFactory dateTimeFactory,
            IAggregateRepository<AgentAggregate> agentRepository)
        {
            _domainEventsConsumer = domainEventsConsumer;
            _dateTimeFactory = dateTimeFactory;
            _agentRepository = agentRepository;
        }
        
        public async Task<CommandResult> Handle(Command command)
        {
            Logger.LogDebug($"Handling command {command}");
            var aggregateId = command.AggregateId;
            
            var agent = await _agentRepository.Get(aggregateId);
            var createdOn = _dateTimeFactory.CreateUtcNow();
            
            agent.Enrol(createdOn, command.BrandIds);
            
            var domainEvents = await agent.ConsumeDomainEventChanges(_domainEventsConsumer);
            
            var commandResult = CommandResult.Create(aggregateId, domainEvents);
            return commandResult;
        }
    }
}

Swagger配置代码

private static IServiceCollection AddOpenApi(this IServiceCollection services, IEnumerable<Assembly> allAssemblies)
{
    var mainAssemblyName = typeof(Startup).Assembly.GetName().Name;

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo
        {
            Title = mainAssemblyName, 
            Version = "v1",
            Description = "示例接口文档",
        });
        
        c.DocumentFilter<LowerCaseDocumentFilter>();
            
        var xmlCommentsWebApi = Path.Combine(AppContext.BaseDirectory, $"{mainAssemblyName}.xml");
        c.IncludeXmlComments(xmlCommentsWebApi);
        
        var allApplicationAssemblies =
            allAssemblies
                .Where(x => x.GetTypes().Any(t => typeof(ICommand).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract))
                .ToList();
        
        foreach (var applicationAssembly in allApplicationAssemblies)
        {
            var xmlCommentsApplication = Path.Combine(AppContext.BaseDirectory, $"{applicationAssembly.GetName().Name}.xml");
            c.IncludeXmlComments(xmlCommentsApplication);
        }
        
        // 解决不同嵌套Command类的命名冲突
        c.CustomSchemaIds(x => x.FullName);
        
        c.AddSecurityDefinition(
            "Bearer",
            new OpenApiSecurityScheme
            {
                Name = "Authorization",
                Type = SecuritySchemeType.Http,
                Scheme = "Bearer",
                In = ParameterLocation.Header,
                Description = "JWT授权头"
            });

        c.AddSecurityRequirement(
            new OpenApiSecurityRequirement()
            {
                {
                    new OpenApiSecurityScheme
                    {
                        Reference = new OpenApiReference()
                        {
                            Type = ReferenceType.SecurityScheme,
                            Id = "Bearer"
                        }
                    },
                    new string[]{}
                }
            });
    });
        
    return services;
}

解决方案

问题核心是x.FullName返回的嵌套类名称包含+符号,而OpenAPI规范中Schema的$ref不允许使用该符号作为标识符,导致Swagger无法正确识别并生成对应Schema组件。

方式一:替换FullName中的+为合法字符

修改CustomSchemaIds配置,将嵌套类的+替换为.或下划线等OpenAPI允许的字符,既保留命名空间区分,又符合Schema标识符规范:

c.CustomSchemaIds(x => x.FullName.Replace("+", "."));

该方式无需修改业务代码,可快速让Swagger正确生成嵌套类Schema,同时XML注释也能正常关联。

方式二:显式指定Schema名称

若需要更自定义的命名,可给每个嵌套Command类添加[SwaggerSchema]特性指定唯一名称:

using Swashbuckle.AspNetCore.Annotations;

public class EnrolAgent
{
    [SwaggerSchema(Title = "EnrolAgentCommand")]
    public class Command : ICommand
    {
        // ... 原有代码
    }
}

此时可保留或移除CustomSchemaIds配置(若全部通过特性指定名称),适合需要更简洁Schema名称的场景,但需逐个修改嵌套类。

验证XML注释生效

确保项目已开启XML文档生成:

  1. 右键项目→属性→生成→勾选“XML文档文件”,确认输出路径与代码读取路径一致;
  2. 检查所有包含Command类的程序集都生成了对应XML文件,且Swagger配置中正确加载。

修改完成后重启项目,Swagger即可正确生成嵌套Command类的Schema并显示XML注释,解析错误也会消失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 21:06:18