domaindrivendev / Swashbuckle.AspNetCore

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

Make `SwaggerGenerator`'s private methods `protected virtual` #2152

Closed abdusco closed 3 weeks ago

abdusco commented 3 years ago

I've come across this usecase where I wanted to serve the same OpenAPI document twice with two different sets of operation IDs.

It would have been easier if I could inject ISwaggerGenerator inside an IDocumentFilter implementation,

private class CloneDocumentFilter: IDocumentFilter
{
    private ISwaggerProvider _provider;

    public CloneDocumentFilter(ISwaggerProvider provider)
    {
        _provider = provider;
    }

    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var doc = _provider.GetSwagger("v1");
        var paths = doc.Paths;
        // ...modify operations
        swaggerDoc.Paths = paths;
    }
}

but I hit this wall:

exception I get
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
    at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
    at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
    at System.Lazy`1.CreateValue()
    at System.Lazy`1.get_Value()
    at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd(String name, Func`1 createOptions)
    at Microsoft.Extensions.Options.OptionsManager`1.Get(String name)
    at Microsoft.Extensions.Options.OptionsManager`1.get_Value()
    at Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.<>c.b__0_1(IServiceProvider s)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.b__0(ServiceProviderEngineScope scope)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
    at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.ConstructorMatcher.CreateInstance(IServiceProvider provider)
    at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.CreateInstance(IServiceProvider provider, Type instanceType, Object[] parameters)
    at Swashbuckle.AspNetCore.SwaggerGen.ConfigureSwaggerGeneratorOptions.CreateFilter[TFilter](FilterDescriptor filterDescriptor)
    at Swashbuckle.AspNetCore.SwaggerGen.ConfigureSwaggerGeneratorOptions.<>c__DisplayClass4_0.b__3(FilterDescriptor filterDescriptor)
    at System.Collections.Generic.List`1.ForEach(Action`1 action)
    at Swashbuckle.AspNetCore.SwaggerGen.ConfigureSwaggerGeneratorOptions.Configure(SwaggerGeneratorOptions options)
    at Microsoft.Extensions.Options.OptionsFactory`1.Create(String name)
    at Microsoft.Extensions.Options.OptionsManager`1.<>c__DisplayClass5_0.b__0()
    at System.Lazy`1.ViaFactory(LazyThreadSafetyMode mode)
    at System.Lazy`1.ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
    at System.Lazy`1.CreateValue()
    at System.Lazy`1.get_Value()
    at Microsoft.Extensions.Options.OptionsCache`1.GetOrAdd(String name, Func`1 createOptions)
    at Microsoft.Extensions.Options.OptionsManager`1.Get(String name)
    at Microsoft.Extensions.Options.OptionsManager`1.get_Value()
    at Microsoft.Extensions.DependencyInjection.SwaggerGenServiceCollectionExtensions.<>c.b__0_1(IServiceProvider s)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitFactory(FactoryCallSite factoryCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitDisposeCache(ServiceCallSite transientCallSite, RuntimeResolverContext context)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass1_0.b__0(ServiceProviderEngineScope scope)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
    at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
    at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.GetService(IServiceProvider sp, Type type, Type middleware)
    at lambda_method1(Closure , Object , HttpContext , IServiceProvider )
    at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.<>c__DisplayClass5_1.b__2(HttpContext context)
    at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

So I decided to go with a custom ISwaggerProvider, but it has no virtual methods. This means I can't access to runtime information to enhance operation ids inside a subclass without reimplementing the whole logic. All I have is the what's left after Swashbuckle does its thing.

class SwaggerDocCustomOperationIdProvider : SwaggerGenerator, ISwaggerProvider
{
    public SwaggerDocCustomOperationIdProvider(SwaggerGeneratorOptions options,
                                               IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
                                               ISchemaGenerator schemaGenerator) : base(options,
        apiDescriptionsProvider, schemaGenerator)
    {
    }

    public new OpenApiDocument GetSwagger(string documentName, string host = null, string basePath = null)
    {
        // ...
        var doc = base.GetSwagger(documentName, host, basePath);
        var operations = doc.Paths
            .SelectMany(p => p.Value.Operations.Values)
            .ToList();

        foreach (var op in operations)
        {
            // modify the operation info
        }

        return doc;
    }
}

If would be nice if methods of the SwaggerGenerator were protected virtual so I could override & modify their output and still have access to ApiDescription info ASP.NET Core provides.

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/21391c2ea7dcb407784689ad861560623169673f/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs#L134

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.

github-actions[bot] commented 3 weeks ago

This issue was closed because it has been inactive for 14 days since being marked as stale.