Introduce ProducesErrorResponseTypeAttribute
Fixes https://github.com/aspnet/Mvc/issues/8288
This commit is contained in:
parent
927e7c8bfc
commit
f90a47c5af
|
|
@ -46,7 +46,13 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
responseMetadataAttributes = apiConventionResult.ResponseMetadataProviders;
|
||||
}
|
||||
|
||||
var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType);
|
||||
var defaultErrorType = typeof(void);
|
||||
if (action.Properties.TryGetValue(typeof(ProducesErrorResponseTypeAttribute), out result))
|
||||
{
|
||||
defaultErrorType = ((ProducesErrorResponseTypeAttribute)result).Type;
|
||||
}
|
||||
|
||||
var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType, defaultErrorType);
|
||||
return apiResponseTypes;
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +75,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
|
||||
private ICollection<ApiResponseType> GetApiResponseTypes(
|
||||
IReadOnlyList<IApiResponseMetadataProvider> responseMetadataAttributes,
|
||||
Type type)
|
||||
Type type,
|
||||
Type defaultErrorType)
|
||||
{
|
||||
var results = new Dictionary<int, ApiResponseType>();
|
||||
|
||||
|
|
@ -83,48 +90,39 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
{
|
||||
metadataAttribute.SetContentTypes(contentTypes);
|
||||
|
||||
ApiResponseType apiResponseType;
|
||||
var statusCode = metadataAttribute.StatusCode;
|
||||
|
||||
if (metadataAttribute is IApiDefaultResponseMetadataProvider)
|
||||
var apiResponseType = new ApiResponseType
|
||||
{
|
||||
apiResponseType = new ApiResponseType
|
||||
Type = metadataAttribute.Type,
|
||||
StatusCode = statusCode,
|
||||
IsDefaultResponse = metadataAttribute is IApiDefaultResponseMetadataProvider,
|
||||
};
|
||||
|
||||
if (apiResponseType.Type == typeof(void))
|
||||
{
|
||||
if (type != null && (statusCode == StatusCodes.Status200OK || statusCode == StatusCodes.Status201Created))
|
||||
{
|
||||
IsDefaultResponse = true,
|
||||
Type = metadataAttribute.Type,
|
||||
};
|
||||
}
|
||||
else if (metadataAttribute.Type == typeof(void) &&
|
||||
type != null &&
|
||||
(metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created))
|
||||
{
|
||||
// ProducesResponseTypeAttribute's constructor defaults to setting "Type" to void when no value is specified.
|
||||
// In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a
|
||||
// [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred
|
||||
// from the return type.
|
||||
apiResponseType = new ApiResponseType
|
||||
// ProducesResponseTypeAttribute's constructor defaults to setting "Type" to void when no value is specified.
|
||||
// In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a
|
||||
// [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred
|
||||
// from the return type.
|
||||
apiResponseType.Type = type;
|
||||
}
|
||||
else if (IsClientError(statusCode) || apiResponseType.IsDefaultResponse)
|
||||
{
|
||||
StatusCode = metadataAttribute.StatusCode,
|
||||
Type = type,
|
||||
};
|
||||
}
|
||||
else if (metadataAttribute.Type != null)
|
||||
{
|
||||
apiResponseType = new ApiResponseType
|
||||
{
|
||||
StatusCode = metadataAttribute.StatusCode,
|
||||
Type = metadataAttribute.Type,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
// Use the default error type for "default" responses or 4xx client errors if no response type is specified.
|
||||
apiResponseType.Type = defaultErrorType;
|
||||
}
|
||||
}
|
||||
|
||||
results[apiResponseType.StatusCode] = apiResponseType;
|
||||
if (apiResponseType.Type != null)
|
||||
{
|
||||
results[apiResponseType.StatusCode] = apiResponseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the default status only when no status has already been set explicitly
|
||||
if (results.Count == 0 && type != null)
|
||||
{
|
||||
|
|
@ -225,5 +223,10 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
|
||||
return declaredReturnType;
|
||||
}
|
||||
|
||||
private static bool IsClientError(int statusCode)
|
||||
{
|
||||
return statusCode >= 400 && statusCode < 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
public class ApiBehaviorApplicationModelProvider : IApplicationModelProvider
|
||||
{
|
||||
private readonly ProducesErrorResponseTypeAttribute DefaultErrorType = new ProducesErrorResponseTypeAttribute(typeof(ProblemDetails));
|
||||
private readonly ApiBehaviorOptions _apiBehaviorOptions;
|
||||
private readonly IModelMetadataProvider _modelMetadataProvider;
|
||||
private readonly ModelStateInvalidFilter _modelStateInvalidFilter;
|
||||
|
|
@ -105,6 +106,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
AddMultipartFormDataConsumesAttribute(actionModel);
|
||||
|
||||
DiscoverApiConvention(actionModel, conventions);
|
||||
|
||||
DiscoverErrorResponseType(actionModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -274,6 +277,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
internal void DiscoverErrorResponseType(ActionModel actionModel)
|
||||
{
|
||||
var errorTypeAttribute =
|
||||
actionModel.Attributes.OfType<ProducesErrorResponseTypeAttribute>().FirstOrDefault() ??
|
||||
actionModel.Controller.Attributes.OfType<ProducesErrorResponseTypeAttribute>().FirstOrDefault() ??
|
||||
actionModel.Controller.ControllerType.Assembly.GetCustomAttribute<ProducesErrorResponseTypeAttribute>();
|
||||
|
||||
if (!_apiBehaviorOptions.SuppressMapClientErrors)
|
||||
{
|
||||
// If ClientErrorFactory is being used and the application does not supply a error response type, assume ProblemDetails.
|
||||
errorTypeAttribute = errorTypeAttribute ?? DefaultErrorType;
|
||||
}
|
||||
|
||||
if (errorTypeAttribute != null)
|
||||
{
|
||||
actionModel.Properties[typeof(ProducesErrorResponseTypeAttribute)] = errorTypeAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ParameterExistsInAnyRoute(ActionModel actionModel, string parameterName)
|
||||
{
|
||||
foreach (var (route, _, _) in ActionAttributeRouteModel.GetAttributeRoutes(actionModel))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies the type returned by default by controllers annotated with <see cref="ApiControllerAttribute"/>.
|
||||
/// <para>
|
||||
/// <see cref="Type"/> specifies the error model type associated with a <see cref="ProducesResponseTypeAttribute"/>
|
||||
/// for a client error (HTTP Status Code 4xx) when no value is provided. When no value is specified, MVC assumes the
|
||||
/// client error type to be <see cref="ProblemDetails"/>, if mapping client errors (<see cref="ApiBehaviorOptions.ClientErrorMapping"/>)
|
||||
/// is used.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Use this <see cref="Attribute"/> to configure the default error type if your application uses a custom error type to respond.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class ProducesErrorResponseTypeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ProducesErrorResponseTypeAttribute"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The error type.</param>
|
||||
public ProducesErrorResponseTypeAttribute(Type type)
|
||||
{
|
||||
Type = type ?? throw new ArgumentNullException(nameof(type));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default error type.
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -309,6 +309,247 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_UsesErrorType_ForClientErrors()
|
||||
{
|
||||
// Arrange
|
||||
var errorType = typeof(InvalidTimeZoneException);
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||
{
|
||||
new ProducesResponseTypeAttribute(200),
|
||||
new ProducesResponseTypeAttribute(404),
|
||||
new ProducesResponseTypeAttribute(415),
|
||||
});
|
||||
|
||||
actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(errorType);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Equal(errorType, responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(415, responseType.StatusCode);
|
||||
Assert.Equal(errorType, responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_UsesErrorType_ForDefaultResponse()
|
||||
{
|
||||
// Arrange
|
||||
var errorType = typeof(ProblemDetails);
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||
{
|
||||
new ProducesResponseTypeAttribute(200),
|
||||
new ProducesDefaultResponseTypeAttribute(),
|
||||
});
|
||||
|
||||
actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(errorType);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(errorType, responseType.Type);
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_DoesNotUseErrorType_IfSpecified()
|
||||
{
|
||||
// Arrange
|
||||
var errorType = typeof(InvalidTimeZoneException);
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||
{
|
||||
new ProducesResponseTypeAttribute(200),
|
||||
new ProducesResponseTypeAttribute(typeof(DivideByZeroException), 415),
|
||||
new ProducesDefaultResponseTypeAttribute(typeof(DivideByZeroException)),
|
||||
});
|
||||
|
||||
actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(errorType);
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(DivideByZeroException), responseType.Type);
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(200, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(415, responseType.StatusCode);
|
||||
Assert.Equal(typeof(DivideByZeroException), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_DoesNotUseErrorType_ForNonClientErrors()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||
{
|
||||
new ProducesResponseTypeAttribute(201),
|
||||
new ProducesResponseTypeAttribute(300),
|
||||
new ProducesResponseTypeAttribute(500),
|
||||
});
|
||||
|
||||
actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(InvalidTimeZoneException));
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(201, responseType.StatusCode);
|
||||
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(300, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(500, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetApiResponseTypes_AllowsUsingVoid()
|
||||
{
|
||||
// Arrange
|
||||
var actionDescriptor = GetControllerActionDescriptor(
|
||||
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||
{
|
||||
new ProducesResponseTypeAttribute(typeof(InvalidCastException), 400),
|
||||
new ProducesResponseTypeAttribute(415),
|
||||
new ProducesDefaultResponseTypeAttribute(),
|
||||
});
|
||||
|
||||
actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(void));
|
||||
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
var result = provider.GetApiResponseTypes(actionDescriptor);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
result.OrderBy(r => r.StatusCode),
|
||||
responseType =>
|
||||
{
|
||||
Assert.True(responseType.IsDefaultResponse);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Equal(typeof(InvalidCastException), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Collection(
|
||||
responseType.ApiResponseFormats,
|
||||
format => Assert.Equal("application/json", format.MediaType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(415, responseType.StatusCode);
|
||||
Assert.Equal(typeof(void), responseType.Type);
|
||||
Assert.False(responseType.IsDefaultResponse);
|
||||
Assert.Empty(responseType.ApiResponseFormats);
|
||||
});
|
||||
}
|
||||
|
||||
private static ApiResponseTypeProvider GetProvider()
|
||||
{
|
||||
var mvcOptions = new MvcOptions
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ using System.Threading.Tasks;
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
|
@ -20,6 +19,8 @@ using Microsoft.Extensions.Options;
|
|||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
[assembly: Microsoft.AspNetCore.Mvc.ProducesErrorResponseType(typeof(InvalidEnumArgumentException))]
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Internal
|
||||
{
|
||||
public class ApiBehaviorApplicationModelProviderTest
|
||||
|
|
@ -1042,9 +1043,6 @@ Environment.NewLine + "int b";
|
|||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
Array.Empty<object>());
|
||||
actionModel.Filters.Add(new AuthorizeFilter());
|
||||
actionModel.Filters.Add(new ServiceFilterAttribute(typeof(object)));
|
||||
actionModel.Filters.Add(new ConsumesAttribute("application/xml"));
|
||||
var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) };
|
||||
|
||||
// Act
|
||||
|
|
@ -1060,6 +1058,167 @@ Environment.NewLine + "int b";
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoverErrorResponseType_SetsProblemDetails_IfActionHasNoAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(ProblemDetails);
|
||||
var controllerModel = new ControllerModel(typeof(object).GetTypeInfo(), new[] { new object() });
|
||||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
Array.Empty<object>())
|
||||
{
|
||||
Controller = controllerModel,
|
||||
};
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
provider.DiscoverErrorResponseType(actionModel);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
actionModel.Properties,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key);
|
||||
var value = Assert.IsType<ProducesErrorResponseTypeAttribute>(kvp.Value);
|
||||
Assert.Equal(expected, value.Type);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoverErrorResponseType_DoesNotSetDefaultProblemDetailsResponse_IfSuppressMapClientErrorsIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(ProblemDetails);
|
||||
var controllerModel = new ControllerModel(typeof(object).GetTypeInfo(), new[] { new object() });
|
||||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
Array.Empty<object>())
|
||||
{
|
||||
Controller = controllerModel,
|
||||
};
|
||||
var provider = GetProvider(new ApiBehaviorOptions
|
||||
{
|
||||
InvalidModelStateResponseFactory = _ => null,
|
||||
SuppressMapClientErrors = true,
|
||||
});
|
||||
|
||||
// Act
|
||||
provider.DiscoverErrorResponseType(actionModel);
|
||||
|
||||
// Assert
|
||||
Assert.Empty(actionModel.Properties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAsssembly()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(InvalidEnumArgumentException);
|
||||
var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new object() });
|
||||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
Array.Empty<object>())
|
||||
{
|
||||
Controller = controllerModel,
|
||||
};
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
provider.DiscoverErrorResponseType(actionModel);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
actionModel.Properties,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key);
|
||||
var value = Assert.IsType<ProducesErrorResponseTypeAttribute>(kvp.Value);
|
||||
Assert.Equal(expected, value.Type);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnController()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(InvalidTimeZoneException);
|
||||
var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new ProducesErrorResponseTypeAttribute(expected) });
|
||||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
Array.Empty<object>())
|
||||
{
|
||||
Controller = controllerModel,
|
||||
};
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
provider.DiscoverErrorResponseType(actionModel);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
actionModel.Properties,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key);
|
||||
var value = Assert.IsType<ProducesErrorResponseTypeAttribute>(kvp.Value);
|
||||
Assert.Equal(expected, value.Type);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnAction()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(InvalidTimeZoneException);
|
||||
var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new ProducesErrorResponseTypeAttribute(typeof(Guid)) });
|
||||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
new[] { new ProducesErrorResponseTypeAttribute(expected) })
|
||||
{
|
||||
Controller = controllerModel,
|
||||
};
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
provider.DiscoverErrorResponseType(actionModel);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
actionModel.Properties,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key);
|
||||
var value = Assert.IsType<ProducesErrorResponseTypeAttribute>(kvp.Value);
|
||||
Assert.Equal(expected, value.Type);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiscoverErrorResponseType_AllowsVoidsType()
|
||||
{
|
||||
// Arrange
|
||||
var expected = typeof(void);
|
||||
var actionModel = new ActionModel(
|
||||
typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)),
|
||||
new[] { new ProducesErrorResponseTypeAttribute(expected) });
|
||||
var provider = GetProvider();
|
||||
|
||||
// Act
|
||||
provider.DiscoverErrorResponseType(actionModel);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
actionModel.Properties,
|
||||
kvp =>
|
||||
{
|
||||
Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key);
|
||||
var value = Assert.IsType<ProducesErrorResponseTypeAttribute>(kvp.Value);
|
||||
Assert.Equal(expected, value.Type);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsClientErrorResultFilter()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1180,9 +1180,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -1215,7 +1215,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
public async Task ApiConvention_ForMethodWithResponseTypeAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
|
||||
var expectedMediaTypes = new[] { "application/json" };
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
|
|
@ -1236,15 +1236,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(403, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiConvention_ForPostMethodThatMatchesConvention()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
$"ApiExplorerResponseTypeWithApiConventionController/PostTaskOfProduct",
|
||||
|
|
@ -1268,15 +1271,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiConvention_ForPutActionThatMatchesConvention()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.PutAsync(
|
||||
$"ApiExplorerResponseTypeWithApiConventionController/Put",
|
||||
|
|
@ -1300,21 +1306,24 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiConvention_ForDeleteActionThatMatchesConvention()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.DeleteAsync(
|
||||
$"ApiExplorerResponseTypeWithApiConventionController/DeleteProductAsync");
|
||||
|
|
@ -1337,21 +1346,24 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(400, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(404, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApiConvention_ForActionWtihApiConventionMethod()
|
||||
{
|
||||
// Arrange
|
||||
var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
|
||||
|
||||
// Act
|
||||
var response = await Client.PostAsync(
|
||||
"ApiExplorerResponseTypeWithApiConventionController/PostItem",
|
||||
|
|
@ -1371,9 +1383,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
},
|
||||
responseType =>
|
||||
{
|
||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||
Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType);
|
||||
Assert.Equal(409, responseType.StatusCode);
|
||||
Assert.Empty(responseType.ResponseFormats);
|
||||
Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue