# Swagger Integration OpenAPI/Swagger documentation for HTTP endpoints. ## Overview Svrnty.CQRS automatically integrates with Swagger/OpenAPI to provide interactive API documentation for all commands and queries. **Benefits:** - ✅ **Automatic documentation** - No manual OpenAPI spec writing - ✅ **Interactive testing** - Test endpoints directly from browser - ✅ **Schema generation** - Request/response types documented - ✅ **Validation docs** - FluentValidation rules reflected - ✅ **Authorization indication** - Security requirements shown ## Basic Setup ### Installation ```bash dotnet add package Swashbuckle.AspNetCore ``` ### Configuration ```csharp var builder = WebApplication.CreateBuilder(args); // Add Swagger services builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); // Register CQRS builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); var app = builder.Build(); // Enable Swagger middleware if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } // Map CQRS endpoints app.MapSvrntyCommands(); app.MapSvrntyQueries(); app.Run(); ``` **Access Swagger UI:** ``` https://localhost:5001/swagger ``` ## Automatic Features ### Endpoint Discovery All command and query endpoints appear automatically: ``` Commands POST /api/command/createUser POST /api/command/updateUser POST /api/command/deleteUser Queries GET /api/query/getUser POST /api/query/getUser GET /api/query/listUsers POST /api/query/listUsers ``` ### Request Schemas ```csharp public record CreateUserCommand { public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; public int Age { get; init; } } ``` **Generated Schema:** ```json { "CreateUserCommand": { "type": "object", "properties": { "name": { "type": "string" }, "email": { "type": "string" }, "age": { "type": "integer", "format": "int32" } } } } ``` ### Response Schemas ```csharp public record UserDto { public int Id { get; init; } public string Name { get; init; } = string.Empty; public string Email { get; init; } = string.Empty; } ``` **Generated Schema:** ```json { "UserDto": { "type": "object", "properties": { "id": { "type": "integer", "format": "int32" }, "name": { "type": "string" }, "email": { "type": "string" } } } } ``` ## Custom Configuration ### API Information ```csharp builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My CQRS API", Version = "v1", Description = "API using Svrnty.CQRS framework", Contact = new OpenApiContact { Name = "Your Name", Email = "your.email@example.com", Url = new Uri("https://example.com") }, License = new OpenApiLicense { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") } }); }); ``` ### XML Comments Enable XML documentation comments: **Project file (.csproj):** ```xml true $(NoWarn);1591 ``` **Code with XML comments:** ```csharp /// /// Creates a new user account /// public record CreateUserCommand { /// /// User's full name /// /// John Doe public string Name { get; init; } = string.Empty; /// /// User's email address /// /// john@example.com public string Email { get; init; } = string.Empty; /// /// User's age in years /// /// 25 public int Age { get; init; } } ``` **Swagger configuration:** ```csharp builder.Services.AddSwaggerGen(options => { var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); options.IncludeXmlComments(xmlPath); }); ``` ### Example Values ```csharp using System.ComponentModel.DataAnnotations; public record CreateUserCommand { [Required] [StringLength(100)] public string Name { get; init; } = "John Doe"; [Required] [EmailAddress] public string Email { get; init; } = "john@example.com"; [Range(18, 150)] public int Age { get; init; } = 25; } ``` ## Authentication Documentation ### JWT Bearer ```csharp using Microsoft.OpenApi.Models; builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "JWT Authorization header using the Bearer scheme. Example: \"Bearer {token}\"" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty() } }); }); ``` **Swagger UI shows "Authorize" button:** ``` Click "Authorize" → Enter: Bearer eyJhbGc... ``` ### API Key ```csharp builder.Services.AddSwaggerGen(options => { options.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme { Name = "X-API-Key", Type = SecuritySchemeType.ApiKey, In = ParameterLocation.Header, Description = "API Key authentication" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "ApiKey" } }, Array.Empty() } }); }); ``` ## Grouping and Organization ### Tag Filtering Commands and queries are automatically tagged: ```json { "paths": { "/api/command/createUser": { "post": { "tags": ["Commands"] } }, "/api/query/getUser": { "get": { "tags": ["Queries"] } } } } ``` ### Custom Tags Add custom tag descriptions: ```csharp builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My CQRS API", Version = "v1" }); // Tag descriptions options.TagActionsBy(api => { if (api.GroupName != null) { return new[] { api.GroupName }; } if (api.ActionDescriptor is EndpointMetadataApiDescriptionProvider.EndpointMetadataApiDescription emad) { if (emad.EndpointMetadata.OfType().FirstOrDefault() is { } tagsAttribute) { return tagsAttribute.Tags; } } return new[] { "Other" }; }); options.DocInclusionPredicate((name, api) => true); }); ``` ## Response Documentation ### Success Responses ```csharp /// /// Creates a new user /// /// User created successfully /// Validation failed /// Not authenticated /// Not authorized public record CreateUserCommand { } ``` ### Validation Error Example Swagger automatically documents validation errors: ```json { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "errors": { "Name": ["Name is required"], "Email": ["Valid email address is required"] } } ``` ## Versioning ### Multiple API Versions ```csharp builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My CQRS API", Version = "v1" }); options.SwaggerDoc("v2", new OpenApiInfo { Title = "My CQRS API", Version = "v2" }); }); var app = builder.Build(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "V1"); options.SwaggerEndpoint("/swagger/v2/swagger.json", "V2"); }); ``` ### Version-Specific Endpoints ```csharp app.MapSvrntyCommands("v1/commands"); app.MapSvrntyCommands("v2/commands"); ``` ## Customization ### UI Customization ```csharp app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); options.RoutePrefix = "api-docs"; // Change from /swagger to /api-docs options.DocumentTitle = "My CQRS API Documentation"; options.DisplayRequestDuration(); options.EnableDeepLinking(); options.EnableFilter(); options.ShowExtensions(); options.EnableValidator(); }); ``` ### Custom CSS ```csharp app.UseSwaggerUI(options => { options.InjectStylesheet("/swagger-ui/custom.css"); }); // Serve custom CSS app.UseStaticFiles(); ``` **wwwroot/swagger-ui/custom.css:** ```css .swagger-ui .topbar { background-color: #2c3e50; } ``` ## Production Deployment ### Disable Swagger in Production ```csharp if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } ``` ### Enable with Authentication ```csharp app.UseSwagger(); app.UseSwaggerUI(options => { options.ConfigObject.AdditionalItems["onComplete"] = new Action(() => { // Require login before showing Swagger }); }); app.MapSwagger().RequireAuthorization(); ``` ## Testing with Swagger ### Try It Out 1. Navigate to Swagger UI: `https://localhost:5001/swagger` 2. Expand endpoint (e.g., `POST /api/command/createUser`) 3. Click "Try it out" 4. Fill in request body: ```json { "name": "John Doe", "email": "john@example.com", "age": 25 } ``` 5. Click "Execute" 6. View response ### Authentication 1. Click "Authorize" button 2. Enter Bearer token: `Bearer eyJhbGc...` 3. Click "Authorize" 4. Close dialog 5. All requests now include authentication ## Complete Example ```csharp using Microsoft.OpenApi.Models; using System.Reflection; var builder = WebApplication.CreateBuilder(args); // CQRS builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); // Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(options => { options.SwaggerDoc("v1", new OpenApiInfo { Title = "My CQRS API", Version = "v1", Description = "An API using Svrnty.CQRS framework", Contact = new OpenApiContact { Name = "Your Team", Email = "team@example.com" } }); // XML comments var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); if (File.Exists(xmlPath)) { options.IncludeXmlComments(xmlPath); } // JWT Authentication options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme { Name = "Authorization", Type = SecuritySchemeType.Http, Scheme = "bearer", BearerFormat = "JWT", In = ParameterLocation.Header, Description = "Enter JWT token" }); options.AddSecurityRequirement(new OpenApiSecurityRequirement { { new OpenApiSecurityScheme { Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "Bearer" } }, Array.Empty() } }); }); var app = builder.Build(); // Enable Swagger in development if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(options => { options.SwaggerEndpoint("/swagger/v1/swagger.json", "V1"); options.DisplayRequestDuration(); options.EnableFilter(); }); } // CQRS endpoints app.MapSvrntyCommands(); app.MapSvrntyQueries(); app.Run(); ``` ## Best Practices ### ✅ DO - Add XML documentation comments - Configure authentication schemes - Use example values - Version your API - Organize endpoints with tags - Test endpoints via Swagger UI - Disable Swagger in production (or secure it) - Document response codes ### ❌ DON'T - Don't expose Swagger publicly without authentication - Don't skip XML comments - Don't ignore API versioning - Don't leave default titles/descriptions - Don't expose internal endpoints ## See Also - [HTTP Integration Overview](README.md) - [Endpoint Mapping](endpoint-mapping.md) - [HTTP Configuration](http-configuration.md) - [HTTP Troubleshooting](http-troubleshooting.md) - [Swashbuckle Documentation](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)