I am using Swashbuckle.AspNetCore 6.4.0
I am using a vertical slice approach where I nest models and the classes have the same name Command
.
Because swagger checks the model name in order to generate the schema, I had to add this to the swager gen options
c.CustomSchemaIds(x => x.FullName);
But now my schemas are broken and, although endpoints display, nothing is shown for the schema. I can also see this error on the top of the page:
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
How could I have nested models generating proper schema as per xml comments?
This is my code
public class EnrolAgent
{
private static readonly ILogger Logger = LoggerFactory.CreateLogger<EnrolAgent>();
public class Command
: ICommand
{
/// <summary>
/// The optional message Id. If empty it will be generated at server side
/// </summary>
/// <example>00000000-0000-0000-0000-000000000000</example>
public Guid MessageId { get; init; }
/// <summary>
/// The agent's Id
/// </summary>
/// <example>00000000-0000-0000-0000-000000000000</example>
public Guid AggregateId { get; init; }
/// <summary>
/// The language codes supported in 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;
}
}
}
This is how I configure 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 = "Sample",
});
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);
}
// It fixes problem when using Command to name different nested commands
c.CustomSchemaIds(x => x.FullName);
c.AddSecurityDefinition(
"Bearer",
new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "Bearer",
In = ParameterLocation.Header,
Description = "JWT Authorization header"
});
c.AddSecurityRequirement(
new OpenApiSecurityRequirement()
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference()
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[]{}
}
});
});
return services;
}