diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescription.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescription.cs
index dddafbd826..d1b4e0c9de 100644
--- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescription.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescription.cs
@@ -43,23 +43,6 @@ public class ApiDescription
///
public string RelativePath { get; set; }
- ///
- /// Gets or sets for the or null.
- ///
- ///
- /// Will be null if is null.
- ///
- public ModelMetadata ResponseModelMetadata { get; set; }
-
- ///
- /// Gets or sets the CLR data type of the response or null.
- ///
- ///
- /// Will be null if the action returns no response, or if the response type is unclear. Use
- /// ProducesAttribute on an action method to specify a response type.
- ///
- public Type ResponseType { get; set; }
-
///
/// Gets the list of possible formats for a response.
///
@@ -76,6 +59,6 @@ public class ApiDescription
/// Will be empty if the action returns no response, or if the response type is unclear. Use
/// ProducesAttribute on an action method to specify a response type.
///
- public IList SupportedResponseFormats { get; } = new List();
+ public IList SupportedResponseTypes { get; } = new List();
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseFormat.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseFormat.cs
index a02a5595fd..bf04db9da7 100644
--- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseFormat.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseFormat.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// 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 Microsoft.AspNetCore.Mvc.Formatters;
@@ -6,18 +6,18 @@
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{
///
- /// Represents a possible format for the body of a response.
+ /// Possible format for an .
///
public class ApiResponseFormat
{
///
- /// The formatter used to output this response.
+ /// Gets or sets the formatter used to output this response.
///
public IOutputFormatter Formatter { get; set; }
///
- /// The media type of the response.
+ /// Gets or sets the media type of the response.
///
public string MediaType { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseType.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseType.cs
new file mode 100644
index 0000000000..37d34b9af6
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseType.cs
@@ -0,0 +1,43 @@
+// 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 System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+
+namespace Microsoft.AspNetCore.Mvc.ApiExplorer
+{
+ ///
+ /// Possible type of the response body which is formatted by .
+ ///
+ public class ApiResponseType
+ {
+ ///
+ /// Gets or sets the response formats supported by this type.
+ ///
+ public IList ApiResponseFormats { get; set; } = new List();
+
+ ///
+ /// Gets or sets for the or null.
+ ///
+ ///
+ /// Will be null if is null or void.
+ ///
+ public ModelMetadata ModelMetadata { get; set; }
+
+ ///
+ /// Gets or sets the CLR data type of the response or null.
+ ///
+ ///
+ /// Will be null if the action returns no response, or if the response type is unclear. Use
+ /// or on an action method
+ /// to specify a response type.
+ ///
+ public Type Type { get; set; }
+
+ ///
+ /// Gets or sets the HTTP response status code.
+ ///
+ public int StatusCode { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs
index 1186588832..d22bdb81a6 100644
--- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs
@@ -8,6 +8,7 @@
using System.Reflection;
#endif
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Formatters;
@@ -112,37 +113,20 @@ private ApiDescription CreateApiDescription(
// Void /Task object/IActionResult will result in no data.
var declaredReturnType = GetDeclaredReturnType(action);
- // Now 'simulate' an action execution. This attempts to figure out to the best of our knowledge
- // what the logical data type is using filters.
- var runtimeReturnType = GetRuntimeReturnType(declaredReturnType, responseMetadataAttributes);
+ var runtimeReturnType = GetRuntimeReturnType(declaredReturnType);
- // We might not be able to figure out a good runtime return type. If that's the case we don't
- // provide any information about outputs. The workaround is to attribute the action.
- if (runtimeReturnType == typeof(void))
+ var apiResponseTypes = GetApiResponseTypes(action, responseMetadataAttributes, runtimeReturnType);
+ foreach (var apiResponseType in apiResponseTypes)
{
- // As a special case, if the return type is void - we want to surface that information
- // specifically, but nothing else. This can be overridden with a filter/attribute.
- apiDescription.ResponseType = runtimeReturnType;
- }
- else if (runtimeReturnType != null)
- {
- apiDescription.ResponseType = runtimeReturnType;
-
- apiDescription.ResponseModelMetadata = _modelMetadataProvider.GetMetadataForType(runtimeReturnType);
-
- var formats = GetResponseFormats(action, responseMetadataAttributes, runtimeReturnType);
- foreach (var format in formats)
- {
- apiDescription.SupportedResponseFormats.Add(format);
- }
+ apiDescription.SupportedResponseTypes.Add(apiResponseType);
}
// It would be possible here to configure an action with multiple body parameters, in which case you
// could end up with duplicate data.
foreach (var parameter in apiDescription.ParameterDescriptions.Where(p => p.Source == BindingSource.Body))
{
- var formats = GetRequestFormats(action, requestMetadataAttributes, parameter.Type);
- foreach (var format in formats)
+ var requestFormats = GetRequestFormats(action, requestMetadataAttributes, parameter.Type);
+ foreach (var format in requestFormats)
{
apiDescription.SupportedRequestFormats.Add(format);
}
@@ -364,13 +348,24 @@ private IReadOnlyList GetRequestFormats(
return results;
}
- private IReadOnlyList GetResponseFormats(
+ private IReadOnlyList GetApiResponseTypes(
ControllerActionDescriptor action,
IApiResponseMetadataProvider[] responseMetadataAttributes,
Type type)
{
- var results = new List();
+ var results = new List();
+ // Build list of all possible return types (and status codes) for an action.
+ var objectTypes = new Dictionary();
+
+ if (type != null && type != typeof(void))
+ {
+ // This return type can be overriden by any response metadata
+ // attributes later if the user wishes to.
+ objectTypes[StatusCodes.Status200OK] = type;
+ }
+
+ // 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
// the results of the previous ones. This is similar to the execution path for content-negotiation.
var contentTypes = new MediaTypeCollection();
@@ -379,6 +374,11 @@ private IReadOnlyList GetResponseFormats(
foreach (var metadataAttribute in responseMetadataAttributes)
{
metadataAttribute.SetContentTypes(contentTypes);
+
+ if (metadataAttribute.Type != null)
+ {
+ objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type;
+ }
}
}
@@ -387,28 +387,53 @@ private IReadOnlyList GetResponseFormats(
contentTypes.Add((string)null);
}
- foreach (var contentType in contentTypes)
+ var responseTypeMetadataProviders = _outputFormatters.OfType();
+
+ foreach (var objectType in objectTypes)
{
- foreach (var formatter in _outputFormatters)
+ if (objectType.Value == typeof(void))
{
- var responseFormatMetadataProvider = formatter as IApiResponseFormatMetadataProvider;
- if (responseFormatMetadataProvider != null)
+ results.Add(new ApiResponseType()
{
- var supportedTypes = responseFormatMetadataProvider.GetSupportedContentTypes(contentType, type);
+ StatusCode = objectType.Key,
+ Type = objectType.Value
+ });
- if (supportedTypes != null)
+ continue;
+ }
+
+ var apiResponseType = new ApiResponseType()
+ {
+ Type = objectType.Value,
+ StatusCode = objectType.Key,
+ ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value)
+ };
+
+ foreach (var contentType in contentTypes)
+ {
+ foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders)
+ {
+ var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes(
+ contentType,
+ objectType.Value);
+
+ if (formatterSupportedContentTypes == null)
{
- foreach (var supportedType in supportedTypes)
+ continue;
+ }
+
+ foreach (var formatterSupportedContentType in formatterSupportedContentTypes)
+ {
+ apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat()
{
- results.Add(new ApiResponseFormat()
- {
- Formatter = formatter,
- MediaType = supportedType,
- });
- }
+ Formatter = (IOutputFormatter)responseTypeMetadataProvider,
+ MediaType = formatterSupportedContentType,
+ });
}
}
}
+
+ results.Add(apiResponseType);
}
return results;
@@ -445,28 +470,8 @@ private static Type GetTaskInnerTypeOrNull(Type type)
return genericType?.GenericTypeArguments[0];
}
- private Type GetRuntimeReturnType(Type declaredReturnType, IApiResponseMetadataProvider[] metadataAttributes)
+ private Type GetRuntimeReturnType(Type declaredReturnType)
{
- // Walk through all of the filter attributes and allow them to set the type. This will execute them
- // in filter-order allowing the desired behavior for overriding.
- if (metadataAttributes != null)
- {
- Type typeSetByAttribute = null;
- foreach (var metadataAttribute in metadataAttributes)
- {
- if (metadataAttribute.Type != null)
- {
- typeSetByAttribute = metadataAttribute.Type;
- }
- }
-
- // If one of the filters set a type, then trust it.
- if (typeSetByAttribute != null)
- {
- return typeSetByAttribute;
- }
- }
-
// If we get here, then a filter didn't give us an answer, so we need to figure out if we
// want to use the declared return type.
//
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs
index c7a073e30c..5ae22c29d2 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs
@@ -7,15 +7,21 @@
namespace Microsoft.AspNetCore.Mvc.ApiExplorer
{
///
- /// Provides a return type and a set of possible content types returned by a successful execution of the action.
+ /// Provides a return type, status code and a set of possible content types returned by a
+ /// successful execution of the action.
///
public interface IApiResponseMetadataProvider
{
///
- /// Optimistic return type of the action.
+ /// Gets the optimistic return type of the action.
///
Type Type { get; }
+ ///
+ /// Gets the HTTP status code of the response.
+ ///
+ int StatusCode { get; }
+
///
/// Configures a collection of allowed content types which can be produced by the action.
///
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs
similarity index 96%
rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs
rename to src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs
index fa8254dd99..8b15eb0989 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseFormatMetadataProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer
/// An should implement this interface to expose metadata information
/// to an IApiDescriptionProvider.
///
- public interface IApiResponseFormatMetadataProvider
+ public interface IApiResponseTypeMetadataProvider
{
///
/// Gets a filtered list of content types which are supported by the
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs
index 9ba3d68b3e..28b0007641 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
///
/// Writes an object to the output stream.
///
- public abstract class OutputFormatter : IOutputFormatter, IApiResponseFormatMetadataProvider
+ public abstract class OutputFormatter : IOutputFormatter, IApiResponseTypeMetadataProvider
{
///
/// Gets the mutable collection of media type elements supported by
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs
index efc2100597..08c156d6e4 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs
@@ -4,12 +4,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Core;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.Formatters.Internal;
-using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc
@@ -64,6 +64,8 @@ public ProducesAttribute(string contentType, params string[] additionalContentTy
public MediaTypeCollection ContentTypes { get; set; }
+ public int StatusCode => StatusCodes.Status200OK;
+
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context == null)
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs
new file mode 100644
index 0000000000..da2a7ca33a
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs
@@ -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.Filters;
+using Microsoft.AspNetCore.Mvc.Formatters;
+
+namespace Microsoft.AspNetCore.Mvc
+{
+ ///
+ /// Specifies the type of the value and status code returned by the action.
+ ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
+ public class ProducesResponseTypeAttribute : Attribute, IApiResponseMetadataProvider, IFilterMetadata
+ {
+ ///
+ /// Initializes an instance of .
+ ///
+ /// The of object that is going to be written in the response.
+ /// HTTP response status code
+ public ProducesResponseTypeAttribute(Type type, int statusCode)
+ {
+ if (type == null)
+ {
+ throw new ArgumentNullException(nameof(type));
+ }
+
+ Type = type;
+ StatusCode = statusCode;
+ }
+
+ ///
+ /// Gets or sets the type of the value returned by an action.
+ ///
+ public Type Type { get; set; }
+
+ ///
+ /// Gets or sets the HTTP status code of the response.
+ ///
+ public int StatusCode { get; set; }
+
+ ///
+ void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes)
+ {
+ // Users are supposed to use the 'Produces' attribute to set the content types that an action can support.
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs
index 581f141b36..5c5e3b9ac2 100644
--- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs
@@ -21,7 +21,6 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Constraints;
using Microsoft.Extensions.Options;
-using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using Moq;
using Xunit;
@@ -218,7 +217,7 @@ public void GetApiDescription_PopulatesParametersThatAppearOnRouteTemplate_AndHa
}
// Only a parameter which comes from a route or model binding or unknown should
- // include route info.
+ // include route info.
[Theory]
[InlineData("api/products/{id}", nameof(FromBody), "Body")]
[InlineData("api/products/{id}", nameof(FromHeader), "Header")]
@@ -374,8 +373,9 @@ public void GetApiDescription_PopulatesResponseType_WithProduct()
// Assert
var description = Assert.Single(descriptions);
- Assert.Equal(typeof(Product), description.ResponseType);
- Assert.NotNull(description.ResponseModelMetadata);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(typeof(Product), responseType.Type);
+ Assert.NotNull(responseType.ModelMetadata);
}
[Fact]
@@ -389,8 +389,9 @@ public void GetApiDescription_PopulatesResponseType_WithTaskOfProduct()
// Assert
var description = Assert.Single(descriptions);
- Assert.Equal(typeof(Product), description.ResponseType);
- Assert.NotNull(description.ResponseModelMetadata);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(typeof(Product), responseType.Type);
+ Assert.NotNull(responseType.ModelMetadata);
}
[Theory]
@@ -410,9 +411,182 @@ public void GetApiDescription_DoesNotPopulatesResponseInformation_WhenUnknown(st
// Assert
var description = Assert.Single(descriptions);
- Assert.Null(description.ResponseType);
- Assert.Null(description.ResponseModelMetadata);
- Assert.Empty(description.SupportedResponseFormats);
+ Assert.Empty(description.SupportedResponseTypes);
+ }
+
+ public static TheoryData ReturnsActionResultWithProducesAndProducesContentTypeData
+ {
+ get
+ {
+ var filterDescriptors = new List()
+ {
+ new FilterDescriptor(
+ new ProducesAttribute("text/json", "application/json") { Type = typeof(Customer) },
+ FilterScope.Action),
+ new FilterDescriptor(
+ new ProducesResponseTypeAttribute(typeof(BadData), 400),
+ FilterScope.Action),
+ new FilterDescriptor(
+ new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500),
+ FilterScope.Action)
+ };
+
+ return new TheoryData>
+ {
+ {
+ typeof(DefaultApiDescriptionProviderTest),
+ nameof(DefaultApiDescriptionProviderTest.ReturnsTaskOfActionResult),
+ filterDescriptors
+ },
+ {
+ typeof(DefaultApiDescriptionProviderTest),
+ nameof(DefaultApiDescriptionProviderTest.ReturnsActionResult),
+ filterDescriptors
+ },
+ {
+ typeof(DerivedProducesController),
+ nameof(DerivedProducesController.ReturnsActionResult),
+ filterDescriptors
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ReturnsActionResultWithProducesAndProducesContentTypeData))]
+ public void GetApiDescription_ReturnsActionResultWithProduces_And_ProducesContentType(
+ Type controllerType,
+ string methodName,
+ List filterDescriptors)
+ {
+ // Arrange
+ var action = CreateActionDescriptor(methodName, controllerType);
+ action.FilterDescriptors = filterDescriptors;
+ var expectedMediaTypes = new[] { "application/json", "text/json" };
+
+ // Act
+ var descriptions = GetApiDescriptions(action);
+
+ // Assert
+ var description = Assert.Single(descriptions);
+ Assert.Equal(3, description.SupportedResponseTypes.Count);
+
+ Assert.Collection(
+ description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
+ responseType =>
+ {
+ Assert.Equal(200, responseType.StatusCode);
+ Assert.Equal(typeof(Customer), responseType.Type);
+ Assert.NotNull(responseType.ModelMetadata);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ },
+ responseType =>
+ {
+ Assert.Equal(400, responseType.StatusCode);
+ Assert.Equal(typeof(BadData), responseType.Type);
+ Assert.NotNull(responseType.ModelMetadata);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ },
+ responseType =>
+ {
+ Assert.Equal(500, responseType.StatusCode);
+ Assert.Equal(typeof(ErrorDetails), responseType.Type);
+ Assert.NotNull(responseType.ModelMetadata);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ });
+ }
+
+ public static TheoryData> ReturnsVoidOrTaskWithProducesContentTypeData
+ {
+ get
+ {
+ var filterDescriptors = new List()
+ {
+ // Since action is returning Void or Task, it does not make sense to provide a value for the
+ // 'Type' property to ProducesAttribute. But the same action could return other types of data
+ // based on runtime conditions.
+ new FilterDescriptor(
+ new ProducesAttribute("text/json", "application/json"),
+ FilterScope.Action),
+ new FilterDescriptor(
+ new ProducesResponseTypeAttribute(typeof(void), 204),
+ FilterScope.Action),
+ new FilterDescriptor(
+ new ProducesResponseTypeAttribute(typeof(BadData), 400),
+ FilterScope.Action),
+ new FilterDescriptor(
+ new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500),
+ FilterScope.Action)
+ };
+
+ return new TheoryData>
+ {
+ {
+ typeof(DefaultApiDescriptionProviderTest),
+ nameof(DefaultApiDescriptionProviderTest.ReturnsVoid),
+ filterDescriptors
+ },
+ {
+ typeof(DefaultApiDescriptionProviderTest),
+ nameof(DefaultApiDescriptionProviderTest.ReturnsTask),
+ filterDescriptors
+ },
+ {
+ typeof(DerivedProducesController),
+ nameof(DerivedProducesController.ReturnsVoid),
+ filterDescriptors
+ },
+ {
+ typeof(DerivedProducesController),
+ nameof(DerivedProducesController.ReturnsTask),
+ filterDescriptors
+ },
+ };
+ }
+ }
+
+ [Theory]
+ [MemberData(nameof(ReturnsVoidOrTaskWithProducesContentTypeData))]
+ public void GetApiDescription_ReturnsVoidWithProducesContentType(
+ Type controllerType,
+ string methodName,
+ List filterDescriptors)
+ {
+ // Arrange
+ var action = CreateActionDescriptor(methodName, controllerType);
+ action.FilterDescriptors = filterDescriptors;
+ var expectedMediaTypes = new[] { "application/json", "text/json" };
+
+ // Act
+ var descriptions = GetApiDescriptions(action);
+
+ // Assert
+ var description = Assert.Single(descriptions);
+ Assert.Equal(3, description.SupportedResponseTypes.Count);
+
+ Assert.Collection(
+ description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
+ responseType =>
+ {
+ Assert.Equal(typeof(void), responseType.Type);
+ Assert.Equal(204, responseType.StatusCode);
+ Assert.Null(responseType.ModelMetadata);
+ Assert.Empty(responseType.ApiResponseFormats);
+ },
+ responseType =>
+ {
+ Assert.Equal(typeof(BadData), responseType.Type);
+ Assert.Equal(400, responseType.StatusCode);
+ Assert.NotNull(responseType.ModelMetadata);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ },
+ responseType =>
+ {
+ Assert.Equal(typeof(ErrorDetails), responseType.Type);
+ Assert.Equal(500, responseType.StatusCode);
+ Assert.NotNull(responseType.ModelMetadata);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ });
}
[Theory]
@@ -422,15 +596,19 @@ public void GetApiDescription_DoesNotPopulatesResponseInformation_WhenVoid(strin
{
// Arrange
var action = CreateActionDescriptor(methodName);
+ var filter = new ProducesResponseTypeAttribute(typeof(void), statusCode: 204);
+ action.FilterDescriptors = new List();
+ action.FilterDescriptors.Add(new FilterDescriptor(filter, FilterScope.Action));
// Act
var descriptions = GetApiDescriptions(action);
// Assert
var description = Assert.Single(descriptions);
- Assert.Equal(typeof(void), description.ResponseType);
- Assert.Null(description.ResponseModelMetadata);
- Assert.Empty(description.SupportedResponseFormats);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(typeof(void), responseType.Type);
+ Assert.Equal(204, responseType.StatusCode);
+ Assert.Null(responseType.ModelMetadata);
}
[Theory]
@@ -459,8 +637,15 @@ public void GetApiDescription_PopulatesResponseInformation_WhenSetByFilter(strin
// Assert
var description = Assert.Single(descriptions);
- Assert.Equal(typeof(Order), description.ResponseType);
- Assert.NotNull(description.ResponseModelMetadata);
+ var responseTypes = Assert.Single(description.SupportedResponseTypes);
+ Assert.NotNull(responseTypes.ModelMetadata);
+ Assert.Equal(200, responseTypes.StatusCode);
+ Assert.Equal(typeof(Order), responseTypes.Type);
+
+ foreach (var responseFormat in responseTypes.ApiResponseFormats)
+ {
+ Assert.StartsWith("text/", responseFormat.MediaType);
+ }
}
[Fact]
@@ -468,18 +653,15 @@ public void GetApiDescription_IncludesResponseFormats()
{
// Arrange
var action = CreateActionDescriptor(nameof(ReturnsProduct));
+ var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
// Act
var descriptions = GetApiDescriptions(action);
// Assert
var description = Assert.Single(descriptions);
- Assert.Collection(
- description.SupportedResponseFormats.OrderBy(f => f.MediaType.ToString()),
- f => Assert.Equal("application/json", f.MediaType.ToString()),
- f => Assert.Equal("application/xml", f.MediaType.ToString()),
- f => Assert.Equal("text/json", f.MediaType.ToString()),
- f => Assert.Equal("text/xml", f.MediaType.ToString()));
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
}
[Fact]
@@ -487,7 +669,7 @@ public void GetApiDescription_IncludesResponseFormats_FilteredByAttribute()
{
// Arrange
var action = CreateActionDescriptor(nameof(ReturnsProduct));
-
+ var expectedMediaTypes = new[] { "text/json", "text/xml" };
action.FilterDescriptors = new List();
action.FilterDescriptors.Add(new FilterDescriptor(new ContentTypeAttribute("text/*"), FilterScope.Action));
@@ -496,10 +678,8 @@ public void GetApiDescription_IncludesResponseFormats_FilteredByAttribute()
// Assert
var description = Assert.Single(descriptions);
- Assert.Collection(
- description.SupportedResponseFormats.OrderBy(f => f.MediaType.ToString()),
- f => Assert.Equal("text/json", f.MediaType.ToString()),
- f => Assert.Equal("text/xml", f.MediaType.ToString()));
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
}
[Fact]
@@ -528,13 +708,12 @@ public void GetApiDescription_IncludesResponseFormats_FilteredByType()
// Assert
var description = Assert.Single(descriptions);
- Assert.Equal(1, description.SupportedResponseFormats.Count);
- Assert.Equal(typeof(Order), description.ResponseType);
- Assert.NotNull(description.ResponseModelMetadata);
-
- var formats = description.SupportedResponseFormats;
- Assert.Single(formats, f => f.MediaType.ToString() == "text/json");
- Assert.Same(formatters[0], formats[0].Formatter);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(typeof(Order), responseType.Type);
+ Assert.NotNull(responseType.ModelMetadata);
+ var apiResponseFormat = Assert.Single(
+ responseType.ApiResponseFormats.Where(responseFormat => responseFormat.MediaType == "text/json"));
+ Assert.Same(formatters[0], apiResponseFormat.Formatter);
}
[Fact]
@@ -1152,7 +1331,7 @@ private ControllerActionDescriptor CreateActionDescriptor(string methodName = nu
{
action.MethodInfo = controllerType.GetMethod(
methodName ?? "ReturnsObject",
- BindingFlags.Instance | BindingFlags.Public);
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
action.ControllerTypeInfo = controllerType.GetTypeInfo();
action.BoundProperties = new List();
@@ -1192,6 +1371,13 @@ private ControllerActionDescriptor CreateActionDescriptor(string methodName = nu
return action;
}
+ private IEnumerable GetSortedMediaTypes(ApiResponseType apiResponseType)
+ {
+ return apiResponseType.ApiResponseFormats
+ .OrderBy(responseType => responseType.MediaType)
+ .Select(responseType => responseType.MediaType);
+ }
+
private object ReturnsObject()
{
return null;
@@ -1357,6 +1543,39 @@ public void FromQueryName([FromQuery] string name)
}
}
+ public class Customer
+ {
+ }
+
+ public class BadData
+ {
+ }
+
+ public class ErrorDetails
+ {
+ }
+
+ public class BaseProducesController : Controller
+ {
+ public IActionResult ReturnsActionResult()
+ {
+ return null;
+ }
+
+ public Task ReturnsTask()
+ {
+ return null;
+ }
+
+ public void ReturnsVoid()
+ {
+ }
+ }
+
+ public class DerivedProducesController : BaseProducesController
+ {
+ }
+
private class Product
{
public int ProductId { get; set; }
@@ -1520,10 +1739,13 @@ private class ContentTypeAttribute :
public ContentTypeAttribute(string mediaType)
{
ContentTypes.Add(mediaType);
+ StatusCode = 200;
}
public MediaTypeCollection ContentTypes { get; } = new MediaTypeCollection();
+ public int StatusCode { get; set; }
+
public Type Type { get; set; }
public void SetContentTypes(MediaTypeCollection contentTypes)
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs
index e121481499..dbc224ae35 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs
@@ -1,6 +1,7 @@
// 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.Linq;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
@@ -406,7 +407,10 @@ public async Task ApiExplorer_ResponseType_VoidWithoutAttribute(string action)
// Assert
var description = Assert.Single(result);
- Assert.Equal(typeof(void).FullName, description.ResponseType);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(typeof(void).FullName, responseType.ResponseType);
+ Assert.Equal(204, responseType.StatusCode);
+ Assert.Empty(responseType.ResponseFormats);
}
[Theory]
@@ -427,7 +431,7 @@ public async Task ApiExplorer_ResponseType_UnknownWithoutAttribute(string action
// Assert
var description = Assert.Single(result);
- Assert.Null(description.ResponseType);
+ Assert.Empty(description.SupportedResponseTypes);
}
[Theory]
@@ -443,17 +447,63 @@ public async Task ApiExplorer_ResponseType_KnownWithoutAttribute(string action,
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject>(body);
+ var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
// Assert
var description = Assert.Single(result);
- Assert.Equal(type, description.ResponseType);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(200, responseType.StatusCode);
+ Assert.Equal(type, responseType.ResponseType);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ }
+
+ [Fact]
+ public async Task ApiExplorer_ResponseType_KnownWithoutAttribute_ReturnVoid()
+ {
+ // Arrange
+ var type = "ApiExplorerWebSite.Customer";
+ var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
+
+ // Act
+ var response = await Client.GetAsync(
+ "http://localhost/ApiExplorerResponseTypeWithAttribute/GetVoid");
+
+ var body = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(body);
+
+ // Assert
+ var description = Assert.Single(result);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(200, responseType.StatusCode);
+ Assert.Equal(type, responseType.ResponseType);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ }
+
+ [Fact]
+ public async Task ApiExplorer_ResponseType_DifferentOnAttributeThanReturnType()
+ {
+ // Arrange
+ var type = "ApiExplorerWebSite.Customer";
+ var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
+
+ // Act
+ var response = await Client.GetAsync(
+ "http://localhost/ApiExplorerResponseTypeWithAttribute/GetProduct");
+
+ var body = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(body);
+
+ // Assert
+ var description = Assert.Single(result);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(200, responseType.StatusCode);
+ Assert.Equal(type, responseType.ResponseType);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
}
[Theory]
- [InlineData("GetVoid", "ApiExplorerWebSite.Customer")]
[InlineData("GetObject", "ApiExplorerWebSite.Product")]
[InlineData("GetIActionResult", "System.String")]
- [InlineData("GetProduct", "ApiExplorerWebSite.Customer")]
[InlineData("GetTask", "System.Int32")]
public async Task ApiExplorer_ResponseType_KnownWithAttribute(string action, string type)
{
@@ -466,24 +516,83 @@ public async Task ApiExplorer_ResponseType_KnownWithAttribute(string action, str
// Assert
var description = Assert.Single(result);
- Assert.Equal(type, description.ResponseType);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(type, responseType.ResponseType);
+ Assert.Equal(200, responseType.StatusCode);
+ var responseFormat = Assert.Single(responseType.ResponseFormats);
+ Assert.Equal("application/json", responseFormat.MediaType);
}
- [Theory]
- [InlineData("Controller", "ApiExplorerWebSite.Product")]
- [InlineData("Action", "ApiExplorerWebSite.Customer")]
- public async Task ApiExplorer_ResponseType_OverrideOnAction(string action, string type)
+ [Fact]
+ public async Task ApiExplorer_ResponseType_InheritingFromController()
{
- // Arrange & Act
+ // Arrange
+ var type = "ApiExplorerWebSite.Product";
+ var errorType = "ApiExplorerWebSite.ErrorInfo";
+
+ // Act
+ var response = await Client.GetAsync(
+ "http://localhost/ApiExplorerResponseTypeOverrideOnAction/Controller");
+
+ var body = await response.Content.ReadAsStringAsync();
+ var result = JsonConvert.DeserializeObject>(body);
+
+ // Assert
+ var description = Assert.Single(result);
+ Assert.Equal(2, description.SupportedResponseTypes.Count);
+
+ Assert.Collection(
+ description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
+ responseType =>
+ {
+ Assert.Equal(type, responseType.ResponseType);
+ Assert.Equal(200, responseType.StatusCode);
+ var responseFormat = Assert.Single(responseType.ResponseFormats);
+ Assert.Equal("application/json", responseFormat.MediaType);
+ },
+ responseType =>
+ {
+ Assert.Equal(errorType, responseType.ResponseType);
+ Assert.Equal(500, responseType.StatusCode);
+ var responseFormat = Assert.Single(responseType.ResponseFormats);
+ Assert.Equal("application/json", responseFormat.MediaType);
+ });
+ }
+
+ [Fact]
+ public async Task ApiExplorer_ResponseType_OverrideOnAction()
+ {
+ // Arrange
+ var type = "ApiExplorerWebSite.Customer";
+ // type overriding the one specified on the controller
+ var errorType = "ApiExplorerWebSite.ErrorInfoOverride";
+ var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" };
+
+ // Act
var response = await Client.GetAsync(
- "http://localhost/ApiExplorerResponseTypeOverrideOnAction/" + action);
+ "http://localhost/ApiExplorerResponseTypeOverrideOnAction/Action");
var body = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject>(body);
// Assert
var description = Assert.Single(result);
- Assert.Equal(type, description.ResponseType);
+ Assert.Equal(2, description.SupportedResponseTypes.Count);
+
+ Assert.Collection(
+ description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode),
+ responseType =>
+ {
+ Assert.Equal(type, responseType.ResponseType);
+ Assert.Equal(200, responseType.StatusCode);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ },
+ responseType =>
+ {
+ Assert.Equal(errorType, responseType.ResponseType);
+ Assert.Equal(500, responseType.StatusCode);
+ Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType));
+ });
}
[ConditionalFact]
@@ -500,17 +609,17 @@ public async Task ApiExplorer_ResponseContentType_Unset()
// Assert
var description = Assert.Single(result);
- var formats = description.SupportedResponseFormats;
- Assert.Equal(4, formats.Count);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(4, responseType.ResponseFormats.Count);
- var textXml = Assert.Single(formats, f => f.MediaType == "text/xml");
+ var textXml = Assert.Single(responseType.ResponseFormats, f => f.MediaType == "text/xml");
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, textXml.FormatterType);
- var applicationXml = Assert.Single(formats, f => f.MediaType == "application/xml");
+ var applicationXml = Assert.Single(responseType.ResponseFormats, f => f.MediaType == "application/xml");
Assert.Equal(typeof(XmlDataContractSerializerOutputFormatter).FullName, applicationXml.FormatterType);
- var textJson = Assert.Single(formats, f => f.MediaType == "text/json");
+ var textJson = Assert.Single(responseType.ResponseFormats, f => f.MediaType == "text/json");
Assert.Equal(typeof(JsonOutputFormatter).FullName, textJson.FormatterType);
- var applicationJson = Assert.Single(formats, f => f.MediaType == "application/json");
+ var applicationJson = Assert.Single(responseType.ResponseFormats, f => f.MediaType == "application/json");
Assert.Equal(typeof(JsonOutputFormatter).FullName, applicationJson.FormatterType);
}
@@ -526,13 +635,15 @@ public async Task ApiExplorer_ResponseContentType_Specific()
// Assert
var description = Assert.Single(result);
- var formats = description.SupportedResponseFormats;
- Assert.Equal(2, formats.Count);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Equal(2, responseType.ResponseFormats.Count);
- var applicationJson = Assert.Single(formats, f => f.MediaType == "application/json");
+ var applicationJson = Assert.Single(
+ responseType.ResponseFormats,
+ format => format.MediaType == "application/json");
Assert.Equal(typeof(JsonOutputFormatter).FullName, applicationJson.FormatterType);
- var textJson = Assert.Single(formats, f => f.MediaType == "text/json");
+ var textJson = Assert.Single(responseType.ResponseFormats, f => f.MediaType == "text/json");
Assert.Equal(typeof(JsonOutputFormatter).FullName, textJson.FormatterType);
}
@@ -547,9 +658,8 @@ public async Task ApiExplorer_ResponseContentType_NoMatch()
// Assert
var description = Assert.Single(result);
-
- var formats = description.SupportedResponseFormats;
- Assert.Empty(formats);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ Assert.Empty(responseType.ResponseFormats);
}
[ConditionalTheory]
@@ -572,9 +682,10 @@ public async Task ApiExplorer_ResponseContentType_OverrideOnAction(
// Assert
var description = Assert.Single(result);
- var format = Assert.Single(description.SupportedResponseFormats);
- Assert.Equal(contentType, format.MediaType);
- Assert.Equal(formatterType, format.FormatterType);
+ var responseType = Assert.Single(description.SupportedResponseTypes);
+ var responseFormat = Assert.Single(responseType.ResponseFormats);
+ Assert.Equal(contentType, responseFormat.MediaType);
+ Assert.Equal(formatterType, responseFormat.FormatterType);
}
[Fact]
@@ -718,6 +829,13 @@ public async Task ApiExplorer_Parameters_SimpleTypes_ComplexModel()
Assert.Equal(typeof(string).FullName, feedback.Type);
}
+ private IEnumerable GetSortedMediaTypes(ApiExplorerResponseType apiResponseType)
+ {
+ return apiResponseType.ResponseFormats
+ .OrderBy(format => format.MediaType)
+ .Select(format => format.MediaType);
+ }
+
// Used to serialize data between client and server
private class ApiExplorerData
{
@@ -729,9 +847,7 @@ private class ApiExplorerData
public string RelativePath { get; set; }
- public string ResponseType { get; set; }
-
- public List SupportedResponseFormats { get; } = new List();
+ public List SupportedResponseTypes { get; } = new List();
}
// Used to serialize data between client and server
@@ -757,7 +873,17 @@ private class ApiExplorerParameterRouteInfo
}
// Used to serialize data between client and server
- private class ApiExplorerResponseData
+ private class ApiExplorerResponseType
+ {
+ public IList ResponseFormats { get; }
+ = new List();
+
+ public string ResponseType { get; set; }
+
+ public int StatusCode { get; set; }
+ }
+
+ private class ApiExplorerResponseFormat
{
public string MediaType { get; set; }
diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs
index db16c34f60..d91179e35c 100644
--- a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs
+++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs
@@ -52,8 +52,7 @@ private ApiExplorerData CreateSerializableData(ApiDescription description)
{
GroupName = description.GroupName,
HttpMethod = description.HttpMethod,
- RelativePath = description.RelativePath,
- ResponseType = description.ResponseType?.FullName,
+ RelativePath = description.RelativePath
};
foreach (var parameter in description.ParameterDescriptions)
@@ -78,15 +77,24 @@ private ApiExplorerData CreateSerializableData(ApiDescription description)
data.ParameterDescriptions.Add(parameterData);
}
- foreach (var response in description.SupportedResponseFormats)
+ foreach (var response in description.SupportedResponseTypes)
{
- var responseData = new ApiExplorerResponseData()
+ var responseType = new ApiExplorerResponseType()
{
- FormatterType = response.Formatter.GetType().FullName,
- MediaType = response.MediaType.ToString(),
+ StatusCode = response.StatusCode,
+ ResponseType = response.Type?.FullName
};
- data.SupportedResponseFormats.Add(responseData);
+ foreach(var responseFormat in response.ApiResponseFormats)
+ {
+ responseType.ResponseFormats.Add(new ApiExplorerResponseFormat()
+ {
+ FormatterType = responseFormat.Formatter?.GetType().FullName,
+ MediaType = responseFormat.MediaType
+ });
+ }
+
+ data.SupportedResponseTypes.Add(responseType);
}
return data;
@@ -103,9 +111,7 @@ private class ApiExplorerData
public string RelativePath { get; set; }
- public string ResponseType { get; set; }
-
- public List SupportedResponseFormats { get; } = new List();
+ public List SupportedResponseTypes { get; } = new List();
}
// Used to serialize data between client and server
@@ -131,7 +137,17 @@ private class ApiExplorerParameterRouteInfo
}
// Used to serialize data between client and server
- private class ApiExplorerResponseData
+ private class ApiExplorerResponseType
+ {
+ public IList ResponseFormats { get; }
+ = new List();
+
+ public string ResponseType { get; set; }
+
+ public int StatusCode { get; set; }
+ }
+
+ private class ApiExplorerResponseFormat
{
public string MediaType { get; set; }
diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs
index 8c6a946710..10f9a95249 100644
--- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs
+++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs
@@ -6,6 +6,7 @@
namespace ApiExplorerWebSite
{
[Produces("application/json", Type = typeof(Product))]
+ [ProducesResponseType(typeof(ErrorInfo), 500)]
[Route("ApiExplorerResponseTypeOverrideOnAction")]
public class ApiExplorerResponseTypeOverrideOnActionController : Controller
{
@@ -16,9 +17,17 @@ public void GetController()
[HttpGet("Action")]
[Produces(typeof(Customer))]
+ [ProducesResponseType(typeof(ErrorInfoOverride), 500)] // overriding the type specified on the server
public object GetAction()
{
return null;
}
}
+
+ public class ErrorInfo
+ {
+ public string Message { get; set; }
+ }
+
+ public class ErrorInfoOverride { }
}
\ No newline at end of file
diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs
index dd11097d18..024ff2a822 100644
--- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs
+++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs
@@ -10,6 +10,7 @@ namespace ApiExplorerWebSite
public class ApiExplorerResponseTypeWithoutAttributeController : Controller
{
[HttpGet]
+ [ProducesResponseType(typeof(void), 204)]
public void GetVoid()
{
}
@@ -45,6 +46,7 @@ public int GetInt()
}
[HttpGet]
+ [ProducesResponseType(typeof(void), 204)]
public Task GetTask()
{
return Task.FromResult(true);