Add support for default response (#8028)
* Add support for default response Fixes #6828
This commit is contained in:
parent
335500ab0e
commit
d2bb674b0a
|
|
@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
_mvcOptions = mvcOptions;
|
_mvcOptions = mvcOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IList<ApiResponseType> GetApiResponseTypes(ControllerActionDescriptor action)
|
public ICollection<ApiResponseType> GetApiResponseTypes(ControllerActionDescriptor action)
|
||||||
{
|
{
|
||||||
// We only provide response info if we can figure out a type that is a user-data type.
|
// We only provide response info if we can figure out a type that is a user-data type.
|
||||||
// Void /Task object/IActionResult will result in no data.
|
// Void /Task object/IActionResult will result in no data.
|
||||||
|
|
@ -67,14 +67,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IList<ApiResponseType> GetApiResponseTypes(
|
private ICollection<ApiResponseType> GetApiResponseTypes(
|
||||||
IReadOnlyList<IApiResponseMetadataProvider> responseMetadataAttributes,
|
IReadOnlyList<IApiResponseMetadataProvider> responseMetadataAttributes,
|
||||||
Type type)
|
Type type)
|
||||||
{
|
{
|
||||||
var results = new List<ApiResponseType>();
|
var results = new Dictionary<int, ApiResponseType>();
|
||||||
|
|
||||||
// Build list of all possible return types (and status codes) for an action.
|
|
||||||
var objectTypes = new Dictionary<int, Type>();
|
|
||||||
|
|
||||||
// Get the content type that the action explicitly set to support.
|
// Get the content type that the action explicitly set to support.
|
||||||
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
// Walk through all 'filter' attributes in order, and allow each one to see or override
|
||||||
|
|
@ -86,7 +83,17 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
{
|
{
|
||||||
metadataAttribute.SetContentTypes(contentTypes);
|
metadataAttribute.SetContentTypes(contentTypes);
|
||||||
|
|
||||||
if (metadataAttribute.Type == typeof(void) &&
|
ApiResponseType apiResponseType;
|
||||||
|
|
||||||
|
if (metadataAttribute is IApiDefaultResponseMetadataProvider)
|
||||||
|
{
|
||||||
|
apiResponseType = new ApiResponseType
|
||||||
|
{
|
||||||
|
IsDefaultResponse = true,
|
||||||
|
Type = metadataAttribute.Type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (metadataAttribute.Type == typeof(void) &&
|
||||||
type != null &&
|
type != null &&
|
||||||
(metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created))
|
(metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created))
|
||||||
{
|
{
|
||||||
|
|
@ -94,20 +101,38 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
// In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a
|
// 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
|
// [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred
|
||||||
// from the return type.
|
// from the return type.
|
||||||
objectTypes[metadataAttribute.StatusCode] = type;
|
apiResponseType = new ApiResponseType
|
||||||
|
{
|
||||||
|
StatusCode = metadataAttribute.StatusCode,
|
||||||
|
Type = type,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
else if (metadataAttribute.Type != null)
|
else if (metadataAttribute.Type != null)
|
||||||
{
|
{
|
||||||
objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
|
apiResponseType = new ApiResponseType
|
||||||
|
{
|
||||||
|
StatusCode = metadataAttribute.StatusCode,
|
||||||
|
Type = metadataAttribute.Type,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
results[apiResponseType.StatusCode] = apiResponseType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set the default status only when no status has already been set explicitly
|
// Set the default status only when no status has already been set explicitly
|
||||||
if (objectTypes.Count == 0 && type != null)
|
if (results.Count == 0 && type != null)
|
||||||
{
|
{
|
||||||
objectTypes[StatusCodes.Status200OK] = type;
|
results[StatusCodes.Status200OK] = new ApiResponseType
|
||||||
|
{
|
||||||
|
StatusCode = StatusCodes.Status200OK,
|
||||||
|
Type = type,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentTypes.Count == 0)
|
if (contentTypes.Count == 0)
|
||||||
|
|
@ -117,25 +142,15 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
|
|
||||||
var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>();
|
var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType<IApiResponseTypeMetadataProvider>();
|
||||||
|
|
||||||
foreach (var objectType in objectTypes)
|
foreach (var apiResponse in results.Values)
|
||||||
{
|
{
|
||||||
if (objectType.Value == null || objectType.Value == typeof(void))
|
var responseType = apiResponse.Type;
|
||||||
|
if (responseType == null || responseType == typeof(void))
|
||||||
{
|
{
|
||||||
results.Add(new ApiResponseType()
|
|
||||||
{
|
|
||||||
StatusCode = objectType.Key,
|
|
||||||
Type = objectType.Value
|
|
||||||
});
|
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiResponseType = new ApiResponseType()
|
apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType);
|
||||||
{
|
|
||||||
Type = objectType.Value,
|
|
||||||
StatusCode = objectType.Key,
|
|
||||||
ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value)
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var contentType in contentTypes)
|
foreach (var contentType in contentTypes)
|
||||||
{
|
{
|
||||||
|
|
@ -143,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
{
|
{
|
||||||
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(
|
var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(
|
||||||
contentType,
|
contentType,
|
||||||
objectType.Value);
|
responseType);
|
||||||
|
|
||||||
if (formatterSupportedContentTypes == null)
|
if (formatterSupportedContentTypes == null)
|
||||||
{
|
{
|
||||||
|
|
@ -152,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
|
|
||||||
foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
|
foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
|
||||||
{
|
{
|
||||||
apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat()
|
apiResponse.ApiResponseFormats.Add(new ApiResponseFormat
|
||||||
{
|
{
|
||||||
Formatter = (IOutputFormatter)responseTypeMetadataProvider,
|
Formatter = (IOutputFormatter)responseTypeMetadataProvider,
|
||||||
MediaType = formatterSupportedContentType,
|
MediaType = formatterSupportedContentType,
|
||||||
|
|
@ -160,11 +175,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
results.Add(apiResponseType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results.Values;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Type GetDeclaredReturnType(ControllerActionDescriptor action)
|
private Type GetDeclaredReturnType(ControllerActionDescriptor action)
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
var errorMessage = Resources.FormatApiConvention_UnsupportedAttributesOnConvention(
|
var errorMessage = Resources.FormatApiConvention_UnsupportedAttributesOnConvention(
|
||||||
methodDisplayName,
|
methodDisplayName,
|
||||||
Environment.NewLine + string.Join(Environment.NewLine, unsupportedAttributes) + Environment.NewLine,
|
Environment.NewLine + string.Join(Environment.NewLine, unsupportedAttributes) + Environment.NewLine,
|
||||||
$"{nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}");
|
$"{nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}");
|
||||||
|
|
||||||
throw new ArgumentException(errorMessage, nameof(conventionType));
|
throw new ArgumentException(errorMessage, nameof(conventionType));
|
||||||
}
|
}
|
||||||
|
|
@ -83,6 +83,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
private static bool IsAllowedAttribute(object attribute)
|
private static bool IsAllowedAttribute(object attribute)
|
||||||
{
|
{
|
||||||
return attribute is ProducesResponseTypeAttribute ||
|
return attribute is ProducesResponseTypeAttribute ||
|
||||||
|
attribute is ProducesDefaultResponseTypeAttribute ||
|
||||||
attribute is ApiConventionNameMatchAttribute;
|
attribute is ApiConventionNameMatchAttribute;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides a return type for all HTTP status codes that are not covered by other <see cref="IApiResponseMetadataProvider"/> instances.
|
||||||
|
/// </summary>
|
||||||
|
public interface IApiDefaultResponseMetadataProvider : IApiResponseMetadataProvider
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
{
|
{
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
|
[ProducesDefaultResponseType]
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||||
public static void Get(
|
public static void Get(
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||||
|
|
@ -18,6 +19,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
|
|
||||||
[ProducesResponseType(StatusCodes.Status201Created)]
|
[ProducesResponseType(StatusCodes.Status201Created)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesDefaultResponseType]
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||||
public static void Post(
|
public static void Post(
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)]
|
||||||
|
|
@ -27,6 +29,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesDefaultResponseType]
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||||
public static void Put(
|
public static void Put(
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||||
|
|
@ -40,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
|
[ProducesDefaultResponseType]
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)]
|
||||||
public static void Delete(
|
public static void Delete(
|
||||||
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
[ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
// 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.ApiExplorer;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A filter that specifies the <see cref="System.Type"/> for all HTTP status codes that are not covered by <see cref="ProducesResponseTypeAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||||
|
public sealed class ProducesDefaultResponseTypeAttribute : Attribute, IApiDefaultResponseMetadataProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="ProducesResponseTypeAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
public ProducesDefaultResponseTypeAttribute()
|
||||||
|
: this(typeof(void))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes an instance of <see cref="ProducesResponseTypeAttribute"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The <see cref="Type"/> of object that is going to be written in the response.</param>
|
||||||
|
public ProducesDefaultResponseTypeAttribute(Type type)
|
||||||
|
{
|
||||||
|
Type = type ?? throw new ArgumentNullException(nameof(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the type of the value returned by an action.
|
||||||
|
/// </summary>
|
||||||
|
public Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the HTTP status code of the response.
|
||||||
|
/// </summary>
|
||||||
|
public int StatusCode { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
|
||||||
|
{
|
||||||
|
// Users are supposed to use the 'Produces' attribute to set the content types that an action can support.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -72,6 +72,60 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
public Task<ActionResult<BaseModel>> Get(int id) => null;
|
public Task<ActionResult<BaseModel>> Get(int id) => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiResponseTypes_CombinesFilters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var filterDescriptors = new[]
|
||||||
|
{
|
||||||
|
new FilterDescriptor(new ProducesResponseTypeAttribute(400), FilterScope.Global),
|
||||||
|
new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(object), 201), FilterScope.Controller),
|
||||||
|
new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(ProblemDetails), 400), FilterScope.Controller),
|
||||||
|
new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(BaseModel), 201), FilterScope.Action),
|
||||||
|
new FilterDescriptor(new ProducesResponseTypeAttribute(404), FilterScope.Action),
|
||||||
|
};
|
||||||
|
|
||||||
|
var actionDescriptor = new ControllerActionDescriptor
|
||||||
|
{
|
||||||
|
FilterDescriptors = filterDescriptors,
|
||||||
|
MethodInfo = typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController).GetMethod(nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get)),
|
||||||
|
};
|
||||||
|
|
||||||
|
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.False(responseType.IsDefaultResponse);
|
||||||
|
Assert.Collection(
|
||||||
|
responseType.ApiResponseFormats,
|
||||||
|
format => Assert.Equal("application/json", format.MediaType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(400, responseType.StatusCode);
|
||||||
|
Assert.Equal(typeof(ProblemDetails), responseType.Type);
|
||||||
|
Assert.False(responseType.IsDefaultResponse);
|
||||||
|
Assert.Collection(
|
||||||
|
responseType.ApiResponseFormats,
|
||||||
|
format => Assert.Equal("application/json", format.MediaType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(404, responseType.StatusCode);
|
||||||
|
Assert.Equal(typeof(void), responseType.Type);
|
||||||
|
Assert.False(responseType.IsDefaultResponse);
|
||||||
|
Assert.Empty(responseType.ApiResponseFormats);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GetApiResponseTypes_ReturnsResponseTypesFromApiConventionItem()
|
public void GetApiResponseTypes_ReturnsResponseTypesFromApiConventionItem()
|
||||||
{
|
{
|
||||||
|
|
@ -159,6 +213,54 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
public Task<ActionResult<BaseModel>> PostModel(int id, BaseModel model) => null;
|
public Task<ActionResult<BaseModel>> PostModel(int id, BaseModel model) => null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetApiResponseTypes_ReturnsDefaultProblemResponse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var actionDescriptor = GetControllerActionDescriptor(
|
||||||
|
typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController),
|
||||||
|
nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase));
|
||||||
|
actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[]
|
||||||
|
{
|
||||||
|
new ProducesResponseTypeAttribute(201),
|
||||||
|
new ProducesResponseTypeAttribute(404),
|
||||||
|
new ProducesDefaultResponseTypeAttribute(typeof(SerializableError)),
|
||||||
|
});
|
||||||
|
|
||||||
|
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(SerializableError), responseType.Type);
|
||||||
|
Assert.Collection(
|
||||||
|
responseType.ApiResponseFormats,
|
||||||
|
format => Assert.Equal("application/json", format.MediaType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(201, responseType.StatusCode);
|
||||||
|
Assert.Equal(typeof(BaseModel), responseType.Type);
|
||||||
|
Assert.False(responseType.IsDefaultResponse);
|
||||||
|
Assert.Collection(
|
||||||
|
responseType.ApiResponseFormats,
|
||||||
|
format => Assert.Equal("application/json", format.MediaType));
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.Equal(404, responseType.StatusCode);
|
||||||
|
Assert.Equal(typeof(void), responseType.Type);
|
||||||
|
Assert.False(responseType.IsDefaultResponse);
|
||||||
|
Assert.Empty(responseType.ApiResponseFormats);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private static ApiResponseTypeProvider GetProvider()
|
private static ApiResponseTypeProvider GetProvider()
|
||||||
{
|
{
|
||||||
var mvcOptions = new MvcOptions
|
var mvcOptions = new MvcOptions
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
|
@ -15,11 +16,10 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute()
|
public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = $"Method {typeof(ConventionWithProducesAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" +
|
var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get);
|
||||||
Environment.NewLine +
|
var attribute = typeof(ProducesAttribute);
|
||||||
typeof(ProducesAttribute).FullName +
|
|
||||||
Environment.NewLine +
|
var expected = GetErrorMessage(methodName, attribute);
|
||||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
ExceptionAssert.ThrowsArgument(
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
|
@ -38,11 +38,9 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public void Constructor_ThrowsIfConventionMethodHasRouteAttribute()
|
public void Constructor_ThrowsIfConventionMethodHasRouteAttribute()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = $"Method {typeof(ConventionWithRouteAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" +
|
var methodName = typeof(ConventionWithRouteAttribute).FullName + '.' + nameof(ConventionWithRouteAttribute.Get);
|
||||||
Environment.NewLine +
|
var attribute = typeof(HttpGetAttribute);
|
||||||
typeof(HttpGetAttribute).FullName +
|
var expected = GetErrorMessage(methodName, attribute);
|
||||||
Environment.NewLine +
|
|
||||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
ExceptionAssert.ThrowsArgument(
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
|
@ -61,11 +59,9 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
public void Constructor_ThrowsIfMultipleUnsupportedAttributesArePresentOnConvention()
|
public void Constructor_ThrowsIfMultipleUnsupportedAttributesArePresentOnConvention()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = $"Method {typeof(ConventionWitUnsupportedAttributes).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" +
|
var methodName = typeof(ConventionWitUnsupportedAttributes).FullName + '.' + nameof(ConventionWitUnsupportedAttributes.Get);
|
||||||
Environment.NewLine +
|
var attributes = new[] { typeof(ProducesAttribute), typeof(ServiceFilterAttribute), typeof(AuthorizeAttribute) };
|
||||||
string.Join(Environment.NewLine, typeof(ProducesAttribute).FullName, typeof(ServiceFilterAttribute).FullName, typeof(AuthorizeAttribute).FullName) +
|
var expected = GetErrorMessage(methodName, attributes);
|
||||||
Environment.NewLine +
|
|
||||||
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
|
||||||
|
|
||||||
// Act & Assert
|
// Act & Assert
|
||||||
ExceptionAssert.ThrowsArgument(
|
ExceptionAssert.ThrowsArgument(
|
||||||
|
|
@ -82,5 +78,14 @@ namespace Microsoft.AspNetCore.Mvc
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public static void Get() { }
|
public static void Get() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetErrorMessage(string methodName, params Type[] attributes)
|
||||||
|
{
|
||||||
|
return $"Method {methodName} is decorated with the following attributes that are not allowed on an API convention method:" +
|
||||||
|
Environment.NewLine +
|
||||||
|
string.Join(Environment.NewLine, attributes.Select(a => a.FullName)) +
|
||||||
|
Environment.NewLine +
|
||||||
|
$"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||||
|
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||||
r => Assert.Equal(200, r.StatusCode),
|
r => Assert.Equal(200, r.StatusCode),
|
||||||
r => Assert.Equal(404, r.StatusCode));
|
r => Assert.Equal(404, r.StatusCode));
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||||
|
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||||
r => Assert.Equal(201, r.StatusCode),
|
r => Assert.Equal(201, r.StatusCode),
|
||||||
r => Assert.Equal(400, r.StatusCode));
|
r => Assert.Equal(400, r.StatusCode));
|
||||||
}
|
}
|
||||||
|
|
@ -152,6 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||||
|
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||||
r => Assert.Equal(204, r.StatusCode),
|
r => Assert.Equal(204, r.StatusCode),
|
||||||
r => Assert.Equal(400, r.StatusCode),
|
r => Assert.Equal(400, r.StatusCode),
|
||||||
r => Assert.Equal(404, r.StatusCode));
|
r => Assert.Equal(404, r.StatusCode));
|
||||||
|
|
@ -175,6 +178,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode),
|
||||||
|
r => Assert.IsAssignableFrom<IApiDefaultResponseMetadataProvider>(r),
|
||||||
r => Assert.Equal(200, r.StatusCode),
|
r => Assert.Equal(200, r.StatusCode),
|
||||||
r => Assert.Equal(400, r.StatusCode),
|
r => Assert.Equal(400, r.StatusCode),
|
||||||
r => Assert.Equal(404, r.StatusCode));
|
r => Assert.Equal(404, r.StatusCode));
|
||||||
|
|
|
||||||
|
|
@ -711,7 +711,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var description = Assert.Single(result);
|
var description = Assert.Single(result);
|
||||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
|
||||||
|
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||||
|
|
@ -749,7 +748,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
var description = Assert.Single(result);
|
var description = Assert.Single(result);
|
||||||
Assert.Equal(2, description.SupportedResponseTypes.Count);
|
|
||||||
|
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
|
||||||
|
|
@ -1171,6 +1169,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||||
responseType =>
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.True(responseType.IsDefaultResponse);
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(typeof(Product).FullName, responseType.ResponseType);
|
Assert.Equal(typeof(Product).FullName, responseType.ResponseType);
|
||||||
Assert.Equal(200, responseType.StatusCode);
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
|
|
@ -1255,6 +1257,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||||
responseType =>
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.True(responseType.IsDefaultResponse);
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||||
Assert.Equal(201, responseType.StatusCode);
|
Assert.Equal(201, responseType.StatusCode);
|
||||||
|
|
@ -1283,6 +1289,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||||
responseType =>
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.True(responseType.IsDefaultResponse);
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||||
Assert.Equal(204, responseType.StatusCode);
|
Assert.Equal(204, responseType.StatusCode);
|
||||||
|
|
@ -1316,6 +1326,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
Assert.Collection(
|
Assert.Collection(
|
||||||
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
description.SupportedResponseTypes.OrderBy(r => r.StatusCode),
|
||||||
responseType =>
|
responseType =>
|
||||||
|
{
|
||||||
|
Assert.True(responseType.IsDefaultResponse);
|
||||||
|
},
|
||||||
|
responseType =>
|
||||||
{
|
{
|
||||||
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
Assert.Equal(typeof(void).FullName, responseType.ResponseType);
|
||||||
Assert.Equal(200, responseType.StatusCode);
|
Assert.Equal(200, responseType.StatusCode);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue