// 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.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.Net.Http.Headers; namespace Microsoft.AspNetCore.Mvc { /// /// A filter that specifies the expected the action will return and the supported /// response content types. The value is used to set /// . /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] public class ProducesAttribute : Attribute, IResultFilter, IOrderedFilter, IApiResponseMetadataProvider { /// /// Initializes an instance of . /// /// The of object that is going to be written in the response. public ProducesAttribute(Type type) { if (type == null) { throw new ArgumentNullException(nameof(type)); } Type = type; ContentTypes = new MediaTypeCollection(); } /// /// Initializes an instance of with allowed content types. /// /// The allowed content type for a response. /// Additional allowed content types for a response. public ProducesAttribute(string contentType, params string[] additionalContentTypes) { if (contentType == null) { throw new ArgumentNullException(nameof(contentType)); } // We want to ensure that the given provided content types are valid values, so // we validate them using the semantics of MediaTypeHeaderValue. MediaTypeHeaderValue.Parse(contentType); for (var i = 0; i < additionalContentTypes.Length; i++) { MediaTypeHeaderValue.Parse(additionalContentTypes[i]); } ContentTypes = GetContentTypes(contentType, additionalContentTypes); } /// public Type Type { get; set; } /// /// Gets or sets the supported response content types. Used to set . /// public MediaTypeCollection ContentTypes { get; set; } /// public int StatusCode => StatusCodes.Status200OK; /// public int Order { get; set; } /// public virtual void OnResultExecuting(ResultExecutingContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.Result is ObjectResult objectResult) { // Check if there are any IFormatFilter in the pipeline, and if any of them is active. If there is one, // do not override the content type value. for (var i = 0; i < context.Filters.Count; i++) { var filter = context.Filters[i] as IFormatFilter; if (filter?.GetFormat(context) != null) { return; } } SetContentTypes(objectResult.ContentTypes); } } /// public virtual void OnResultExecuted(ResultExecutedContext context) { } /// public void SetContentTypes(MediaTypeCollection contentTypes) { contentTypes.Clear(); foreach (var contentType in ContentTypes) { contentTypes.Add(contentType); } } private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { var completeArgs = new List(); completeArgs.Add(firstArg); completeArgs.AddRange(args); var contentTypes = new MediaTypeCollection(); foreach (var arg in completeArgs) { var contentType = new MediaType(arg); if (contentType.HasWildcard) { throw new InvalidOperationException( Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); } contentTypes.Add(arg); } return contentTypes; } } }