Search code examples
asp.netmsbuildswaggerswashbucklenswag

How can I generate at compile-time a separate OpenAPI Swagger.json file for each Controller in my ASP.NET project?


Our ASP.NET Core web application has an ever-growing list of Controllers. At build-time of the project, I want to programmatically generate an individual separate swagger.json file for each Controller. (We need these files on disk to be picked up by another process.)

I've tried using NSwag and Swashbuckle. I configured them to generate a Document per Controller but their build-time CLI libraries seem to only support generating a single swagger.json file for a single specified document. I do not want to manually hard-code any specific document or Controller references.

https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2578

https://github.com/RicoSuter/NSwag/issues/4292


Solution

  • Here is a solution I came up with using NSWag. But it could ultimately use any library that can generate files.

    Basically what happens is the msbuild step "boots" your web application after it is built and the process checks for a special flag (generate-swagger) to indicate that you just want the swagger generated and the process to exit.

    Helper extension method

    public static class WebApplicationExtensions
    {
        public static async Task GenerateSwaggerFilesAsync(this WebApplication app, string folderPath)
        {
            Console.WriteLine($"Generating Swagger Files > \"{Path.GetFullPath(folderPath)}\"");
    
            if (Directory.Exists(folderPath))
            {
                Directory.Delete(folderPath, true);
            }
    
            var apiGroups = app.Services.GetService<IApiDescriptionGroupCollectionProvider>()!.ApiDescriptionGroups;
            foreach (var apiGroup in apiGroups.Items)
            {
                var settings = new AspNetCoreOpenApiDocumentGeneratorSettings
                {
                    Title = apiGroup.GroupName,
                    DocumentName = apiGroup.GroupName,
                    ApiGroupNames = new string[] { apiGroup.GroupName! },
                    SchemaType = NJsonSchema.SchemaType.OpenApi3,
                    SerializerSettings = new JsonSerializerSettings
                    {
                        ContractResolver = new CamelCasePropertyNamesContractResolver()
                    }
                };
    
                // TODO: Add any processors you want
                settings.OperationProcessors.Add(new ActionNameOperationProcessor());
    
                var apiGenerator = new AspNetCoreOpenApiDocumentGenerator(settings);
    
                var apiDocument = await apiGenerator.GenerateAsync(apiGroups);
    
                var json = apiDocument.ToJson();
    
                var subFolder = folderPath.UrlCombine(apiGroup.GroupName!);
    
                Directory.CreateDirectory(subFolder);
    
                var file = $"{subFolder}/swagger.json";
    
                await File.WriteAllTextAsync(file, json);
            }
    
            if (app.Configuration["generate-swagger"] == "true")
            {
                await app.StopAsync();
                Environment.Exit(0);
            }
        }
    
        public class ActionNameOperationProcessor : IOperationProcessor
        {
            public bool Process(OperationProcessorContext context)
            {
                var aspNetCoreContext = (AspNetCoreOperationProcessorContext)context;
                var controllerActionDescriptor = (ControllerActionDescriptor)aspNetCoreContext.ApiDescription.ActionDescriptor;
    
                context.OperationDescription.Operation.OperationId = controllerActionDescriptor.ActionName;
                context.OperationDescription.Operation.Tags = new List<string> { aspNetCoreContext.ApiDescription.GroupName! };
    
                return true;
            }
        }
    
    }
    
    

    Call it in Program/Startup.cs and provide a directory

    app.MapControllers();
    
    // Generate Swagger
    await app.GenerateSwaggerFilesAsync("./.swagger");
    
    app.Run();
    
    

    Add this to your web .csproj to generate on build

    <Target Name="GenerateSwagger" AfterTargets="Build" Condition="'$(Configuration)'=='Debug'">
        <Exec WorkingDirectory="$(RunWorkingDirectory)" Command="$(RunCommand) --generate-swagger true" />
    </Target>