请参考以下Web API约定以及对应的控制器:
public static class MyAppConventions
{
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound,
MediaTypeNames.Application.ProblemJson)]
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
public static void Get()
{ }
}
[ApiConventionType(typeof(MyAppConvention))]
[Route("test")]
public class TestController : ControllerBase
{
[HttpGet]
public IActionResult GetData()
{
if (condition)
return NotFound();
else
return Ok(new TestDto());
}
}
问题
如果我添加额外的
ProducesResponseType
来标记相关约定中不存在的自定义响应代码,那么它会忽略约定属性。
[ApiConventionType(typeof(MyAppConvention))]
[Route("test")]
public class TestController : ControllerBase
{
[ProducesResponseType(typeof(TestDto), StatusCodes.Status200Ok, MediaTypeNames.Application.Json)]
[HttpGet]
public IActionResult GetData()
{
if (condition)
return NotFound();
else
return Ok(new TestDto());
}
}
有什么解决方法吗?这与添加到项目中的 OpenAPI 分析器 混淆。
参考文献
我们遇到了同样的问题,并决定编写自己的 EndpointBuilder 中间件来更改默认行为。
middlewar 将检查您的控制器操作是否包含
ProducesResponseTypeAttribute
和 ApiConventionResult
属性。如果是这种情况,它会将 ProducesResponseTypeAttribute
从约定复制到端点的元数据中。如果您确实想要覆盖 API 约定中定义的响应类型,您可以添加 IgnoreConventionResponseTypesAttribute
/// <summary>
/// Extension methods for <see cref="IEndpointConventionBuilder"/>.
/// </summary>
public static class IEndpointConventionBuilderExtensions
{
/// <summary>
/// By default reponse types defined by <see cref="ApiConventionTypeAttribute"/> are overwritten by <see cref="ProducesResponseTypeAttribute"/> on the action.
/// To always apply response types from the convention, this method can be used.
/// </summary>
/// <remarks>
/// To disable this behavior on a specific action, apply <see cref="IgnoreConventionResponseTypesAttribute"/> attribute on the action.
/// </remarks>
/// <param name="builder">The builder.</param>
public static void AddApiConventionResponseTypes(this IEndpointConventionBuilder builder)
{
builder.Add(builder =>
{
var action = builder.Metadata.OfType<ControllerActionDescriptor>().FirstOrDefault();
if (action is not null)
{
if (builder.Metadata.OfType<IgnoreConventionResponseTypesAttribute>().Any()) return;
var explicitResponseTypes = builder.Metadata.OfType<ProducesResponseTypeAttribute>().ToList();
if (explicitResponseTypes.Count > 0)
{
if (action?.Properties.TryGetValue(typeof(ApiConventionResult), out var result) == true && result is ApiConventionResult apiConventionResult)
{
foreach (var response in apiConventionResult.ResponseMetadataProviders)
{
action.EndpointMetadata.Add(response);
action.FilterDescriptors.Add(new FilterDescriptor(response, 30));
}
}
}
}
});
}
}
这还允许您在想要覆盖 API 约定中定义的响应类型的控制器操作上添加以下属性
/// <summary>
/// An attribute that specifies that the controller method that this attribute is applied to
/// should ignore additional response types defined by <see cref="ApiConventionTypeAttribute"/>.
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class IgnoreConventionResponseTypesAttribute : Attribute
{
}
您必须将中间件附加到启动时的
MapControllers()
调用
app.MapControllers().AddApiConventionResponseTypes();