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文档生成:
- 右键项目→属性→生成→勾选“XML文档文件”,确认输出路径与代码读取路径一致;
- 检查所有包含
Command类的程序集都生成了对应XML文件,且Swagger配置中正确加载。
修改完成后重启项目,Swagger即可正确生成嵌套Command类的Schema并显示XML注释,解析错误也会消失。
内容的提问来源于stack exchange,提问作者diegosasw
相关产品推荐
相关产品推荐

