# 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)