我正在使用 swagger ui,我的控制器中有这样的操作:
[HttpGet("{id}")]
[AllowAnonymous]
[ProducesResponseType(typeof(PublicDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(PrivateDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(string), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(string), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetUser(string id)
{
try
{
...
if (...)
{
var dto = _mapper.Map<PrivateDto>(user);
return Ok(dto);
}
else
{
var dto = _mapper.Map<PublicDto>(user);
return Ok(dto);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Error inside action");
return StatusCode(500, "Internal server error");
}
}
问题是,在我的 swagger UI 页面上,我只能在“响应”区域中看到其中之一,并且只获得其中一个的方案。有没有一种方法可以让多个对象响应一个状态码?
这不是一个错误,而是 SB 所基于的开放 API 规范的限制。具体来说,每个响应代码只能有一个响应对象。请参阅https://spec.openapis.org/oas/v3.1.0#responses-object
我使用 https://github.com/mcintyre321/OneOf 来实现此目的。
ClientFilePatchResult.cs(响应模型)
[GenerateOneOf]
public partial class ClientFilePatchResult
: OneOfBase<
ClientUpdateResult,
DivisionUpdateResult,
LinkedPartyUpdateResult,
ContactUpdateResult,
None>
{
}
ClientFileController.cs
[HttpPatch]
[Route("{id}")]
[ProducesResponseType(typeof(ClientFilePatchResult), StatusCodes.Status200OK)]
public async Task<IActionResult> UpdateField([FromRoute] int id, [FromBody] ClientFilePatchVm model)
{
...
var result = await _jobService.PatchAsync(id, sm);
return result.Value is None ? Ok() : Ok(result.Value);
}
OneOfOperationFilter.cs(请参阅 https://github.com/domaindrivendev/Swashbuckle.AspNetCore#operation-filters)
public class OneOfOperationFilter : IOperationFilter
{
private static IReadOnlyList<Type> GetGenericParameterTypes(Type type)
{
if (type.IsGenericType && type.IsAssignableTo(typeof(IOneOf)))
{
return type.GetGenericArguments().ToList();
}
else
{
throw new ArgumentException($"Type {type.FullName} is not assignable to {typeof(IOneOf)}");
}
}
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var oneOfResponseAttributes = context.MethodInfo
.GetCustomAttributes()
.OfType<ProducesResponseTypeAttribute>()
.ToList()
.AsReadOnly();
if (!oneOfResponseAttributes.Any())
return;
// process all ProducesResponseType attributes
foreach (var oneOfResponseAttribute in oneOfResponseAttributes)
{
var statusCode = oneOfResponseAttribute.StatusCode;
var oneOfResponseType = oneOfResponseAttribute.Type;
// skip if current type is not IOneOf
if (!oneOfResponseType.IsAssignableTo(typeof(IOneOf)))
continue;
// find parent class of type OneOf<T1, ...> or OneOfBase<T1, ...>
while (oneOfResponseType is { IsGenericType: false })
{
oneOfResponseType = oneOfResponseType.BaseType;
}
// skip if unable to find appropriate parent class
if (oneOfResponseType == null)
continue;
var allOneOfTypes = GetGenericParameterTypes(oneOfResponseType);
var oneOfSchemas = new List<OpenApiSchema>();
foreach (var oneOfType in allOneOfTypes)
{
var oneOfTypeSchema = context.SchemaGenerator.GenerateSchema(oneOfType, context.SchemaRepository);
if (oneOfTypeSchema != null)
{
oneOfSchemas.Add(oneOfTypeSchema);
}
}
if (oneOfSchemas.Any())
{
var operationResponse = operation.Responses[statusCode.ToStringInvariant()];
operationResponse.Content.Clear();
operationResponse.Content["application/json"] = new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Type = "object",
OneOf = oneOfSchemas,
},
};
}
}
}
}
因此,我可以在我的 swagger UI 中看到适当的模式:
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "object",
"oneOf": [
{
"$ref": "#/components/schemas/ClientUpdateResult"
},
{
"$ref": "#/components/schemas/DivisionUpdateResult"
},
{
"$ref": "#/components/schemas/LinkedPartyUpdateResult"
},
{
"$ref": "#/components/schemas/ContactUpdateResult"
},
{
"$ref": "#/components/schemas/None"
}
]
}
}
}
}