# HTTP Configuration Configuration and customization for HTTP integration. ## Basic Configuration ### Minimal Setup ```csharp var builder = WebApplication.CreateBuilder(args); // Register CQRS services builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); var app = builder.Build(); // Map endpoints with default settings app.MapSvrntyCommands(); // POST /api/command/{name} app.MapSvrntyQueries(); // GET/POST /api/query/{name} app.Run(); ``` ## Route Prefix Configuration ### Custom Command Prefix ```csharp app.MapSvrntyCommands("my-commands"); // POST /my-commands/{name} ``` ### Custom Query Prefix ```csharp app.MapSvrntyQueries("my-queries"); // GET/POST /my-queries/{name} ``` ### Remove Prefix ```csharp app.MapSvrntyCommands(""); // POST /{commandName} app.MapSvrntyQueries(""); // GET/POST /{queryName} ``` ### Versioned Routes ```csharp app.MapSvrntyCommands("v1/commands"); app.MapSvrntyQueries("v1/queries"); // POST /v1/commands/{name} // GET/POST /v1/queries/{name} ``` ## CORS Configuration ### Basic CORS ```csharp var builder = WebApplication.CreateBuilder(args); builder.Services.AddCors(options => { options.AddDefaultPolicy(policy => { policy.WithOrigins("https://example.com") .AllowAnyMethod() .AllowAnyHeader(); }); }); var app = builder.Build(); app.UseCors(); // Must be before MapSvrntyCommands/Queries app.MapSvrntyCommands(); app.MapSvrntyQueries(); app.Run(); ``` ### Named CORS Policy ```csharp builder.Services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", policy => { policy.WithOrigins("https://app.example.com", "https://admin.example.com") .WithMethods("GET", "POST") .WithHeaders("Content-Type", "Authorization") .AllowCredentials(); }); }); var app = builder.Build(); app.UseCors("AllowSpecificOrigin"); app.MapSvrntyCommands(); app.MapSvrntyQueries(); ``` ### Development CORS ```csharp if (app.Environment.IsDevelopment()) { app.UseCors(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); } ``` ## Authentication ### JWT Bearer Authentication ```csharp using Microsoft.AspNetCore.Authentication.JwtBearer; var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://your-auth-server.com"; options.Audience = "your-api-resource"; }); builder.Services.AddAuthorization(); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapSvrntyCommands(); app.MapSvrntyQueries(); app.Run(); ``` ### Cookie Authentication ```csharp using Microsoft.AspNetCore.Authentication.Cookies; builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/login"; options.LogoutPath = "/logout"; }); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); ``` ### API Key Authentication ```csharp // Custom API key middleware app.Use(async (context, next) => { if (!context.Request.Headers.TryGetValue("X-API-Key", out var apiKey)) { context.Response.StatusCode = 401; await context.Response.WriteAsync("API Key missing"); return; } // Validate API key if (!IsValidApiKey(apiKey)) { context.Response.StatusCode = 401; await context.Response.WriteAsync("Invalid API Key"); return; } await next(); }); app.MapSvrntyCommands(); app.MapSvrntyQueries(); ``` ## Authorization ### Require Authentication for All Endpoints ```csharp app.MapSvrntyCommands().RequireAuthorization(); app.MapSvrntyQueries().RequireAuthorization(); ``` ### Role-Based Authorization ```csharp app.MapSvrntyCommands().RequireAuthorization(policy => { policy.RequireRole("Admin"); }); ``` ### Policy-Based Authorization ```csharp builder.Services.AddAuthorization(options => { options.AddPolicy("RequireAdminRole", policy => { policy.RequireRole("Admin"); }); options.AddPolicy("RequireVerifiedAccount", policy => { policy.RequireClaim("EmailVerified", "true"); }); }); var app = builder.Build(); app.UseAuthentication(); app.UseAuthorization(); app.MapSvrntyCommands().RequireAuthorization("RequireAdminRole"); app.MapSvrntyQueries(); // No global authorization for queries ``` ### Per-Command Authorization Use `ICommandAuthorizationService` for fine-grained control: ```csharp public class DeleteUserCommandAuthorization : ICommandAuthorizationService { public Task CanExecuteAsync( DeleteUserCommand command, ClaimsPrincipal user, CancellationToken cancellationToken) { // Only admins or the user themselves can delete return Task.FromResult( user.IsInRole("Admin") || user.FindFirst(ClaimTypes.NameIdentifier)?.Value == command.UserId.ToString()); } } // Registration builder.Services.AddScoped, DeleteUserCommandAuthorization>(); ``` ## Rate Limiting ### ASP.NET Core Rate Limiting ```csharp using System.Threading.RateLimiting; builder.Services.AddRateLimiter(options => { options.GlobalLimiter = PartitionedRateLimiter.Create(context => { var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? "anonymous"; return RateLimitPartition.GetFixedWindowLimiter(userId, _ => new FixedWindowRateLimiterOptions { PermitLimit = 100, Window = TimeSpan.FromMinutes(1) }); }); }); var app = builder.Build(); app.UseRateLimiter(); app.MapSvrntyCommands(); app.MapSvrntyQueries(); ``` ### Per-Endpoint Rate Limiting ```csharp app.MapSvrntyCommands().RequireRateLimiting("fixed"); // Define named policy builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter("fixed", limiterOptions => { limiterOptions.PermitLimit = 10; limiterOptions.Window = TimeSpan.FromSeconds(10); }); }); ``` ## Request Size Limits ### Global Request Size Limit ```csharp builder.Services.Configure(options => { options.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB }); builder.Services.Configure(options => { options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10 MB }); ``` ### Per-Endpoint Size Limit ```csharp app.MapPost("/api/command/uploadLargeFile", async (HttpContext context) => { context.Features.Get().MaxRequestBodySize = 100 * 1024 * 1024; // 100 MB // Handle large file upload }) .DisableRequestSizeLimit(); ``` ## Compression ### Response Compression ```csharp using Microsoft.AspNetCore.ResponseCompression; builder.Services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add(); options.Providers.Add(); }); var app = builder.Build(); app.UseResponseCompression(); app.MapSvrntyCommands(); app.MapSvrntyQueries(); ``` ## Content Negotiation ### JSON Configuration ```csharp using System.Text.Json; builder.Services.ConfigureHttpJsonOptions(options => { options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; options.SerializerOptions.WriteIndented = app.Environment.IsDevelopment(); }); ``` ### XML Support ```csharp builder.Services.AddControllers() .AddXmlSerializerFormatters(); ``` ## HTTPS Configuration ### Require HTTPS ```csharp if (!app.Environment.IsDevelopment()) { app.UseHttpsRedirection(); } ``` ### HSTS ```csharp if (!app.Environment.IsDevelopment()) { app.UseHsts(); } ``` ## Logging ### Request Logging ```csharp app.UseHttpLogging(); ``` ### Custom Request Logging ```csharp app.Use(async (context, next) => { var logger = context.RequestServices.GetRequiredService>(); logger.LogInformation( "HTTP {Method} {Path} from {RemoteIp}", context.Request.Method, context.Request.Path, context.Connection.RemoteIpAddress); await next(); }); ``` ## Health Checks ### Basic Health Checks ```csharp builder.Services.AddHealthChecks(); var app = builder.Build(); app.MapHealthChecks("/health"); app.MapSvrntyCommands(); app.MapSvrntyQueries(); ``` ### Detailed Health Checks ```csharp builder.Services.AddHealthChecks() .AddDbContextCheck(); app.MapHealthChecks("/health", new HealthCheckOptions { ResponseWriter = async (context, report) => { context.Response.ContentType = "application/json"; var response = new { status = report.Status.ToString(), checks = report.Entries.Select(e => new { name = e.Key, status = e.Value.Status.ToString(), description = e.Value.Description }) }; await context.Response.WriteAsJsonAsync(response); } }); ``` ## Error Handling ### Problem Details ```csharp builder.Services.AddProblemDetails(); var app = builder.Build(); app.UseExceptionHandler(); app.UseStatusCodePages(); app.MapSvrntyCommands(); app.MapSvrntyQueries(); ``` ### Custom Error Handling ```csharp app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = StatusCodes.Status500InternalServerError; context.Response.ContentType = "application/json"; var exceptionHandlerFeature = context.Features.Get(); var exception = exceptionHandlerFeature?.Error; var problem = new { type = "https://tools.ietf.org/html/rfc7231#section-6.6.1", title = "An error occurred", status = 500, detail = app.Environment.IsDevelopment() ? exception?.Message : "An internal error occurred" }; await context.Response.WriteAsJsonAsync(problem); }); }); ``` ## Environment-Specific Configuration ### Development ```csharp if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); // Allow any CORS app.UseCors(policy => { policy.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader(); }); } ``` ### Production ```csharp if (app.Environment.IsProduction()) { app.UseExceptionHandler("/error"); app.UseHsts(); app.UseHttpsRedirection(); // Strict CORS app.UseCors("ProductionCorsPolicy"); } ``` ## Complete Example ```csharp var builder = WebApplication.CreateBuilder(args); // CQRS services builder.Services.AddSvrntyCQRS(); builder.Services.AddDefaultCommandDiscovery(); builder.Services.AddDefaultQueryDiscovery(); // Authentication & Authorization builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = builder.Configuration["Auth:Authority"]; options.Audience = builder.Configuration["Auth:Audience"]; }); builder.Services.AddAuthorization(options => { options.AddPolicy("AdminOnly", policy => policy.RequireRole("Admin")); }); // CORS builder.Services.AddCors(options => { options.AddPolicy("AllowFrontend", policy => { policy.WithOrigins(builder.Configuration["Frontend:Url"]) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials(); }); }); // Rate Limiting builder.Services.AddRateLimiter(options => { options.AddFixedWindowLimiter("api", limiterOptions => { limiterOptions.PermitLimit = 100; limiterOptions.Window = TimeSpan.FromMinutes(1); }); }); // Health Checks builder.Services.AddHealthChecks() .AddDbContextCheck(); // Swagger builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Middleware Pipeline if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } else { app.UseHsts(); app.UseHttpsRedirection(); } app.UseCors("AllowFrontend"); app.UseAuthentication(); app.UseAuthorization(); app.UseRateLimiter(); // Health Checks app.MapHealthChecks("/health"); // CQRS Endpoints app.MapSvrntyCommands("v1/commands").RequireRateLimiting("api"); app.MapSvrntyQueries("v1/queries").RequireRateLimiting("api"); app.Run(); ``` ## Best Practices ### ✅ DO - Configure authentication and authorization - Use HTTPS in production - Implement rate limiting - Enable CORS appropriately - Configure request size limits - Use health checks - Log requests in production - Enable compression - Use environment-specific settings ### ❌ DON'T - Don't allow any CORS in production - Don't skip authentication - Don't expose detailed errors in production - Don't use unlimited request sizes - Don't skip rate limiting - Don't ignore health checks ## See Also - [HTTP Integration Overview](README.md) - [Endpoint Mapping](endpoint-mapping.md) - [Naming Conventions](naming-conventions.md) - [Swagger Integration](swagger-integration.md) - [HTTP Troubleshooting](http-troubleshooting.md)