// Shared with EndpointMetadataApiDescriptionProvider internal static List <ApiResponseType> ReadResponseMetadata( IReadOnlyList <IApiResponseMetadataProvider> responseMetadataAttributes, Type?type, Type defaultErrorType, MediaTypeCollection contentTypes) { var results = new Dictionary <int, ApiResponseType>(); // 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. if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) { metadataAttribute.SetContentTypes(contentTypes); var statusCode = metadataAttribute.StatusCode; var 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)) { // 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(typeof(Person), 201] when typeof(Person) can be inferred // from the return type. apiResponseType.Type = type; } else if (IsClientError(statusCode)) { // Determine whether or not the type was provided by the user. If so, favor it over the default // error type for 4xx client errors if no response type is specified.. var setByDefault = metadataAttribute is ProducesResponseTypeAttribute { IsResponseTypeSetByDefault : true }; apiResponseType.Type = setByDefault ? defaultErrorType : apiResponseType.Type; } else if (apiResponseType.IsDefaultResponse) { apiResponseType.Type = defaultErrorType; } } if (apiResponseType.Type != null) { results[apiResponseType.StatusCode] = apiResponseType; } } } return(results.Values.ToList()); }
internal static ApiResponseType Clone(this ApiResponseType responseType) { var clone = new ApiResponseType() { ModelMetadata = responseType.ModelMetadata, StatusCode = responseType.StatusCode, Type = responseType.Type, }; foreach (var responseFormat in responseType.ApiResponseFormats) { clone.ApiResponseFormats.Add(responseFormat.Clone()); } return(clone); }
internal static ApiResponseType Clone(this ApiResponseType responseType) { Contract.Requires(responseType != null); Contract.Ensures(Contract.Result <ApiResponseType>() != null); var clone = new ApiResponseType() { ModelMetadata = responseType.ModelMetadata, StatusCode = responseType.StatusCode, Type = responseType.Type, }; foreach (var responseFormat in responseType.ApiResponseFormats) { clone.ApiResponseFormats.Add(responseFormat.Clone()); } return(clone); }
private IReadOnlyList <ApiResponseType> GetApiResponseTypes( IApiResponseMetadataProvider[] responseMetadataAttributes, Type type) { var results = new List <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. // 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(); if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) { metadataAttribute.SetContentTypes(contentTypes); if (metadataAttribute.Type != null) { objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; } } } // Set the default status only when no status has already been set explicitly if (objectTypes.Count == 0 && type != null) { objectTypes[StatusCodes.Status200OK] = type; } if (contentTypes.Count == 0) { contentTypes.Add((string)null); } var responseTypeMetadataProviders = _outputFormatters.OfType <IApiResponseTypeMetadataProvider>(); foreach (var objectType in objectTypes) { if (objectType.Value == typeof(void)) { results.Add(new ApiResponseType() { StatusCode = objectType.Key, Type = objectType.Value }); 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) { continue; } foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat() { Formatter = (IOutputFormatter)responseTypeMetadataProvider, MediaType = formatterSupportedContentType, }); } } } results.Add(apiResponseType); } return(results); }
// Shared with EndpointMetadataApiDescriptionProvider internal static void CalculateResponseFormatForType(ApiResponseType apiResponse, MediaTypeCollection declaredContentTypes, IEnumerable <IApiResponseTypeMetadataProvider>?responseTypeMetadataProviders, IModelMetadataProvider?modelMetadataProvider) { // If response formats have already been calculate for this type, // then exit early. This avoids populating the ApiResponseFormat for // types that have already been handled, specifically ProducesResponseTypes. if (apiResponse.ApiResponseFormats.Count > 0) { return; } // Given the content-types that were declared for this action, determine the formatters that support the content-type for the given // response type. // 1. Responses that do not specify an type do not have any associated content-type. This usually is meant for status-code only responses such // as return NotFound(); // 2. When a type is specified, use GetSupportedContentTypes to expand wildcards and get the range of content-types formatters support. // 3. When no formatter supports the specified content-type, use the user specified value as is. This is useful in actions where the user // dictates the content-type. // e.g. [Produces("application/pdf")] Action() => FileStream("somefile.pdf", "application/pdf"); var responseType = apiResponse.Type; if (responseType == null || responseType == typeof(void)) { return; } apiResponse.ModelMetadata = modelMetadataProvider?.GetMetadataForType(responseType); foreach (var contentType in declaredContentTypes) { var isSupportedContentType = false; if (responseTypeMetadataProviders != null) { foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders) { var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( contentType, responseType); if (formatterSupportedContentTypes == null) { continue; } isSupportedContentType = true; foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { apiResponse.ApiResponseFormats.Add(new ApiResponseFormat { Formatter = (IOutputFormatter)responseTypeMetadataProvider, MediaType = formatterSupportedContentType, }); } } } if (!isSupportedContentType && contentType != null) { // No output formatter was found that supports this content type. Add the user specified content type as-is to the result. apiResponse.ApiResponseFormats.Add(new ApiResponseFormat { MediaType = contentType, }); } } }
// Shared with EndpointMetadataApiDescriptionProvider internal static List <ApiResponseType> ReadResponseMetadata( IReadOnlyList <IApiResponseMetadataProvider> responseMetadataAttributes, Type?type, Type defaultErrorType, MediaTypeCollection contentTypes, IEnumerable <IApiResponseTypeMetadataProvider>?responseTypeMetadataProviders = null, IModelMetadataProvider?modelMetadataProvider = null) { var results = new Dictionary <int, ApiResponseType>(); // 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. if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) { // All ProducesXAttributes, except for ProducesResponseTypeAttribute do // not allow multiple instances on the same method/class/etc. For those // scenarios, the `SetContentTypes` method on the attribute continuously // clears out more general content types in favor of more specific ones // since we iterate through the attributes in order. For example, if a // Produces exists on both a controller and an action within the controller, // we favor the definition in the action. This is a semantic that does not // apply to ProducesResponseType, which allows multiple instances on an target. if (metadataAttribute is not ProducesResponseTypeAttribute) { metadataAttribute.SetContentTypes(contentTypes); } var statusCode = metadataAttribute.StatusCode; var 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)) { // 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(typeof(Person), 201] when typeof(Person) can be inferred // from the return type. apiResponseType.Type = type; } else if (IsClientError(statusCode)) { // Determine whether or not the type was provided by the user. If so, favor it over the default // error type for 4xx client errors if no response type is specified.. var setByDefault = metadataAttribute is ProducesResponseTypeAttribute { IsResponseTypeSetByDefault : true }; apiResponseType.Type = setByDefault ? defaultErrorType : apiResponseType.Type; } else if (apiResponseType.IsDefaultResponse) { apiResponseType.Type = defaultErrorType; } } // We special case the handling of ProcuesResponseTypeAttributes since // multiple ProducesResponseTypeAttributes are permitted on a single // action/controller/etc. In that scenario, instead of picking the most-specific // set of content types (like we do with the Produces attribute above) we process // the content types for each attribute independently. if (metadataAttribute is ProducesResponseTypeAttribute) { var attributeContentTypes = new MediaTypeCollection(); metadataAttribute.SetContentTypes(attributeContentTypes); CalculateResponseFormatForType(apiResponseType, attributeContentTypes, responseTypeMetadataProviders, modelMetadataProvider); } if (apiResponseType.Type != null) { results[apiResponseType.StatusCode] = apiResponseType; } } } return(results.Values.ToList()); }
private ICollection <ApiResponseType> GetApiResponseTypes( IReadOnlyList <IApiResponseMetadataProvider> responseMetadataAttributes, Type type, Type defaultErrorType) { var results = new Dictionary <int, ApiResponseType>(); // 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(); if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) { metadataAttribute.SetContentTypes(contentTypes); var statusCode = metadataAttribute.StatusCode; var 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)) { // 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) { // Use the default error type for "default" responses or 4xx client errors if no response type is specified. apiResponseType.Type = defaultErrorType; } } 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) { results[StatusCodes.Status200OK] = new ApiResponseType { StatusCode = StatusCodes.Status200OK, Type = type, }; } if (contentTypes.Count == 0) { contentTypes.Add((string)null); } var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType <IApiResponseTypeMetadataProvider>(); foreach (var apiResponse in results.Values) { var responseType = apiResponse.Type; if (responseType == null || responseType == typeof(void)) { continue; } apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType); foreach (var contentType in contentTypes) { foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders) { var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( contentType, responseType); if (formatterSupportedContentTypes == null) { continue; } foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { apiResponse.ApiResponseFormats.Add(new ApiResponseFormat { Formatter = (IOutputFormatter)responseTypeMetadataProvider, MediaType = formatterSupportedContentType, }); } } } } return(results.Values); }
private ICollection <ApiResponseType> GetApiResponseTypes( IReadOnlyList <IApiResponseMetadataProvider> responseMetadataAttributes, Type type, Type defaultErrorType) { var results = new Dictionary <int, ApiResponseType>(); // 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(); if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) { metadataAttribute.SetContentTypes(contentTypes); var statusCode = metadataAttribute.StatusCode; var 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)) { // 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) { // Use the default error type for "default" responses or 4xx client errors if no response type is specified. apiResponseType.Type = defaultErrorType; } } 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) { results[StatusCodes.Status200OK] = new ApiResponseType { StatusCode = StatusCodes.Status200OK, Type = type, }; } if (contentTypes.Count == 0) { // None of the IApiResponseMetadataProvider specified a content type. This is common for actions that // specify one or more ProducesResponseType but no ProducesAttribute. In this case, formatters will participate in conneg // and respond to the incoming request. // Querying IApiResponseTypeMetadataProvider.GetSupportedContentTypes with "null" should retrieve all supported // content types that each formatter may respond in. contentTypes.Add((string)null); } var responseTypes = results.Values; CalculateResponseFormats(responseTypes, contentTypes); return(responseTypes); }
private IList <ApiResponseType> GetApiResponseTypes( IReadOnlyList <IApiResponseMetadataProvider> responseMetadataAttributes, Type type) { var results = new List <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. // 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(); if (responseMetadataAttributes != null) { foreach (var metadataAttribute in responseMetadataAttributes) { metadataAttribute.SetContentTypes(contentTypes); 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. objectTypes[metadataAttribute.StatusCode] = type; } else if (metadataAttribute.Type != null) { objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; } } } // Set the default status only when no status has already been set explicitly if (objectTypes.Count == 0 && type != null) { objectTypes[StatusCodes.Status200OK] = type; } if (contentTypes.Count == 0) { contentTypes.Add((string)null); } var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType <IApiResponseTypeMetadataProvider>(); foreach (var objectType in objectTypes) { if (objectType.Value == null || objectType.Value == typeof(void)) { results.Add(new ApiResponseType() { StatusCode = objectType.Key, Type = objectType.Value }); 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) { continue; } foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat() { Formatter = (IOutputFormatter)responseTypeMetadataProvider, MediaType = formatterSupportedContentType, }); } } } results.Add(apiResponseType); } return(results); }
private static IEnumerable <string> GetSortedMediaTypes(ApiResponseType apiResponseType) { return(apiResponseType.ApiResponseFormats .OrderBy(format => format.MediaType) .Select(format => format.MediaType)); }