Search code examples
c#asp.net-coreswagger.net-6.0swashbuckle

How do I turn this Swashbuckle .NET 6 operation filter into a generic filter that can handle multiple class types in C# ASP.NET Core?


I have the following code for operation filter that helps me serialize arrays then sends it to a FromForm binder, how I turned it generic so I can get several class types and pass it over as T type to the following class so I wrote this:

public class ArraySettingsOperationFilter<T> where T : IOperationFilter, new()
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (operation.RequestBody != null && operation.RequestBody.Content.TryGetValue("multipart/form-data", out var openApiMediaType))
        {
            var options = new JsonSerializerOptions { WriteIndented = true };
            var array = new OpenApiArray
         {
        new OpenApiString(JsonSerializer.Serialize(new T(),options)),
         };
            openApiMediaType.Schema.Properties["Competences"].Example = array;
        }
    }
}

on startup class i'm getting every class that is marked by an interface and pass them over to operationfilter within Swagger generator settings:

var types = typeof(IMarkerInterface)
            .Assembly.GetTypes()
            .Where(t => t.IsSubclassOf(typeof(IMarkerInterface)) && !t.IsAbstract)
            .Select(t => Activator.CreateInstance(t) as IMarkerInterface).ToList();

then:

services.AddSwaggerGen(c =>
{
    foreach (var type in types)
    {
    c.OperationFilter<ArraySettingsOperationFilter<type>>(); //however here error says type is a variable but used like a type.
    }      
});

Anyone has a clue how to fix this issue? Or a workaround?


Solution

  • Well, c.OperationFilter<T> is just a generic extension method that adds FilterDescriptors to OperationFilterDescriptors list. So you don't have to rely on that generic method. A few modifications like the below will get you there:

    First change your generic ArraySettingsOperationFilter to a class that accepts a type in the constructor

    public class ArraySettingsOperationFilter : IOperationFilter
    {
        private readonly Type type;
    
        public ArraySettingsOperationFilter(Type type)
        { 
            // you can check this type implements the interface here
            this.type = type;
        }
    
        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            if (operation.RequestBody != null && operation.RequestBody.Content.TryGetValue("multipart/form-data", out var openApiMediaType))
            {
                var options = new JsonSerializerOptions { WriteIndented = true };
                var array = new OpenApiArray
             {
            new OpenApiString(JsonSerializer.Serialize(Activator.CreateInstance(this.type),options)),
             };
                openApiMediaType.Schema.Properties["Competences"].Example = array;
            }
        }
    }
    

    Make FilterDescriptors from the marked classes

     var filterDescriptors = typeof(IMarkerInterface)
            .Assembly.GetTypes()
            .Where(t => t.IsSubclassOf(typeof(IMarkerInterface)) && !t.IsAbstract)
            .Select(r => new FilterDescriptor { Type = typeof(ArraySettingsOperationFilter), Arguments = new object[] { r } })
            .ToArray();
    // Arguments array will be used for instantiating ArraySettingsOperationFilter
    

    Configure Swaggergen

       builder.Services.AddSwaggerGen(c => c.OperationFilterDescriptors.AddRange(filterDescriptors));
    

    Edit

    You may skip instantiating ArraySettingsOperationFilter if your marked types do the same thing.

       var filterDescriptors = typeof(IMarkerInterface)
           .Assembly.GetTypes()
           .Where(t => t.IsSubclassOf(typeof(IMarkerInterface)) && !t.IsAbstract)
           .Select(r => new FilterDescriptor { Type = r } )
           .ToArray();