Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2765 Allow Filter instance reuse #2839

Merged
7 changes: 7 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ jobs:
- name: Checkout code
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4

- name: Setup .NET SDKs
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with:
dotnet-version: |
6.0.x
7.0.x

- name: Setup .NET SDK
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
id: setup-dotnet
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<PackageVersion Include="Microsoft.OpenApi.Readers" Version="1.6.14" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
<PackageVersion Include="Nswag.MSbuild" Version="13.17.0" />
<PackageVersion Include="ReportGenerator" Version="5.2.2" />
<PackageVersion Include="System.Text.Json" Version="4.6.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public void Configure(SchemaGeneratorOptions options)

// Create and add any filters that were specified through the FilterDescriptor lists
_swaggerGenOptions.SchemaFilterDescriptors.ForEach(
filterDescriptor => options.SchemaFilters.Add(CreateFilter<ISchemaFilter>(filterDescriptor)));
filterDescriptor => options.SchemaFilters.Add(GetOrCreateFilter<ISchemaFilter>(filterDescriptor)));
}

private void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptions target)
Expand All @@ -44,10 +44,10 @@ private void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptions targ
target.SchemaFilters = new List<ISchemaFilter>(source.SchemaFilters);
}

private TFilter CreateFilter<TFilter>(FilterDescriptor filterDescriptor)
private TFilter GetOrCreateFilter<TFilter>(FilterDescriptor filterDescriptor)
{
return (TFilter)ActivatorUtilities
.CreateInstance(_serviceProvider, filterDescriptor.Type, filterDescriptor.Arguments);
return (TFilter)(filterDescriptor.FilterInstance
?? ActivatorUtilities.CreateInstance(_serviceProvider, filterDescriptor.Type, filterDescriptor.Arguments));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ public void Configure(SwaggerGeneratorOptions options)
// Create and add any filters that were specified through the FilterDescriptor lists ...

_swaggerGenOptions.ParameterFilterDescriptors.ForEach(
filterDescriptor => options.ParameterFilters.Add(CreateFilter<IParameterFilter>(filterDescriptor)));
filterDescriptor => options.ParameterFilters.Add(GetOrCreateFilter<IParameterFilter>(filterDescriptor)));

_swaggerGenOptions.RequestBodyFilterDescriptors.ForEach(
filterDescriptor => options.RequestBodyFilters.Add(CreateFilter<IRequestBodyFilter>(filterDescriptor)));
filterDescriptor => options.RequestBodyFilters.Add(GetOrCreateFilter<IRequestBodyFilter>(filterDescriptor)));

_swaggerGenOptions.OperationFilterDescriptors.ForEach(
filterDescriptor => options.OperationFilters.Add(CreateFilter<IOperationFilter>(filterDescriptor)));
filterDescriptor => options.OperationFilters.Add(GetOrCreateFilter<IOperationFilter>(filterDescriptor)));

_swaggerGenOptions.DocumentFilterDescriptors.ForEach(
filterDescriptor => options.DocumentFilters.Add(CreateFilter<IDocumentFilter>(filterDescriptor)));
filterDescriptor => options.DocumentFilters.Add(GetOrCreateFilter<IDocumentFilter>(filterDescriptor)));

if (!options.SwaggerDocs.Any())
{
Expand Down Expand Up @@ -74,10 +74,10 @@ public void DeepCopy(SwaggerGeneratorOptions source, SwaggerGeneratorOptions tar
target.SecuritySchemesSelector = source.SecuritySchemesSelector;
}

private TFilter CreateFilter<TFilter>(FilterDescriptor filterDescriptor)
private TFilter GetOrCreateFilter<TFilter>(FilterDescriptor filterDescriptor)
{
return (TFilter)ActivatorUtilities
.CreateInstance(_serviceProvider, filterDescriptor.Type, filterDescriptor.Arguments);
return (TFilter)(filterDescriptor.FilterInstance
?? ActivatorUtilities.CreateInstance(_serviceProvider, filterDescriptor.Type, filterDescriptor.Arguments));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,7 @@ public class FilterDescriptor
public Type Type { get; set; }

public object[] Arguments { get; set; }

public object FilterInstance { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -328,93 +328,193 @@ public static void SupportNonNullableReferenceTypes(this SwaggerGenOptions swagg
/// <summary>
/// Extend the Swagger Generator with "filters" that can modify Schemas after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from ISchemaFilter</typeparam>
/// <typeparam name="TFilter">A type that derives from <see cref="ISchemaFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="arguments">Optionally inject parameters through filter constructors</param>
public static void SchemaFilter<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
params object[] arguments)
where TFilter : ISchemaFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
swaggerGenOptions.SchemaFilterDescriptors.Add(new FilterDescriptor
{
Type = typeof(TFilter),
Arguments = arguments
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify Schemas after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from <see cref="ISchemaFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="filterInstance">The filter instance to use.</param>
public static void AddSchemaFilterInstance<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
TFilter filterInstance)
where TFilter : ISchemaFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance));
swaggerGenOptions.SchemaFilterDescriptors.Add(new FilterDescriptor
{
FilterInstance = filterInstance
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify Parameters after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from IParameterFilter</typeparam>
/// <typeparam name="TFilter">A type that derives from <see cref="IParameterFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="arguments">Optionally inject parameters through filter constructors</param>
public static void ParameterFilter<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
params object[] arguments)
where TFilter : IParameterFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
swaggerGenOptions.ParameterFilterDescriptors.Add(new FilterDescriptor
{
Type = typeof(TFilter),
Arguments = arguments
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify Parameters after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from <see cref="IParameterFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="filterInstance">The filter instance to use.</param>
public static void AddParameterFilterInstance<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
TFilter filterInstance)
where TFilter : IParameterFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance));
swaggerGenOptions.ParameterFilterDescriptors.Add(new FilterDescriptor
{
FilterInstance = filterInstance
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify RequestBodys after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from IRequestBodyFilter</typeparam>
/// <typeparam name="TFilter">A type that derives from <see cref="IRequestBodyFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="arguments">Optionally inject parameters through filter constructors</param>
public static void RequestBodyFilter<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
params object[] arguments)
where TFilter : IRequestBodyFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
swaggerGenOptions.RequestBodyFilterDescriptors.Add(new FilterDescriptor
{
Type = typeof(TFilter),
Arguments = arguments
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify RequestBodys after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from <see cref="IRequestBodyFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="filterInstance">The filter instance to use.</param>
public static void AddRequestBodyFilterInstance<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
TFilter filterInstance)
where TFilter : IRequestBodyFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance));
swaggerGenOptions.RequestBodyFilterDescriptors.Add(new FilterDescriptor
{
FilterInstance = filterInstance
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify Operations after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from IOperationFilter</typeparam>
/// <typeparam name="TFilter">A type that derives from <see cref="IOperationFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="arguments">Optionally inject parameters through filter constructors</param>
public static void OperationFilter<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
params object[] arguments)
where TFilter : IOperationFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
swaggerGenOptions.OperationFilterDescriptors.Add(new FilterDescriptor
{
Type = typeof(TFilter),
Arguments = arguments
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify Operations after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from <see cref="IOperationFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="filterInstance">The filter instance to use.</param>
public static void AddOperationFilterInstance<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
TFilter filterInstance)
martincostello marked this conversation as resolved.
Show resolved Hide resolved
where TFilter : IOperationFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance));
swaggerGenOptions.OperationFilterDescriptors.Add(new FilterDescriptor
{
FilterInstance = filterInstance
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify SwaggerDocuments after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from IDocumentFilter</typeparam>
/// <typeparam name="TFilter">A type that derives from <see cref="IDocumentFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="arguments">Optionally inject parameters through filter constructors</param>
public static void DocumentFilter<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
params object[] arguments)
where TFilter : IDocumentFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
swaggerGenOptions.DocumentFilterDescriptors.Add(new FilterDescriptor
{
Type = typeof(TFilter),
Arguments = arguments
});
}

/// <summary>
/// Extend the Swagger Generator with "filters" that can modify SwaggerDocuments after they're initially generated
/// </summary>
/// <typeparam name="TFilter">A type that derives from <see cref="IDocumentFilter"/></typeparam>
/// <param name="swaggerGenOptions"></param>
/// <param name="filterInstance">The filter instance to use.</param>
public static void AddDocumentFilterInstance<TFilter>(
this SwaggerGenOptions swaggerGenOptions,
TFilter filterInstance)
where TFilter : IDocumentFilter
{
if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions));
if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance));
swaggerGenOptions.DocumentFilterDescriptors.Add(new FilterDescriptor
martincostello marked this conversation as resolved.
Show resolved Hide resolved
{
FilterInstance = filterInstance
});
}

/// <summary>
/// Inject human-friendly descriptions for Operations, Parameters and Schemas based on XML Comment files
/// </summary>
Expand Down
martincostello marked this conversation as resolved.
Show resolved Hide resolved
martincostello marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Swashbuckle.AspNetCore.IntegrationTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100795482b2b019ad690196ad34ca1d13abb1c3ed0e24fdaea1ca82ca11462a2d883e149864773aada8b40f41c1b1ea7824f23fb569cfc20e9fd03763896a2905fa23e1a62a940d40d14e3107e8cf90f6d705848e39fe25121a7f8c1cd5dcd6701aea982d13c4a42b3a0766dfc27fc02fa40194ed0a7841a8768d33f7aaabde87a7")]
[assembly: InternalsVisibleTo("Swashbuckle.AspNetCore.SwaggerGen.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010055d6060294c6e19033fac1eb2d1a17f29d7ddd3a09b31f460302a2a7a9955cd1afaa6bd579125ee2ccce330e4dc84f238ed10d372a5c7db774ab2bf2dd77bf9fc0abb3fc84a32f0aacf4cf166c5af981e80419f83c0e4bb9868c3b9dd3a3c7e0a022b7da66ebfed28c073e99e3847491a84678ac55bcff5a2f573838518f4fef")]
Original file line number Diff line number Diff line change
Expand Up @@ -619,9 +619,9 @@ public void GenerateSchema_Errors_IfTypesHaveConflictingSchemaIds()
[InlineData(TypeNameHandling.Arrays, TypeNameAssemblyFormatHandling.Full, false,
null)]
[InlineData(TypeNameHandling.Objects, TypeNameAssemblyFormatHandling.Full, true,
"Swashbuckle.AspNetCore.TestSupport.{0}, Swashbuckle.AspNetCore.TestSupport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
"Swashbuckle.AspNetCore.TestSupport.{0}, Swashbuckle.AspNetCore.TestSupport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2b5885af12a1980d")]
[InlineData(TypeNameHandling.All, TypeNameAssemblyFormatHandling.Full, true,
"Swashbuckle.AspNetCore.TestSupport.{0}, Swashbuckle.AspNetCore.TestSupport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
"Swashbuckle.AspNetCore.TestSupport.{0}, Swashbuckle.AspNetCore.TestSupport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2b5885af12a1980d")]
[InlineData(TypeNameHandling.Auto, TypeNameAssemblyFormatHandling.Simple, true,
"Swashbuckle.AspNetCore.TestSupport.{0}, Swashbuckle.AspNetCore.TestSupport")]
public void GenerateSchema_HonorsSerializerSetting_TypeNameHandling(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
using System.Reflection;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using NSubstitute;
using Xunit;

namespace Swashbuckle.AspNetCore.SwaggerGen.Test;
Expand All @@ -15,4 +20,46 @@ public static void DeepCopy_Copies_All_Properties()
// to SwaggerGeneratorOptions and ConfigureSchemaGeneratorOptions.DeepCopy() needs to be updated
Assert.Equal(12, publicProperties.Length);
}

[Fact]
public static void AddingSchemaFilterInstances_WhenConfiguringOptions_SameInstanceIsAdded()
{
var webhostingEnvironment = Substitute.For<IWebHostEnvironment>();
webhostingEnvironment.ApplicationName.Returns("Swashbuckle.AspNetCore.SwaggerGen.Test");

var testSchemaFilter = new TestSchemaFilter();

var options = new SwaggerGenOptions();
options.AddSchemaFilterInstance(testSchemaFilter);
options.AddSchemaFilterInstance(testSchemaFilter);

var configureSchemaGeneratorOptions = new ConfigureSchemaGeneratorOptions(Options.Create(options), null);
var schemaGeneratorOptions = new SchemaGeneratorOptions();

configureSchemaGeneratorOptions.Configure(schemaGeneratorOptions);

Assert.Equal(2, schemaGeneratorOptions.SchemaFilters.Count);
Assert.Same(testSchemaFilter, schemaGeneratorOptions.SchemaFilters.First());
Assert.Same(testSchemaFilter, schemaGeneratorOptions.SchemaFilters.Last());
}


[Fact]
public static void AddingSchemaFilterTypes_WhenConfiguringOptions_DifferentInstancesAreAdded()
{
var webhostingEnvironment = Substitute.For<IWebHostEnvironment>();
webhostingEnvironment.ApplicationName.Returns("Swashbuckle.AspNetCore.SwaggerGen.Test");

var options = new SwaggerGenOptions();
options.SchemaFilter<TestSchemaFilter>();
options.SchemaFilter<TestSchemaFilter>();

var configureSchemaGeneratorOptions = new ConfigureSchemaGeneratorOptions(Options.Create(options), null);
var schemaGeneratorOptions = new SchemaGeneratorOptions();

configureSchemaGeneratorOptions.Configure(schemaGeneratorOptions);

Assert.Equal(2, schemaGeneratorOptions.SchemaFilters.Count);
Assert.NotSame(schemaGeneratorOptions.SchemaFilters.First(), schemaGeneratorOptions.SchemaFilters.Last());
}
}