├── src ├── key.snk ├── Swashbuckle.AspNetCore.Filters.Abstractions │ ├── Examples │ │ ├── IExamplesProvider.cs │ │ ├── IMultipleExamplesProvider.cs │ │ ├── ISwaggerExample.cs │ │ ├── SwaggerResponseExampleAttribute.cs │ │ ├── SwaggerRequestExampleAttribute.cs │ │ └── SwaggerExample.cs │ ├── Swashbuckle.AspNetCore.Filters.Abstractions.csproj │ └── ResponseHeaders │ │ └── SwaggerResponseHeaderAttribute.cs ├── Swashbuckle.AspNetCore.Filters │ ├── AppendAuthorizeToSummary │ │ ├── PolicySelectorWithLabel.cs │ │ ├── AppendAuthorizeToSummaryOperationFilter.cs │ │ └── AppendAuthorizeToSummaryOperationFilterT.cs │ ├── Extensions │ │ ├── ObjectExtensions.cs │ │ ├── OperationFilterContextExtensions.cs │ │ ├── SwaggerGenOptionsExtensions.cs │ │ ├── TypeExtensions.cs │ │ ├── ServiceProviderExtensions.cs │ │ └── ServiceCollectionExtensions.cs │ ├── Swashbuckle.AspNetCore.Filters.csproj │ ├── AddHeaderOperationFilter.cs │ ├── SecurityRequirementsOperationFilter │ │ ├── SecurityRequirementsOperationFilter.cs │ │ └── SecurityRequirementsOperationFilterT.cs │ ├── ResponseHeaders │ │ └── AddResponseHeadersFilter.cs │ └── Examples │ │ ├── ExamplesOperationFilter.cs │ │ ├── ExamplesConverter.cs │ │ ├── ServiceProviderExamplesOperationFilter.cs │ │ ├── ResponseExample.cs │ │ ├── RequestExample.cs │ │ └── MvcOutputFormatter.cs └── Directory.Build.props ├── test ├── Swashbuckle.AspNetCore.Filters.Test │ ├── Properties │ │ └── debugSettings.json │ ├── key.snk │ ├── TestFixtures │ │ ├── Examples │ │ │ ├── RequestWrapper.cs │ │ │ ├── Title.cs │ │ │ ├── Job.cs │ │ │ ├── Child.cs │ │ │ ├── TitleExample.cs │ │ │ ├── ListStringExample.cs │ │ │ ├── PersonRequestExample.cs │ │ │ ├── IPersonRequest.cs │ │ │ ├── IPersonRequestExample.cs │ │ │ ├── PersonRequestAutoExample.cs │ │ │ ├── PersonResponseExample.cs │ │ │ ├── PersonResponseAutoExample.cs │ │ │ ├── DictionaryRequestExample.cs │ │ │ ├── DictionaryAutoRequestExample.cs │ │ │ ├── PersonRequest.cs │ │ │ ├── PeopleResponseExample.cs │ │ │ ├── PersonResponseMultipleExamples.cs │ │ │ ├── PersonResponse.cs │ │ │ └── PersonRequestMultipleExamples.cs │ │ └── Fakes │ │ │ ├── FakeLoggerFactory.cs │ │ │ ├── FakeApiDescriptionGroupCollectionProvider.cs │ │ │ ├── FakeEndpointConventionBuilder.cs │ │ │ ├── FormatterOptions.cs │ │ │ ├── FakeControllers.cs │ │ │ └── FakeActions.cs │ ├── Extensions │ │ ├── ObjectExtensions.cs │ │ ├── OpenApiExtensions.cs │ │ └── ExampleAssertExtensions.cs │ ├── Examples │ │ ├── TypeExtensionsTests.cs │ │ ├── ServiceProviderExamplesOperationFilterWithXmlDataContractTests.cs │ │ └── MvcFormatterTests.cs │ ├── Swashbuckle.AspNetCore.Filters.Test.csproj │ ├── BaseOperationFilterTests.cs │ ├── AppendAuthorizeToSummaryOperationFilterTests.cs │ └── SecurityRequirementsOperationFilterTests.cs ├── WebApi8 │ ├── Models │ │ ├── RequestWrapper.cs │ │ ├── ErrorResponse.cs │ │ ├── Title.cs │ │ ├── ResponseWrapper.cs │ │ ├── Examples │ │ │ ├── TitleExample.cs │ │ │ ├── StringResponseExample.cs │ │ │ ├── StringRequestExample.cs │ │ │ ├── InternalServerResponseExample.cs │ │ │ ├── NotFoundResponseExample.cs │ │ │ ├── PersonRequestExample.cs │ │ │ ├── MaleRequestExample.cs │ │ │ ├── PersonRequestExample2.cs │ │ │ ├── PersonRequestAutoExample.cs │ │ │ ├── PersonResponseExample.cs │ │ │ ├── PersonResponseExample2.cs │ │ │ ├── PersonResponseAutoExample.cs │ │ │ ├── DynamicDataRequestExample.cs │ │ │ ├── WrappedPersonRequestExample.cs │ │ │ ├── DictionaryRequestExample.cs │ │ │ ├── DictionaryResponseExample.cs │ │ │ ├── DynamicDataResponseExample.cs │ │ │ ├── PersonRequestDependencyInjectionExample.cs │ │ │ ├── JsonPatchPersonRequestExample.cs │ │ │ ├── WrappedPersonResponseExample.cs │ │ │ ├── ListPeopleRequestExample.cs │ │ │ ├── PeopleResponseExample.cs │ │ │ ├── PersonResponseMultipleExamples.cs │ │ │ └── PersonRequestMultipleExamples.cs │ │ ├── PeopleRequest.cs │ │ ├── MaleRequest.cs │ │ ├── PersonRequest.cs │ │ ├── DynamicData.cs │ │ └── PersonResponse.cs │ ├── WebApi8.http │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Properties │ │ └── launchSettings.json │ ├── WebApi8.csproj │ ├── Program.cs │ └── Controllers │ │ └── ValuesController.cs └── MinimalApi8 │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── MinimalApi8.csproj │ ├── Properties │ └── launchSettings.json │ └── Program.cs ├── tools ├── key.snk └── NuGet instructions.txt ├── NuGet.config ├── .gitignore ├── .editorconfig ├── .vscode ├── tasks.json └── launch.json ├── LICENSE ├── appveyor.yml ├── Swashbuckle.AspNetCore.Filters.sln └── CHANGELOG.md /src/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattfrear/Swashbuckle.AspNetCore.Filters/HEAD/src/key.snk -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Properties/debugSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Profiles": [] 3 | } -------------------------------------------------------------------------------- /tools/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattfrear/Swashbuckle.AspNetCore.Filters/HEAD/tools/key.snk -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattfrear/Swashbuckle.AspNetCore.Filters/HEAD/test/Swashbuckle.AspNetCore.Filters.Test/key.snk -------------------------------------------------------------------------------- /test/WebApi8/Models/RequestWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Models 2 | { 3 | public class RequestWrapper 4 | { 5 | public T Body { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /test/WebApi8/WebApi8.http: -------------------------------------------------------------------------------- 1 | @WebApi8_HostAddress = http://localhost:5075 2 | 3 | GET {{WebApi8_HostAddress}}/weatherforecast/ 4 | Accept: application/json 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /test/WebApi8/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/MinimalApi8/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/WebApi8/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /test/MinimalApi8/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /test/WebApi8/Models/ErrorResponse.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Models 2 | { 3 | public class ErrorResponse 4 | { 5 | public int ErrorCode { get; set; } 6 | 7 | public string Message { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Examples/IExamplesProvider.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters 2 | { 3 | public interface IExamplesProvider 4 | { 5 | T GetExamples(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Title.cs: -------------------------------------------------------------------------------- 1 | namespace WebApi.Models 2 | { 3 | public enum Title 4 | { 5 | None = 0, 6 | Dr, 7 | Miss, 8 | Mr, 9 | Mrs, 10 | Ms 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/RequestWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | public class RequestWrapper 4 | { 5 | public T Body { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/ResponseWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace WebApi.Models 4 | { 5 | public class ResponseWrapper 6 | { 7 | public HttpStatusCode StatusCode { get; set; } 8 | public T Body { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Examples/IMultipleExamplesProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters 4 | { 5 | public interface IMultipleExamplesProvider 6 | { 7 | IEnumerable> GetExamples(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/Title.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | public enum Title 4 | { 5 | None = 0, 6 | Dr, 7 | Miss, 8 | Mr, 9 | Mrs, 10 | Ms 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/TitleExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class TitleExample : IExamplesProvider 6 | { 7 | public Title? GetExamples() 8 | { 9 | return Title.Miss; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | App_Data 4 | [Pp]ackages 5 | Logs 6 | PrecompiledWeb/ 7 | 8 | _ReSharper.* 9 | *crunch* 10 | 11 | *.user 12 | *.suo 13 | *.cache 14 | *.dotCover 15 | *.dotsettings 16 | !*.proj 17 | 18 | # Visual Studio 2015 cache/options directory 19 | .vs/ 20 | .ionide/ 21 | *.nupkg 22 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/Job.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | public class Job 6 | { 7 | [Description("The name of the job")] 8 | public string Name { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/Child.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | public class Child 6 | { 7 | [Description("The child's full name")] 8 | public string Name { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/StringResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class StringResponseExample : IExamplesProvider 6 | { 7 | public string GetExamples() 8 | { 9 | return "Hello"; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Swashbuckle.AspNetCore.Filters.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/TitleExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | internal class TitleExample : IExamplesProvider 4 | { 5 | public Title? GetExamples() 6 | { 7 | return Title.Miss; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/StringRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class StringRequestExample : IExamplesProvider 6 | { 7 | public string GetExamples() 8 | { 9 | return "{\"test\":\"string request example\"}"; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/InternalServerResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class InternalServerResponseExample : IExamplesProvider 6 | { 7 | public ErrorResponse GetExamples() 8 | { 9 | return new ErrorResponse { ErrorCode = 500 }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/NotFoundResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class NotFoundResponseExample : IExamplesProvider 6 | { 7 | public ErrorResponse GetExamples() 8 | { 9 | return new ErrorResponse { ErrorCode = 404, Message = "The entity was not found" }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonRequestExample : IExamplesProvider 6 | { 7 | public PersonRequest GetExamples() 8 | { 9 | return new PersonRequest { Title = Title.Mr, Age = 24, FirstName = "Dave", Income = null }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/PeopleRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace WebApi.Models 4 | { 5 | public class PeopleRequest 6 | { 7 | public Title Title { get; set; } 8 | 9 | public int Age { get; set; } 10 | 11 | [Description("The first name in a list")] 12 | public string FirstName { get; set; } 13 | 14 | public decimal? Income { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/AppendAuthorizeToSummary/PolicySelectorWithLabel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System; 3 | 4 | namespace Swashbuckle.AspNetCore.Filters 5 | { 6 | public class PolicySelectorWithLabel where T : Attribute 7 | { 8 | public Func, IEnumerable> Selector { get; set; } 9 | 10 | public string Label { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/MaleRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class MaleRequestExample : IExamplesProvider 6 | { 7 | public MaleRequest GetExamples() 8 | { 9 | return new MaleRequest { Title = Title.Mr, Age = 24, FirstName = "Steve Auto", Income = null }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonRequestExample2.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonRequestExample2 : IExamplesProvider 6 | { 7 | public PersonRequest GetExamples() 8 | { 9 | return new PersonRequest { Title = Title.Miss, Age = 32, FirstName = "Angela", Income = null }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonRequestAutoExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonRequestAutoExample : IExamplesProvider 6 | { 7 | public PersonRequest GetExamples() 8 | { 9 | return new PersonRequest { Title = Title.Dr, FirstName = "Jack Auto!", Age = 27, Income = null }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/ListStringExample.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | internal class ListStringExample : IExamplesProvider> 6 | { 7 | public IEnumerable GetExamples() 8 | { 9 | return new[] { "Hello", "there" }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonRequestExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | internal class PersonRequestExample : IExamplesProvider 4 | { 5 | public PersonRequest GetExamples() 6 | { 7 | return new PersonRequest { Title = Title.Mr, Age = 24, FirstName = "Dave", Income = null }; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/IPersonRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | public interface IPersonRequest 4 | { 5 | int Age { get; set; } 6 | Child[] Children { get; set; } 7 | string FirstName { get; set; } 8 | decimal? Income { get; set; } 9 | Job Job { get; set; } 10 | Title Title { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonResponseExample : IExamplesProvider 6 | { 7 | public PersonResponse GetExamples() 8 | { 9 | return new PersonResponse { Id = 123, Title = Title.Dr, FirstName = "John", LastName = "Doe", Age = 27, Income = null }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/IPersonRequestExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | internal class IPersonRequestAutoExample : IExamplesProvider 4 | { 5 | public IPersonRequest GetExamples() 6 | { 7 | return new PersonRequest { Title = Title.Mr, Age = 24, FirstName = "Dave Auto", Income = null }; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonRequestAutoExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | internal class PersonRequestAutoExample : IExamplesProvider 4 | { 5 | public PersonRequest GetExamples() 6 | { 7 | return new PersonRequest { Title = Title.Mr, Age = 24, FirstName = "Dave Auto", Income = null }; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonResponseExample2.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonResponseExample2 : IExamplesProvider 6 | { 7 | public PersonResponse GetExamples() 8 | { 9 | return new PersonResponse { Id = 123, Title = Title.Dr, FirstName = "Hank", LastName = "Thomas", Age = 27, Income = null }; 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/MaleRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace WebApi.Models 5 | { 6 | public class MaleRequest 7 | { 8 | public Title Title { get; set; } 9 | 10 | public int Age { get; set; } 11 | 12 | [Description("The first name of the person")] 13 | public string FirstName { get; set; } 14 | 15 | [Obsolete] 16 | public decimal? Income { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonResponseAutoExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonResponseAutoExample : IExamplesProvider 6 | { 7 | public PersonResponse GetExamples() 8 | { 9 | return new PersonResponse { Id = 123, Title = Title.Dr, FirstName = "John Auto!", LastName = "Doe", Age = 27, Income = null }; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonResponseExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | internal class PersonResponseExample : IExamplesProvider 4 | { 5 | public PersonResponse GetExamples() 6 | { 7 | return new PersonResponse { Id = 123, Title = Title.Dr, FirstName = "John", LastName = "Doe", Age = 27, Income = null }; 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonResponseAutoExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 2 | { 3 | internal class PersonResponseAutoExample : IExamplesProvider 4 | { 5 | public PersonResponse GetExamples() 6 | { 7 | return new PersonResponse { Id = 123, Title = Title.Dr, FirstName = "John Auto!", LastName = "Doe", Age = 27, Income = null }; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Extensions 4 | { 5 | internal static class ObjectExtensions 6 | { 7 | public static string FormatXml(this string unformattedXml) 8 | { 9 | if (string.IsNullOrEmpty(unformattedXml)) 10 | { 11 | return string.Empty; 12 | } 13 | 14 | return XDocument.Parse(unformattedXml).ToString(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/DynamicDataRequestExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Swashbuckle.AspNetCore.Filters; 6 | 7 | namespace WebApi.Models.Examples 8 | { 9 | public class DynamicDataRequestExample : IExamplesProvider 10 | { 11 | public DynamicData GetExamples() 12 | { 13 | var ret = new DynamicData(); 14 | ret.Payload.Add("DynamicProp", 1); 15 | return ret; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/WrappedPersonRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class WrappedPersonRequestExample : IExamplesProvider> 6 | { 7 | public RequestWrapper GetExamples() 8 | { 9 | return new RequestWrapper 10 | { 11 | Body = new PersonRequest {Title = Title.Ms, Age = 24, FirstName = "Generic Sally", Income = null} 12 | }; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/DictionaryRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | using System.Collections.Generic; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | public class DictionaryRequestExample : IExamplesProvider> 7 | { 8 | public Dictionary GetExamples() 9 | { 10 | return new Dictionary() 11 | { 12 | {"PropertyInt", 1}, 13 | {"PropertyString", "Some string"} 14 | }; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/DictionaryResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | using System.Collections.Generic; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | public class DictionaryResponseExample : IExamplesProvider> 7 | { 8 | public Dictionary GetExamples() 9 | { 10 | return new Dictionary() 11 | { 12 | {"PropertyInt", 5}, 13 | {"PropertyString", "Another string"} 14 | }; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/MinimalApi8/MinimalApi8.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | MinimalApi8 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/DictionaryRequestExample.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | public class DictionaryRequestExample : IExamplesProvider> 6 | { 7 | public Dictionary GetExamples() 8 | { 9 | return new Dictionary() 10 | { 11 | {"PropertyInt", 1}, 12 | {"PropertyString", "Some string"} 13 | }; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/DictionaryAutoRequestExample.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | public class DictionaryAutoRequestExample : IExamplesProvider> 6 | { 7 | public Dictionary GetExamples() 8 | { 9 | return new Dictionary() 10 | { 11 | {"PropertyInt", 1}, 12 | {"PropertyString", "Some string"} 13 | }; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Swashbuckle.AspNetCore.Filters.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/DynamicDataResponseExample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Swashbuckle.AspNetCore.Filters; 6 | 7 | namespace WebApi.Models.Examples 8 | { 9 | public class DynamicDataResponseExample : IExamplesProvider 10 | { 11 | public DynamicData GetExamples() 12 | { 13 | var ret = new DynamicData(); 14 | ret.Payload.Add("DynamicProp", 12); 15 | ret.Payload.Add("Another", "String data"); 16 | return ret; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/WebApi8/Models/PersonRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace WebApi.Models 5 | { 6 | public class PersonRequest 7 | { 8 | public Title Title { get; set; } 9 | 10 | /// 11 | /// The person's Age, in years 12 | /// 13 | public int Age { get; set; } 14 | 15 | /// 16 | /// The first name of the person 17 | /// 18 | [JsonPropertyName("first")] 19 | public string FirstName { get; set; } 20 | 21 | public decimal? Income { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonRequest.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | public class PersonRequest : IPersonRequest 6 | { 7 | public Title Title { get; set; } 8 | 9 | public int Age { get; set; } 10 | 11 | [Description("The first name of the person")] 12 | public string FirstName { get; set; } 13 | 14 | public decimal? Income { get; set; } 15 | 16 | [Description("The person's children")] 17 | public Child[] Children { get; set; } 18 | 19 | public Job Job { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; EditorConfig to support per-solution formatting. 2 | ; Use the EditorConfig VS add-in to make this work. 3 | ; http://editorconfig.org/ 4 | 5 | ; This is the default for the codeline. 6 | root = true 7 | 8 | [*] 9 | end_of_line = CRLF 10 | 11 | [*.{config,cs,xml}] 12 | indent_style = space 13 | indent_size = 4 14 | trim_trailing_whitespace = true 15 | 16 | [*.{proj,props,sln,targets}] 17 | indent_style = tab 18 | trim_trailing_whitespace = true 19 | 20 | [*.{kproj,csproj,json,ps1,psd1,psm1,resx,rst}] 21 | indent_style = space 22 | indent_size = 2 23 | trim_trailing_whitespace = true 24 | 25 | [NuGet.Config] 26 | indent_style = space 27 | indent_size = 2 28 | trim_trailing_whitespace = true 29 | -------------------------------------------------------------------------------- /tools/NuGet instructions.txt: -------------------------------------------------------------------------------- 1 | 1. Update the VersionPrefix, PackageVersion and PackageReleaseNotes in the Directory.Build.props 2 | 2. Update Changelog 3 | 3. Commit changes 4 | 4. git tag -a v1.2.5 -m 'Fix issue #89' 5 | 5. git push --follow-tags 6 | 7 | dotnet pack -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -c Release 8 | 9 | cd src\Swashbuckle.AspNetCore.Filters.Abstractions\bin\Release 10 | nuget push Swashbuckle.AspNetCore.Filters.Abstrations.7.0.0.nupkg MySecretNuGetApiKeyHere -Source https://api.nuget.org/v3/index.json 11 | 12 | cd src\Swashbuckle.AspNetCore.Filters\bin\Release 13 | nuget push Swashbuckle.AspNetCore.Filters.7.0.0.nupkg MySecretNuGetApiKeyHere -Source https://api.nuget.org/v3/index.json 14 | 15 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PeopleResponseExample.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | internal class PeopleResponseExample : IExamplesProvider> 6 | { 7 | public IEnumerable GetExamples() 8 | { 9 | return new[] 10 | { 11 | new PersonResponse { Id = 123, Title = Title.Dr, FirstName = "John", LastName = "Doe", Age = 27, Income = null }, 12 | new PersonResponse { Id = 456, Title = Title.Dr, FirstName = "Jane", LastName = "Smith", Age = 26, Income = null } 13 | }; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonRequestDependencyInjectionExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | 3 | namespace WebApi.Models.Examples 4 | { 5 | internal class PersonRequestDependencyInjectionExample : IExamplesProvider 6 | { 7 | private readonly IWebHostEnvironment hostingEnvironment; 8 | 9 | public PersonRequestDependencyInjectionExample(IWebHostEnvironment hostingEnvironment) 10 | { 11 | this.hostingEnvironment = hostingEnvironment; 12 | } 13 | 14 | public PersonRequest GetExamples() 15 | { 16 | return new PersonRequest { Title = Title.Mr, Age = 24, FirstName = $"Dave ({hostingEnvironment.EnvironmentName})", Income = null }; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonResponseMultipleExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | internal class PersonResponseMultipleExamples : IMultipleExamplesProvider 6 | { 7 | public IEnumerable> GetExamples() 8 | { 9 | yield return SwaggerExample.Create("Dave", 10 | "Posts Dave", 11 | new PersonResponse {FirstName = "Dave", Title = Title.Mr}); 12 | yield return SwaggerExample.Create("Angela", 13 | "Posts Angela", 14 | new PersonResponse {FirstName = "Angela", Title = Title.Dr}); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/JsonPatchPersonRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.JsonPatch.Operations; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | public class JsonPatchPersonRequestExample : IExamplesProvider> 7 | { 8 | public IEnumerable GetExamples() 9 | { 10 | return new[] 11 | { 12 | new Operation 13 | { 14 | op = "replace", 15 | path = "/firstname", 16 | value = "Steve" 17 | }, 18 | new Operation 19 | { 20 | op = "remove", 21 | path = "/income" 22 | } 23 | }; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/WrappedPersonResponseExample.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | internal class WrappedPersonResponseExample : IExamplesProvider> 7 | { 8 | public ResponseWrapper GetExamples() 9 | { 10 | return new ResponseWrapper 11 | { 12 | StatusCode = HttpStatusCode.OK, 13 | Body = new PersonResponse 14 | { 15 | Id = 123, 16 | Title = Title.Mrs, 17 | FirstName = "Generic Jane", 18 | LastName = "Doe", 19 | Age = 27, 20 | Income = null 21 | } 22 | }; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/ListPeopleRequestExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace WebApi.Models.Examples 6 | { 7 | 8 | //internal class ListPeopleRequestExample : IExamplesProvider 9 | //{ 10 | // public PeopleRequest GetExamples() 11 | // { 12 | // return new PeopleRequest { Title = Title.Mr, Age = 24, FirstName = "Dave in a list", Income = null }; 13 | // } 14 | //} 15 | 16 | internal class ListPeopleRequestExample : IExamplesProvider> 17 | { 18 | public IEnumerable GetExamples() 19 | { 20 | return new List { new PeopleRequest { Title = Title.Mr, Age = 24, FirstName = "Dave in a list", Income = null } }.AsEnumerable(); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /test/WebApi8/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:28350", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5075", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/WebApi8/Models/DynamicData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | 7 | namespace WebApi.Models 8 | { 9 | /// 10 | /// This example of dynamic data and using of 11 | /// 12 | /// 13 | /// You can inherit this class to mix static and dynamic property: 14 | /// 15 | /// public class MixedData : DynamicData 16 | /// { 17 | /// public string FixedProperty {get;set;} 18 | /// } 19 | /// 20 | /// In this case JSON property "FixedProperty" will be added to FixedProperty, all other to Payload 21 | /// 22 | public class DynamicData 23 | { 24 | [JsonExtensionData] 25 | public Dictionary Payload { get; set; } = new Dictionary(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/test/Swashbuckle.AspNetCore.Filters.Test/Swashbuckle.AspNetCore.Filters.Test.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | }, 14 | { 15 | "label": "test", 16 | "command": "dotnet", 17 | "type": "shell", 18 | "group": "test", 19 | "args": [ 20 | "test", 21 | "${workspaceFolder}/test/Swashbuckle.AspNetCore.Filters.Test/Swashbuckle.AspNetCore.Filters.Test.csproj" 22 | ], 23 | "presentation": { 24 | "reveal": "silent" 25 | }, 26 | "problemMatcher": "$msCompile" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/MinimalApi8/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:39502", 8 | "sslPort": 0 9 | } 10 | }, 11 | "profiles": { 12 | "WebApi6._0_Swashbuckle6": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:5178", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Extensions/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace Swashbuckle.AspNetCore.Filters.Test.Extensions 6 | { 7 | public static class ObjectExtensions 8 | { 9 | public static T With(this T o, Expression> prop, TProp value) 10 | { 11 | var memberExpression = prop.Body as MemberExpression; 12 | 13 | if (memberExpression == null) 14 | throw new Exception($"A property must be provided. ({prop})"); 15 | 16 | var propertyInfo = (PropertyInfo) memberExpression.Member; 17 | 18 | if (propertyInfo.CanWrite) 19 | propertyInfo.SetValue(o, value); 20 | else 21 | typeof(T).GetField($"<{propertyInfo.Name}>k__BackingField", 22 | BindingFlags.Instance | BindingFlags.NonPublic)?.SetValue(o, value); 23 | 24 | return o; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Fakes/FakeLoggerFactory.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Moq; 3 | 4 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes 5 | { 6 | internal class FakeLoggerFactory : ILoggerFactory 7 | { 8 | private readonly ILoggerFactory factory; 9 | 10 | public FakeLoggerFactory() 11 | { 12 | var loggerMock = new Mock(); 13 | 14 | var factoryMock = new Mock(); 15 | factoryMock.Setup(f => f.CreateLogger(It.IsAny())) 16 | .Returns(loggerMock.Object); 17 | 18 | factory = factoryMock.Object; 19 | } 20 | 21 | public void Dispose() 22 | => factory.Dispose(); 23 | 24 | public ILogger CreateLogger(string categoryName) 25 | => factory.CreateLogger(categoryName); 26 | 27 | public void AddProvider(ILoggerProvider provider) 28 | => factory.AddProvider(provider); 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Matt Frear 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PeopleResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.Filters; 2 | using System.Collections.Generic; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | internal class PeopleResponseExample : IExamplesProvider> 7 | { 8 | public IEnumerable GetExamples() 9 | { 10 | return new PersonResponse[] 11 | { 12 | new PersonResponse 13 | { 14 | Id = 123, 15 | Title = Title.Dr, 16 | FirstName = "John", 17 | LastName = "Doe", 18 | Age = 27, 19 | Income = null 20 | }, 21 | new PersonResponse 22 | { 23 | Id = 124, 24 | Title = Title.Miss, 25 | FirstName = "Angela", 26 | LastName = "Doe", 27 | Age = 28, 28 | Income = null 29 | } 30 | }; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Examples/ISwaggerExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters 2 | { 3 | /// 4 | /// A single example out of multiple. 5 | /// 6 | /// 7 | /// It's important that the interface is co-variant. That allows instances 8 | /// of the interface to be found with "x as ISwaggerExample{object}" instead 9 | /// of having to go through reflection gymnastics. 10 | /// 11 | public interface ISwaggerExample 12 | { 13 | /// 14 | /// Name of the example. Required. 15 | /// 16 | string Name { get; } 17 | 18 | /// 19 | /// Optional summary of the example. 20 | /// 21 | string Summary { get; } 22 | 23 | /// 24 | /// Optional description of the example. 25 | /// 26 | string Description { get; } 27 | 28 | /// 29 | /// The example value. Required. 30 | /// 31 | T Value { get; } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Examples/TypeExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples; 3 | using Swashbuckle.AspNetCore.Filters.Extensions; 4 | using System; 5 | using Xunit; 6 | 7 | namespace Swashbuckle.AspNetCore.Filters.Test.Examples 8 | { 9 | public class TypeExtensionsTests 10 | { 11 | [Theory] 12 | [InlineData(typeof(PersonRequest), "PersonRequest")] 13 | // [InlineData(typeof(RequestWrapper), "RequestWrapper[PersonRequest]")] // Swashbuckle.AspNetCore v4 14 | [InlineData(typeof(RequestWrapper), "PersonRequestRequestWrapper")] // Swashbuckle.AspNetCore v5 15 | [InlineData(typeof(Title?), "Title")] 16 | public void SchemaDefinitionName_ShouldCalculate(Type type, string expectedName) 17 | { 18 | // Arrange 19 | var sut = new RequestExample(null, null); 20 | 21 | // Act 22 | var result = type.SchemaDefinitionName(); 23 | 24 | // Assert 25 | result.ShouldBe(expectedName); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Fakes/FakeApiDescriptionGroupCollectionProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 4 | 5 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes 6 | { 7 | public class FakeApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider 8 | { 9 | private readonly IEnumerable _apiDescriptions; 10 | 11 | public FakeApiDescriptionGroupCollectionProvider(IEnumerable apiDescriptions) 12 | { 13 | _apiDescriptions = apiDescriptions; 14 | } 15 | 16 | public ApiDescriptionGroupCollection ApiDescriptionGroups 17 | { 18 | get 19 | { 20 | var apiDescriptionGroups = _apiDescriptions 21 | .GroupBy(item => item.GroupName) 22 | .Select(grouping => new ApiDescriptionGroup(grouping.Key, grouping.ToList())) 23 | .ToList(); 24 | 25 | return new ApiDescriptionGroupCollection(apiDescriptionGroups, 1); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonResponseMultipleExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | internal class PersonResponseMultipleExamples : IMultipleExamplesProvider 7 | { 8 | public IEnumerable> GetExamples() 9 | { 10 | yield return new SwaggerExample() 11 | { 12 | Name = "John", 13 | Summary = "can return John", 14 | Description = "which is a really *cool* feature.", 15 | Value = new PersonResponse {Id = 123, Title = Title.Dr, FirstName = "John", LastName = "Doe", Age = 27, Income = null} 16 | }; 17 | yield return new SwaggerExample() 18 | { 19 | Name = "Angela", 20 | Summary = "or Angela", 21 | Description = "which is also _great_!", 22 | Value = new PersonResponse {Id = 124, Title = Title.Miss, FirstName = "Angela", LastName = "Doe", Age = 28, Income = null} 23 | }; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Fakes/FakeEndpointConventionBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes 7 | { 8 | public class FakeEndpointConventionBuilder : IEndpointConventionBuilder 9 | { 10 | internal EndpointBuilder EndpointBuilder { get; } 11 | 12 | private readonly List> conventions; 13 | 14 | public FakeEndpointConventionBuilder(EndpointBuilder endpointBuilder) 15 | { 16 | EndpointBuilder = endpointBuilder; 17 | conventions = new List>(); 18 | } 19 | 20 | public void Add(Action convention) 21 | { 22 | conventions.Add(convention); 23 | } 24 | 25 | public Endpoint Build() 26 | { 27 | foreach (var convention in conventions) 28 | { 29 | convention(EndpointBuilder); 30 | } 31 | 32 | return EndpointBuilder.Build(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Extensions/OpenApiExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using System.Text; 3 | using System.Xml; 4 | using Microsoft.OpenApi; 5 | 6 | namespace Swashbuckle.AspNetCore.Filters.Test.Extensions 7 | { 8 | public static class OpenApiExtensions 9 | { 10 | public static T DeserializeDataContractXmlExampleAs(this OpenApiRequestBody response) 11 | { 12 | var value = response.Content["application/xml"].Example.ToString(); 13 | return DeserializeDataContractXmlAs(value); 14 | } 15 | 16 | public static T DeserializeDataContractXmlExampleAs(this OpenApiResponse response) 17 | { 18 | var value = response.Content["application/xml"].Example.ToString(); 19 | return DeserializeDataContractXmlAs(value); 20 | } 21 | 22 | private static T DeserializeDataContractXmlAs(string value) 23 | { 24 | var reader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(value), new XmlDictionaryReaderQuotas()); 25 | var deserializer = new DataContractSerializer(typeof(T)); 26 | return (T)deserializer.ReadObject(reader); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /test/WebApi8/WebApi8.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | True 9 | 10 | 11 | 12 | 1701;1702;1591 13 | 14 | 15 | 16 | 1701;1702;1591 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Examples/SwaggerResponseExampleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters 4 | { 5 | /// 6 | /// 7 | /// This is used for generating Swagger documentation. Should be used in conjuction with SwaggerResponse - will add examples to SwaggerResponse. 8 | /// See https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters 9 | /// 10 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] 11 | public class SwaggerResponseExampleAttribute : Attribute 12 | { 13 | /// 14 | /// 15 | /// Add custom example data to your SwaggerResponse 16 | /// 17 | /// The HTTP status code, e.g. 200 18 | /// A type that inherits from IExamplesProvider 19 | public SwaggerResponseExampleAttribute(int statusCode, Type examplesProviderType) 20 | { 21 | StatusCode = statusCode; 22 | ExamplesProviderType = examplesProviderType; 23 | } 24 | 25 | public Type ExamplesProviderType { get; } 26 | 27 | public int StatusCode { get; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '1.0.{build}' 2 | image: Visual Studio 2022 3 | branches: 4 | only: 5 | - master 6 | init: 7 | # Good practise, because Windows line endings are different from Unix/Linux ones 8 | - cmd: git config --global core.autocrlf true 9 | install: 10 | # Install repo specific stuff here 11 | before_build: 12 | # Display .NET Core version 13 | - cmd: dotnet --version 14 | # Display minimal restore text 15 | - cmd: dotnet restore ./src/Swashbuckle.AspNetCore.Filters/Swashbuckle.AspNetCore.Filters.csproj --verbosity m 16 | build_script: 17 | # output will be in ./src/bin/debug/netcoreapp1.1/publish 18 | - cmd: dotnet build ./src/Swashbuckle.AspNetCore.Filters/Swashbuckle.AspNetCore.Filters.csproj 19 | after_build: 20 | # For once the build has completed 21 | artifacts: 22 | # - path: '\src\bin\Debug\netcoreapp1.1\publish' 23 | # name: WebSite 24 | # type: WebDeployPackage 25 | clone_depth: 1 26 | test_script: 27 | # restore packages for our unit tests 28 | - cmd: dotnet restore ./test/Swashbuckle.AspNetCore.Filters.Test/Swashbuckle.AspNetCore.Filters.Test.csproj --verbosity m 29 | # run the unit tests (requires changing into the test directory) 30 | - cmd: cd ./test/Swashbuckle.AspNetCore.Filters.Test 31 | - cmd: dotnet test 32 | on_finish : 33 | # any cleanup in here 34 | deploy: off -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/ResponseHeaders/SwaggerResponseHeaderAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.OpenApi; 3 | 4 | namespace Swashbuckle.AspNetCore.Filters 5 | { 6 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] 7 | public class SwaggerResponseHeaderAttribute : Attribute 8 | { 9 | public SwaggerResponseHeaderAttribute(int statusCode, string name, JsonSchemaType type, string description, string format = "") 10 | { 11 | StatusCodes = new int[] { statusCode }; 12 | Name = name; 13 | Type = type; 14 | Description = description; 15 | Format = format; 16 | } 17 | 18 | public SwaggerResponseHeaderAttribute(int[] statusCode, string name, JsonSchemaType type, string description, string format = "") 19 | { 20 | StatusCodes = statusCode; 21 | Name = name; 22 | Type = type; 23 | Description = description; 24 | Format = format; 25 | } 26 | 27 | public int[] StatusCodes { get; } 28 | 29 | public string Name { get; } 30 | 31 | public JsonSchemaType Type { get; } 32 | 33 | public string Description { get; } 34 | 35 | public string Format { get; } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/WebApi8/Models/PersonResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Runtime.Serialization; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace WebApi.Models 8 | { 9 | [DataContract] 10 | public class PersonResponse 11 | { 12 | [DataMember] 13 | public int Id { get; set; } 14 | 15 | [DataMember] 16 | public Title Title { get; set; } 17 | 18 | [DataMember(Name = "first")] 19 | [Description("The first name of the person")] 20 | public string FirstName { get; set; } 21 | 22 | [JsonPropertyName("laster")] // System.Text.Json 23 | [JsonProperty("last")] // Newtonsoft.Json 24 | [Description("The last name of the person")] 25 | public string LastName { get; set; } 26 | 27 | [Obsolete] 28 | [DataMember] 29 | [Description("His age, in years")] 30 | public int Age { get; set; } 31 | 32 | [DataMember] 33 | [Description("His income, in dollars, if known. If unknown then null")] 34 | public decimal? Income { get; set; } 35 | 36 | [Description("Not a data member. This must be hidden in data contract")] 37 | public string InternalNeedsOnly => "For internal needs only. Should not be exposed in XML examples."; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Extensions/OperationFilterContextExtensions.cs: -------------------------------------------------------------------------------- 1 | using Swashbuckle.AspNetCore.SwaggerGen; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Swashbuckle.AspNetCore.Filters.Extensions 8 | { 9 | internal static class OperationFilterContextExtensions 10 | { 11 | public static IEnumerable GetControllerAndActionAttributes(this OperationFilterContext context) where T : Attribute 12 | { 13 | var result = new List(); 14 | 15 | if (context.MethodInfo != null) 16 | { 17 | var controllerAttributes = context.MethodInfo.ReflectedType?.GetTypeInfo().GetCustomAttributes(); 18 | result.AddRange(controllerAttributes); 19 | 20 | var actionAttributes = context.MethodInfo.GetCustomAttributes(); 21 | result.AddRange(actionAttributes); 22 | } 23 | 24 | #if NETCOREAPP3_1_OR_GREATER 25 | if (context.ApiDescription.ActionDescriptor.EndpointMetadata != null) 26 | { 27 | var endpointAttributes = context.ApiDescription.ActionDescriptor.EndpointMetadata.OfType(); 28 | result.AddRange(endpointAttributes); 29 | } 30 | #endif 31 | return result.Distinct(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonResponse.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.ComponentModel; 4 | using System.Runtime.Serialization; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 8 | { 9 | [DataContract] 10 | public class PersonResponse 11 | { 12 | [DataMember] 13 | public int Id { get; set; } 14 | 15 | [DataMember] 16 | public Title Title { get; set; } 17 | 18 | [DataMember(Name = "first")] 19 | [Description("The first name of the person")] 20 | public string FirstName { get; set; } 21 | 22 | [JsonPropertyName("lastagain")] 23 | [JsonProperty("last")] 24 | [Description("The last name of the person")] 25 | public string LastName { get; set; } 26 | 27 | [DataMember] 28 | [Description("His age, in years")] 29 | [Obsolete] 30 | public int Age { get; set; } 31 | 32 | [DataMember] 33 | [Description("His income, in dollars, if known. If unknown then null")] 34 | public decimal? Income { get; set; } 35 | 36 | [Description("Not a data member. This must be hidden in data contract")] 37 | public string InternalNeedsOnly => "For internal needs only. Should not be exposed in XML examples"; 38 | } 39 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/AddHeaderOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | using System.Collections.Generic; 4 | 5 | namespace Swashbuckle.AspNetCore.Filters 6 | { 7 | public class AddHeaderOperationFilter : IOperationFilter 8 | { 9 | private readonly string parameterName; 10 | private readonly string description; 11 | private readonly bool required; 12 | 13 | public AddHeaderOperationFilter(string parameterName, string description, bool required = false) 14 | { 15 | this.parameterName = parameterName; 16 | this.description = description; 17 | this.required = required; 18 | } 19 | 20 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 21 | { 22 | if (operation.Parameters == null) 23 | { 24 | operation.Parameters = new List(); 25 | } 26 | 27 | operation.Parameters.Add(new OpenApiParameter 28 | { 29 | Name = parameterName, 30 | In = ParameterLocation.Header, 31 | Description = description, 32 | Required = required, 33 | Schema = new OpenApiSchema 34 | { 35 | Type = JsonSchemaType.String 36 | } 37 | }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Examples/SwaggerRequestExampleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters 4 | { 5 | /// 6 | /// 7 | /// Adds example requests to your controller endpoints. 8 | /// See: https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters 9 | /// 10 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] 11 | public class SwaggerRequestExampleAttribute : Attribute 12 | { 13 | /// 14 | /// 15 | /// Add example data for a request 16 | /// 17 | /// The type passed to the request 18 | /// A type that inherits from IExamplesProvider 19 | public SwaggerRequestExampleAttribute(Type requestType, Type examplesProviderType) 20 | { 21 | RequestType = requestType; 22 | ExamplesProviderType = examplesProviderType; 23 | 24 | // todo - can (and should) I introduce a check here to check that the examplesProviderType's ImplementedInterfaces contains IExamplesProvider of requestType 25 | // Remember that there was an issue with ListPeopleRequestExample which is now commented out. 26 | } 27 | 28 | public Type ExamplesProviderType { get; } 29 | 30 | public Type RequestType { get; } 31 | } 32 | } -------------------------------------------------------------------------------- /test/WebApi8/Models/Examples/PersonRequestMultipleExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | namespace WebApi.Models.Examples 5 | { 6 | public class PersonRequestMultipleExamples : IMultipleExamplesProvider 7 | { 8 | public IEnumerable> GetExamples() 9 | { 10 | yield return SwaggerExample.Create("Dave", 11 | "Posts Dave", 12 | "This description is rendered as _Markdown_ by *SwaggerUI*", 13 | new PersonRequest {FirstName = "Dave", Title = Title.Mr}); 14 | yield return SwaggerExample.Create("Angela", 15 | "Let's add Angela", 16 | @" 17 | ## Markdown rules! 18 | 19 | This is a realy great feature for longer descriptions. 20 | ", 21 | new PersonRequest {FirstName = "Angela", Title = Title.Dr}); 22 | yield return SwaggerExample.Create("Diane", 23 | "Diane is also fine to post", 24 | @" 25 | - enummerations 26 | - are 27 | - also 28 | - supported 29 | ", 30 | new PersonRequest {FirstName = "Diane", Title = Title.Mrs, Age = 30}); 31 | yield return SwaggerExample.Create("Michael", 32 | "And the last example", 33 | // not specifying a description is fine too 34 | new PersonRequest {FirstName = "Michael", Income = 321.7m}); 35 | yield return SwaggerExample.Create("Null guy", null); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 10.0.1 6 | Matt Frear 7 | Some additional useful filters for Swashbuckle.AspNetCore. This package replaces Swashbuckle.AspNetCore.Examples. 8 | false 9 | https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters 10 | MIT 11 | 2025 12 | Swagger Swashbuckle 13 | 14 | Fix issue #264 (thanks @ntark) 15 | 16 | git 17 | https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters 18 | true 19 | README.md 20 | 21 | 22 | 23 | 10.0.1 24 | 25 | True 26 | ..\key.snk 27 | False 28 | false 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Extensions/SwaggerGenOptionsExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using Swashbuckle.AspNetCore.SwaggerGen; 3 | 4 | namespace Swashbuckle.AspNetCore.Filters 5 | { 6 | public static class SwaggerGenOptionsExtensions 7 | { 8 | /// 9 | /// Enable Swashbuckle filters which provide sensible examples for requests and respnoses. Please implement IExamplesProvider. 10 | /// 11 | /// 12 | public static void ExampleFilters(this SwaggerGenOptions swaggerGenOptions) 13 | { 14 | swaggerGenOptions.OperationFilter(); 15 | swaggerGenOptions.OperationFilter(); 16 | } 17 | 18 | /// 19 | /// Enable Swashbuckle filters which provide sensible examples for requests and respnoses. Please implement IExamplesProvider. 20 | /// This option reverses the order the filters are registered which might be useful for some edge cases. 21 | /// See: https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters/pull/210#issuecomment-1195673783 22 | /// 23 | /// 24 | public static void ExampleFilters_PrioritizingExplicitlyDefinedExamples(this SwaggerGenOptions swaggerGenOptions) 25 | { 26 | swaggerGenOptions.OperationFilter(); 27 | swaggerGenOptions.OperationFilter(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/AppendAuthorizeToSummary/AppendAuthorizeToSummaryOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.OpenApi; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | using System.Linq; 5 | 6 | namespace Swashbuckle.AspNetCore.Filters 7 | { 8 | public class AppendAuthorizeToSummaryOperationFilter : IOperationFilter 9 | { 10 | private readonly AppendAuthorizeToSummaryOperationFilter filter; 11 | 12 | public AppendAuthorizeToSummaryOperationFilter() 13 | { 14 | var policySelector = new PolicySelectorWithLabel 15 | { 16 | Label = "policies", 17 | Selector = authAttributes => 18 | authAttributes 19 | .Where(a => !string.IsNullOrEmpty(a.Policy)) 20 | .Select(a => a.Policy) 21 | }; 22 | 23 | var rolesSelector = new PolicySelectorWithLabel 24 | { 25 | Label = "roles", 26 | Selector = authAttributes => 27 | authAttributes 28 | .Where(a => !string.IsNullOrEmpty(a.Roles)) 29 | .Select(a => a.Roles) 30 | }; 31 | 32 | filter = new AppendAuthorizeToSummaryOperationFilter(new[] { policySelector, rolesSelector }.AsEnumerable()); 33 | } 34 | 35 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 36 | { 37 | filter.Apply(operation, context); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/SecurityRequirementsOperationFilter/SecurityRequirementsOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.AspNetCore.Authorization; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | using System.Linq; 6 | using Microsoft.OpenApi; 7 | 8 | namespace Swashbuckle.AspNetCore.Filters 9 | { 10 | public class SecurityRequirementsOperationFilter : IOperationFilter 11 | { 12 | private readonly SecurityRequirementsOperationFilter filter; 13 | 14 | /// 15 | /// Constructor for SecurityRequirementsOperationFilter 16 | /// 17 | /// If true (default), then 401 and 403 responses will be added to every operation 18 | /// Name of the security schema. Default value is "oauth2" 19 | public SecurityRequirementsOperationFilter(bool includeUnauthorizedAndForbiddenResponses = true, string securitySchemaName = "oauth2") 20 | { 21 | Func, IEnumerable> policySelector = authAttributes => 22 | authAttributes 23 | .Where(a => !string.IsNullOrEmpty(a.Policy)) 24 | .Select(a => a.Policy); 25 | 26 | filter = new SecurityRequirementsOperationFilter(policySelector, includeUnauthorizedAndForbiddenResponses, securitySchemaName); 27 | } 28 | 29 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 30 | { 31 | filter.Apply(operation, context); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Runtime.CompilerServices; 5 | 6 | [assembly: InternalsVisibleTo("Swashbuckle.AspNetCore.Filters.Test,PublicKey=" + 7 | "00240000048000009400000006020000002400005253413100040000010001000d2acb3789e62b" + 8 | "52eccb8a2050a611e9290675dd2f48f2b21167c94a0cba514cfd3e0bfcb297d8f806d3c3972683" + 9 | "aae72598e9182e50e7b058763b904e5dfd3b98d0b730b618d594969fcf49044ffa46ae81f350a7" + 10 | "b72933a64e4e4f912ff7bad06cffbd28fc33d5b3768ce554acd1addb1e5a5118559f4d20b9ceec" + 11 | "3bfc55b7")] 12 | namespace Swashbuckle.AspNetCore.Filters.Extensions 13 | { 14 | internal static class TypeExtensions 15 | { 16 | public static string SchemaDefinitionName(this Type type) 17 | { 18 | string name = null; 19 | 20 | if (!type.GetTypeInfo().IsGenericType) 21 | { 22 | name = type.Name; // this doesn't work for generic types 23 | } 24 | else 25 | { 26 | var nullableUnderlyingType = Nullable.GetUnderlyingType(type); 27 | if (nullableUnderlyingType != null) 28 | { 29 | return nullableUnderlyingType.Name; 30 | } 31 | 32 | // remove `# from the generic type name 33 | var friendlyName = type.Name.Remove(type.Name.IndexOf('`')); 34 | // for generic, Schema will be TypeName[GenericTypeName] 35 | var genericArguments = type.GetGenericArguments(); 36 | name = $"{string.Concat(genericArguments.Select(a => a.Name).ToList())}{friendlyName}"; 37 | } 38 | 39 | return name; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Swashbuckle.AspNetCore.Filters.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | True 34 | key.snk 35 | False 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Fakes/FormatterOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.Formatters; 3 | using Microsoft.Extensions.Options; 4 | using Newtonsoft.Json; 5 | using System.Buffers; 6 | using System.Text.Json; 7 | using System.Text.Json.Serialization.Metadata; 8 | using WebApiContrib.Core.Formatter.Csv; 9 | 10 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes 11 | { 12 | internal class FormatterOptions : IOptions 13 | { 14 | private static NewtonsoftJsonOutputFormatter jsonOutputFormatter = new NewtonsoftJsonOutputFormatter( 15 | new JsonSerializerSettings 16 | { 17 | Formatting = Formatting.Indented, 18 | }, 19 | ArrayPool.Shared, 20 | new MvcOptions()); 21 | 22 | private static SystemTextJsonOutputFormatter systemTextJsonFormatter = new SystemTextJsonOutputFormatter( 23 | new JsonSerializerOptions { WriteIndented = true, TypeInfoResolver = new DefaultJsonTypeInfoResolver() }); 24 | 25 | private FormatterOptions(params IOutputFormatter[] formatters) 26 | { 27 | Value = new MvcOptions(); 28 | foreach (var formatter in formatters) 29 | Value.OutputFormatters.Add(formatter); 30 | } 31 | 32 | public MvcOptions Value { get; } 33 | 34 | public static FormatterOptions WithXmlDataContractFormatter 35 | => new FormatterOptions(new XmlDataContractSerializerOutputFormatter()); 36 | 37 | public static FormatterOptions WithNewtonsoftFormatter => new FormatterOptions(jsonOutputFormatter); 38 | 39 | public static FormatterOptions WithSystemTextJsonFormatter = new FormatterOptions(systemTextJsonFormatter); 40 | public static FormatterOptions WithXmlAndNewtonsoftJsonAndCsvFormatters => new FormatterOptions(new XmlSerializerOutputFormatter(), jsonOutputFormatter, new CsvOutputFormatter(new CsvFormatterOptions())); 41 | public static FormatterOptions WithoutFormatters => new FormatterOptions(); 42 | } 43 | } -------------------------------------------------------------------------------- /test/MinimalApi8/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi; 2 | using Swashbuckle.AspNetCore.Filters; 3 | 4 | var builder = WebApplication.CreateBuilder(args); 5 | 6 | // Add services to the container. 7 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 8 | builder.Services.AddEndpointsApiExplorer(); 9 | 10 | builder.Services.AddAuthorization(); 11 | 12 | builder.Services.AddSwaggerGen(c => 13 | { 14 | c.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme 15 | { 16 | Description = "Standard Authorization header using the Bearer scheme. Example: \"bearer {token}\"", 17 | In = ParameterLocation.Header, 18 | Name = "Authorization", 19 | Type = SecuritySchemeType.ApiKey 20 | }); 21 | 22 | c.ExampleFilters(); 23 | c.OperationFilter(); 24 | }); 25 | 26 | builder.Services.AddSwaggerExamplesFromAssemblyOf(); 27 | 28 | var app = builder.Build(); 29 | 30 | app.UseAuthorization(); 31 | 32 | // Configure the HTTP request pipeline. 33 | if (app.Environment.IsDevelopment()) 34 | { 35 | app.UseSwagger(); 36 | app.UseSwaggerUI(); 37 | } 38 | 39 | var summaries = new[] 40 | { 41 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 42 | }; 43 | 44 | app.MapGet("/weatherforecast", () => 45 | { 46 | var forecast = Enumerable.Range(1, 5).Select(index => 47 | new WeatherForecast 48 | ( 49 | DateTime.Now.AddDays(index), 50 | Random.Shared.Next(-20, 55), 51 | summaries[Random.Shared.Next(summaries.Length)] 52 | )) 53 | .ToArray(); 54 | return forecast; 55 | }) 56 | .WithName("GetWeatherForecast") 57 | .RequireAuthorization(); 58 | 59 | app.Run(); 60 | 61 | internal record WeatherForecast(DateTime Date, int TemperatureC, string? Summary) 62 | { 63 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 64 | } 65 | 66 | internal class WeatherForecastExample : IExamplesProvider 67 | { 68 | public WeatherForecast[] GetExamples() 69 | { 70 | return new[] { new WeatherForecast(DateTime.Today, 20, "Sunny") }; 71 | } 72 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Fakes/FakeControllers.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Swashbuckle.AspNetCore.Annotations; 4 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples; 5 | using System; 6 | 7 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes 8 | { 9 | public static class FakeControllers 10 | { 11 | public class NotAnnotated 12 | { 13 | public IActionResult None() 14 | { 15 | throw new NotImplementedException(); 16 | } 17 | } 18 | 19 | [Authorize] 20 | public class AuthController 21 | { 22 | [AllowAnonymous] 23 | public IActionResult AllowAnonymous() 24 | { 25 | throw new NotImplementedException(); 26 | } 27 | 28 | [Authorize("Customer")] 29 | public IActionResult Customer() 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public IActionResult None() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | } 39 | 40 | [Authorize] 41 | public class AuthControllerDerived : NotAnnotated 42 | { 43 | } 44 | 45 | [AllowAnonymous] 46 | public class AllowAnonymousController 47 | { 48 | [Authorize("Customer")] // this does nothing, because [AllowAnonymous] on the controller overrides it 49 | public IActionResult Customer() 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | } 54 | 55 | [SwaggerResponse(200, type: typeof(PersonResponse))] 56 | [SwaggerResponseExample(200, typeof(PersonResponseExample))] 57 | public class SwaggerResponseExampleController 58 | { 59 | public IActionResult None() 60 | { 61 | throw new NotImplementedException(); 62 | } 63 | } 64 | 65 | [SwaggerResponse(200, type: typeof(PersonResponse))] 66 | public class SwaggerResponseController 67 | { 68 | public IActionResult None() 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/ResponseHeaders/AddResponseHeadersFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi; 2 | using Swashbuckle.AspNetCore.Filters.Extensions; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Linq; 7 | 8 | namespace Swashbuckle.AspNetCore.Filters 9 | { 10 | public class AddResponseHeadersFilter : IOperationFilter 11 | { 12 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 13 | { 14 | var actionAttributes = context.GetControllerAndActionAttributes(); 15 | 16 | foreach (var attr in actionAttributes) 17 | { 18 | foreach (var statusCode in attr.StatusCodes) 19 | { 20 | var response = operation.Responses.FirstOrDefault(x => x.Key == (statusCode).ToString(CultureInfo.InvariantCulture)).Value; 21 | 22 | if (response != null) 23 | { 24 | // In Microsoft.OpenApi 2.0 response.Headers is null and readonly, so we need to create a new OpenApiResponse and replace it in the Responses dictionary 25 | 26 | var existingHeaders = response.Headers ?? new Dictionary(); 27 | var newHeaders = new Dictionary(existingHeaders) 28 | { 29 | [attr.Name] = new OpenApiHeader 30 | { 31 | Description = attr.Description, 32 | Schema = new OpenApiSchema { Description = attr.Description, Type = attr.Type, Format = attr.Format } 33 | } 34 | }; 35 | 36 | var newResponse = new OpenApiResponse 37 | { 38 | Description = response.Description, 39 | Content = response.Content, 40 | Headers = newHeaders, 41 | Links = response.Links, 42 | Extensions = response.Extensions 43 | }; 44 | 45 | operation.Responses[statusCode.ToString(CultureInfo.InvariantCulture)] = newResponse; 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/AppendAuthorizeToSummary/AppendAuthorizeToSummaryOperationFilterT.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.OpenApi; 3 | using Swashbuckle.AspNetCore.Filters.Extensions; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Swashbuckle.AspNetCore.Filters 11 | { 12 | public class AppendAuthorizeToSummaryOperationFilter : IOperationFilter where T : Attribute 13 | { 14 | private readonly IEnumerable> policySelectors; 15 | 16 | /// 17 | /// Constructor for AppendAuthorizeToSummaryOperationFilter 18 | /// 19 | /// Used to select the authorization policy from the attribute e.g. (a => a.Policy) 20 | public AppendAuthorizeToSummaryOperationFilter(IEnumerable> policySelectors) 21 | { 22 | this.policySelectors = policySelectors; 23 | } 24 | 25 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 26 | { 27 | if (context.GetControllerAndActionAttributes().Any()) 28 | { 29 | return; 30 | } 31 | 32 | var authorizeAttributes = context.GetControllerAndActionAttributes(); 33 | 34 | if (authorizeAttributes.Any()) 35 | { 36 | var authorizationDescription = new StringBuilder(" (Auth"); 37 | 38 | foreach (var policySelector in policySelectors) 39 | { 40 | AppendPolicies(authorizeAttributes, authorizationDescription, policySelector); 41 | } 42 | 43 | operation.Summary += authorizationDescription.ToString().TrimEnd(';') + ")"; 44 | } 45 | } 46 | 47 | private void AppendPolicies(IEnumerable authorizeAttributes, StringBuilder authorizationDescription, PolicySelectorWithLabel policySelector) 48 | { 49 | var policies = policySelector.Selector(authorizeAttributes) 50 | .OrderBy(policy => policy); 51 | 52 | if (policies.Any()) 53 | { 54 | authorizationDescription.Append($" {policySelector.Label}: {string.Join(", ", policies)};"); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Web Api 1.1", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceRoot}/test/WebApi1.1/bin/Debug/netcoreapp1.1/WebApi1.1.dll", 13 | "args": [], 14 | "cwd": "${workspaceRoot}/test/WebApi1.1", 15 | "stopAtEntry": false, 16 | "launchBrowser": { 17 | "enabled": true, 18 | "args": "${auto-detect-url}", 19 | "windows": { 20 | "command": "cmd.exe", 21 | "args": "/C start ${auto-detect-url}" 22 | }, 23 | "osx": { 24 | "command": "open" 25 | }, 26 | "linux": { 27 | "command": "xdg-open" 28 | } 29 | }, 30 | "env": { 31 | "ASPNETCORE_ENVIRONMENT": "Development" 32 | }, 33 | "sourceFileMap": { 34 | "/Views": "${workspaceRoot}/Views" 35 | } 36 | }, 37 | { 38 | "name": "Launch Web Api 2.0", 39 | "type": "coreclr", 40 | "request": "launch", 41 | "preLaunchTask": "build", 42 | "program": "${workspaceRoot}/test/WebApi2.0/bin/Debug/netcoreapp2.0/WebApi2.0.dll", 43 | "args": [], 44 | "cwd": "${workspaceRoot}/test/WebApi1.1", 45 | "stopAtEntry": false, 46 | "launchBrowser": { 47 | "enabled": true, 48 | "args": "${auto-detect-url}", 49 | "windows": { 50 | "command": "cmd.exe", 51 | "args": "/C start ${auto-detect-url}" 52 | }, 53 | "osx": { 54 | "command": "open" 55 | }, 56 | "linux": { 57 | "command": "xdg-open" 58 | } 59 | }, 60 | "env": { 61 | "ASPNETCORE_ENVIRONMENT": "Development" 62 | }, 63 | "sourceFileMap": { 64 | "/Views": "${workspaceRoot}/Views" 65 | } 66 | } 67 | ,] 68 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Examples/ExamplesOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi; 2 | using Swashbuckle.AspNetCore.Filters.Extensions; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | using System; 5 | using System.Reflection; 6 | 7 | namespace Swashbuckle.AspNetCore.Filters 8 | { 9 | /// 10 | /// Adds custom Request and Response examples. 11 | /// You should install it using the .AddSwaggerExamples() extension method 12 | /// 13 | internal class ExamplesOperationFilter : IOperationFilter 14 | { 15 | private readonly IServiceProvider serviceProvider; 16 | private readonly RequestExample requestExample; 17 | private readonly ResponseExample responseExample; 18 | 19 | public ExamplesOperationFilter( 20 | IServiceProvider serviceProvider, 21 | RequestExample requestExample, 22 | ResponseExample responseExample) 23 | { 24 | this.serviceProvider = serviceProvider; 25 | this.requestExample = requestExample; 26 | this.responseExample = responseExample; 27 | } 28 | 29 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 30 | { 31 | SetRequestExamples(operation, context); 32 | SetResponseExamples(operation, context); 33 | } 34 | 35 | private void SetRequestExamples(OpenApiOperation operation, OperationFilterContext context) 36 | { 37 | var actionAttributes = context.GetControllerAndActionAttributes(); 38 | 39 | foreach (var attr in actionAttributes) 40 | { 41 | var example = serviceProvider.GetExampleWithExamplesProviderType(attr.ExamplesProviderType); 42 | 43 | requestExample.SetRequestBodyExampleForOperation( 44 | operation, 45 | context.SchemaRepository, 46 | attr.RequestType, 47 | example); 48 | } 49 | } 50 | 51 | private void SetResponseExamples(OpenApiOperation operation, OperationFilterContext context) 52 | { 53 | var responseAttributes = context.GetControllerAndActionAttributes(); 54 | 55 | foreach (var attr in responseAttributes) 56 | { 57 | var example = serviceProvider.GetExampleWithExamplesProviderType(attr.ExamplesProviderType); 58 | 59 | responseExample.SetResponseExampleForStatusCode( 60 | operation, 61 | attr.StatusCode, 62 | example); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Examples/PersonRequestMultipleExamples.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples 4 | { 5 | internal class PersonRequestMultipleExamples : IMultipleExamplesProvider 6 | { 7 | public IEnumerable> GetExamples() 8 | { 9 | yield return SwaggerExample.Create("Dave", 10 | "Posts Dave", 11 | "Here's a description", 12 | new PersonRequest {FirstName = "Dave", Title = Title.Mr}); 13 | yield return SwaggerExample.Create("Angela", 14 | "Let's add Angela", 15 | new PersonRequest {FirstName = "Angela", Title = Title.Dr}); 16 | yield return SwaggerExample.Create("Diane", 17 | "Diane is also fine to post", 18 | new PersonRequest {FirstName = "Diane", Title = Title.Mrs, Age = 30}); 19 | yield return SwaggerExample.Create("Michael", 20 | "And the last example", 21 | new PersonRequest {FirstName = "Michael", Income = 321.7m}); 22 | } 23 | } 24 | 25 | internal class PersonRequestMultipleExamplesDuplicatedKeys : IMultipleExamplesProvider 26 | { 27 | public IEnumerable> GetExamples() 28 | { 29 | yield return SwaggerExample.Create("Dave", 30 | "Posts Dave", 31 | new PersonRequest {FirstName = "Dave", Title = Title.Mr}); 32 | yield return SwaggerExample.Create("Dave", 33 | "Posts other Dave", 34 | new PersonRequest {FirstName = "Dave", Title = Title.Dr}); 35 | } 36 | } 37 | 38 | internal class PersonRequestMultipleExamplesNull : IMultipleExamplesProvider 39 | { 40 | public IEnumerable> GetExamples() 41 | { 42 | return null; 43 | } 44 | } 45 | 46 | internal class PersonRequestMultipleExamplesNullExample : IMultipleExamplesProvider 47 | { 48 | public IEnumerable> GetExamples() 49 | { 50 | yield return SwaggerExample.Create("Dave", 51 | "Posts Dave", 52 | new PersonRequest { FirstName = "Dave", Title = Title.Mr }); 53 | yield return SwaggerExample.Create("Null", 54 | "Posts null", 55 | null); 56 | } 57 | } 58 | 59 | internal class PersonRequestMultipleExamplesEmpty : IMultipleExamplesProvider 60 | { 61 | public IEnumerable> GetExamples() 62 | { 63 | return new List>(); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Extensions/ServiceProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace Swashbuckle.AspNetCore.Filters.Extensions 5 | { 6 | internal static class ServiceProviderExtensions 7 | { 8 | /// 9 | /// Gets a specific IExamplesProvider from the ServiceProvider and calls GetExamples() on it 10 | /// 11 | /// 12 | /// Either an IExamplesProvider, or a concrete type, e.g. PersonRequestExample 13 | /// The result of calling GetExamples on the given examplesProviderType 14 | public static object GetExampleWithExamplesProviderType(this IServiceProvider serviceProvider, Type examplesProviderType) 15 | { 16 | var exampleProviderObject = serviceProvider.GetService(examplesProviderType); 17 | return InvokeGetExamples(examplesProviderType, exampleProviderObject); 18 | } 19 | 20 | /// 21 | /// Searches the serviceProvider for an IExamplesProvider, where T is the requested type 22 | /// 23 | /// 24 | /// 25 | /// 26 | public static object GetExampleForType(this IServiceProvider serviceProvider, Type type) 27 | { 28 | if (type == null || type == typeof(void) || IsPrimitiveType()) 29 | { 30 | return null; 31 | } 32 | 33 | bool IsPrimitiveType() 34 | { 35 | return !type.GetTypeInfo().IsClass 36 | && !type.GetTypeInfo().IsGenericType 37 | && !type.GetTypeInfo().IsInterface; 38 | } 39 | 40 | var exampleProviderType = typeof(IExamplesProvider<>).MakeGenericType(type); 41 | var singleExample = GetExampleWithExamplesProviderType(serviceProvider, exampleProviderType); 42 | if (singleExample != null) 43 | { 44 | return singleExample; 45 | } 46 | 47 | var multipleExampleProviderType = typeof(IMultipleExamplesProvider<>).MakeGenericType(type); 48 | return GetExampleWithExamplesProviderType(serviceProvider, multipleExampleProviderType); 49 | } 50 | 51 | private static object InvokeGetExamples(Type exampleProviderType, object exampleProviderObject) 52 | { 53 | if (exampleProviderObject == null) 54 | { 55 | return null; 56 | } 57 | 58 | var methodInfo = exampleProviderType.GetMethod("GetExamples"); 59 | var example = methodInfo.Invoke(exampleProviderObject, null); // yay, we've got the example! Now just need to set it. 60 | return example; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Extensions/ExampleAssertExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi; 2 | using Newtonsoft.Json; 3 | using Shouldly; 4 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace Swashbuckle.AspNetCore.Filters.Test.Extensions 10 | { 11 | public static class ExampleAssertExtensions 12 | { 13 | public static void ShouldMatch(this PersonRequest actualExample, PersonRequest expectedExample) 14 | { 15 | if (actualExample is null) 16 | { 17 | expectedExample.ShouldBeNull(); 18 | return; 19 | } 20 | 21 | actualExample.Title.ShouldBe(expectedExample.Title); 22 | actualExample.FirstName.ShouldBe(expectedExample.FirstName); 23 | actualExample.Age.ShouldBe(expectedExample.Age); 24 | } 25 | 26 | public static void ShouldMatch(this PersonResponse actualExample, PersonResponse expectedExample) 27 | { 28 | actualExample.Id.ShouldBe(expectedExample.Id); 29 | actualExample.Title.ShouldBe(expectedExample.Title); 30 | actualExample.FirstName.ShouldBe(expectedExample.FirstName); 31 | actualExample.LastName.ShouldBe(expectedExample.LastName); 32 | actualExample.Age.ShouldBe(expectedExample.Age); 33 | actualExample.Income.ShouldBe(expectedExample.Income); 34 | } 35 | 36 | public static void ShouldMatch(this IOpenApiExample actualExample, ISwaggerExample expectedExample, Action matcher) 37 | { 38 | actualExample.Summary.ShouldBe(expectedExample.Summary); 39 | var actualRequestValue = JsonConvert.DeserializeObject(actualExample.Value.ToString()); 40 | matcher(actualRequestValue, expectedExample.Value); 41 | } 42 | 43 | public static void ShouldAllMatch( 44 | this IDictionary actualExamples, 45 | IEnumerable> expectedExamples, 46 | Action matcher) 47 | { 48 | var expectedExamplesList = expectedExamples.ToList(); 49 | 50 | // All expected examples should be in dictionary of actual examples 51 | foreach (var expectedExample in expectedExamplesList) 52 | { 53 | actualExamples.ShouldContainKey(expectedExample.Name); 54 | var actualExample = actualExamples[expectedExample.Name]; 55 | actualExample.ShouldMatch(expectedExample, matcher); 56 | } 57 | 58 | // No other examples should occur in the dictionary of actual examples 59 | foreach (var actualExampleKey in actualExamples.Keys) 60 | { 61 | expectedExamplesList.ShouldContain(expectedExample => expectedExample.Name == actualExampleKey); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /test/WebApi8/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.OpenApi; 2 | using Swashbuckle.AspNetCore.Filters; 3 | using System.Text.Json.Serialization; 4 | using WebApi.Models.Examples; 5 | using WebApiContrib.Core.Formatter.Csv; 6 | 7 | var builder = WebApplication.CreateBuilder(args); 8 | 9 | // Add services to the container. 10 | 11 | builder.Services 12 | .AddControllers() 13 | .AddCsvSerializerFormatters(new CsvFormatterOptions { CsvDelimiter = "," }) 14 | //.AddNewtonsoftJson(opt => 15 | //{ 16 | // opt.SerializerSettings.Converters.Add(new StringEnumConverter()); 17 | // opt.SerializerSettings.ContractResolver = new ExcludeObsoletePropertiesResolver(opt.SerializerSettings.ContractResolver); 18 | // // opt.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; 19 | //}) 20 | .AddJsonOptions(opt => opt.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter())) 21 | .AddXmlSerializerFormatters(); 22 | //.AddXmlDataContractSerializerFormatters(); 23 | 24 | builder.Services.AddEndpointsApiExplorer(); 25 | 26 | builder.Services.AddSwaggerGen(options => 27 | { 28 | options.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v2" }); 29 | 30 | options.ExampleFilters(); 31 | 32 | options.OperationFilter("correlationId", "Correlation Id for the request"); 33 | 34 | options.OperationFilter(); 35 | 36 | var filePath = Path.Combine(AppContext.BaseDirectory, "WebApi8.xml"); 37 | options.IncludeXmlComments(filePath); 38 | 39 | options.OperationFilter(); 40 | 41 | options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme 42 | { 43 | Description = "Standard Authorization header using the Bearer scheme. Example: \"bearer {token}\"", 44 | In = ParameterLocation.Header, 45 | Name = "Authorization", 46 | Type = SecuritySchemeType.ApiKey 47 | }); 48 | 49 | options.OperationFilter(); 50 | }); 51 | 52 | builder.Services.AddSwaggerExamplesFromAssemblyOf(); 53 | 54 | // builder.Services.Configure(c => c.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0); 55 | 56 | builder.Services.AddAuthorization(options => 57 | { 58 | options.AddPolicy("Administrator", authBuilder => 59 | { 60 | authBuilder.AddAuthenticationSchemes("bearer"); 61 | authBuilder.RequireRole("Administrator"); 62 | }); 63 | options.AddPolicy("Customer", authBuilder => 64 | { 65 | authBuilder.AddAuthenticationSchemes("Bearer"); 66 | authBuilder.RequireRole("Customer"); 67 | }); 68 | }); 69 | 70 | 71 | var app = builder.Build(); 72 | 73 | // Configure the HTTP request pipeline. 74 | if (app.Environment.IsDevelopment()) 75 | { 76 | app.UseSwagger(); 77 | app.UseSwaggerUI(); 78 | } 79 | 80 | app.UseAuthorization(); 81 | 82 | app.MapControllers(); 83 | 84 | app.Run(); 85 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters.Abstractions/Examples/SwaggerExample.cs: -------------------------------------------------------------------------------- 1 | namespace Swashbuckle.AspNetCore.Filters 2 | { 3 | /// 4 | /// A single example out of multiple. 5 | /// 6 | /// Type that the example is for. 7 | public class SwaggerExample : ISwaggerExample 8 | { 9 | /// 10 | /// Name of the example. Required. 11 | /// 12 | public string Name { get; set; } 13 | 14 | /// 15 | /// Optional summary of the example. 16 | /// 17 | public string Summary { get; set; } 18 | 19 | /// 20 | /// Optional description of the example. SwaggerUI renders this with markdown support. 21 | /// 22 | public string Description { get; set; } 23 | 24 | /// 25 | /// The example value. Required. 26 | /// 27 | public T Value { get; set; } 28 | } 29 | 30 | /// 31 | /// Helper class to reduce generic boilerplate 32 | /// 33 | public static class SwaggerExample 34 | { 35 | /// 36 | /// Create an example for a type. 37 | /// 38 | /// Name of the example. 39 | /// Example value. 40 | /// Type that the example is for. 41 | /// An example for the type. 42 | public static SwaggerExample Create(string name, T value) 43 | { 44 | return new SwaggerExample { Name = name, Value = value }; 45 | } 46 | 47 | /// 48 | /// Create an example for a type. 49 | /// 50 | /// Name of the example. 51 | /// Summary of the example. 52 | /// Example value. 53 | /// Type that the example is for. 54 | /// An example for the type. 55 | public static SwaggerExample Create(string name, string summary, T value) 56 | { 57 | return new SwaggerExample { Name = name, Summary = summary, Value = value }; 58 | } 59 | 60 | /// 61 | /// Create an example for a type. 62 | /// 63 | /// Name of the example. 64 | /// Summary of the example. 65 | /// Description of the example. 66 | /// Example value. 67 | /// Type that the example is for. 68 | /// An example for the type. 69 | public static SwaggerExample Create(string name, string summary, string description, T value) 70 | { 71 | return new SwaggerExample { Name = name, Summary = summary, Description = description, Value = value }; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | using System; 3 | using System.Reflection; 4 | 5 | namespace Swashbuckle.AspNetCore.Filters 6 | { 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddSwaggerExamplesFromAssemblyOf(this IServiceCollection services) 10 | { 11 | AddSwaggerExamples(services); 12 | 13 | services.Scan(scan => scan 14 | .FromAssemblyOf() 15 | .AddClasses(classes => classes.AssignableTo(typeof(IExamplesProvider<>))) 16 | .AsImplementedInterfaces() 17 | .AsSelf() 18 | .WithSingletonLifetime() 19 | ); 20 | services.Scan(scan => scan 21 | .FromAssemblyOf() 22 | .AddClasses(classes => classes.AssignableTo(typeof(IMultipleExamplesProvider<>))) 23 | .AsImplementedInterfaces() 24 | .AsSelf() 25 | .WithSingletonLifetime() 26 | ); 27 | 28 | return services; 29 | } 30 | 31 | public static IServiceCollection AddSwaggerExamplesFromAssemblyOf(this IServiceCollection services, params Type[] types) 32 | { 33 | AddSwaggerExamples(services); 34 | 35 | services.Scan(scan => scan 36 | .FromAssembliesOf(types) 37 | .AddClasses(classes => classes.AssignableTo(typeof(IExamplesProvider<>))) 38 | .AsImplementedInterfaces() 39 | .AsSelf() 40 | .WithSingletonLifetime() 41 | ); 42 | 43 | services.Scan(scan => scan 44 | .FromAssembliesOf(types) 45 | .AddClasses(classes => classes.AssignableTo(typeof(IMultipleExamplesProvider<>))) 46 | .AsImplementedInterfaces() 47 | .AsSelf() 48 | .WithSingletonLifetime() 49 | ); 50 | 51 | return services; 52 | } 53 | 54 | public static IServiceCollection AddSwaggerExamplesFromAssemblies(this IServiceCollection services, params Assembly[] assemblies) 55 | { 56 | AddSwaggerExamples(services); 57 | 58 | services.Scan(scan => scan 59 | .FromAssemblies(assemblies) 60 | .AddClasses(classes => classes.AssignableTo(typeof(IExamplesProvider<>))) 61 | .AsImplementedInterfaces() 62 | .AsSelf() 63 | .WithSingletonLifetime() 64 | ); 65 | 66 | services.Scan(scan => scan 67 | .FromAssemblies(assemblies) 68 | .AddClasses(classes => classes.AssignableTo(typeof(IMultipleExamplesProvider<>))) 69 | .AsImplementedInterfaces() 70 | .AsSelf() 71 | .WithSingletonLifetime() 72 | ); 73 | 74 | return services; 75 | } 76 | 77 | public static void AddSwaggerExamples(this IServiceCollection services) 78 | { 79 | services.AddSingleton(); 80 | services.AddSingleton(); 81 | services.AddSingleton(); 82 | services.AddSingleton(); 83 | services.AddSingleton(); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Examples/ExamplesConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Net.Http.Headers; 2 | using Microsoft.OpenApi; 3 | using Swashbuckle.AspNetCore.Filters.Extensions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text.Json.Nodes; 8 | 9 | namespace Swashbuckle.AspNetCore.Filters 10 | { 11 | internal class ExamplesConverter 12 | { 13 | private static readonly MediaTypeHeaderValue ApplicationXml = MediaTypeHeaderValue.Parse("application/xml; charset=utf-8"); 14 | private static readonly MediaTypeHeaderValue ApplicationJson = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); 15 | private static readonly MediaTypeHeaderValue TextCsv = MediaTypeHeaderValue.Parse("text/csv"); 16 | 17 | private readonly MvcOutputFormatter mvcOutputFormatter; 18 | 19 | public ExamplesConverter(MvcOutputFormatter mvcOutputFormatter) 20 | { 21 | this.mvcOutputFormatter = mvcOutputFormatter; 22 | } 23 | 24 | public JsonNode SerializeExampleCsv(object value) 25 | { 26 | var type = value.GetType(); 27 | if (type.IsPrimitive || type.IsValueType || type == typeof(string)) 28 | { 29 | return value.ToString(); 30 | } 31 | 32 | try 33 | { 34 | return mvcOutputFormatter.Serialize(value, TextCsv); 35 | } 36 | catch (MvcOutputFormatter.FormatterNotFoundException ex) 37 | { 38 | return $"{ex.GetType()}: {ex.Message} for example of {type.FullName}."; 39 | } 40 | } 41 | 42 | public JsonNode SerializeExampleXml(object value) 43 | { 44 | return mvcOutputFormatter.Serialize(value, ApplicationXml).FormatXml(); 45 | } 46 | 47 | public JsonNode SerializeExampleJson(object value) 48 | { 49 | return JsonNode.Parse(mvcOutputFormatter.Serialize(value, ApplicationJson)); 50 | } 51 | 52 | public IDictionary ToOpenApiExamplesDictionaryXml( 53 | IEnumerable> examples) 54 | { 55 | return ToOpenApiExamplesDictionary(examples, SerializeExampleXml); 56 | } 57 | 58 | public IDictionary ToOpenApiExamplesDictionaryCsv( 59 | IEnumerable> examples) 60 | { 61 | return ToOpenApiExamplesDictionary(examples, SerializeExampleCsv); 62 | } 63 | 64 | public IDictionary ToOpenApiExamplesDictionaryJson( 65 | IEnumerable> examples) 66 | { 67 | return ToOpenApiExamplesDictionary(examples, SerializeExampleJson); 68 | } 69 | 70 | private static IDictionary ToOpenApiExamplesDictionary( 71 | IEnumerable> examples, 72 | Func exampleConverter) 73 | { 74 | var groupedExamples = examples.GroupBy( 75 | ex => ex.Name, 76 | ex => (IOpenApiExample)new OpenApiExample 77 | { 78 | Summary = ex.Summary, 79 | Description = ex.Description, 80 | Value = exampleConverter(ex.Value) 81 | }); 82 | 83 | // If names are duplicated, only the first one is taken 84 | var examplesDict = groupedExamples.ToDictionary( 85 | grouping => grouping.Key, 86 | grouping => grouping.First()); 87 | 88 | return examplesDict; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/SecurityRequirementsOperationFilter/SecurityRequirementsOperationFilterT.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.OpenApi; 3 | using Swashbuckle.AspNetCore.Filters.Extensions; 4 | using Swashbuckle.AspNetCore.SwaggerGen; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace Swashbuckle.AspNetCore.Filters 10 | { 11 | public class SecurityRequirementsOperationFilter : IOperationFilter where T : Attribute 12 | { 13 | // inspired by https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/test/WebSites/OAuth2Integration/ResourceServer/Swagger/SecurityRequirementsOperationFilter.cs 14 | 15 | private readonly bool includeUnauthorizedAndForbiddenResponses; 16 | private readonly string securitySchemaName; 17 | private readonly Func, IEnumerable> policySelector; 18 | 19 | /// 20 | /// Constructor for SecurityRequirementsOperationFilter 21 | /// 22 | /// Used to select the authorization policy from the attribute e.g. (a => a.Policy) 23 | /// If true (default), then 401 and 403 responses will be added to every operation 24 | /// Name of the security schema. Default value is "oauth2" 25 | public SecurityRequirementsOperationFilter( 26 | Func, IEnumerable> policySelector, 27 | bool includeUnauthorizedAndForbiddenResponses = true, 28 | string securitySchemaName = "oauth2") 29 | { 30 | this.policySelector = policySelector; 31 | this.includeUnauthorizedAndForbiddenResponses = includeUnauthorizedAndForbiddenResponses; 32 | this.securitySchemaName = securitySchemaName; 33 | } 34 | 35 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 36 | { 37 | if (context.GetControllerAndActionAttributes().Any()) 38 | { 39 | return; 40 | } 41 | 42 | var actionAttributes = context.GetControllerAndActionAttributes(); 43 | 44 | if (!actionAttributes.Any()) 45 | { 46 | return; 47 | } 48 | 49 | if (includeUnauthorizedAndForbiddenResponses) 50 | { 51 | if (!operation.Responses.ContainsKey("401")) 52 | { 53 | operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" }); 54 | } 55 | 56 | if (!operation.Responses.ContainsKey("403")) 57 | { 58 | operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" }); 59 | } 60 | } 61 | 62 | if (operation?.Security?.Any(requirement => requirement.Any(scheme => scheme.Key.Reference.Id == securitySchemaName)) != true) 63 | { 64 | var policies = policySelector(actionAttributes) ?? Enumerable.Empty(); 65 | 66 | var schema = new OpenApiSecuritySchemeReference(securitySchemaName, context.Document); 67 | 68 | var securityRequirement = new OpenApiSecurityRequirement 69 | { 70 | [schema] = policies.ToList() 71 | }; 72 | 73 | if (operation.Security is null) 74 | { 75 | operation.Security = []; 76 | } 77 | 78 | operation.Security.Add(securityRequirement); 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Examples/ServiceProviderExamplesOperationFilter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ModelBinding; 3 | using Microsoft.OpenApi; 4 | using Swashbuckle.AspNetCore.Filters.Extensions; 5 | using Swashbuckle.AspNetCore.SwaggerGen; 6 | using System; 7 | using System.Linq; 8 | using System.Reflection; 9 | 10 | namespace Swashbuckle.AspNetCore.Filters 11 | { 12 | internal class ServiceProviderExamplesOperationFilter : IOperationFilter 13 | { 14 | private readonly IServiceProvider serviceProvider; 15 | private readonly RequestExample requestExample; 16 | private readonly ResponseExample responseExample; 17 | 18 | public ServiceProviderExamplesOperationFilter( 19 | IServiceProvider serviceProvider, 20 | RequestExample requestExample, 21 | ResponseExample responseExample) 22 | { 23 | this.serviceProvider = serviceProvider; 24 | this.requestExample = requestExample; 25 | this.responseExample = responseExample; 26 | } 27 | 28 | public void Apply(OpenApiOperation operation, OperationFilterContext context) 29 | { 30 | SetRequestExamples(operation, context); 31 | SetResponseExamples(operation, context); 32 | } 33 | 34 | private void SetRequestExamples(OpenApiOperation operation, OperationFilterContext context) 35 | { 36 | var actionAttributes = context.GetControllerAndActionAttributes(); 37 | 38 | foreach (var parameterDescription in context.ApiDescription.ParameterDescriptions) 39 | { 40 | if (actionAttributes.Any(a => a.RequestType == parameterDescription.Type)) 41 | { 42 | continue; // if [SwaggerRequestExample] is defined, then let ExamplesOperationFilter define the example 43 | } 44 | 45 | var example = serviceProvider.GetExampleForType(parameterDescription.Type); 46 | if (parameterDescription.Source == BindingSource.Body || parameterDescription.Source == BindingSource.Form || parameterDescription.Source == BindingSource.Custom) 47 | { 48 | requestExample.SetRequestBodyExampleForOperation(operation, context.SchemaRepository, parameterDescription.Type, example); 49 | } 50 | } 51 | } 52 | 53 | private class StatusCodeWithType 54 | { 55 | public StatusCodeWithType(int statusCode, Type type) 56 | { 57 | StatusCode = statusCode; 58 | Type = type; 59 | } 60 | 61 | public int StatusCode { get; } 62 | public Type Type { get; } 63 | } 64 | 65 | private void SetResponseExamples(OpenApiOperation operation, OperationFilterContext context) 66 | { 67 | var actionAttributes = context.GetControllerAndActionAttributes().Select(a => new StatusCodeWithType(a.StatusCode, a.ExamplesProviderType)); 68 | var responseAttributes = context.GetControllerAndActionAttributes().Select(a => new StatusCodeWithType(a.StatusCode, a.Type)); 69 | var autodetectedResponses = context.ApiDescription.SupportedResponseTypes.Select(r => new StatusCodeWithType(r.StatusCode, r.Type)); 70 | 71 | var responses = responseAttributes.Concat(autodetectedResponses); 72 | 73 | foreach (var response in responses) 74 | { 75 | if (actionAttributes.Any(a => a.StatusCode == response.StatusCode)) 76 | { 77 | continue; // if [SwaggerResponseExample] is defined, then let ExamplesOperationFilter define the example 78 | } 79 | 80 | var example = serviceProvider.GetExampleForType(response.Type); 81 | 82 | responseExample.SetResponseExampleForStatusCode(operation, response.StatusCode, example); 83 | } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Examples/ServiceProviderExamplesOperationFilterWithXmlDataContractTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | using Microsoft.Extensions.Options; 5 | using Microsoft.OpenApi; 6 | using NSubstitute; 7 | using Shouldly; 8 | using Swashbuckle.AspNetCore.Filters.Test.Extensions; 9 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes; 10 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples; 11 | using Swashbuckle.AspNetCore.SwaggerGen; 12 | using System; 13 | using System.Collections.Generic; 14 | using Xunit; 15 | 16 | namespace Swashbuckle.AspNetCore.Filters.Test.Examples 17 | { 18 | public class ServiceProviderExamplesOperationFilterWithXmlDataContractTests : BaseOperationFilterTests 19 | { 20 | private readonly IOperationFilter sut; 21 | private readonly IServiceProvider serviceProvider; 22 | 23 | public ServiceProviderExamplesOperationFilterWithXmlDataContractTests() 24 | { 25 | serviceProvider = Substitute.For(); 26 | serviceProvider.GetService(typeof(IExamplesProvider)).Returns(new PersonResponseAutoExample()); 27 | serviceProvider.GetService(typeof(IOptions)).Returns(Options.Create(new MvcOptions())); 28 | 29 | var mvcOutputFormatter = new MvcOutputFormatter(FormatterOptions.WithXmlDataContractFormatter, serviceProvider, new FakeLoggerFactory()); 30 | 31 | sut = new ServiceProviderExamplesOperationFilter( 32 | serviceProvider, 33 | new RequestExample(mvcOutputFormatter, Options.Create(new Swagger.SwaggerOptions())), 34 | new ResponseExample(mvcOutputFormatter, Options.Create(new Swagger.SwaggerOptions()))); 35 | } 36 | 37 | [Fact] 38 | public void Apply_WhenPassingDictionary_ShouldSetExampleOnRequestSchema() 39 | { 40 | // Arrange 41 | serviceProvider.GetService(typeof(IExamplesProvider>)).Returns(new DictionaryAutoRequestExample()); 42 | var requestBody = new OpenApiRequestBody 43 | { 44 | Content = new Dictionary 45 | { 46 | { "application/xml", new OpenApiMediaType() { Schema = new OpenApiSchema() } } 47 | } 48 | }; 49 | var operation = new OpenApiOperation { OperationId = "foobar", RequestBody = requestBody }; 50 | var parameterDescriptions = new List() { new ApiParameterDescription { Type = typeof(Dictionary), Source = BindingSource.Body } }; 51 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.DictionaryRequestAttribute), parameterDescriptions); 52 | 53 | // Act 54 | sut.Apply(operation, filterContext); 55 | 56 | // Assert 57 | var actualExample = requestBody.DeserializeDataContractXmlExampleAs>(); 58 | actualExample["PropertyInt"].ShouldBe(1); 59 | actualExample["PropertyString"].ShouldBe("Some string"); 60 | } 61 | 62 | [Fact] 63 | public void Apply_SetsResponseExamples_CorrectlyFormatsXmlExample() 64 | { 65 | // Arrange 66 | var response = new OpenApiResponse { Content = new Dictionary { { "application/xml", new OpenApiMediaType() } } }; 67 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 68 | operation.Responses.Add("200", response); 69 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AnnotatedWithSwaggerResponseAttribute)); 70 | SetSwaggerResponses(operation, filterContext); 71 | 72 | // Act 73 | sut.Apply(operation, filterContext); 74 | 75 | // Assert 76 | var formattedExample = response.Content["application/xml"].Example.ToString(); 77 | 78 | formattedExample.Contains("").ShouldBeFalse(); 79 | formattedExample.Contains("").ShouldBeTrue(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Swashbuckle.AspNetCore.Filters.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C3C314E2-1A92-43F6-B38B-7FA0FC1863EA}" 7 | ProjectSection(SolutionItems) = preProject 8 | appveyor.yml = appveyor.yml 9 | CHANGELOG.md = CHANGELOG.md 10 | src\Directory.Build.props = src\Directory.Build.props 11 | src\key.snk = src\key.snk 12 | LICENSE = LICENSE 13 | NuGet.config = NuGet.config 14 | README.md = README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{32C79B97-862D-4E61-99FA-9D4A894BA3A1}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Swashbuckle.AspNetCore.Filters", "src\Swashbuckle.AspNetCore.Filters\Swashbuckle.AspNetCore.Filters.csproj", "{BF2A4583-1466-440D-B818-0169634DA8F0}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{B47A2133-3F12-4616-BC3B-1C85E25087E9}" 22 | ProjectSection(SolutionItems) = preProject 23 | tools\key.snk = tools\key.snk 24 | tools\NuGet instructions.txt = tools\NuGet instructions.txt 25 | EndProjectSection 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C0D0AB6-777A-4327-92B6-41AA086FC049}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Swashbuckle.AspNetCore.Filters.Test", "test\Swashbuckle.AspNetCore.Filters.Test\Swashbuckle.AspNetCore.Filters.Test.csproj", "{8D343046-925C-4D1E-829E-BF7A03E2426D}" 30 | EndProject 31 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Swashbuckle.AspNetCore.Filters.Abstractions", "src\Swashbuckle.AspNetCore.Filters.Abstractions\Swashbuckle.AspNetCore.Filters.Abstractions.csproj", "{0965C00A-B186-4816-981D-EF3A68A75FB5}" 32 | EndProject 33 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi8", "test\WebApi8\WebApi8.csproj", "{0314EFF6-5E24-4DEE-8931-256BFB11FB89}" 34 | EndProject 35 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalApi8", "test\MinimalApi8\MinimalApi8.csproj", "{DB2E6F91-ACEB-4F9F-A0BF-B3F287220301}" 36 | EndProject 37 | Global 38 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 39 | Debug|Any CPU = Debug|Any CPU 40 | Release|Any CPU = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 43 | {BF2A4583-1466-440D-B818-0169634DA8F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {BF2A4583-1466-440D-B818-0169634DA8F0}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {BF2A4583-1466-440D-B818-0169634DA8F0}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {BF2A4583-1466-440D-B818-0169634DA8F0}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {8D343046-925C-4D1E-829E-BF7A03E2426D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {8D343046-925C-4D1E-829E-BF7A03E2426D}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {8D343046-925C-4D1E-829E-BF7A03E2426D}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {8D343046-925C-4D1E-829E-BF7A03E2426D}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {0965C00A-B186-4816-981D-EF3A68A75FB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {0965C00A-B186-4816-981D-EF3A68A75FB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {0965C00A-B186-4816-981D-EF3A68A75FB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {0965C00A-B186-4816-981D-EF3A68A75FB5}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {0314EFF6-5E24-4DEE-8931-256BFB11FB89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {0314EFF6-5E24-4DEE-8931-256BFB11FB89}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {0314EFF6-5E24-4DEE-8931-256BFB11FB89}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {0314EFF6-5E24-4DEE-8931-256BFB11FB89}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {DB2E6F91-ACEB-4F9F-A0BF-B3F287220301}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {DB2E6F91-ACEB-4F9F-A0BF-B3F287220301}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {DB2E6F91-ACEB-4F9F-A0BF-B3F287220301}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {DB2E6F91-ACEB-4F9F-A0BF-B3F287220301}.Release|Any CPU.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(NestedProjects) = preSolution 68 | {BF2A4583-1466-440D-B818-0169634DA8F0} = {32C79B97-862D-4E61-99FA-9D4A894BA3A1} 69 | {8D343046-925C-4D1E-829E-BF7A03E2426D} = {0C0D0AB6-777A-4327-92B6-41AA086FC049} 70 | {0965C00A-B186-4816-981D-EF3A68A75FB5} = {32C79B97-862D-4E61-99FA-9D4A894BA3A1} 71 | {0314EFF6-5E24-4DEE-8931-256BFB11FB89} = {0C0D0AB6-777A-4327-92B6-41AA086FC049} 72 | {DB2E6F91-ACEB-4F9F-A0BF-B3F287220301} = {0C0D0AB6-777A-4327-92B6-41AA086FC049} 73 | EndGlobalSection 74 | GlobalSection(ExtensibilityGlobals) = postSolution 75 | SolutionGuid = {24267CCB-C313-45F2-83DE-727185B6A4D7} 76 | EndGlobalSection 77 | EndGlobal 78 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Examples/ResponseExample.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Microsoft.OpenApi; 3 | using Swashbuckle.AspNetCore.Swagger; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text.Json.Nodes; 8 | 9 | namespace Swashbuckle.AspNetCore.Filters 10 | { 11 | internal class ResponseExample 12 | { 13 | private readonly MvcOutputFormatter mvcOutputFormatter; 14 | private readonly SwaggerOptions swaggerOptions; 15 | 16 | public ResponseExample( 17 | MvcOutputFormatter mvcOutputFormatter, 18 | IOptions options) 19 | { 20 | this.mvcOutputFormatter = mvcOutputFormatter; 21 | this.swaggerOptions = options?.Value; 22 | } 23 | 24 | public void SetResponseExampleForStatusCode( 25 | OpenApiOperation operation, 26 | int statusCode, 27 | object example) 28 | { 29 | if (example == null) 30 | { 31 | return; 32 | } 33 | 34 | var key = statusCode == 0 ? "default" : statusCode.ToString(); 35 | var response = operation.Responses.FirstOrDefault(r => r.Key == key); 36 | 37 | if (response.Equals(default(KeyValuePair)) || response.Value == null) 38 | { 39 | return; 40 | } 41 | 42 | var examplesConverter = new ExamplesConverter(mvcOutputFormatter); 43 | 44 | var multiple = example as IEnumerable>; 45 | if (multiple == null) 46 | { 47 | SetSingleResponseExampleForStatusCode(response, example, examplesConverter); 48 | } 49 | else 50 | { 51 | if (swaggerOptions.OpenApiVersion == Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0) 52 | { 53 | // Swashbuckle.AspNetCore 8 & 9 duplicates the examples when using 2.0, which it shouldn't do. 54 | // As a workaround I'm going to just set the first example 55 | SetSingleResponseExampleForStatusCode(response, multiple.First().Value, examplesConverter); 56 | } 57 | else 58 | { 59 | SetMultipleResponseExampleForStatusCode(response, multiple, examplesConverter); 60 | } 61 | } 62 | } 63 | 64 | private void SetSingleResponseExampleForStatusCode( 65 | KeyValuePair response, 66 | object example, 67 | ExamplesConverter examplesConverter) 68 | { 69 | var jsonExample = new Lazy(() => examplesConverter.SerializeExampleJson(example)); 70 | var xmlExample = new Lazy(() => examplesConverter.SerializeExampleXml(example)); 71 | var csvExample = new Lazy(() => examplesConverter.SerializeExampleCsv(example)); 72 | 73 | foreach (var content in response.Value.Content) 74 | { 75 | if (content.Key.Contains("csv")) 76 | { 77 | content.Value.Example = csvExample.Value; 78 | } 79 | else if (content.Key.Contains("xml")) 80 | { 81 | content.Value.Example = xmlExample.Value; 82 | } 83 | else 84 | { 85 | content.Value.Example = jsonExample.Value; 86 | } 87 | } 88 | } 89 | 90 | private void SetMultipleResponseExampleForStatusCode( 91 | KeyValuePair response, 92 | IEnumerable> examples, 93 | ExamplesConverter examplesConverter) 94 | { 95 | var jsonExamples = new Lazy>(() => 96 | examplesConverter.ToOpenApiExamplesDictionaryJson(examples) 97 | ); 98 | 99 | var xmlExamples = new Lazy>(() => 100 | examplesConverter.ToOpenApiExamplesDictionaryXml(examples) 101 | ); 102 | 103 | var csvExamples = new Lazy>(() => 104 | examplesConverter.ToOpenApiExamplesDictionaryCsv(examples) 105 | ); 106 | 107 | foreach (var content in response.Value.Content) 108 | { 109 | if (content.Key.Contains("xml")) 110 | { 111 | content.Value.Examples = xmlExamples.Value; 112 | } 113 | else if (content.Key.Contains("csv")) 114 | { 115 | content.Value.Examples = csvExamples.Value; 116 | } 117 | else 118 | { 119 | content.Value.Examples = jsonExamples.Value; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Examples/RequestExample.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | using Microsoft.OpenApi; 3 | using Swashbuckle.AspNetCore.Filters.Extensions; 4 | using Swashbuckle.AspNetCore.Swagger; 5 | using Swashbuckle.AspNetCore.SwaggerGen; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text.Json.Nodes; 10 | 11 | namespace Swashbuckle.AspNetCore.Filters 12 | { 13 | internal class RequestExample 14 | { 15 | private readonly MvcOutputFormatter mvcOutputFormatter; 16 | private readonly SwaggerOptions swaggerOptions; 17 | 18 | public RequestExample( 19 | MvcOutputFormatter mvcOutputFormatter, 20 | IOptions options) 21 | { 22 | this.mvcOutputFormatter = mvcOutputFormatter; 23 | this.swaggerOptions = options?.Value; 24 | } 25 | 26 | public void SetRequestBodyExampleForOperation( 27 | OpenApiOperation operation, 28 | SchemaRepository schemaRepository, 29 | Type requestType, 30 | object example) 31 | { 32 | if (example == null) 33 | { 34 | return; 35 | } 36 | 37 | if (operation.RequestBody == null || operation.RequestBody.Content == null) 38 | { 39 | return; 40 | } 41 | 42 | var examplesConverter = new ExamplesConverter(mvcOutputFormatter); 43 | 44 | JsonNode firstOpenApiExample; 45 | var multiple = example as IEnumerable>; 46 | if (multiple == null) 47 | { 48 | firstOpenApiExample = SetSingleRequestExampleForOperation(operation, example, examplesConverter); 49 | } 50 | else 51 | { 52 | firstOpenApiExample = SetMultipleRequestExamplesForOperation(operation, multiple, examplesConverter); 53 | } 54 | 55 | if (swaggerOptions.OpenApiVersion == Microsoft.OpenApi.OpenApiSpecVersion.OpenApi2_0) 56 | { 57 | // Swagger v2 doesn't have a request example on the path 58 | // Fallback to setting it on the object in the "definitions" 59 | 60 | string schemaDefinitionName = requestType.SchemaDefinitionName(); 61 | if (schemaRepository.Schemas.ContainsKey(schemaDefinitionName)) 62 | { 63 | var schemaDefinition = schemaRepository.Schemas[schemaDefinitionName]; 64 | if (schemaDefinition.Example == null) 65 | { 66 | ((OpenApiSchema)schemaDefinition).Example = firstOpenApiExample; 67 | } 68 | } 69 | } 70 | } 71 | 72 | /// 73 | /// Sets an example on the operation for all of the operation's content types 74 | /// 75 | /// The first example so that it can be reused on the definition for V2 76 | private JsonNode SetSingleRequestExampleForOperation( 77 | OpenApiOperation operation, 78 | object example, 79 | ExamplesConverter examplesConverter) 80 | { 81 | var jsonExample = new Lazy(() => examplesConverter.SerializeExampleJson(example)); 82 | var xmlExample = new Lazy(() => examplesConverter.SerializeExampleXml(example)); 83 | var csvExample = new Lazy(() => examplesConverter.SerializeExampleCsv(example)); 84 | 85 | foreach (var content in operation.RequestBody.Content) 86 | { 87 | if (content.Key.Contains("xml")) 88 | { 89 | content.Value.Example = xmlExample.Value; 90 | } 91 | else if (content.Key.Contains("csv")) 92 | { 93 | content.Value.Example = csvExample.Value; 94 | } 95 | else 96 | { 97 | content.Value.Example = jsonExample.Value; 98 | } 99 | } 100 | 101 | return operation.RequestBody.Content.FirstOrDefault().Value?.Example; 102 | } 103 | 104 | /// 105 | /// Sets multiple examples on the operation for all of the operation's content types 106 | /// 107 | /// The first example so that it can be reused on the definition for V2 108 | private JsonNode SetMultipleRequestExamplesForOperation( 109 | OpenApiOperation operation, 110 | IEnumerable> examples, 111 | ExamplesConverter examplesConverter) 112 | { 113 | var jsonExamples = new Lazy>(() => 114 | examplesConverter.ToOpenApiExamplesDictionaryJson(examples) 115 | ); 116 | 117 | var xmlExamples = new Lazy>(() => 118 | examplesConverter.ToOpenApiExamplesDictionaryXml(examples) 119 | ); 120 | 121 | var csvExamples = new Lazy>(() => 122 | examplesConverter.ToOpenApiExamplesDictionaryCsv(examples) 123 | ); 124 | 125 | foreach (var content in operation.RequestBody.Content) 126 | { 127 | if (content.Key.Contains("xml")) 128 | { 129 | content.Value.Examples = xmlExamples.Value; 130 | } 131 | else if (content.Key.Contains("csv")) 132 | { 133 | content.Value.Examples = csvExamples.Value; 134 | } 135 | else 136 | { 137 | content.Value.Examples = jsonExamples.Value; 138 | } 139 | } 140 | 141 | return operation.RequestBody.Content.FirstOrDefault().Value?.Examples?.FirstOrDefault().Value?.Value; 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/BaseOperationFilterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc.Abstractions; 3 | using Microsoft.AspNetCore.Mvc.ApiExplorer; 4 | using Microsoft.AspNetCore.Mvc.Controllers; 5 | using Microsoft.AspNetCore.Routing; 6 | using Microsoft.AspNetCore.Routing.Patterns; 7 | using Microsoft.OpenApi; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Serialization; 10 | using Swashbuckle.AspNetCore.Annotations; 11 | using Swashbuckle.AspNetCore.Filters.Extensions; 12 | using Swashbuckle.AspNetCore.Filters.Test.Extensions; 13 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes; 14 | using Swashbuckle.AspNetCore.Newtonsoft; 15 | using Swashbuckle.AspNetCore.SwaggerGen; 16 | using System; 17 | using System.Collections.Generic; 18 | using System.IO; 19 | using System.Linq; 20 | using System.Reflection; 21 | using System.Threading.Tasks; 22 | 23 | namespace Swashbuckle.AspNetCore.Filters.Test 24 | { 25 | public abstract class BaseOperationFilterTests 26 | { 27 | /// 28 | /// Used for testing against Minimal APIs / Endpoints 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | protected OperationFilterContext FilterContextFor(Endpoint endpoint, List parameterDescriptions = null, List supportedResponseTypes = null) 35 | { 36 | var apiDescription = new ApiDescription 37 | { 38 | ActionDescriptor = new ActionDescriptor 39 | { 40 | EndpointMetadata = endpoint.Metadata.ToList() 41 | } 42 | }; 43 | 44 | return FilterContextFor(apiDescription, new CamelCasePropertyNamesContractResolver(), parameterDescriptions, supportedResponseTypes); 45 | } 46 | 47 | /// 48 | /// Used for testing against Controller/Actions 49 | /// 50 | /// 51 | /// 52 | /// 53 | /// 54 | /// 55 | protected OperationFilterContext FilterContextFor(Type controllerType, string actionName, List parameterDescriptions = null, List supportedResponseTypes = null) 56 | { 57 | var apiDescription = new ApiDescription 58 | { 59 | ActionDescriptor = new ControllerActionDescriptor 60 | { 61 | ControllerTypeInfo = controllerType.GetTypeInfo(), 62 | MethodInfo = controllerType.GetMethod(actionName), 63 | } 64 | }; 65 | 66 | var schemaRepository = new SchemaRepository(); 67 | 68 | var methodInfo = controllerType.GetMethod(actionName); 69 | 70 | foreach (var parameterInfo in methodInfo.GetParameters()) 71 | { 72 | var dictionary = new Dictionary { [parameterInfo.Name] = new OpenApiSchema { Type = JsonSchemaType.String } }; 73 | var schema = new OpenApiSchema { Type = JsonSchemaType.Object, Properties = dictionary }; 74 | var schemaId = parameterInfo.ParameterType.SchemaDefinitionName(); 75 | 76 | schemaRepository.AddDefinition(schemaId, schema); 77 | } 78 | 79 | return FilterContextFor(apiDescription, new CamelCasePropertyNamesContractResolver(), parameterDescriptions, supportedResponseTypes, schemaRepository); 80 | } 81 | 82 | protected OperationFilterContext FilterContextFor( 83 | ApiDescription apiDescription, 84 | IContractResolver contractResolver, 85 | List parameterDescriptions = null, 86 | List supportedResponseTypes = null, 87 | SchemaRepository schemaRepository = null) 88 | { 89 | if (parameterDescriptions != null) 90 | { 91 | apiDescription.With(api => api.ParameterDescriptions, parameterDescriptions); 92 | } 93 | 94 | if (supportedResponseTypes != null) 95 | { 96 | apiDescription.With(api => api.SupportedResponseTypes, supportedResponseTypes); 97 | } 98 | 99 | var jsonSerializerSettings = new JsonSerializerSettings 100 | { 101 | ContractResolver = contractResolver 102 | }; 103 | 104 | var schemaOptions = new SchemaGeneratorOptions(); 105 | 106 | var methodInfo = apiDescription.ActionDescriptor is ControllerActionDescriptor descriptor ? 107 | descriptor.MethodInfo : null; 108 | 109 | return new OperationFilterContext( 110 | apiDescription, 111 | new SchemaGenerator(schemaOptions, new NewtonsoftDataContractResolver(jsonSerializerSettings)), 112 | schemaRepository, 113 | new OpenApiDocument(), 114 | methodInfo); 115 | } 116 | 117 | protected void SetSwaggerResponses(OpenApiOperation operation, OperationFilterContext filterContext) 118 | { 119 | var swaggerResponseFilter = new AnnotationsOperationFilter(); 120 | swaggerResponseFilter.Apply(operation, filterContext); 121 | } 122 | 123 | static readonly RequestDelegate EmptyRequestDelegate = (context) => Task.CompletedTask; 124 | 125 | protected FakeEndpointConventionBuilder CreateBuilder() 126 | { 127 | var conventionBuilder = new FakeEndpointConventionBuilder(new RouteEndpointBuilder( 128 | EmptyRequestDelegate, 129 | RoutePatternFactory.Parse("/test"), 130 | order: 0)); 131 | 132 | return conventionBuilder; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/TestFixtures/Fakes/FakeActions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Swashbuckle.AspNetCore.Annotations; 4 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples; 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes 9 | { 10 | public class FakeActions 11 | { 12 | [SwaggerResponse(200, type: typeof(PersonResponse))] 13 | [SwaggerResponseExample(200, typeof(PersonResponseExample))] 14 | public IActionResult AnnotatedWithSwaggerResponseExampleAttribute() 15 | { 16 | throw new NotImplementedException(); 17 | } 18 | 19 | [SwaggerResponse(200, type: typeof(PersonResponse))] 20 | [SwaggerResponseExample(200, typeof(PersonResponseMultipleExamples))] 21 | public IActionResult AnnotatedWithSwaggerResponseMultipleExamplesAttribute() 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | [SwaggerResponse(200, type: typeof(IEnumerable))] 27 | [SwaggerResponseExample(200, typeof(PeopleResponseExample))] 28 | public IActionResult AnnotatedWithSwaggerResponseExampleAttributeOfTypeEnumerable() 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | [SwaggerResponse(200, type: typeof(IEnumerable))] 34 | public IActionResult AnnotatedWithSwaggerResponseAttributeOfTypeEnumerable() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | [SwaggerResponse(200, type: typeof(PersonResponse))] 40 | public IActionResult AnnotatedWithSwaggerResponseAttribute() 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | [SwaggerResponse(200, type: typeof(IEnumerable))] 46 | public IActionResult GenericAnnotatedWithSwaggerResponseAttribute() 47 | { 48 | throw new NotImplementedException(); 49 | } 50 | 51 | [SwaggerRequestExample(typeof(PersonRequest), typeof(PersonRequestExample))] 52 | public IActionResult AnnotatedWithSwaggerRequestExampleAttribute(PersonRequest personRequest) 53 | { 54 | throw new NotImplementedException(); 55 | } 56 | 57 | [SwaggerRequestExample(typeof(PersonRequest), typeof(PersonRequestMultipleExamples))] 58 | public IActionResult AnnotatedWithSwaggerRequestMultipleExamplesAttribute(PersonRequest personRequest) 59 | { 60 | throw new NotImplementedException(); 61 | } 62 | 63 | public IActionResult PersonRequestUnannotated(PersonRequest personRequest) 64 | { 65 | throw new NotImplementedException(); 66 | } 67 | 68 | public IActionResult IPersonRequestUnannotated(IPersonRequest personRequest) 69 | { 70 | throw new NotImplementedException(); 71 | } 72 | 73 | [SwaggerRequestExample(typeof(PersonResponse), typeof(PersonRequestExample))] 74 | public IActionResult AnnotatedWithIncorrectSwaggerRequestExampleAttribute(PersonRequest personRequest) 75 | { 76 | throw new NotImplementedException(); 77 | } 78 | 79 | [SwaggerRequestExample(typeof(Dictionary), typeof(DictionaryRequestExample))] 80 | public IActionResult AnnotatedWithDictionarySwaggerRequestExampleAttribute(Dictionary personRequest) 81 | { 82 | throw new NotImplementedException(); 83 | } 84 | 85 | public IActionResult DictionaryRequestAttribute(Dictionary personRequest) 86 | { 87 | throw new NotImplementedException(); 88 | } 89 | 90 | [Authorize] 91 | public IActionResult Authorize() 92 | { 93 | throw new NotImplementedException(); 94 | } 95 | 96 | [Authorize("Administrator")] 97 | public IActionResult AuthorizeAdministratorPolicy() 98 | { 99 | throw new NotImplementedException(); 100 | } 101 | 102 | [Authorize("Administrator")] 103 | [Authorize("Customer")] 104 | public IActionResult AuthorizeMultiplePolicies() 105 | { 106 | throw new NotImplementedException(); 107 | } 108 | 109 | [Authorize(Roles = "Administrator")] 110 | public IActionResult AuthorizeAdministratorRole() 111 | { 112 | throw new NotImplementedException(); 113 | } 114 | 115 | [Authorize(Roles = "Administrator")] 116 | [Authorize(Roles = "Customer")] 117 | public IActionResult AuthorizeMultipleRoles() 118 | { 119 | throw new NotImplementedException(); 120 | } 121 | 122 | [Authorize(Roles = "Administrator, Customer")] 123 | public IActionResult AuthorizeMultipleRolesInOneAttribute() 124 | { 125 | throw new NotImplementedException(); 126 | } 127 | 128 | [Authorize(Policy = "Administrator")] 129 | [Authorize(Roles = "Customer")] 130 | public IActionResult AuthorizePolicyAndRole() 131 | { 132 | throw new NotImplementedException(); 133 | } 134 | 135 | [AllowAnonymous] 136 | public IActionResult AllowAnonymous() 137 | { 138 | throw new NotImplementedException(); 139 | } 140 | 141 | public IActionResult RequestTakesAnInt([FromBody]int blah) 142 | { 143 | throw new NotImplementedException(); 144 | } 145 | 146 | public IActionResult RequestTakesANullableEnum([FromBody]Title? title) 147 | { 148 | throw new NotImplementedException(); 149 | } 150 | 151 | public IActionResult None() 152 | { 153 | throw new NotImplementedException(); 154 | } 155 | 156 | public PersonResponse PersonResponseNotAnnotated() 157 | { 158 | throw new NotImplementedException(); 159 | } 160 | } 161 | 162 | [Authorize] 163 | public class BaseController 164 | { 165 | } 166 | 167 | public class FakeInheritedController : BaseController 168 | { 169 | public IActionResult Authorize() 170 | { 171 | throw new NotImplementedException(); 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/Examples/MvcFormatterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Options; 3 | using Microsoft.Net.Http.Headers; 4 | using NSubstitute; 5 | using Shouldly; 6 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes; 7 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.Examples; 8 | using System; 9 | using System.Xml.Linq; 10 | using Xunit; 11 | using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions; 12 | 13 | namespace Swashbuckle.AspNetCore.Filters.Test.Examples 14 | { 15 | public class GivenAMvcFormatterWithNoOutputFormatters_WhenSerializingAnObject 16 | { 17 | private readonly MvcOutputFormatter sut; 18 | 19 | public GivenAMvcFormatterWithNoOutputFormatters_WhenSerializingAnObject() 20 | => sut = new MvcOutputFormatter(FormatterOptions.WithoutFormatters, null, null); 21 | 22 | [Fact] 23 | public void ThenAFormatNotFoundExceptionIsThrown() 24 | { 25 | var value = new PersonResponseExample().GetExamples(); 26 | var contentType = MediaTypeHeaderValue.Parse("application/xml; charset=utf-8"); 27 | 28 | Should 29 | .Throw(() => sut.Serialize(value, contentType)) 30 | .Message.ShouldBe($"OutputFormatter not found for '{contentType}'"); 31 | } 32 | 33 | [Fact] 34 | public void ThenWillFallbackToSystemTextJson() 35 | { 36 | var value = new PersonRequestExample().GetExamples(); 37 | var contentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); 38 | 39 | sut.Serialize(value, contentType) 40 | .ShouldBe("{\"title\":3,\"age\":24,\"firstName\":\"Dave\",\"income\":null,\"children\":null,\"job\":null}"); 41 | } 42 | } 43 | 44 | public class GivenAMvcFormatterWithNoOutputFormatters_WhenSerializingAnObjectAndJsonOptionsAreConfigured 45 | { 46 | private readonly MvcOutputFormatter sut; 47 | private readonly IServiceProvider serviceProvider; 48 | private readonly IOptions jsonOptions; 49 | 50 | public GivenAMvcFormatterWithNoOutputFormatters_WhenSerializingAnObjectAndJsonOptionsAreConfigured() 51 | { 52 | jsonOptions = Substitute.For>(); 53 | jsonOptions.Value.Returns(new JsonOptions 54 | { 55 | SerializerOptions = { PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseUpper } 56 | }); 57 | 58 | serviceProvider = Substitute.For(); 59 | serviceProvider.GetService(typeof(IOptions)).Returns(jsonOptions); 60 | 61 | sut = new MvcOutputFormatter(FormatterOptions.WithoutFormatters, serviceProvider, null); 62 | } 63 | 64 | [Fact] 65 | public void ThenWillFallbackToSystemTextJsonWithJsonOptions() 66 | { 67 | var value = new PersonRequestExample().GetExamples(); 68 | var contentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); 69 | 70 | sut.Serialize(value, contentType) 71 | .ShouldBe("{\"TITLE\":3,\"AGE\":24,\"FIRST_NAME\":\"Dave\",\"INCOME\":null,\"CHILDREN\":null,\"JOB\":null}"); 72 | } 73 | } 74 | 75 | public class GivenAMvcFormatterWithOutputFormattersButNoLoggerFactory_WhenSerializingAnObject 76 | { 77 | private readonly MvcOutputFormatter sut; 78 | 79 | public GivenAMvcFormatterWithOutputFormattersButNoLoggerFactory_WhenSerializingAnObject() 80 | { 81 | sut = new MvcOutputFormatter(FormatterOptions.WithXmlDataContractFormatter, null, null); 82 | } 83 | 84 | [Fact] 85 | public void ThenAnArgumentNullExceptionIsThrown() 86 | { 87 | var value = new PersonResponseExample().GetExamples(); 88 | var contentType = MediaTypeHeaderValue.Parse("application/xml; charset=utf-8"); 89 | 90 | Should 91 | .Throw(() => sut.Serialize(value, contentType)) 92 | .ParamName.ShouldBe("loggerFactory"); 93 | } 94 | } 95 | 96 | public class GivenAMvcFormatterWithOutputFormatters_WhenSerializingAnObjectForAContentTypeThatIsNotConfigured 97 | { 98 | private readonly MvcOutputFormatter sut; 99 | 100 | public GivenAMvcFormatterWithOutputFormatters_WhenSerializingAnObjectForAContentTypeThatIsNotConfigured() 101 | => sut = new MvcOutputFormatter(FormatterOptions.WithXmlDataContractFormatter, null, new FakeLoggerFactory()); 102 | 103 | [Fact] 104 | public void ThenAFormatNotFoundExceptionIsThrown() 105 | { 106 | var value = new PersonResponseExample().GetExamples(); 107 | var contentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); 108 | 109 | Should 110 | .Throw(() => sut.Serialize(value, contentType)) 111 | .Message.ShouldBe($"OutputFormatter not found for '{contentType}'"); 112 | } 113 | } 114 | 115 | public class GivenAMvcFormatterWitXmlOutputFormatter_WhenSerializingAnObjectAsXml 116 | { 117 | private readonly MvcOutputFormatter sut; 118 | private readonly IServiceProvider serviceProvider; 119 | 120 | public GivenAMvcFormatterWitXmlOutputFormatter_WhenSerializingAnObjectAsXml() 121 | { 122 | serviceProvider = Substitute.For(); 123 | serviceProvider.GetService(typeof(IOptions)).Returns(Options.Create(new MvcOptions())); 124 | 125 | sut = new MvcOutputFormatter(FormatterOptions.WithXmlDataContractFormatter, serviceProvider, new FakeLoggerFactory()); 126 | } 127 | 128 | [Fact] 129 | public void ThenAnXmlStringIsReturned() 130 | { 131 | var value = new PersonResponseExample().GetExamples(); 132 | var contentType = MediaTypeHeaderValue.Parse("application/xml; charset=utf-8"); 133 | 134 | var serializedValue = sut.Serialize(value, contentType); 135 | Should.NotThrow(() => XDocument.Parse(serializedValue)); 136 | } 137 | 138 | [Fact] 139 | public void ThenValueIsSerialized() 140 | { 141 | var value = new PersonResponseExample().GetExamples(); 142 | var contentType = MediaTypeHeaderValue.Parse("application/xml; charset=utf-8"); 143 | 144 | sut.Serialize(value, contentType) 145 | .ShouldBe( 146 | "" + 147 | "27" + 148 | "123" + 149 | "" + 150 | "Dr" + 151 | "John" + 152 | ""); 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/Swashbuckle.AspNetCore.Filters/Examples/MvcOutputFormatter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.Formatters; 4 | using Microsoft.AspNetCore.Mvc.Infrastructure; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Logging; 7 | using Microsoft.Extensions.Options; 8 | using Microsoft.Net.Http.Headers; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Text; 14 | using System.Text.Json; 15 | 16 | namespace Swashbuckle.AspNetCore.Filters 17 | { 18 | public class MvcOutputFormatter 19 | { 20 | private readonly object initializeLock = new object(); 21 | private bool initializedOutputFormatterSelector; 22 | 23 | private readonly IOptions options; 24 | private readonly IServiceProvider serviceProvider; 25 | private readonly ILoggerFactory loggerFactory; 26 | 27 | private OutputFormatterSelector outputFormatterSelector; 28 | 29 | private OutputFormatterSelector OutputFormatterSelector 30 | { 31 | get 32 | { 33 | lock (initializeLock) 34 | { 35 | if (!initializedOutputFormatterSelector) 36 | { 37 | var selectorOptions = GetSelectorOptions(options); 38 | if (selectorOptions != null) 39 | { 40 | outputFormatterSelector = new DefaultOutputFormatterSelector( 41 | selectorOptions, 42 | loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory))); 43 | } 44 | initializedOutputFormatterSelector = true; 45 | } 46 | } 47 | 48 | return outputFormatterSelector; 49 | } 50 | } 51 | 52 | public MvcOutputFormatter(IOptions options, IServiceProvider serviceProvider, ILoggerFactory loggerFactory) 53 | { 54 | this.initializedOutputFormatterSelector = false; 55 | this.options = options; 56 | this.serviceProvider = serviceProvider; 57 | this.loggerFactory = loggerFactory; 58 | } 59 | 60 | public string Serialize(T value, MediaTypeHeaderValue contentType) 61 | { 62 | if (OutputFormatterSelector == null) 63 | { 64 | return SerializeWithoutMvc(value, contentType); 65 | } 66 | 67 | if (value == null) 68 | { 69 | return contentType.SubType == "json" ? "\"null\"" : string.Empty; 70 | } 71 | 72 | using (var stringWriter = new StringWriter()) 73 | { 74 | var outputFormatterContext = GetOutputFormatterContext( 75 | stringWriter, 76 | value, 77 | value.GetType(), 78 | contentType, 79 | serviceProvider); 80 | 81 | var formatter = OutputFormatterSelector.SelectFormatter( 82 | outputFormatterContext, 83 | new List(), 84 | new MediaTypeCollection()); 85 | 86 | if (formatter == null) 87 | { 88 | throw new FormatterNotFoundException(contentType); 89 | } 90 | 91 | formatter.WriteAsync(outputFormatterContext).GetAwaiter().GetResult(); 92 | stringWriter.FlushAsync().GetAwaiter().GetResult(); 93 | var result = stringWriter.ToString(); 94 | if (string.IsNullOrEmpty(result)) 95 | { 96 | // workaround for SystemTextJsonOutputFormatter 97 | var ms = (MemoryStream)outputFormatterContext.HttpContext.Response.Body; 98 | result = Encoding.UTF8.GetString(ms.ToArray()); 99 | } 100 | 101 | return result; 102 | } 103 | } 104 | 105 | private string SerializeWithoutMvc(T value, MediaTypeHeaderValue contentType) 106 | { 107 | if (contentType.MediaType.Value.StartsWith("application/json")) 108 | { 109 | var jsonOptions = serviceProvider?.GetService>(); 110 | var jsonSerializerOptions = jsonOptions != null 111 | ? jsonOptions.Value.SerializerOptions 112 | : new JsonSerializerOptions(JsonSerializerDefaults.Web); 113 | 114 | return JsonSerializer.Serialize(value, jsonSerializerOptions); 115 | } 116 | 117 | throw new FormatterNotFoundException(contentType); 118 | } 119 | 120 | private static IOptions GetSelectorOptions(IOptions options) 121 | { 122 | if (options?.Value?.OutputFormatters == null || !options.Value.OutputFormatters.Any()) 123 | { 124 | return null; 125 | } 126 | 127 | var selectorOptions = new MvcOptions 128 | { 129 | ReturnHttpNotAcceptable = true, 130 | RespectBrowserAcceptHeader = true 131 | }; 132 | 133 | foreach (var formatter in options.Value.OutputFormatters) 134 | { 135 | selectorOptions.OutputFormatters.Add(formatter); 136 | } 137 | 138 | return new OptionsWrapper(selectorOptions); 139 | } 140 | 141 | private static OutputFormatterWriteContext GetOutputFormatterContext( 142 | TextWriter writer, 143 | object outputValue, 144 | Type outputType, 145 | MediaTypeHeaderValue contentType, 146 | IServiceProvider serviceProvider) 147 | { 148 | return new OutputFormatterWriteContext( 149 | GetHttpContext(contentType, serviceProvider), 150 | (stream, encoding) => writer, 151 | outputType, 152 | outputValue); 153 | } 154 | 155 | private static HttpContext GetHttpContext( 156 | MediaTypeHeaderValue contentType, 157 | IServiceProvider serviceProvider) 158 | { 159 | var httpContext = new DefaultHttpContext(); 160 | 161 | httpContext.Request.Headers[HeaderNames.AcceptCharset] = contentType.Charset.ToString(); 162 | httpContext.Request.Headers[HeaderNames.Accept] = contentType.MediaType.Value; 163 | httpContext.Request.ContentType = contentType.MediaType.Value; 164 | 165 | httpContext.Response.Body = new MemoryStream(); 166 | httpContext.RequestServices = serviceProvider; 167 | 168 | return httpContext; 169 | } 170 | 171 | internal class FormatterNotFoundException : Exception 172 | { 173 | public FormatterNotFoundException(MediaTypeHeaderValue contentType) 174 | : base($"OutputFormatter not found for '{contentType}'") 175 | { } 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/AppendAuthorizeToSummaryOperationFilterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.Builder; 3 | using Xunit; 4 | using Shouldly; 5 | using Swashbuckle.AspNetCore.SwaggerGen; 6 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes; 7 | using static Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.FakeControllers; 8 | using Microsoft.OpenApi; 9 | 10 | namespace Swashbuckle.AspNetCore.Filters.Test 11 | { 12 | public class AppendAuthorizeToSummaryOperationFilterTests : BaseOperationFilterTests 13 | { 14 | private readonly IOperationFilter sut; 15 | 16 | public AppendAuthorizeToSummaryOperationFilterTests() 17 | { 18 | sut = new AppendAuthorizeToSummaryOperationFilter(); 19 | } 20 | 21 | [Fact] 22 | public void Apply_AppendsAuth() 23 | { 24 | // Arrange 25 | var operation = new OpenApiOperation { Summary = "Test summary" }; 26 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.Authorize)); 27 | 28 | // Act 29 | sut.Apply(operation, filterContext); 30 | 31 | // Assert 32 | operation.Summary.ShouldBe("Test summary (Auth)"); 33 | } 34 | 35 | [Fact] 36 | public void Apply_AppendsPolicy() 37 | { 38 | // Arrange 39 | var operation = new OpenApiOperation { Summary = "Test summary" }; 40 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeAdministratorPolicy)); 41 | 42 | // Act 43 | sut.Apply(operation, filterContext); 44 | 45 | // Assert 46 | operation.Summary.ShouldBe("Test summary (Auth policies: Administrator)"); 47 | } 48 | 49 | [Fact] 50 | public void Apply_AppendsMultiplePolicies() 51 | { 52 | // Arrange 53 | var operation = new OpenApiOperation { Summary = "Test summary" }; 54 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeMultiplePolicies)); 55 | 56 | // Act 57 | sut.Apply(operation, filterContext); 58 | 59 | // Assert 60 | operation.Summary.ShouldBe("Test summary (Auth policies: Administrator, Customer)"); 61 | } 62 | 63 | [Fact] 64 | public void Apply_AppendsRole() 65 | { 66 | // Arrange 67 | var operation = new OpenApiOperation { Summary = "Test summary" }; 68 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeAdministratorRole)); 69 | 70 | // Act 71 | sut.Apply(operation, filterContext); 72 | 73 | // Assert 74 | operation.Summary.ShouldBe("Test summary (Auth roles: Administrator)"); 75 | } 76 | 77 | [Fact] 78 | public void Apply_AppendsMultipleRoles() 79 | { 80 | // Arrange 81 | var operation = new OpenApiOperation { Summary = "Test summary" }; 82 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeMultipleRoles)); 83 | 84 | // Act 85 | sut.Apply(operation, filterContext); 86 | 87 | // Assert 88 | operation.Summary.ShouldBe("Test summary (Auth roles: Administrator, Customer)"); 89 | } 90 | 91 | [Fact] 92 | public void Apply_AppendsMultipleRolesInOneAttribute() 93 | { 94 | // Arrange 95 | var operation = new OpenApiOperation { Summary = "Test summary" }; 96 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeMultipleRolesInOneAttribute)); 97 | 98 | // Act 99 | sut.Apply(operation, filterContext); 100 | 101 | // Assert 102 | operation.Summary.ShouldBe("Test summary (Auth roles: Administrator, Customer)"); 103 | } 104 | 105 | [Fact] 106 | public void Apply_AppendsPolicyAndRole() 107 | { 108 | // Arrange 109 | var operation = new OpenApiOperation { Summary = "Test summary" }; 110 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizePolicyAndRole)); 111 | 112 | // Act 113 | sut.Apply(operation, filterContext); 114 | 115 | // Assert 116 | operation.Summary.ShouldBe("Test summary (Auth policies: Administrator; roles: Customer)"); 117 | } 118 | 119 | [Fact] 120 | public void Apply_WorksWhenNoAuthorize() 121 | { 122 | // Arrange 123 | var operation = new OpenApiOperation { Summary = "Test summary" }; 124 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.None)); 125 | 126 | // Act 127 | sut.Apply(operation, filterContext); 128 | 129 | // Assert 130 | operation.Summary.ShouldBe("Test summary"); 131 | } 132 | 133 | [Fact] 134 | public void Apply_AppendsWhenAuthIsOnController() 135 | { 136 | // Arrange 137 | var operation = new OpenApiOperation { Summary = "Test summary" }; 138 | var filterContext = FilterContextFor(typeof(AuthController), nameof(FakeActions.None)); 139 | 140 | // Act 141 | sut.Apply(operation, filterContext); 142 | 143 | // Assert 144 | operation.Summary.ShouldBe("Test summary (Auth)"); 145 | } 146 | 147 | [Fact] 148 | public void Apply_DoesNotAppendWhenMethodHasAllowAnonymous() 149 | { 150 | // Arrange 151 | var operation = new OpenApiOperation { Summary = "Test summary" }; 152 | var filterContext = FilterContextFor(typeof(AuthController), nameof(AuthController.AllowAnonymous)); 153 | 154 | // Act 155 | sut.Apply(operation, filterContext); 156 | 157 | // Assert 158 | operation.Summary.ShouldBe("Test summary"); 159 | } 160 | 161 | [Fact] 162 | public void Apply_AppendsPolicyAndRole_Endpoint() 163 | { 164 | // Arrange 165 | var operation = new OpenApiOperation { Summary = "Test summary" }; 166 | var builder = CreateBuilder().RequireAuthorization("Administrator").RequireAuthorization(new AuthorizeAttribute { Roles = "Customer" }); 167 | var endpoint = builder.Build(); 168 | var filterContext = FilterContextFor(endpoint); 169 | 170 | // Act 171 | sut.Apply(operation, filterContext); 172 | 173 | // Assert 174 | operation.Summary.ShouldBe("Test summary (Auth policies: Administrator; roles: Customer)"); 175 | } 176 | 177 | [Fact] 178 | public void Apply_WorksWhenNoAuthorize_Endpoint() 179 | { 180 | // Arrange 181 | var operation = new OpenApiOperation { Summary = "Test summary" }; 182 | var builder = CreateBuilder(); 183 | var endpoint = builder.Build(); 184 | var filterContext = FilterContextFor(endpoint); 185 | 186 | // Act 187 | sut.Apply(operation, filterContext); 188 | 189 | // Assert 190 | operation.Summary.ShouldBe("Test summary"); 191 | } 192 | 193 | [Fact] 194 | public void Apply_AppendsAuthToInheritedControllers() 195 | { 196 | // Arrange 197 | var operation = new OpenApiOperation { Summary = "Test summary" }; 198 | var filterContext = FilterContextFor(typeof(FakeInheritedController), nameof(FakeInheritedController.Authorize)); 199 | 200 | // Act 201 | sut.Apply(operation, filterContext); 202 | 203 | // Assert 204 | operation.Summary.ShouldBe("Test summary (Auth)"); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /test/WebApi8/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Authorization; 2 | using Microsoft.AspNetCore.JsonPatch; 3 | using Microsoft.AspNetCore.JsonPatch.Operations; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.OpenApi; 6 | using Swashbuckle.AspNetCore.Annotations; 7 | using Swashbuckle.AspNetCore.Filters; 8 | using System.Net; 9 | using WebApi.Models; 10 | using WebApi.Models.Examples; 11 | 12 | namespace WebApi.Controllers 13 | { 14 | [ApiController] 15 | [Authorize] 16 | [Route("api/[controller]")] 17 | [SwaggerResponse(404, type: typeof(ErrorResponse), description: "Could not find the person")] 18 | [SwaggerResponseExample(404, typeof(NotFoundResponseExample))] 19 | [SwaggerResponseHeader([200, 401, 403, 404], "CustomHeader", JsonSchemaType.String, "A custom header")] 20 | public class ValuesController : ControllerBase 21 | { 22 | [HttpPost] 23 | [Route("api/test/string/endpoint")] 24 | [SwaggerResponse(StatusCodes.Status200OK, null, typeof(string))] 25 | [SwaggerResponseExample(200, typeof(StringResponseExample))] 26 | [SwaggerRequestExample(typeof(PersonRequest), typeof(StringRequestExample))] 27 | public IActionResult StringEndpoint(string input) 28 | { 29 | return Ok(input); 30 | } 31 | 32 | ///// 33 | ///// Gets all people 34 | ///// 35 | ///// All people. 36 | [HttpGet] 37 | [Route("api/values/people")] 38 | [SwaggerResponse(200, type: typeof(IEnumerable), description: "Successfully found the person")] 39 | [SwaggerResponseExample(200, typeof(PeopleResponseExample))] 40 | [SwaggerResponse(500, type: null, description: "There was an unexpected error")] 41 | [SwaggerResponseExample(500, typeof(InternalServerResponseExample))] 42 | [Authorize("Customer")] 43 | [ProducesResponseType(401)] 44 | public IEnumerable GetPeople() 45 | { 46 | return new PersonResponse[] 47 | { 48 | new PersonResponse { Id = 1, FirstName = "Dave" }, 49 | new PersonResponse { Id = 2, FirstName = "Sally" }, 50 | }; 51 | } 52 | 53 | ///// 54 | ///// Gets a person 55 | ///// 56 | ///// 57 | ///// 58 | [HttpGet] 59 | [Route("api/values/person/{personId}")] 60 | [SwaggerResponse(200, type: typeof(PersonResponse), description: "Successfully found the person")] 61 | [SwaggerResponseExample(200, typeof(PersonResponseExample))] 62 | [SwaggerResponse(500, type: null, description: "There was an unexpected error")] 63 | [SwaggerResponseExample(500, typeof(InternalServerResponseExample))] 64 | [Authorize("Customer")] 65 | [ProducesResponseType(401)] 66 | public PersonResponse GetPerson(int personId) 67 | { 68 | var personResponse = new PersonResponse { Id = personId, FirstName = "Dave" }; 69 | return personResponse; 70 | } 71 | 72 | /// 73 | /// Posts a person 74 | /// 75 | /// 76 | /// 77 | /// You are not authorized to access this endpoint 78 | [HttpPost] 79 | [Route("api/values/person")] 80 | [SwaggerResponse(200, type: typeof(PersonResponse), description: "Successfully found the person")] 81 | [SwaggerResponseExample(200, typeof(PersonResponseExample))] 82 | [SwaggerResponse(500, type: null, description: "There was an unexpected error")] 83 | [SwaggerResponseExample(500, typeof(InternalServerResponseExample))] 84 | 85 | [SwaggerRequestExample(typeof(PersonRequest), typeof(PersonRequestExample))] 86 | 87 | [SwaggerResponseHeader(StatusCodes.Status200OK, "Location", JsonSchemaType.String, "Location of the newly created resource")] 88 | [SwaggerResponseHeader(200, "ETag", JsonSchemaType.String, "An ETag of the resource")] 89 | 90 | [Authorize("Customer")] 91 | public PersonResponse PostPerson([FromBody] PersonRequest personRequest) 92 | { 93 | var personResponse = new PersonResponse { Id = 1, FirstName = "Dave" }; 94 | return personResponse; 95 | } 96 | 97 | /// 98 | /// Posts a person. Multiple request body and response examples provided. 99 | /// 100 | /// 101 | /// 102 | /// You are not authorized to access this endpoint 103 | [HttpPost] 104 | [AllowAnonymous] 105 | [Route("api/values/anyone")] 106 | [SwaggerResponse(200, type: typeof(PersonResponse), description: "Successfully added the person")] 107 | [SwaggerRequestExample(typeof(PersonRequest), typeof(PersonRequestMultipleExamples))] 108 | [SwaggerResponseExample(200, typeof(PersonResponseMultipleExamples))] 109 | public PersonResponse PostAnyone([FromBody] PersonRequest personRequest) 110 | { 111 | var personResponse = new PersonResponse { Id = 1, FirstName = "Dave", LastName = "Multi" }; 112 | return personResponse; 113 | } 114 | 115 | /// 116 | /// No [SwaggerRequestExample] or [SwaggerResponseExample] attributes on this one 117 | /// 118 | /// 119 | /// 120 | [HttpPost] 121 | [Route("api/values/male/")] 122 | [ProducesResponseType(typeof(PersonResponse), 200)] 123 | public PersonResponse PostMale([FromBody] MaleRequest maleRequest) 124 | { 125 | var personResponse = new PersonResponse { Id = 7, FirstName = "Dave" }; 126 | return personResponse; 127 | } 128 | 129 | [HttpPost] 130 | [Route("api/values/genericperson")] 131 | [SwaggerResponse(200, type: typeof(ResponseWrapper), description: "Successfully found the person")] 132 | [SwaggerResponseExample(200, typeof(WrappedPersonResponseExample))] 133 | [SwaggerRequestExample(typeof(RequestWrapper), typeof(WrappedPersonRequestExample))] 134 | [Authorize(Roles = "Customer")] 135 | public ResponseWrapper PostGenericPerson([FromBody] RequestWrapper personRequest) 136 | { 137 | var personResponse = new ResponseWrapper 138 | { 139 | StatusCode = HttpStatusCode.OK, 140 | Body = new PersonResponse { Id = 1, FirstName = "Dave" } 141 | }; 142 | 143 | return personResponse; 144 | } 145 | 146 | [HttpPost] 147 | [AllowAnonymous] 148 | [Route("api/values/listperson")] 149 | [SwaggerResponse(200, type: typeof(IEnumerable), description: "Successfully found the people")] 150 | [SwaggerRequestExample(typeof(IEnumerable), typeof(ListPeopleRequestExample))] 151 | public IEnumerable PostPersonList([FromBody] List peopleRequest) 152 | { 153 | var people = new[] { new PersonResponse { Id = 1, FirstName = "Sally" } }; 154 | return people; 155 | } 156 | 157 | /// 158 | /// Gets dynamic data passing a Dictionary of string, object and returns a Dictionary 159 | /// 160 | /// 161 | /// 162 | [HttpPost] 163 | [Route("api/values/dictionary")] 164 | [SwaggerResponse(200, type: typeof(Dictionary), description: "Successfully found the data")] 165 | [SwaggerResponseExample(200, typeof(DictionaryResponseExample))] 166 | [SwaggerRequestExample(typeof(Dictionary), typeof(DictionaryRequestExample))] 167 | [Consumes("application/json")] // exclude text/csv 168 | [Produces("application/json")] // exclude text/csv 169 | public Dictionary PostDictionary([FromBody] Dictionary dynamicDictionary) 170 | { 171 | return new Dictionary { { "Some", 1 } }; 172 | } 173 | 174 | /// 175 | /// Gets dynamic data passing a DynamicData and returning a DynamicData 176 | /// 177 | /// 178 | /// 179 | [HttpPost] 180 | [Route("api/values/data")] 181 | [SwaggerResponse(200, type: typeof(DynamicData), description: "Successfully found the data")] 182 | [SwaggerResponseExample(200, typeof(DynamicDataResponseExample))] 183 | [SwaggerRequestExample(typeof(DynamicData), typeof(DynamicDataRequestExample))] 184 | public DynamicData GetData([FromBody] DynamicData personRequest) 185 | { 186 | var personResponse = new DynamicData(); 187 | personResponse.Payload.Add("Property", "val"); 188 | return personResponse; 189 | } 190 | 191 | [HttpPost] 192 | [Route("api/values/differentperson")] 193 | [SwaggerRequestExample(typeof(PersonRequest), typeof(PersonRequestExample2))] 194 | [SwaggerResponse(200, type: typeof(PersonResponse), description: "Successfully found the person")] 195 | [SwaggerResponseExample(200, typeof(PersonResponseExample2))] 196 | public PersonResponse PostDifferentPerson([FromBody] PersonRequest personRequest) 197 | { 198 | var personResponse = new PersonResponse { Id = 1, FirstName = "Dave" }; 199 | return personResponse; 200 | } 201 | 202 | [HttpPost] 203 | [Route("api/values/dependencyinjectionperson")] 204 | [SwaggerResponse(200, type: typeof(PersonResponse), description: "Successfully found the person")] 205 | [SwaggerRequestExample(typeof(PersonRequest), typeof(PersonRequestDependencyInjectionExample))] 206 | #if NETCOREAPP3_0 207 | [ProducesDefaultResponseType(typeof(ErrorResponse))] 208 | #endif 209 | public PersonResponse PostDependencyInjectedExampleResponsePerson([FromBody] PersonRequest personRequest) 210 | { 211 | var personResponse = new PersonResponse { Id = 1, FirstName = "Dave" }; 212 | return personResponse; 213 | } 214 | 215 | [HttpPatch] 216 | [Route("api/values/patchperson")] 217 | [SwaggerRequestExample(typeof(JsonPatchDocument), typeof(JsonPatchPersonRequestExample))] // ASP.NET Core 1.1 218 | [SwaggerRequestExample(typeof(Operation), typeof(JsonPatchPersonRequestExample))] // ASP.NET Core 2.0 219 | public PersonResponse JsonPatchPerson([FromBody] JsonPatchDocument personRequest) 220 | { 221 | var personResponse = new PersonResponse { Id = 1, FirstName = "Dave" }; 222 | return personResponse; 223 | } 224 | 225 | [HttpPost("api/values/title")] 226 | public void NullableEnumTest([FromBody] Title? someEnum) 227 | { 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /test/Swashbuckle.AspNetCore.Filters.Test/SecurityRequirementsOperationFilterTests.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes; 3 | using Swashbuckle.AspNetCore.SwaggerGen; 4 | using Xunit; 5 | using System.Linq; 6 | using static Swashbuckle.AspNetCore.Filters.Test.TestFixtures.Fakes.FakeControllers; 7 | using Microsoft.OpenApi; 8 | 9 | namespace Swashbuckle.AspNetCore.Filters.Test 10 | { 11 | public class SecurityRequirementsOperationFilterTests : BaseOperationFilterTests 12 | { 13 | private readonly IOperationFilter sut; 14 | 15 | public SecurityRequirementsOperationFilterTests() 16 | { 17 | sut = new SecurityRequirementsOperationFilter(); 18 | } 19 | 20 | [Fact] 21 | public void Apply_SetsAuthorize_WithNoPolicy() 22 | { 23 | // Arrange 24 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 25 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.Authorize)); 26 | 27 | // Act 28 | sut.Apply(operation, filterContext); 29 | 30 | // Assert 31 | operation.Security.Count.ShouldBe(1); 32 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 33 | securityScheme.Value.ShouldNotBeNull(); 34 | securityScheme.Value.Count().ShouldBe(0); 35 | } 36 | 37 | [Fact] 38 | public void Apply_SetsAuthorize_WithNoPolicy_WhenCustomSecuritySchemaIsSet() 39 | { 40 | // Arrange 41 | const string securitySchemaName = "customSchema"; 42 | var sut = new SecurityRequirementsOperationFilter(true, securitySchemaName); 43 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 44 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.Authorize)); 45 | 46 | // Act 47 | sut.Apply(operation, filterContext); 48 | 49 | // Assert 50 | operation.Security.Count.ShouldBe(1); 51 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == securitySchemaName); 52 | securityScheme.Value.ShouldNotBeNull(); 53 | securityScheme.Value.Count().ShouldBe(0); 54 | } 55 | [Fact] 56 | public void Apply_SetsAuthorize_WithMultipleSecuritySchemas() 57 | { 58 | // Arrange 59 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 60 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.Authorize)); 61 | 62 | const string securitySchemaName = "customSchema"; 63 | var sut = new SecurityRequirementsOperationFilter(); 64 | var sut2 = new SecurityRequirementsOperationFilter(true, securitySchemaName); 65 | 66 | // Act 67 | sut.Apply(operation, filterContext); 68 | sut2.Apply(operation, filterContext); 69 | 70 | // Assert 71 | operation.Security.Count.ShouldBe(2); 72 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 73 | securityScheme.Value.ShouldNotBeNull(); 74 | securityScheme.Value.Count().ShouldBe(0); 75 | 76 | var securityScheme2 = operation.Security[1].SingleOrDefault(ss => ss.Key.Reference.Id == securitySchemaName); 77 | securityScheme2.Value.ShouldNotBeNull(); 78 | securityScheme2.Value.Count().ShouldBe(0); 79 | } 80 | 81 | [Fact] 82 | public void Apply_DoesNotSetSecurity_WhenNoAuthorize() 83 | { 84 | // Arrange 85 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 86 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.None)); 87 | 88 | // Act 89 | sut.Apply(operation, filterContext); 90 | 91 | // Assert 92 | operation.Security?.Count.ShouldBe(0); 93 | } 94 | 95 | [Fact] 96 | public void Apply_Adds401And403_ToResponses() 97 | { 98 | // Arrange 99 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 100 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.Authorize)); 101 | 102 | // Act 103 | sut.Apply(operation, filterContext); 104 | 105 | // Assert 106 | operation.Responses["401"].Description.ShouldBe("Unauthorized"); 107 | operation.Responses["403"].Description.ShouldBe("Forbidden"); 108 | } 109 | 110 | [Fact] 111 | public void Apply_DoesNotAdd401And403_WhenConfiguredNotTo() 112 | { 113 | // Arrange 114 | var sut = new SecurityRequirementsOperationFilter(false); 115 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 116 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.Authorize)); 117 | 118 | // Act 119 | sut.Apply(operation, filterContext); 120 | 121 | // Assert 122 | operation.Responses.ShouldNotContainKey("401"); 123 | operation.Responses.ShouldNotContainKey("403"); 124 | } 125 | 126 | [Fact] 127 | public void Apply_DoesNotCrash_When403AlreadyPresent() 128 | { 129 | // Arrange 130 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 131 | operation.Responses.Add("403", new OpenApiResponse { Description = "Forbidden" }); 132 | 133 | var filterContext = FilterContextFor(typeof(AuthController), nameof(AuthController.Customer)); 134 | 135 | // Act 136 | sut.Apply(operation, filterContext); 137 | 138 | // Assert 139 | operation.Responses.ShouldContainKey("401"); 140 | operation.Responses.ShouldContainKey("403"); 141 | operation.Responses.Count.ShouldBe(2); 142 | } 143 | 144 | [Fact] 145 | public void Apply_DoesNotCrash_When401AlreadyPresent() 146 | { 147 | // Arrange 148 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 149 | operation.Responses.Add("401", new OpenApiResponse { Description = "Unauthorized" }); 150 | 151 | var filterContext = FilterContextFor(typeof(AuthController), nameof(AuthController.Customer)); 152 | 153 | // Act 154 | sut.Apply(operation, filterContext); 155 | 156 | // Assert 157 | operation.Responses.ShouldContainKey("401"); 158 | operation.Responses.ShouldContainKey("403"); 159 | operation.Responses.Count.ShouldBe(2); 160 | } 161 | 162 | [Fact] 163 | public void Apply_SetsAuthorize_WithOnePolicy() 164 | { 165 | // Arrange 166 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 167 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeAdministratorPolicy)); 168 | 169 | // Act 170 | sut.Apply(operation, filterContext); 171 | 172 | // Assert 173 | operation.Security.Count.ShouldBe(1); 174 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 175 | securityScheme.Value.ShouldNotBeNull(); 176 | var policies = securityScheme.Value; 177 | policies.Single().ShouldBe("Administrator"); 178 | } 179 | 180 | [Fact] 181 | public void Apply_SetsAuthorize_WithMultiplePolicies() 182 | { 183 | // Arrange 184 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 185 | var filterContext = FilterContextFor(typeof(FakeActions), nameof(FakeActions.AuthorizeMultiplePolicies)); 186 | 187 | // Act 188 | sut.Apply(operation, filterContext); 189 | 190 | // Assert 191 | operation.Security.Count.ShouldBe(1); 192 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 193 | securityScheme.Value.ShouldNotBeNull(); 194 | var policies = securityScheme.Value; 195 | policies.Count().ShouldBe(2); 196 | policies.ShouldContain("Administrator"); 197 | policies.ShouldContain("Customer"); 198 | } 199 | 200 | [Fact] 201 | public void Apply_SetsAuthorize_WithController() 202 | { 203 | // Arrange 204 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 205 | var filterContext = FilterContextFor(typeof(AuthController), nameof(AuthController.None)); 206 | 207 | // Act 208 | sut.Apply(operation, filterContext); 209 | 210 | // Assert 211 | operation.Security.Count.ShouldBe(1); 212 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 213 | securityScheme.Value.ShouldNotBeNull(); 214 | securityScheme.Value.Count().ShouldBe(0); 215 | } 216 | 217 | [Fact] 218 | public void Apply_SetsAuthorize_WithControllerAndMethod() 219 | { 220 | // Arrange 221 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 222 | var filterContext = FilterContextFor(typeof(AuthController), nameof(AuthController.Customer)); 223 | 224 | // Act 225 | sut.Apply(operation, filterContext); 226 | 227 | // Assert 228 | operation.Security.Count.ShouldBe(1); 229 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 230 | securityScheme.Value.ShouldNotBeNull(); 231 | var policies = securityScheme.Value; 232 | policies.Single().ShouldBe("Customer"); 233 | } 234 | 235 | [Fact] 236 | public void Apply_DoesNotSetSecurity_WhenActionHasAllowAnonymous() 237 | { 238 | // Arrange 239 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 240 | var filterContext = FilterContextFor(typeof(AuthController), nameof(AuthController.AllowAnonymous)); 241 | 242 | // Act 243 | sut.Apply(operation, filterContext); 244 | 245 | // Assert 246 | operation.Security?.Count.ShouldBe(0); 247 | } 248 | 249 | [Fact] 250 | public void Apply_DoesNotSetSecurity_WhenControllerHasAllowAnonymous() 251 | { 252 | // Arrange 253 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 254 | var filterContext = FilterContextFor(typeof(AllowAnonymousController), nameof(AllowAnonymousController.Customer)); 255 | 256 | // Act 257 | sut.Apply(operation, filterContext); 258 | 259 | // Assert 260 | operation.Security?.Count.ShouldBe(0); 261 | } 262 | 263 | [Fact] 264 | public void Apply_SetsAuthorizeToTheActionFromBaseController_WithController() 265 | { 266 | // Arrange 267 | var operation = new OpenApiOperation { OperationId = "foobar", Responses = new OpenApiResponses() }; 268 | var filterContext = FilterContextFor(typeof(AuthControllerDerived), nameof(AuthController.None)); 269 | 270 | // Act 271 | sut.Apply(operation, filterContext); 272 | 273 | // Assert 274 | operation.Security.Count.ShouldBe(1); 275 | var securityScheme = operation.Security[0].SingleOrDefault(ss => ss.Key.Reference.Id == "oauth2"); 276 | securityScheme.Value.ShouldNotBeNull(); 277 | securityScheme.Value.Count().ShouldBe(0); 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## [10.0.1] - 2025-11-24 8 | ### Fixed 9 | - Fix issue #264 (thanks @ntark) 10 | 11 | ## [10.0.0] - 2025-11-13 12 | ### Changed 13 | - Upgrade from Swashbuckle.AspNetCore 9.0.0 to 10.0.0 which meant a of breaking changes in Microsoft.OpenApi. 14 | - SwaggerResponseHeaderAttribute `type` parameter is now `JsonSchemaType` instead of string 15 | 16 | ### Removed 17 | - Support for .NET Standard 2.0 because Swashbuckle.AspNetCore 10.0.0 only supports .NET 8.0 and above 18 | 19 | ## [9.0.1] - 2025-10-14 20 | ### Fixed 21 | - Fix issue #261 (thanks @ntark) 22 | 23 | ## [9.0.0] - 2025-06-17 24 | ### Changed 25 | - Issue #257 upgrade from Swashbuckle.AspNetCore.SwaggerGen 5.0.0 to 8.0.0 because Swashbuckle.AspNetCore 9.0.0 didn't work. No longer support .netcore3.1 or .net5.0, only support .netstandard20 and .net80. 26 | 27 | ### Fixed 28 | - Issue #253 29 | 30 | ## [8.0.3] - 2025-05-03 31 | ### Changed 32 | - Issue #254 - Made MvcOutputformatter public so that it can be overridden. 33 | 34 | ## [8.0.2] - 2024-05-01 35 | ### Fixed 36 | - Issue #244 - SecurityRequirementsOperationFilter adds duplicate security requirement when using .WithOpenApi() 37 | 38 | ## [8.0.1] - 2024-02-16 39 | ### Added 40 | - Issue #242. Add ReadMe to NuGet package. 41 | 42 | ## [8.0.0] - 2024-01-09 43 | ### Changed 44 | - Issue #237 - breaking change. Default to camelCase instead of PascalCase for examples in minimal APIs. 45 | 46 | ## [7.0.12] - 2023-10-24 47 | ### Added 48 | - PR #235 added support for text/csv in request examples and IMultipleExamplesProvider (thanks @Bl4d3s) 49 | 50 | ## [7.0.11] - 2023-09-07 51 | ### Changed 52 | - Issue #230, inject IServiceProvider to MvcOutputFormatter instead of creating a new one 53 | 54 | ## [7.0.10] - 2023-09-07 55 | ### Fixed 56 | - Fix bug in GetControllerAndActionAttributes, introduced by the last release which causes AddResponseHeadersFilter to fail. 57 | 58 | ## [7.0.9] - 2023-09-06 59 | ### Fixed 60 | - Issue #227 - SecurityRequirementsOperationFilter for minimal APIs doesn't work with Swashbuckle.AspNetCore > 6.3.0 (thanks @hartmair) 61 | 62 | ## [7.0.8] - 2023-07-15 63 | ### Fixed 64 | - Issue #231 - NuGet package now built in Release mode 65 | 66 | ## [7.0.7] - 2023-07-14 67 | ### Fixed 68 | - Issue #225 - SecurityRequirementsOperationFilter doesn't add Auth to methods derived from a BaseController (thanks @andrew-yustyk) 69 | 70 | ## [7.0.6] - 2022-11-14 71 | ### Changed 72 | - Allow SwaggerResponseHeaderAttribute to be used on classes. 73 | 74 | ## [7.0.5] - 2022-08-22 75 | ### Fixed 76 | - Issue #209 - Request example clash when using API versioning 77 | 78 | ## [7.0.4] - 2022-08-01 79 | 80 | ### Added 81 | - Issues #43 and #129 - added support for text/csv in response examples (thanks @icnocop) 82 | 83 | ## [7.0.3] - 2022-05-24 84 | ### Fixed 85 | - Issue #189 - fix exception when using Minimal APIs (thanks @dotdiego) 86 | 87 | ### Added 88 | - Issue #165 - add Description to multiple examples (thanks @penenkel) 89 | 90 | ### Removed 91 | - Issue #162 - removed internal OpenApiRawString and use OpenApiString with isRawString parameter (thanks @WillGunn) 92 | 93 | ## [7.0.2] - 2021-04-03 94 | ### Fixed 95 | - Fixed License 96 | 97 | ## [7.0.1] - 2021-04-03 98 | ### Added 99 | - Add symbols NuGet package 100 | 101 | ## [7.0.0] - 2021-04-03 102 | ### Security 103 | - PR #176 update NuGet packages 104 | 105 | ### Changed 106 | - PR #178 dependency cleanup 107 | 108 | ### Added 109 | - PR #179 introduce Swashbuckle.AspNetCore.Filters.Abstractions package so that IExamplesProvider can be consumed without Swashbuckle dependency. 110 | - Target .NET 5.0 111 | - Microsoft.SourceLink.GitHub NuGet reference 112 | 113 | ### Removed 114 | - Issue #174 remove ExcludeObsoletePropertiesResolver and NewtonsoftJson dependency. 115 | 116 | ## [6.1.0] - 2021-02-15 117 | ### Added 118 | - Issue #171 support auto example for `ProducesDefaultResponseType`. 119 | 120 | ## [6.0.1] - 2020-10-16 121 | ### Fixed 122 | - PR #161 restore NewtonsoftJson dependency. 123 | 124 | ## [6.0.0] - 2020-09-27 125 | ### Fixed 126 | - Issue #132 Support `[System.Text.JsonPropertyNameAttribute]`. This is a breaking change which rewrote how the examples 127 | are generated. Instead of explicitly using Newtonsoft's `JsonConvert.SerializeObject()`, I now use whichever JSON 128 | serializer is registered with the MVC pipeline, i.e. during `services.AddControllers()`. 129 | 130 | ### Removed 131 | - contractResolver and jsonConverter parameters from `[SwaggerRequestExampleAttribute]`. 132 | - contractResolver and jsonConverter parameters from `[SwaggerResponseExampleAttribute]`. 133 | - `Microsoft.AspNetCore.Mvc.NewtonsoftJson` dependency. 134 | 135 | ### Changed 136 | - If you are using `options.IgnoreObsoleteProperties();` and you want your Examples to not have the 137 | obsolete properties, then you will need to register my custom Newtonsoft `ExcludeObsoletePropertiesResolver`, 138 | e.g. 139 | ```csharp 140 | services.AddControllers() 141 | .AddNewtonsoftJson(opt => 142 | { 143 | opt.SerializerSettings.ContractResolver = new ExcludeObsoletePropertiesResolver(opt.SerializerSettings.ContractResolver); 144 | ``` 145 | 146 | ## [5.1.2] - 2020-06-25 147 | ### Fixed 148 | - #154 Upgrade to Microsoft.OpenApi 1.2.2 because 1.2.0 had breaking changes 149 | ### Added 150 | - Add `services.AddSwaggerExamples()` extension method to allow examples without automatic annotation 151 | 152 | ## [5.1.1] - 2020-04-12 153 | ### Fixed 154 | - #115 Added workaround for request examples when SerializeAsV2 = true 155 | - #148 AddSwaggerExamplesFromAssemblies method does not scan for IMultipleExamplesProvider implementations 156 | 157 | ## [5.1.0] - 2020-04-05 158 | ### Added 159 | - PR #147 add support for multiple request and response examples. Thanks to @tomkludy and @pozy for the contribution. 160 | 161 | ## [5.0.2] - 2020-02-25 162 | ### Added 163 | - PR #140 add extension methods AddSwaggerExamplesFromAssemblyOf and AddSwaggerExamplesFromAssemblies 164 | 165 | ## [5.0.1] 2020-02-25 166 | ### Fixed 167 | - Fix #136 use either XmlSerializer or DataContractSerializer to output XML examples, depending on what is configured. 168 | Thanks to @CumpsD and @ridingwolf for the PR. 169 | 170 | ## [5.0.0] - 2020-01-21 171 | ### Changed 172 | - Use Swashbuckle.AspNetCore 5.0.0 173 | 174 | ## [5.0.0-rc9] - 2019-12-31 175 | ### Fixed 176 | - PR #110, where using IgnoreObsoleteProperties option causes PascalCase to be emitted instead of camelCase. 177 | ### Changed 178 | - Use Swashbuckle.AspNetCore 5.0.0-rc5 179 | 180 | ## [5.0.0-rc8] - 2019-08-02 181 | ### Fixed 182 | - Issue #106 SecurityRequirementsOperationFilter removes existing OpenApiSecurityRequirements 183 | ## Added 184 | - PR #104 add optional format parameter to the SwaggerResponseHeaderAttribute 185 | 186 | ## [5.0.0-rc7] 2019-07-26 187 | ### Fixed 188 | - Issue #98 check schemaGeneratorOptions.IgnoreObsoleteProperties when generating json examples 189 | 190 | ## [5.0.0-rc6] - 2019-07-26 191 | ### Added 192 | - PR #103 - Response Headers filter can now take an array of status codes 193 | 194 | ## [5.0.0-rc5] - 2019-07-24 195 | ### Fixed 196 | - Issue #101, Exception with SecurityRequirementsOperationFilter when you have already added a 401 or a 403 197 | 198 | ## [5.0.0-rc4] - 2019-07-13 199 | ### Fixed 200 | - Issue #99, JSON examples were encoded JSON 201 | 202 | ## [5.0.0-rc3] - 2019-06-10 203 | ### Removed 204 | - Remove IExamplesProvider interface. Only support IExamplesProvider 205 | 206 | ## [5.0.0-rc2] - 2019-05-30 207 | ### Changed 208 | - Use Swashbuckle.AspNetCore 5.0.0-rc2 209 | - Support .NET Core 3.0 preview 5 210 | 211 | ## [5.0.0-beta] - 2019-04-23 212 | ### Changed 213 | - Use Swashbuckle.AspNetCore 5.0.0-beta 214 | - Drop support for .NET Standard 1.6 and .NET Framework, since Swashbuckle.AspNetCore doesn't support them any more 215 | - Only set request example on the operation, no longer set it on the type. This means you can have different 216 | request examples for different operations which use the same request type, which is an often requested feature. 217 | ### Added 218 | - XML examples 219 | ### Deprecated 220 | - Removed AuthorizationInputOperationFilter 221 | - Removed DescriptionOperationFilter 222 | - Removed AddFileParamTypesOperationFilter 223 | 224 | ### [4.5.5] - 2018-03-04 225 | ### Fixed 226 | - Repository URL in NuGet package 227 | - Issue #89 - use Json.NET SerializationBinder property when generating examples. Thanks @dmitry-baryshev for the PR. 228 | 229 | ## [4.5.4] - 2018-02-08 230 | ### Deprecated 231 | - Marked AddFileParamTypesOperationFilter as Obsolete, because Swashbuckle 4.0 supports IFormFile directly. 232 | ### Changed 233 | - Issue #84 - allow security schema to have a name other than "oauth2" via configuration 234 | 235 | ## [4.5.3] - 2018-01-14 236 | ### Fixed 237 | - Issue #80 - allow interfaces when resolving IExampleProvider 238 | 239 | ## [4.5.2] - 2018-12-02 240 | ### Fixed 241 | - Issue #69 242 | - Only set request examples on the schema registry object. The request parameter will only be set if 243 | a schema registry object is not found. This fix prevents a warning in Redoc. Thanks @Leon99 for the 244 | pull request. 245 | - Issue #72 do not override SerializerSettings.NullValueHandling because underlying issue seems to have been fixed 246 | 247 | ## [4.5.1] - 2018-11-12 248 | ### Fixed 249 | - Issue #67 3rd time 250 | - Support Swashbuckle.AspNetCore 4.0.0 for .NET Framework 4.6.1 projects 251 | 252 | ## [4.5.0] - 2018-11-12 253 | ### Fixed 254 | - Issue #67 again 255 | - Support Swashbuckle.AspNetCore 4.0.0 for .NET Framework 4.6.1 projects 256 | 257 | ## [4.4.0] - 2018-11-08 258 | ### Fixed 259 | - Issue #67 260 | - Support Swashbuckle.AspNetCore 4.0.0 for .NET Standard 2.0 projects 261 | 262 | ## [4.3.1] - 2018-10-08 263 | ### Added 264 | - Fix issue #63 265 | - Add an optional true/false value to the AddHeaderOperationFilter to determine whether the header 266 | is required or not. 267 | 268 | ## [4.3.0] - 2018-09-14 269 | ### Changed 270 | - Issue #60 271 | - No longer support .NET Framework 4.5.1 because it doesn't work with Scrutor (because Scrutor is unsigned) 272 | - Support .NET Framework 4.6.1 273 | - Support .NET Standard 2.0 (still support 1.6 too) 274 | 275 | ### Deprecated 276 | - Mark DescriptionOperationFilter as obsolete, because you can accomplish the same thing with summary tags 277 | 278 | ## [4.2.0] - 2018-08-15 279 | ### Changed 280 | - It is no longer necessary to specify a ProducesResponseType or SwaggerResponse attribute in order to get 281 | response examples, so long as it is obvious what Type your action method returns. 282 | 283 | ## [4.1.0] - 2018-08-06 284 | ### Added 285 | - Add generic version of SecurityRequirementsOperationFilter and AppendAuthorizeToSummaryOperationFilter so that 286 | they can be used with other attributes. Reason: a client had implemented their own `TypeFilterAttribute` 287 | which did Authorization but wasn't an `AuthorizeAttribute` 288 | 289 | ## [4.0.3] - 2018-08-03 290 | ### Fixed 291 | - Issue #54 where child objects weren't having their descriptions set if parent property was missing `[Description]` 292 | 293 | ## [4.0.2] - 2018-07-26 294 | ### Fixed 295 | - Issue #54 where arrays of child objects weren't having their description set 296 | 297 | ## [4.0.1] - 2018-07-24 298 | ### Fixed 299 | - Fix bug where generic types weren't being automatically annotated 300 | 301 | ## [4.0.0] - 2018-07-23 302 | ### Added 303 | - Automatic annotation of request and response examples 304 | - Dependency on [Scrutor](https://github.com/khellang/Scrutor) 2.2.2 305 | ### Changed 306 | - How ExamplesOperationFilter is installed - see Installation section of the Readme 307 | 308 | ## [3.1.0] - 2018-07-21 309 | ### Added 310 | - SecurityRequirementsOperationFilter to correctly set bearer token stuff when using `[Authorize]` 311 | 312 | ## [3.0.4] - 2018-07-18 313 | ### Fixed 314 | - Fix bug with DescriptionOperationFilter where Description not set if using a DefaultContractResolver 315 | 316 | ## [3.0.3] - 2018-07-17 317 | ### Fixed 318 | - Fix where Examples doesn't work on ASP.NET Core 2.0 319 | 320 | ## [3.0.2] - 2018-07-15 321 | ### Fixed 322 | - Port of bug #36 from Swashbuckle.Examples 323 | 324 | ## [3.0.1] - 2018-07-15 325 | ### Changed 326 | - Rename from Swashbuckle.AspNetCore.Examples 327 | - Dependency on [Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore) 3.0 instead of 1.0 --------------------------------------------------------------------------------