Skip to content

Swagger in ConnectSoft Microservice Template

Purpose & Overview

Swagger (OpenAPI) is a standard specification for describing RESTful APIs, providing machine-readable documentation that enables API discovery, testing, and client code generation. In the ConnectSoft Microservice Template, Swagger is implemented using Swashbuckle.AspNetCore, providing automatic OpenAPI specification generation, interactive API documentation via Swagger UI, and seamless integration with XML documentation comments.

Swagger provides:

  • Automatic API Documentation: OpenAPI specification generated from code
  • Interactive Testing: Swagger UI for testing API endpoints
  • XML Comments Integration: API documentation from XML documentation comments
  • Code Generation: Client SDK generation from OpenAPI specification
  • API Discovery: Self-documenting APIs for developers
  • gRPC Support: Swagger documentation for gRPC services via ServiceModel.Grpc
  • Theming: Customizable Swagger UI themes

Swagger Philosophy

Swagger makes APIs self-documenting and discoverable. By automatically generating OpenAPI specifications from code and integrating XML documentation comments, the template ensures that API documentation is always up-to-date and comprehensive. Swagger UI provides an interactive interface for exploring and testing APIs, making it easier for developers to understand and consume the API.

Architecture Overview

Swagger Integration Flow

API Controllers (ServiceModel.RestApi)
    ├── XML Documentation Comments
    ├── Swagger Annotations
    └── Data Annotations
Swashbuckle.AspNetCore
    ├── SwaggerGen (OpenAPI Generation)
    │   ├── Include XML Comments
    │   ├── Enable Annotations
    │   └── Configure OpenAPI Info
    └── Swagger UI (Interactive Documentation)
OpenAPI Specification
    ├── /swagger/v1/swagger.json (JSON)
    └── /swagger/{documentName}/swagger.json
Swagger UI
    └── /swagger (Interactive UI)

Swagger Components

SwaggerExtensions.cs
├── AddSwagger() - Service Registration
│   ├── SwaggerGen Configuration
│   │   ├── OpenAPI Info (Title, Version)
│   │   ├── XML Comments Integration
│   │   ├── Annotations Support
│   │   └── Inheritance Configuration
│   └── ServiceModel.Grpc Integration (if UseGrpc)
├── UseMicroserviceSwagger() - Middleware
│   ├── UseSwagger() - JSON Endpoint
│   └── UseSwaggerUI() - Interactive UI
│       ├── Theme Configuration
│       ├── Endpoint Configuration
│       └── UI Options
└── MapMicroserviceSwagger() - Endpoint Mapping
    └── Rate Limiting Exemption

SwaggerOptions.cs
├── EnableSwagger (bool)
├── EnableSwaggerUI (bool)
└── SwaggerUITheme (string?)

Service Registration

AddSwagger Extension

Swagger is registered via AddSwagger() extension method:

// MicroserviceRegistrationExtensions.cs
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
    services.AddSwagger();
#endif

Implementation:

// SwaggerExtensions.cs
internal static IServiceCollection AddSwagger(this IServiceCollection services)
{
    ArgumentNullException.ThrowIfNull(services);

    if (OptionsExtensions.SwaggerOptions.EnableSwagger)
    {
        // Find XML documentation files
        string serviceModelXmlCommentsFilePath = null;
        string apiModelXmlCommentsFilePath = null;

        // ServiceModel XML comments
        string[] files = Directory.GetFiles(
            AppDomain.CurrentDomain.BaseDirectory, 
            searchPattern: "ConnectSoft.MicroserviceTemplate.ServiceModel.xml", 
            searchOption: SearchOption.AllDirectories);
        if (files != null && files.Length == 1)
        {
            serviceModelXmlCommentsFilePath = files[0];
        }

        // RestApi XML comments
        files = Directory.GetFiles(
            AppDomain.CurrentDomain.BaseDirectory, 
            searchPattern: "ConnectSoft.MicroserviceTemplate.ServiceModel.RestApi.xml", 
            searchOption: SearchOption.AllDirectories);
        if (files != null && files.Length == 1)
        {
            apiModelXmlCommentsFilePath = files[0];
        }

#if UseGrpc
        // ServiceModel.Grpc Swagger integration
        if (OptionsExtensions.SwaggerOptions.EnableSwagger)
        {
            services.AddServiceModelGrpcSwagger();
        }
#endif

        services.AddSwaggerGen(c =>
        {
            // OpenAPI document information
            c.SwaggerDoc("v1", new OpenApiInfo 
            { 
                Title = "ConnectSoft.MicroserviceTemplate's microservice back-end API", 
                Version = "v1" 
            });

            // Include XML comments from ServiceModel
            if (!string.IsNullOrEmpty(serviceModelXmlCommentsFilePath))
            {
                c.IncludeXmlComments(serviceModelXmlCommentsFilePath);
            }

            // Include XML comments from RestApi
            if (!string.IsNullOrEmpty(apiModelXmlCommentsFilePath))
            {
                c.IncludeXmlComments(apiModelXmlCommentsFilePath);
            }

            // Enable Swagger annotations
            c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true);

            // Use allOf for inheritance (OpenAPI 3.0)
            c.UseAllOfForInheritance();

#if UseGrpc
            // Force wrapped JSON for gRPC operations
            c.OperationFilter<ForceWrappedJsonForGrpcFilter>();
#endif
        });
    }

    return services;
}

Key Features: - Conditional Registration: Only registers if EnableSwagger is true - XML Comments: Automatically finds and includes XML documentation files - gRPC Support: Integrates ServiceModel.Grpc Swagger support - Annotations: Enables Swagger annotations for enhanced documentation

Middleware Configuration

UseMicroserviceSwagger Middleware

Swagger middleware is registered in the application pipeline:

// MicroserviceRegistrationExtensions.cs
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
    application.UseMicroserviceSwagger();
#endif

Implementation:

// SwaggerExtensions.cs
internal static IApplicationBuilder UseMicroserviceSwagger(this IApplicationBuilder application)
{
    ArgumentNullException.ThrowIfNull(application);

    SwaggerOptions swaggerOptions = application.ApplicationServices
        .GetRequiredService<IOptions<SwaggerOptions>>().Value;

    if (swaggerOptions.EnableSwagger)
    {
        // Enable middleware to serve generated Swagger as a JSON endpoint
        application.UseSwagger();

        if (swaggerOptions.EnableSwaggerUI)
        {
#if UseGrpc
            // Enable ServiceModel.Grpc HTTP/1.1 JSON gateway for Swagger UI
            // This allows "Try it out" functionality for gRPC services
            application.UseServiceModelGrpcSwaggerGateway();
#endif

            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.)
            application.UseSwaggerUI(options =>
            {
                // Set the Swagger UI browser document title
                options.DocumentTitle = "ConnectSoft.MicroserviceTemplate's microservice back-end API";

                // Inject custom theme (optional)
                if (!string.IsNullOrEmpty(swaggerOptions.SwaggerUITheme))
                {
                    // Example: /swagger-ui/themes/3.x/theme-monokai.css
                    options.InjectStylesheet(swaggerOptions.SwaggerUITheme);
                }

                // Configure Swagger JSON endpoint
                options.SwaggerEndpoint(
                    "/swagger/v1/swagger.json", 
                    "ConnectSoft.MicroserviceTemplate's microservice back-end API.");

                // Display operation IDs
                options.DisplayOperationId();

                // Display request duration
                options.DisplayRequestDuration();
            });
        }
    }

    return application;
}

Middleware Order: - Swagger middleware should be placed before routing - Typically placed after exception handling and logging - Before authentication/authorization middleware

Endpoint Mapping

MapMicroserviceSwagger Extension

Swagger endpoints are mapped in the endpoint configuration:

// MicroserviceRegistrationExtensions.cs
#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
    endpoints.MapMicroserviceSwagger();
#endif

Implementation:

// SwaggerExtensions.cs
internal static IEndpointRouteBuilder MapMicroserviceSwagger(this IEndpointRouteBuilder endpoints)
{
    ArgumentNullException.ThrowIfNull(endpoints);

    SwaggerOptions swaggerOptions = endpoints.ServiceProvider
        .GetRequiredService<IOptions<SwaggerOptions>>().Value;

    if (swaggerOptions.EnableSwagger)
    {
        var endpointsBuilder = endpoints.MapSwagger();

#if RateLimiting
        // Disable rate limiting for Swagger endpoints
        endpointsBuilder.DisableRateLimiting();
#endif
    }

    return endpoints;
}

Available Endpoints: - /swagger/v1/swagger.json - OpenAPI JSON specification - /swagger/{documentName}/swagger.json - Versioned OpenAPI specification - /swagger - Swagger UI (if EnableSwaggerUI is true)

Configuration

SwaggerOptions

Swagger configuration is managed through SwaggerOptions:

// SwaggerOptions.cs
public sealed class SwaggerOptions
{
    public const string SwaggerOptionsSectionName = "Swagger";

    [Required]
    required public bool EnableSwagger { get; set; }

    [Required]
    required public bool EnableSwaggerUI { get; set; }

    public string? SwaggerUITheme { get; set; }
}

Configuration File

appsettings.json:

{
  "Swagger": {
    "EnableSwagger": true,
    "EnableSwaggerUI": true,
    "SwaggerUITheme": "/swagger-ui/themes/3.x/theme-monokai.css"
  }
}

Environment-Specific Configuration:

// appsettings.Development.json
{
  "Swagger": {
    "EnableSwagger": true,
    "EnableSwaggerUI": true
  }
}

// appsettings.Production.json
{
  "Swagger": {
    "EnableSwagger": true,
    "EnableSwaggerUI": false  // Disable UI in production
  }
}

Configuration Properties:

Property Type Required Description
EnableSwagger bool Yes Enables Swagger JSON endpoint generation
EnableSwaggerUI bool Yes Enables Swagger UI interactive documentation
SwaggerUITheme string? No Path to custom CSS theme for Swagger UI

XML Documentation Integration

Enabling XML Documentation

Project Configuration:

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Directory.Build.props (applied to all projects):

<PropertyGroup>
  <GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

XML Documentation Files

Swagger automatically locates and includes XML documentation files:

  1. ServiceModel.xml: DTOs and service model documentation
  2. Pattern: ConnectSoft.MicroserviceTemplate.ServiceModel.xml
  3. Location: AppDomain.CurrentDomain.BaseDirectory

  4. RestApi.xml: REST API controllers and endpoints documentation

  5. Pattern: ConnectSoft.MicroserviceTemplate.ServiceModel.RestApi.xml
  6. Location: AppDomain.CurrentDomain.BaseDirectory

Writing XML Comments

Controller Documentation:

/// <summary>
/// Processes microservice aggregate root operations.
/// </summary>
/// <remarks>
/// This controller handles creation, update, and deletion of aggregate roots
/// following Domain-Driven Design principles.
/// </remarks>
[ApiController]
[Route("api/[controller]")]
public class MicroserviceAggregateRootsServiceController : ControllerBase
{
    /// <summary>
    /// Creates a new microservice aggregate root.
    /// </summary>
    /// <param name="request">The creation request containing aggregate root details.</param>
    /// <param name="token">Cancellation token to cancel the operation.</param>
    /// <returns>The created aggregate root.</returns>
    /// <exception cref="ValidationException">Thrown when input validation fails.</exception>
    /// <exception cref="DomainModelException">Thrown when business rules are violated.</exception>
    [HttpPost("MicroserviceAggregateRoots/", Name = nameof(CreateMicroserviceAggregateRoot))]
    [SwaggerOperation(
        Summary = "Process, create and store a MicroserviceAggregateRoots.",
        Description = "Process, create and store a MicroserviceAggregateRoots.",
        OperationId = nameof(CreateMicroserviceAggregateRoot),
        Tags = ["MicroserviceAggregateRoots"])]
    [SwaggerResponse(StatusCodes.Status200OK, "Created MicroserviceAggregateRoot.", typeof(CreateMicroserviceAggregateRootResponse))]
    [SwaggerResponse(StatusCodes.Status400BadRequest, "The request is invalid.", typeof(ValidationProblemDetails))]
    [SwaggerResponse(StatusCodes.Status500InternalServerError, "An un-handled exception occurred.", typeof(ProblemDetails))]
    public async Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRoot(
        [FromBody, SwaggerRequestBody("Create MicroserviceAggregateRoot request payload", Required = true)] 
        CreateMicroserviceAggregateRootRequest request, 
        CancellationToken token = default)
    {
        // Implementation
    }
}

DTO Documentation:

/// <summary>
/// Request DTO for creating a microservice aggregate root.
/// </summary>
public class CreateMicroserviceAggregateRootRequest
{
    /// <summary>
    /// Gets or sets the unique identifier for the aggregate root.
    /// </summary>
    /// <value>
    /// A <see cref="Guid"/> that uniquely identifies the aggregate root.
    /// </value>
    [Required]
    public Guid ObjectId { get; set; }

    /// <summary>
    /// Gets or sets the name of the aggregate root.
    /// </summary>
    /// <value>
    /// A string representing the name of the aggregate root.
    /// </value>
    [Required]
    [StringLength(100)]
    public string Name { get; set; }
}

Swagger Annotations

SwaggerOperation Attribute

Usage:

[SwaggerOperation(
    Summary = "Short summary of the operation",
    Description = "Detailed description of the operation",
    OperationId = "UniqueOperationId",
    Tags = ["Tag1", "Tag2"])]
[HttpPost]
public async Task<ActionResult> Create(CreateRequest request)
{
    // Implementation
}

SwaggerResponse Attribute

Usage:

[SwaggerResponse(StatusCodes.Status200OK, "Success description", typeof(ResponseDto))]
[SwaggerResponse(StatusCodes.Status400BadRequest, "Bad request description", typeof(ValidationProblemDetails))]
[SwaggerResponse(StatusCodes.Status404NotFound, "Not found description")]
[SwaggerResponse(StatusCodes.Status500InternalServerError, "Error description", typeof(ProblemDetails))]
[HttpGet("{id}")]
public async Task<ActionResult<ResponseDto>> Get(Guid id)
{
    // Implementation
}

SwaggerRequestBody Attribute

Usage:

[HttpPost]
public async Task<ActionResult> Create(
    [FromBody, SwaggerRequestBody("Description of request body", Required = true)] 
    CreateRequest request)
{
    // Implementation
}

SwaggerSchemaAttribute

Usage:

[SwaggerSchema(Description = "Description of the schema", Title = "Schema Title")]
public class MyDto
{
    // Properties
}

Swagger UI Features

Interactive Testing

Swagger UI provides "Try it out" functionality: - Test API endpoints directly from the browser - View request/response examples - See response status codes and headers - Test authentication (if configured)

Operation Details

Each operation displays: - Summary: Brief description - Description: Detailed description - Parameters: Request parameters with types and descriptions - Request Body: Request body schema and examples - Responses: Response schemas and status codes - Operation ID: Unique identifier for the operation

Schema Viewer

Swagger UI displays: - Data Models: All DTOs and models - Schema Details: Properties, types, and descriptions - Example Values: Sample values for each property - Inheritance: Parent-child relationships

gRPC Integration

ServiceModel.Grpc Swagger Support

When UseGrpc is enabled, Swagger integrates with ServiceModel.Grpc:

#if UseGrpc
    // Add ServiceModel.Grpc Swagger integration
    services.AddServiceModelGrpcSwagger();

    // Enable HTTP/1.1 JSON gateway for Swagger UI
    application.UseServiceModelGrpcSwaggerGateway();

    // Force wrapped JSON for gRPC operations
    c.OperationFilter<ForceWrappedJsonForGrpcFilter>();
#endif

Features: - gRPC services documented in Swagger - "Try it out" functionality via HTTP/1.1 JSON gateway - Automatic conversion between gRPC and REST representations

Customization

Custom Themes

Swagger UI supports custom themes:

Configuration:

{
  "Swagger": {
    "SwaggerUITheme": "/swagger-ui/themes/3.x/theme-monokai.css"
  }
}

Custom Theme Files: - Place theme CSS files in wwwroot/swagger-ui/themes/ - Reference in SwaggerUITheme configuration - Themes available: monokai, material, dark, etc.

Additional SwaggerGen Options

Advanced Configuration:

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo 
    { 
        Title = "My API",
        Version = "v1",
        Description = "API Description",
        Contact = new OpenApiContact
        {
            Name = "Support",
            Email = "support@example.com"
        },
        License = new OpenApiLicense
        {
            Name = "MIT",
            Url = new Uri("https://opensource.org/licenses/MIT")
        }
    });

    // Security definitions
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using the Bearer scheme",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });

    // Security requirements
    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

Security Considerations

Production Deployment

Best Practices:

  1. Disable Swagger UI in Production:

    {
      "Swagger": {
        "EnableSwagger": true,
        "EnableSwaggerUI": false  // Disable UI in production
      }
    }
    

  2. Use Authentication Middleware:

    // Require authentication for Swagger endpoint
    app.MapSwagger()
        .RequireAuthorization();
    

  3. Restrict Access by IP:

    // Restrict to internal IPs
    app.MapSwagger()
        .RequireHost("internal.example.com");
    

  4. Use Authorization Policies:

    app.MapSwagger()
        .RequireAuthorization("ApiDocumentationPolicy");
    

Rate Limiting

Swagger endpoints are exempt from rate limiting:

var endpointsBuilder = endpoints.MapSwagger();
#if RateLimiting
    endpointsBuilder.DisableRateLimiting();
#endif

This ensures that API documentation is always accessible, even under load.

Best Practices

Do's

  1. Enable XML Documentation

    <PropertyGroup>
      <GenerateDocumentationFile>true</GenerateDocumentationFile>
    </PropertyGroup>
    

  2. Write Comprehensive XML Comments

    /// <summary>
    /// Creates a new aggregate root.
    /// </summary>
    /// <param name="request">The creation request.</param>
    /// <returns>The created aggregate root.</returns>
    [HttpPost]
    public async Task<ActionResult> Create(CreateRequest request)
    

  3. Use Swagger Annotations

    [SwaggerOperation(Summary = "Creates a new aggregate root")]
    [SwaggerResponse(StatusCodes.Status200OK, "Created successfully")]
    

  4. Document All Response Codes

    [SwaggerResponse(StatusCodes.Status200OK, "Success")]
    [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request")]
    [SwaggerResponse(StatusCodes.Status404NotFound, "Not found")]
    [SwaggerResponse(StatusCodes.Status500InternalServerError, "Error")]
    

  5. Use Descriptive Operation IDs

    [SwaggerOperation(OperationId = "CreateAggregateRoot")]
    

  6. Group Operations with Tags

    [SwaggerOperation(Tags = ["AggregateRoots"])]
    

Don'ts

  1. Don't Expose Swagger UI in Production Without Authentication

    // ❌ BAD - Swagger UI accessible without auth
    {
      "Swagger": {
        "EnableSwaggerUI": true
      }
    }
    
    // ✅ GOOD - Disable in production or require authentication
    {
      "Swagger": {
        "EnableSwaggerUI": false  // Or use authentication middleware
      }
    }
    

  2. Don't Skip XML Documentation

    // ❌ BAD - No documentation
    [HttpPost]
    public async Task<ActionResult> Create(CreateRequest request)
    
    // ✅ GOOD - Documented
    /// <summary>
    /// Creates a new aggregate root.
    /// </summary>
    [HttpPost]
    public async Task<ActionResult> Create(CreateRequest request)
    

  3. Don't Use Generic Operation IDs

    // ❌ BAD - Generic operation ID
    [SwaggerOperation(OperationId = "Post")]
    
    // ✅ GOOD - Descriptive operation ID
    [SwaggerOperation(OperationId = "CreateAggregateRoot")]
    

  4. Don't Document Secrets in Examples

    // ❌ BAD - Secret in example
    [SwaggerExample("ApiKey", "sk-1234567890abcdef")]
    
    // ✅ GOOD - Placeholder
    [SwaggerExample("ApiKey", "your-api-key-here")]
    

Integration with Scalar

Coexistence

Swagger UI and Scalar can coexist:

#if Swagger && (UseGrpc || UseRestApi || UseAzureFunction)
    endpoints.MapMicroserviceSwagger();
#endif
#if Scalar && (UseGrpc || UseRestApi || UseAzureFunction)
    endpoints.MapScalar();
#endif

Benefits: - Provide choice to API consumers - Different teams can prefer different tools - Gradual migration from Swagger UI to Scalar

Shared OpenAPI Specification

Both Swagger UI and Scalar use the same OpenAPI specification:

/swagger/v1/swagger.json
    ├── Swagger UI (/swagger)
    └── Scalar (/scalar)

For detailed information on Scalar, see Scalar.

Troubleshooting

Issue: Swagger Not Loading

Symptoms: Swagger endpoints return 404 or not accessible.

Solutions: 1. Verify EnableSwagger is true in configuration 2. Check feature flags: #if Swagger && (UseGrpc || UseRestApi || UseAzureFunction) 3. Verify middleware is registered: application.UseMicroserviceSwagger() 4. Check endpoint mapping: endpoints.MapMicroserviceSwagger()

Issue: XML Comments Not Appearing

Symptoms: XML documentation comments not showing in Swagger UI.

Solutions: 1. Verify GenerateDocumentationFile is true in project file 2. Check XML files are generated in output directory 3. Verify XML files match expected patterns: - ConnectSoft.MicroserviceTemplate.ServiceModel.xml - ConnectSoft.MicroserviceTemplate.ServiceModel.RestApi.xml 4. Check XML files are included in deployment package

Issue: Swagger UI Not Displaying

Symptoms: Swagger JSON endpoint works, but UI doesn't load.

Solutions: 1. Verify EnableSwaggerUI is true in configuration 2. Check UseSwaggerUI() is called in middleware pipeline 3. Verify Swagger UI assets are accessible 4. Check browser console for JavaScript errors

Issue: gRPC Operations Not Documented

Symptoms: gRPC services not appearing in Swagger.

Solutions: 1. Verify UseGrpc feature flag is enabled 2. Check AddServiceModelGrpcSwagger() is called 3. Verify UseServiceModelGrpcSwaggerGateway() is in middleware pipeline 4. Check ForceWrappedJsonForGrpcFilter is added

Issue: Custom Theme Not Applied

Symptoms: Custom theme CSS not loading.

Solutions: 1. Verify theme file path is correct 2. Check theme file is in wwwroot/swagger-ui/themes/ 3. Verify SwaggerUITheme configuration matches file path 4. Check file permissions and static file serving

Summary

Swagger in the ConnectSoft Microservice Template provides:

  • Automatic Documentation: OpenAPI specification generated from code
  • XML Comments Integration: API documentation from XML comments
  • Interactive Testing: Swagger UI for testing endpoints
  • gRPC Support: Documentation for gRPC services
  • Customization: Themes and advanced configuration options
  • Security: Configurable authentication and authorization
  • Best Practices: Comprehensive documentation patterns

By following these patterns, teams can:

  • Document APIs Automatically: Keep documentation in sync with code
  • Enable API Discovery: Self-documenting APIs for developers
  • Facilitate Testing: Interactive testing via Swagger UI
  • Generate Client Code: SDK generation from OpenAPI specification
  • Maintain Consistency: Standardized API documentation approach

Swagger is an essential tool for API development, providing comprehensive documentation, interactive testing, and client code generation capabilities that make APIs more accessible and easier to consume.