domaindrivendev / Swashbuckle.AspNetCore

Swagger tools for documenting API's built on ASP.NET Core
MIT License
5.25k stars 1.31k forks source link

[Feature request]: Document specific SwaggerGenOptions #2995

Open LennardF1989 opened 3 months ago

LennardF1989 commented 3 months ago

Is your feature request related to a specific problem? Or an existing feature?

If you extend existing solutions that come with a preconfigured Swashbuckle (for example Umbraco), you may want to add your own documents for your own API's, but not wish to inherit the existing configuration (eg. UseOneOfForPolymorphism or existing Filters).

Describe the solution you'd like

Currently, there can only ever be one SwaggerGenOptions, and it's split into a SwaggerGeneratorOptions and SchemaGeneratorOptions during startup using ConfigureSwaggerGeneratorOptions and ConfigureSchemaGeneratorOptions.

As such, the SwaggerGenerator and SchemaGenerator will use the same configuration for all documents. While you can make Filters opt-in by checking the documentName before proceeding with the logic, it's pretty much impossible to do something as simple as enabling/disabling UseOneOfForPolymorphism for one specific document without reinventing the wheel quite a bit.

My proposed solution is making the default ISwaggerProvider/IAsyncSwaggerProvider "document-aware". If the DI has a specific configuration for the string documentName, that one is used instead of the "global" one.

I made a proof of concept for this by creating a custom DocumentAwareSwaggerGenerator with this particular logic:

private SwaggerGenerator GetSwaggerGenerator(string documentName)
{
    var swaggerGenOptions = _serviceProvider
        .GetKeyedService<SwaggerGenOptions>(documentName);

    SwaggerGeneratorOptions swaggerGeneratorOptions;
    SchemaGeneratorOptions schemaGeneratorOptions;

    if (swaggerGenOptions == null)
    {
        swaggerGeneratorOptions = _swaggerGeneratorOptions;
        schemaGeneratorOptions = _schemaGeneratorOptions;
    }
    else
    {
        swaggerGeneratorOptions = swaggerGenOptions.SwaggerGeneratorOptions;
        schemaGeneratorOptions = swaggerGenOptions.SchemaGeneratorOptions;
    }

    var schemaGenerator = new SchemaGenerator(schemaGeneratorOptions, _serializerDataContractResolver);

    return new SwaggerGenerator(swaggerGeneratorOptions, _apiDescriptionsProvider, schemaGenerator);
}

I then added an Extension method:

        public static IServiceCollection ConfigureSwaggerGen<T>(
            this IServiceCollection services,
            string name,
            OpenApiInfo info
        )
            where T : class, IConfigureOptions<SwaggerGenOptions>
        {
            services.Configure<SwaggerGenOptions>(options =>
            {
                options.SwaggerDoc(name, info);
            });

            //NOTE: Transient so the constructor can inject all types of scope
            services.AddKeyedTransient<IConfigureOptions<SwaggerGenOptions>, T>(name);

            //NOTE: Singleton so the factory is only run once
            services.AddKeyedSingleton(name, (provider, _) =>
            {
                var swaggerGenOptions = new SwaggerGenOptions();

                swaggerGenOptions.SwaggerGeneratorOptions.SwaggerDocs.Add(
                    name,
                    info
                );

                provider
                    .GetRequiredKeyedService<IConfigureOptions<SwaggerGenOptions>>(name)
                    .Configure(swaggerGenOptions);

                //Snip: ConfigureSwaggerGeneratorOptions CreateFilter logic
                //Snip: ConfigureSchemaGeneratorOptions CreateFilter logic

                return swaggerGenOptions;
            });

            return services;
        }

All-in-all, this works! But I feel something like this should be included as part of Swashbuckle by default.

Additional context

No response

github-actions[bot] commented 1 month ago

This issue is stale because it has been open for 60 days with no activity. It will be automatically closed in 14 days if no further updates are made.

LennardF1989 commented 4 weeks ago

Bump to remove stale tag.