13 KiB
13 KiB
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
dotnet add package Swashbuckle.AspNetCore
Configuration
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
public record CreateUserCommand
{
public string Name { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
public int Age { get; init; }
}
Generated Schema:
{
"CreateUserCommand": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"age": { "type": "integer", "format": "int32" }
}
}
}
Response Schemas
public record UserDto
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
}
Generated Schema:
{
"UserDto": {
"type": "object",
"properties": {
"id": { "type": "integer", "format": "int32" },
"name": { "type": "string" },
"email": { "type": "string" }
}
}
}
Custom Configuration
API Information
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):
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
Code with XML comments:
/// <summary>
/// Creates a new user account
/// </summary>
public record CreateUserCommand
{
/// <summary>
/// User's full name
/// </summary>
/// <example>John Doe</example>
public string Name { get; init; } = string.Empty;
/// <summary>
/// User's email address
/// </summary>
/// <example>john@example.com</example>
public string Email { get; init; } = string.Empty;
/// <summary>
/// User's age in years
/// </summary>
/// <example>25</example>
public int Age { get; init; }
}
Swagger configuration:
builder.Services.AddSwaggerGen(options =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
Example Values
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
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<string>()
}
});
});
Swagger UI shows "Authorize" button:
Click "Authorize" → Enter: Bearer eyJhbGc...
API Key
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<string>()
}
});
});
Grouping and Organization
Tag Filtering
Commands and queries are automatically tagged:
{
"paths": {
"/api/command/createUser": {
"post": {
"tags": ["Commands"]
}
},
"/api/query/getUser": {
"get": {
"tags": ["Queries"]
}
}
}
}
Custom Tags
Add custom tag descriptions:
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<TagsAttribute>().FirstOrDefault() is { } tagsAttribute)
{
return tagsAttribute.Tags;
}
}
return new[] { "Other" };
});
options.DocInclusionPredicate((name, api) => true);
});
Response Documentation
Success Responses
/// <summary>
/// Creates a new user
/// </summary>
/// <response code="200">User created successfully</response>
/// <response code="400">Validation failed</response>
/// <response code="401">Not authenticated</response>
/// <response code="403">Not authorized</response>
public record CreateUserCommand { }
Validation Error Example
Swagger automatically documents validation errors:
{
"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
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
app.MapSvrntyCommands("v1/commands");
app.MapSvrntyCommands("v2/commands");
Customization
UI Customization
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
app.UseSwaggerUI(options =>
{
options.InjectStylesheet("/swagger-ui/custom.css");
});
// Serve custom CSS
app.UseStaticFiles();
wwwroot/swagger-ui/custom.css:
.swagger-ui .topbar {
background-color: #2c3e50;
}
Production Deployment
Disable Swagger in Production
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
Enable with Authentication
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
- Navigate to Swagger UI:
https://localhost:5001/swagger - Expand endpoint (e.g.,
POST /api/command/createUser) - Click "Try it out"
- Fill in request body:
{ "name": "John Doe", "email": "john@example.com", "age": 25 } - Click "Execute"
- View response
Authentication
- Click "Authorize" button
- Enter Bearer token:
Bearer eyJhbGc... - Click "Authorize"
- Close dialog
- All requests now include authentication
Complete Example
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<string>()
}
});
});
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