I've implemented the Swagger Documentation in a web API made with C# (.NET Core 5).
I have 3 classes: DefaultCommandResult
, SuccessCommandResult
and ErrorCommandResult
.
The DefaultCommandResult
is the base class witch the other two inherit from. The object returned in the endpoint is either a SuccessCommandResult
or ErrorCommandResult
, and DefaultCommandResult
is never returned.
What I need to do is hide the schema generated in the endpoints' responses related to the base class DefaultCommandResult
:
The request in the controller:
public ActionResult<DefaultCommandResult<ServerIdCommand>> Get([FromQuery] RecoverServerIdCommand command)
{
try
{
var id = handler.RecoverServerId(command);
if (id is null)
return NotFound();
var output = new ServerIdCommand { Id = id };
var result = new SuccessCommandResult<ServerIdCommand>(output);
return Ok(result);
}
catch (ObjectNotFoundException ex)
{
var result = new ErrorCommandResult<string>(ex.Message);
return NotFound(result);
}
catch (Exception ex)
{
var result = new ErrorCommandResult<string>(ex.Message);
return BadRequest(result);
}
}
The DefaultCommandResult
class:
[SwaggerSubType(typeof(SuccessCommandResult<object>))]
[SwaggerSubType(typeof(ErrorCommandResult<object>))]
public class DefaultCommandResult<T> where T : class
{
public bool Success { get; set; }
protected DefaultCommandResult(bool success)
{
Success = success;
}
}
The SuccessCommandResult
class (ErrorCommandResult
follows the same structure, but for errors):
[SwaggerDiscriminator("successCommandResult")]
public class SuccessCommandResult<T> : DefaultCommandResult<T> where T : class
{
public List<T> Data { get; private set; }
public SuccessCommandResult() : base(true) { }
public SuccessCommandResult(List<T> data) : base(true)
{
Data = data;
}
public SuccessCommandResult(T @object) : base(true)
{
if (Data is null)
Data = new List<T> { @object };
else
Data.Add(@object);
}
}
How can I achieve that using the Swashbuckle
framework for C#? Thanks in advance.
The way I found a resolution for that was creating a Document Filter for the name of the schema:
public class SchemaNameFilter : IDocumentFilter
{
// w/o the filter: ServerIdCommandSuccessCommandResult. / with the filter: SuccessComandResult
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var schema in swaggerDoc.Components.Schemas)
{
foreach (var schemaRepository in context.SchemaRepository.Schemas)
{
if (schema.Key.Equals(schemaRepository.Key))
{
switch (schema.Key)
{
case string x when !x.Equals(nameof(DefaultCommandResult)) && x.Contains(nameof(DefaultCommandResult)):
schema.Value.Title = nameof(DefaultCommandResult);
break;
case string x when !x.Equals(nameof(SuccessCommandResult<string>)) && x.Contains(nameof(SuccessCommandResult<string>)):
schema.Value.Title = nameof(SuccessCommandResult<string>);
break;
case string a when !a.Equals(nameof(OutputCommandResult)) && a.Contains(nameof(OutputCommandResult)):
schema.Value.Title = nameof(OutputCommandResult);
break;
}
}
}
}
}
}
What it does, basically, is rename the Swagger schema (with the incorrect name) to the name of the class.
Then, in the Startup
class, add the document filter:
services.AddSwaggerGen(c =>
{
c.DocumentFilter<SchemaNameFilter>();
});