public void Add_MediaTypeHeaderValue_AddsTheStringSegmentRepresentationOfTheMediaType() { // Arrange var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse("application/json;charset=utf-16"); var collection = new MediaTypeCollection(); // Act collection.Add(mediaTypeHeaderValue); // Assert Assert.Contains("application/json; charset=utf-16", collection); }
public void Remove_MediaTypeHeaderValue_RemovesTheStringSegmentRepresentationOfTheMediaType() { // Arrange var collection = new MediaTypeCollection { MediaTypeHeaderValue.Parse("text/plain"), MediaTypeHeaderValue.Parse("text/xml") }; // Act collection.Remove(MediaTypeHeaderValue.Parse("text/xml")); // Assert Assert.DoesNotContain("text/xml", collection); }
public void Insert_MediaTypeHeaderValue_AddsTheStringSegmentRepresentationOfTheMediaTypeOnTheGivenIndex() { // Arrange var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse("application/json;charset=utf-16"); var collection = new MediaTypeCollection { MediaTypeHeaderValue.Parse("text/plain"), MediaTypeHeaderValue.Parse("text/xml") }; // Act collection.Insert(1, mediaTypeHeaderValue); // Assert Assert.Equal(1, collection.IndexOf("application/json; charset=utf-16")); }
private string GetResponseContentType(string format) { var mediaTypes = new MediaTypeCollection(); if (format != null) { mediaTypes.Add( new MediaTypeHeaderValue( this.options.FormatterMappings.GetMediaTypeMappingForFormat( format))); } var formatter = this.selector.SelectFormatter( new OutputFormatterWriteContext( this.HttpContext, (s, e) => null, typeof(IGraph), null), this.options.OutputFormatters.OfType <GraphOutputFormatter>().Cast <IOutputFormatter>().ToList(), mediaTypes); return(((GraphOutputFormatter)formatter).SupportedMediaTypes.Single()); }
public ProblemDetailsOptions() { SourceCodeLineCount = 6; Mappers = new List <ExceptionMapper>(); RethrowPolicies = new List <Func <HttpContext, Exception, bool> >(); ContentTypes = new MediaTypeCollection(); TraceIdPropertyName = DefaultTraceIdPropertyName; ExceptionDetailsPropertyName = DefaultExceptionDetailsPropertyName; ValidationProblemStatusCode = StatusCodes.Status422UnprocessableEntity; AllowedHeaderNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase) { HeaderNames.AccessControlAllowCredentials, HeaderNames.AccessControlAllowHeaders, HeaderNames.AccessControlAllowMethods, HeaderNames.AccessControlAllowOrigin, HeaderNames.AccessControlExposeHeaders, HeaderNames.AccessControlMaxAge, HeaderNames.StrictTransportSecurity, HeaderNames.WWWAuthenticate, }; }
private string GetResponseContentType(QueryParameters parameters) { var mediaTypes = new MediaTypeCollection(); if (!(parameters.Format is null)) { mediaTypes.Add( new MediaTypeHeaderValue( this.options.FormatterMappings.GetMediaTypeMappingForFormat( parameters.Format))); } var formatter = this.selector.SelectFormatter( new OutputFormatterWriteContext( this.HttpContext, (s, e) => null, typeof(Feed), null), this.options.OutputFormatters.OfType <FeedFormatter>().Cast <IOutputFormatter>().ToList(), mediaTypes); return(((FeedFormatter)formatter).SupportedMediaTypes.Single()); }
private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { var completeArgs = new List <string>(); completeArgs.Add(firstArg); completeArgs.AddRange(args); var contentTypes = new MediaTypeCollection(); foreach (var arg in completeArgs) { var mediaType = new MediaType(arg); if (mediaType.MatchesAllSubTypes || mediaType.MatchesAllTypes) { throw new InvalidOperationException( Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); } contentTypes.Add(arg); } return(contentTypes); }
private IReadOnlyList <ApiRequestFormat> GetSupportedFormats(MediaTypeCollection contentTypes, Type type) { if (contentTypes.Count == 0) { contentTypes = new MediaTypeCollection { (string)null, }; } var results = new List <ApiRequestFormat>(); foreach (var contentType in contentTypes) { foreach (var formatter in _inputFormatters) { if (formatter is IApiRequestFormatMetadataProvider requestFormatMetadataProvider) { var supportedTypes = requestFormatMetadataProvider.GetSupportedContentTypes(contentType, type); if (supportedTypes != null) { foreach (var supportedType in supportedTypes) { results.Add(new ApiRequestFormat() { Formatter = formatter, MediaType = supportedType, }); } } } } } return(results); }
public override void OnException(ExceptionContext context) { var apiException = context.Exception as ApiException; if (apiException == null) { return; } int statusCode = apiException.StatusCode; if (string.IsNullOrEmpty(apiException.ResponseText)) { context.Result = new StatusCodeResult(statusCode); } else { var contentTypes = new MediaTypeCollection(); foreach (var type in apiException.Headers["Content-Type"]) { contentTypes.Add(type); } context.Result = new JsonResult(JObject.Parse(apiException.ResponseText)) { StatusCode = statusCode, }; } context.ExceptionHandled = true; logger.Warning(apiException.Message); }
private IOutputFormatter SelectFormatterUsingAnyAcceptableContentType( OutputFormatterCanWriteContext formatterContext, IList <IOutputFormatter> formatters, MediaTypeCollection acceptableContentTypes) { if (formatterContext == null) { throw new ArgumentNullException(nameof(formatterContext)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } if (acceptableContentTypes == null) { throw new ArgumentNullException(nameof(acceptableContentTypes)); } foreach (var formatter in formatters) { foreach (var contentType in acceptableContentTypes) { formatterContext.ContentType = new StringSegment(contentType); formatterContext.ContentTypeIsServerDefined = true; if (formatter.CanWriteResult(formatterContext)) { return(formatter); } } } return(null); }
/// <summary> /// Selects the <see cref="IOutputFormatter"/> to write the response. /// </summary> /// <param name="formatterContext">The <see cref="OutputFormatterWriteContext"/>.</param> /// <param name="contentTypes"> /// The list of content types provided by <see cref="ObjectResult.ContentTypes"/>. /// </param> /// <param name="formatters"> /// The list of <see cref="IOutputFormatter"/> instances to consider. /// </param> /// <returns> /// The selected <see cref="IOutputFormatter"/> or <c>null</c> if no formatter can write the response. /// </returns> protected virtual IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, MediaTypeCollection contentTypes, IList <IOutputFormatter> formatters) { if (formatterContext == null) { throw new ArgumentNullException(nameof(formatterContext)); } if (contentTypes == null) { throw new ArgumentNullException(nameof(contentTypes)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } var request = formatterContext.HttpContext.Request; var acceptableMediaTypes = GetAcceptableMediaTypes(contentTypes, request); var selectFormatterWithoutRegardingAcceptHeader = false; IOutputFormatter selectedFormatter = null; if (acceptableMediaTypes.Count == 0) { // There is either no Accept header value, or it contained */* and we // are not currently respecting the 'browser accept header'. Logger.NoAcceptForNegotiation(); selectFormatterWithoutRegardingAcceptHeader = true; } else { if (contentTypes.Count == 0) { // Use whatever formatter can meet the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( formatterContext, formatters, acceptableMediaTypes); } else { // Verify that a content type from the context is compatible with the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeadersAndContentTypes( formatterContext, formatters, acceptableMediaTypes, contentTypes); } if (selectedFormatter == null && !ReturnHttpNotAcceptable) { Logger.NoFormatterFromNegotiation(acceptableMediaTypes); selectFormatterWithoutRegardingAcceptHeader = true; } } if (selectFormatterWithoutRegardingAcceptHeader) { if (contentTypes.Count == 0) { selectedFormatter = SelectFormatterNotUsingContentType( formatterContext, formatters); } else { selectedFormatter = SelectFormatterUsingAnyAcceptableContentType( formatterContext, formatters, contentTypes); } } return(selectedFormatter); }
private void CalculateResponseFormats(ICollection <ApiResponseType> responseTypes, MediaTypeCollection declaredContentTypes) { var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType <IApiResponseTypeMetadataProvider>(); // 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"); foreach (var apiResponse in responseTypes) { var responseType = apiResponse.Type; if (responseType == null || responseType == typeof(void)) { continue; } apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType); foreach (var contentType in declaredContentTypes) { var isSupportedContentType = false; 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, }); } } } }
/// <inheritdoc /> void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes) => Produces.SetContentTypes(contentTypes);
/// <summary> /// Selects the <see cref="IOutputFormatter"/> to write the response based on the content type values /// present in <paramref name="acceptableContentTypes"/>. /// </summary> /// <param name="formatterContext">The <see cref="OutputFormatterWriteContext"/>.</param> /// <param name="formatters"> /// The list of <see cref="IOutputFormatter"/> instances to consider. /// </param> /// <param name="acceptableContentTypes"> /// The ordered content types from <see cref="ObjectResult.ContentTypes"/> in descending priority order. /// </param> /// <returns> /// The selected <see cref="IOutputFormatter"/> or <c>null</c> if no formatter can write the response. /// </returns> protected virtual IOutputFormatter SelectFormatterUsingAnyAcceptableContentType( OutputFormatterWriteContext formatterContext, IList<IOutputFormatter> formatters, MediaTypeCollection acceptableContentTypes) { if (formatterContext == null) { throw new ArgumentNullException(nameof(formatterContext)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } if (acceptableContentTypes == null) { throw new ArgumentNullException(nameof(acceptableContentTypes)); } foreach (var formatter in formatters) { foreach (var contentType in acceptableContentTypes) { formatterContext.ContentType = new StringSegment(contentType); if (formatter.CanWriteResult(formatterContext)) { return formatter; } } } return null; }
public void SetContentTypes(MediaTypeCollection contentTypes) { throw new NotImplementedException(); }
public override IOutputFormatter?SelectFormatter(OutputFormatterCanWriteContext context, IList <IOutputFormatter> formatters, MediaTypeCollection contentTypes) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } if (contentTypes == null) { throw new ArgumentNullException(nameof(contentTypes)); } ValidateContentTypes(contentTypes); if (formatters.Count == 0) { formatters = _formatters; if (formatters.Count == 0) { throw new InvalidOperationException(Resources.FormatOutputFormattersAreRequired( typeof(MvcOptions).FullName, nameof(MvcOptions.OutputFormatters), typeof(IOutputFormatter).FullName)); } } Log.RegisteredOutputFormatters(_logger, formatters); var request = context.HttpContext.Request; var acceptableMediaTypes = GetAcceptableMediaTypes(request); var selectFormatterWithoutRegardingAcceptHeader = false; IOutputFormatter?selectedFormatter = null; if (acceptableMediaTypes.Count == 0) { // There is either no Accept header value, or it contained */* and we // are not currently respecting the 'browser accept header'. Log.NoAcceptForNegotiation(_logger); selectFormatterWithoutRegardingAcceptHeader = true; } else { if (contentTypes.Count == 0) { Log.SelectingOutputFormatterUsingAcceptHeader(_logger, acceptableMediaTypes); // Use whatever formatter can meet the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( context, formatters, acceptableMediaTypes); } else { Log.SelectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes(_logger, acceptableMediaTypes, contentTypes); // Verify that a content type from the context is compatible with the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeadersAndContentTypes( context, formatters, acceptableMediaTypes, contentTypes); } if (selectedFormatter == null) { Log.NoFormatterFromNegotiation(_logger, acceptableMediaTypes); if (!_returnHttpNotAcceptable) { selectFormatterWithoutRegardingAcceptHeader = true; } } } if (selectFormatterWithoutRegardingAcceptHeader) { if (contentTypes.Count == 0) { Log.SelectingOutputFormatterWithoutUsingContentTypes(_logger); selectedFormatter = SelectFormatterNotUsingContentType( context, formatters); } else { Log.SelectingOutputFormatterUsingContentTypes(_logger, contentTypes); selectedFormatter = SelectFormatterUsingAnyAcceptableContentType( context, formatters, contentTypes); } } if (selectedFormatter != null) { Log.FormatterSelected(_logger, selectedFormatter, context); } return(selectedFormatter); }
/// <summary> /// Selects the <see cref="IOutputFormatter"/> to write the response. /// </summary> /// <param name="formatterContext">The <see cref="OutputFormatterWriteContext"/>.</param> /// <param name="contentTypes"> /// The list of content types provided by <see cref="ObjectResult.ContentTypes"/>. /// </param> /// <param name="formatters"> /// The list of <see cref="IOutputFormatter"/> instances to consider. /// </param> /// <returns> /// The selected <see cref="IOutputFormatter"/> or <c>null</c> if no formatter can write the response. /// </returns> protected virtual IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, MediaTypeCollection contentTypes, IList<IOutputFormatter> formatters) { if (formatterContext == null) { throw new ArgumentNullException(nameof(formatterContext)); } if (contentTypes == null) { throw new ArgumentNullException(nameof(contentTypes)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } // Check if any content-type was explicitly set (for example, via ProducesAttribute // or URL path extension mapping). If yes, then ignore content-negotiation and use this content-type. if (contentTypes.Count == 1) { Logger.SkippedContentNegotiation(contentTypes[0]); return SelectFormatterUsingAnyAcceptableContentType(formatterContext, formatters, contentTypes); } var request = formatterContext.HttpContext.Request; var mediaTypes = GetMediaTypes(contentTypes, request); IOutputFormatter selectedFormatter = null; if (contentTypes.Count == 0) { // Check if we have enough information to do content-negotiation, otherwise get the first formatter // which can write the type. Let the formatter choose the Content-Type. if (!(mediaTypes.Count > 0)) { Logger.NoAcceptForNegotiation(); return SelectFormatterNotUsingAcceptHeaders(formatterContext, formatters); } // // Content-Negotiation starts from this point on. // // 1. Select based on sorted accept headers. selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( formatterContext, formatters, mediaTypes); // 2. No formatter was found based on Accept header. Fallback to the first formatter which can write // the type. Let the formatter choose the Content-Type. if (selectedFormatter == null) { Logger.NoFormatterFromNegotiation(mediaTypes); // Set this flag to indicate that content-negotiation has failed to let formatters decide // if they want to write the response or not. formatterContext.FailedContentNegotiation = true; return SelectFormatterNotUsingAcceptHeaders(formatterContext, formatters); } } else { if (mediaTypes.Count > 0) { selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( formatterContext, formatters, mediaTypes); } if (selectedFormatter == null) { // Either there were no acceptHeaders that were present OR // There were no accept headers which matched OR // There were acceptHeaders which matched but there was no formatter // which supported any of them. // In any of these cases, if the user has specified content types, // do a last effort to find a formatter which can write any of the user specified content type. selectedFormatter = SelectFormatterUsingAnyAcceptableContentType( formatterContext, formatters, contentTypes); } } return selectedFormatter; }
private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { var completeArgs = new List<string>(); completeArgs.Add(firstArg); completeArgs.AddRange(args); var contentTypes = new MediaTypeCollection(); foreach (var arg in completeArgs) { var mediaType = new MediaType(arg); if (mediaType.MatchesAllSubTypes || mediaType.MatchesAllTypes) { throw new InvalidOperationException( Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); } contentTypes.Add(arg); } return contentTypes; }
/// <inheritdoc /> public void SetContentTypes(MediaTypeCollection contentTypes) { contentTypes.Clear(); foreach (var contentType in ContentTypes) { contentTypes.Add(contentType); } }
public ObjectResult(object value) { Value = value; Formatters = new FormatterCollection<IOutputFormatter>(); ContentTypes = new MediaTypeCollection(); }
private void ValidateContentTypes(MediaTypeCollection contentTypes) { if (contentTypes == null) { return; } for (var i = 0; i < contentTypes.Count; i++) { var contentType = contentTypes[i]; var parsedContentType = new MediaType(contentType); if (parsedContentType.MatchesAllTypes || parsedContentType.MatchesAllSubTypes) { var message = Resources.FormatObjectResult_MatchAllContentType( contentType, nameof(ObjectResult.ContentTypes)); throw new InvalidOperationException(message); } } }
/// <summary> /// Selects the <see cref="IOutputFormatter"/> to write the response based on the content type values /// present in <paramref name="sortedAcceptableContentTypes"/> and <paramref name="possibleOutputContentTypes"/>. /// </summary> /// <param name="formatterContext">The <see cref="OutputFormatterWriteContext"/>.</param> /// <param name="formatters"> /// The list of <see cref="IOutputFormatter"/> instances to consider. /// </param> /// <param name="sortedAcceptableContentTypes"> /// The ordered content types from the <c>Accept</c> header, sorted by descending q-value. /// </param> /// <param name="possibleOutputContentTypes"> /// The ordered content types from <see cref="ObjectResult.ContentTypes"/> in descending priority order. /// </param> /// <returns> /// The selected <see cref="IOutputFormatter"/> or <c>null</c> if no formatter can write the response. /// </returns> protected virtual IOutputFormatter SelectFormatterUsingSortedAcceptHeadersAndContentTypes( OutputFormatterWriteContext formatterContext, IList<IOutputFormatter> formatters, IList<MediaTypeSegmentWithQuality> sortedAcceptableContentTypes, MediaTypeCollection possibleOutputContentTypes) { for (var i = 0; i < sortedAcceptableContentTypes.Count; i++) { var acceptableContentType = new MediaType(sortedAcceptableContentTypes[i].MediaType); for (var j = 0; j < possibleOutputContentTypes.Count; j++) { var candidateContentType = new MediaType(possibleOutputContentTypes[j]); if (candidateContentType.IsSubsetOf(acceptableContentType)) { for (var k = 0; k < formatters.Count; k++) { var formatter = formatters[k]; formatterContext.ContentType = new StringSegment(possibleOutputContentTypes[j]); if (formatter.CanWriteResult(formatterContext)) { return formatter; } } } } } return null; }
public static partial void ActionDoesNotSupportFormatFilterContentType(ILogger logger, string formatFilterContentType, MediaTypeCollection supportedMediaTypes);
private static void SetAllowedMediaType(MediaTypeHeaderValue mediaType, MediaTypeCollection supportedMediaTypes) { supportedMediaTypes.Clear(); supportedMediaTypes.Add(mediaType); }
public void SetContentTypes_ClearsAndSetsContentTypes() { // Arrange var attribute = new ConsumesAttribute("application/json", "text/json"); var contentTypes = new MediaTypeCollection() { MediaTypeHeaderValue.Parse("application/xml"), MediaTypeHeaderValue.Parse("text/xml"), }; // Act attribute.SetContentTypes(contentTypes); // Assert Assert.Collection( contentTypes.OrderBy(t => t), t => Assert.Equal("application/json", t), t => Assert.Equal("text/json", t)); }
public override IOutputFormatter SelectFormatter(OutputFormatterCanWriteContext context, IList <IOutputFormatter> formatters, MediaTypeCollection mediaTypes) { if (!context.HttpContext.Request.Headers["Accept"].First().StartsWith("application/vnd.")) { return(_fallbackSelector.SelectFormatter(context, formatters, mediaTypes)); } if (formatters.Count == 0) { formatters = _formatters; } context.ContentType = GetContentTypeFromAcceptHeader(context.HttpContext.Request); var formatter = formatters.FirstOrDefault(x => x.CanWriteResult(context)); context.ContentType = context.HttpContext.Request.Headers["Accept"].First(); return(formatter); }
public void SetContentTypes(MediaTypeCollection contentTypes) { contentTypes.Add(new MediaTypeHeaderValue("application/json")); }
/// <summary> /// Selects the <see cref="IOutputFormatter"/> to write the response. /// </summary> /// <param name="formatterContext">The <see cref="OutputFormatterWriteContext"/>.</param> /// <param name="contentTypes"> /// The list of content types provided by <see cref="ObjectResult.ContentTypes"/>. /// </param> /// <param name="formatters"> /// The list of <see cref="IOutputFormatter"/> instances to consider. /// </param> /// <returns> /// The selected <see cref="IOutputFormatter"/> or <c>null</c> if no formatter can write the response. /// </returns> protected virtual IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, MediaTypeCollection contentTypes, IList<IOutputFormatter> formatters) { if (formatterContext == null) { throw new ArgumentNullException(nameof(formatterContext)); } if (contentTypes == null) { throw new ArgumentNullException(nameof(contentTypes)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } var request = formatterContext.HttpContext.Request; var acceptableMediaTypes = GetAcceptableMediaTypes(contentTypes, request); var selectFormatterWithoutRegardingAcceptHeader = false; IOutputFormatter selectedFormatter = null; if (acceptableMediaTypes.Count == 0) { // There is either no Accept header value, or it contained */* and we // are not currently respecting the 'browser accept header'. Logger.NoAcceptForNegotiation(); selectFormatterWithoutRegardingAcceptHeader = true; } else { if (contentTypes.Count == 0) { // Use whatever formatter can meet the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( formatterContext, formatters, acceptableMediaTypes); } else { // Verify that a content type from the context is compatible with the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeadersAndContentTypes( formatterContext, formatters, acceptableMediaTypes, contentTypes); } if (selectedFormatter == null && !ReturnHttpNotAcceptable) { Logger.NoFormatterFromNegotiation(acceptableMediaTypes); selectFormatterWithoutRegardingAcceptHeader = true; } } if (selectFormatterWithoutRegardingAcceptHeader) { if (contentTypes.Count == 0) { selectedFormatter = SelectFormatterNotUsingContentType( formatterContext, formatters); } else { selectedFormatter = SelectFormatterUsingAnyAcceptableContentType( formatterContext, formatters, contentTypes); } } return selectedFormatter; }
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 List<MediaTypeSegmentWithQuality> GetAcceptableMediaTypes( MediaTypeCollection contentTypes, HttpRequest request) { var result = new List<MediaTypeSegmentWithQuality>(); AcceptHeaderParser.ParseAcceptHeader(request.Headers[HeaderNames.Accept], result); for (int i = 0; i < result.Count; i++) { var mediaType = new MediaType(result[i].MediaType); if (!RespectBrowserAcceptHeader && mediaType.MatchesAllSubTypes && mediaType.MatchesAllTypes) { result.Clear(); return result; } } result.Sort((left, right) => left.Quality > right.Quality ? -1 : (left.Quality == right.Quality ? 0 : 1)); return result; }
/// <summary> /// Initializes an instance of <see cref="ProducesAttribute"/>. /// </summary> /// <param name="type">The <see cref="Type"/> of object that is going to be written in the response.</param> public ProducesAttribute(Type type) { Type = type ?? throw new ArgumentNullException(nameof(type)); ContentTypes = new MediaTypeCollection(); }
// 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 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); }
private static bool InAcceptableMediaTypes(StringSegment mediaType, MediaTypeCollection acceptableMediaTypes) { if (acceptableMediaTypes.Count == 0) { return true; } var parsedMediaType = new MediaType(mediaType); for (int i = 0; i < acceptableMediaTypes.Count; i++) { var acceptableMediaType = new MediaType(acceptableMediaTypes[i]); if (acceptableMediaType.IsSubsetOf(parsedMediaType)) { return true; } } return false; }
/// <inheritdoc /> void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes) { // Users are supposed to use the 'Produces' attribute to set the content types that an action can support. }
public void SelectFormatter_WithMultipleProvidedContentTypes_DoesConneg( MediaTypeCollection contentTypes, string acceptHeader, string expectedContentType) { // Arrange var executor = CreateExecutor(); var formatters = new List<IOutputFormatter> { new CannotWriteFormatter(), new TestJsonOutputFormatter(), }; var context = new OutputFormatterWriteContext( new DefaultHttpContext(), new TestHttpResponseStreamWriterFactory().CreateWriter, objectType: null, @object: null); context.HttpContext.Request.Headers[HeaderNames.Accept] = acceptHeader; // Act var formatter = executor.SelectFormatter( context, contentTypes, formatters); // Assert Assert.Same(formatters[1], formatter); Assert.Equal(new StringSegment(expectedContentType), context.ContentType); }
// 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, }); } } }
public async Task ExecuteAsync_MatchAllContentType_Throws(string[] contentTypes, string invalidContentType) { // Arrange var result = new ObjectResult("input"); var mediaTypes = new MediaTypeCollection(); foreach (var contentType in contentTypes) { mediaTypes.Add(contentType); } result.ContentTypes = mediaTypes; var executor = CreateExecutor(); var actionContext = new ActionContext() { HttpContext = new DefaultHttpContext() }; // Act & Assert var exception = await Assert.ThrowsAsync<InvalidOperationException>( () => executor.ExecuteAsync(actionContext, result)); var expectedMessage = string.Format("The content-type '{0}' added in the 'ContentTypes' property is " + "invalid. Media types which match all types or match all subtypes are not supported.", invalidContentType); Assert.Equal(expectedMessage, exception.Message); }
public static void SelectingOutputFormatterUsingContentTypes(this ILogger logger, MediaTypeCollection mediaTypeCollection) { _selectingOutputFormatterUsingContentTypes(logger, mediaTypeCollection, null); }
new public IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, MediaTypeCollection contentTypes, IList<IOutputFormatter> formatters) { return base.SelectFormatter(formatterContext, contentTypes, formatters); }
public override IOutputFormatter SelectFormatter(OutputFormatterCanWriteContext context, IList <IOutputFormatter> formatters, MediaTypeCollection contentTypes) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (formatters == null) { throw new ArgumentNullException(nameof(formatters)); } if (contentTypes == null) { throw new ArgumentNullException(nameof(contentTypes)); } ValidateContentTypes(contentTypes); if (formatters.Count == 0) { formatters = _formatters; if (formatters.Count == 0) { throw new InvalidOperationException($"At least one '{nameof(IOutputFormatter)}' is required to format a response."); } } _logger.RegisteredOutputFormatters(formatters); var request = context.MazeContext.Request; var acceptableMediaTypes = GetAcceptableMediaTypes(request); var selectFormatterWithoutRegardingAcceptHeader = false; IOutputFormatter selectedFormatter = null; if (acceptableMediaTypes.Count == 0) { // There is either no Accept header value, or it contained */* and we // are not currently respecting the 'browser accept header'. _logger.NoAcceptForNegotiation(); selectFormatterWithoutRegardingAcceptHeader = true; } else { if (contentTypes.Count == 0) { _logger.SelectingOutputFormatterUsingAcceptHeader(acceptableMediaTypes); // Use whatever formatter can meet the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( context, formatters, acceptableMediaTypes); } else { _logger.SelectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes(acceptableMediaTypes, contentTypes); // Verify that a content type from the context is compatible with the client's request selectedFormatter = SelectFormatterUsingSortedAcceptHeadersAndContentTypes( context, formatters, acceptableMediaTypes, contentTypes); } if (selectedFormatter == null) { _logger.NoFormatterFromNegotiation(acceptableMediaTypes); if (!_returnHttpNotAcceptable) { selectFormatterWithoutRegardingAcceptHeader = true; } } } if (selectFormatterWithoutRegardingAcceptHeader) { if (contentTypes.Count == 0) { _logger.SelectingOutputFormatterWithoutUsingContentTypes(); selectedFormatter = SelectFormatterNotUsingContentType( context, formatters); } else { _logger.SelectingOutputFormatterUsingContentTypes(contentTypes); selectedFormatter = SelectFormatterUsingAnyAcceptableContentType( context, formatters, contentTypes); } } if (selectedFormatter == null) { // No formatter supports this. _logger.NoFormatter(context); return(null); } _logger.FormatterSelected(selectedFormatter, context); return(selectedFormatter); }
protected override IOutputFormatter SelectFormatter( OutputFormatterWriteContext formatterContext, MediaTypeCollection contentTypes, IList<IOutputFormatter> formatters) { SelectedOutputFormatter = base.SelectFormatter(formatterContext, contentTypes, formatters); return SelectedOutputFormatter; }
/// <summary> /// Selects an <see cref="IOutputFormatter"/> to write the response based on the provided values and the current request. /// </summary> /// <param name="context">The <see cref="OutputFormatterCanWriteContext"/> associated with the current request.</param> /// <param name="formatters">A list of formatters to use; this acts as an override to <see cref="MvcOptions.OutputFormatters"/>.</param> /// <param name="mediaTypes">A list of media types to use; this acts as an override to the <c>Accept</c> header. </param> /// <returns>The selected <see cref="IOutputFormatter"/>, or <c>null</c> if one could not be selected.</returns> public abstract IOutputFormatter?SelectFormatter(OutputFormatterCanWriteContext context, IList <IOutputFormatter> formatters, MediaTypeCollection mediaTypes);
private bool IsSuperSetOfAnySupportedMediaType(string contentType, MediaTypeCollection supportedMediaTypes) { var parsedContentType = new MediaType(contentType); for (var i = 0; i < supportedMediaTypes.Count; i++) { var supportedMediaType = new MediaType(supportedMediaTypes[i]); if (supportedMediaType.IsSubsetOf(parsedContentType)) { return true; } } return false; }
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); }
/// <summary> /// As a <see cref="IResourceFilter"/>, this filter looks at the request and rejects it before going ahead if /// 1. The format in the request does not match any format in the map. /// 2. If there is a conflicting producesFilter. /// </summary> /// <param name="context">The <see cref="ResourceExecutingContext"/>.</param> public void OnResourceExecuting(ResourceExecutingContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } var format = GetFormat(context); if (format == null) { // no format specified by user, so the filter is muted return; } var contentType = _options.FormatterMappings.GetMediaTypeMappingForFormat(format); if (contentType == null) { // no contentType exists for the format, return 404 context.Result = new NotFoundResult(); return; } // Determine media types this action supports. var responseTypeFilters = context.Filters.OfType<IApiResponseMetadataProvider>(); var supportedMediaTypes = new MediaTypeCollection(); foreach (var filter in responseTypeFilters) { filter.SetContentTypes(supportedMediaTypes); } // Check if support is adequate for requested media type. if (supportedMediaTypes.Count != 0) { // We need to check if the action can generate the content type the user asked for. That is, treat the // request's format and IApiResponseMetadataProvider-provided content types similarly to an Accept // header and an output formatter's SupportedMediaTypes: Confirm action supports a more specific media // type than requested e.g. OK if "text/*" requested and action supports "text/plain". if (!IsSuperSetOfAnySupportedMediaType(contentType, supportedMediaTypes)) { context.Result = new NotFoundResult(); } } }
public void SetContentTypes(MediaTypeCollection contentTypes) { }
public static partial void SelectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes(ILogger logger, IEnumerable <MediaTypeSegmentWithQuality> acceptHeader, MediaTypeCollection explicitContentTypes);
/// <summary> /// Creates a new <see cref="ObjectResult"/> instance with the provided <paramref name="value"/>. /// </summary> /// <param name="value"></param> public ObjectResult(object?value) { Value = value; Formatters = new FormatterCollection <IOutputFormatter>(); _contentTypes = new MediaTypeCollection(); }
public static partial void SelectingOutputFormatterUsingContentTypes(ILogger logger, MediaTypeCollection explicitContentTypes);