Introduce ProducesErrorResponseTypeAttribute

Fixes https://github.com/aspnet/Mvc/issues/8288
This commit is contained in:
Pranav K 2018-08-22 13:17:42 -07:00
parent 927e7c8bfc
commit f90a47c5af
No known key found for this signature in database
GPG Key ID: 1963DA6D96C3057A
6 changed files with 531 additions and 56 deletions

View File

@ -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;
}
}
}

View File

@ -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))

View File

@ -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; }
}
}

View File

@ -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

View File

@ -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()
{

View File

@ -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));
});
}