IronAlpine.Web
3.0.0
dotnet add package IronAlpine.Web --version 3.0.0
NuGet\Install-Package IronAlpine.Web -Version 3.0.0
<PackageReference Include="IronAlpine.Web" Version="3.0.0" />
<PackageVersion Include="IronAlpine.Web" Version="3.0.0" />
<PackageReference Include="IronAlpine.Web" />
paket add IronAlpine.Web --version 3.0.0
#r "nuget: IronAlpine.Web, 3.0.0"
#:package IronAlpine.Web@3.0.0
#addin nuget:?package=IronAlpine.Web&version=3.0.0
#tool nuget:?package=IronAlpine.Web&version=3.0.0
IronAlpine.Web
ASP.NET Core defaults, Swagger, health checks, middleware, and error handling.
- Target Frameworks: net9.0, net10.0
- Dependencies: ASP.NET Core, Swashbuckle, YARP
- Package Size: ~100 KB
- Replaces: 2 v2 packages (Web.AspNetCore, Framework.Abstractions)
What It Is
IronAlpine.Web provides ASP.NET Core infrastructure:
- Automatic Swagger — OpenAPI documentation without configuration
- Health Checks — /health endpoint with readiness/liveness
- CORS Configuration — sensible defaults + customization
- Error Handling — automatic ProblemDetails for exceptions
- Middleware Stack — request logging, correlation tracking
- YARP Integration — reverse proxy for API gateway patterns
Installation
dotnet add package IronAlpine.Web
Quick Setup
var builder = WebApplication.CreateBuilder(args);
// Services
builder.Services
.AddIronAlpineWebDefaults(builder.Configuration, "TimeOffService")
.AddIronAlpineHealthChecks()
.AddControllers();
// Middleware
var app = builder.Build();
app.UseIronAlpineWebDefaults();
app.MapControllers();
app.MapIronAlpineHealthChecks();
await app.RunAsync();
Configuration
{
"IronAlpine": {
"Web": {
"AspNetCore": {
"ServiceName": "TimeOffService",
"ErrorCodePrefix": "IA",
"CorsPolicyName": "IronAlpineDefaultCorsPolicy",
"AllowedOrigins": [
"http://localhost:3000",
"http://localhost:5173",
"https://app.example.com"
],
"EnableSwagger": true,
"SwaggerRoutePrefix": "api-docs"
}
}
}
}
| Setting | Default | Purpose |
|---|---|---|
ServiceName |
- | Service identifier (required) |
ErrorCodePrefix |
"IA" | Error code prefix (e.g., "IA-001") |
CorsPolicyName |
"IronAlpineDefaultCorsPolicy" | CORS policy name |
AllowedOrigins |
["localhost:*"] | Allowed CORS origins |
EnableSwagger |
true | Enable Swagger UI |
SwaggerRoutePrefix |
"api-docs" | Swagger endpoint path |
Automatic Features
Swagger Documentation
Configured automatically from controllers:
[ApiController]
[Route("api/[controller]")]
[Tags("TimeOff")]
public class TimeOffController : ControllerBase
{
/// <summary>
/// Get time off request by ID
/// </summary>
/// <param name="id">TimeOff ID</param>
/// <returns>TimeOff details</returns>
[HttpGet("{id}")]
[ProduceResponseType(typeof(TimeOffDetailsResponse), StatusCodes.Status200OK)]
[ProduceResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> Get(Guid id)
{
var response = await _mediator.Send(new GetTimeOffByIdQuery { TimeOffId = id });
return Ok(response);
}
}
// Swagger automatically generated:
// ✅ Endpoint: GET /api/timeoff/{id}
// ✅ Parameters: id (Guid, required)
// ✅ Responses: 200 (TimeOffDetailsResponse), 404
// ✅ Tag: TimeOff
// ✅ Summary & description from XML comments
Access at: /api-docs (or custom SwaggerRoutePrefix)
Health Checks
Automatic readiness and liveness endpoints:
// Automatically registered checks:
// ✅ Database connectivity
// ✅ Kafka producer
// ✅ Redis (if configured)
// ✅ Memory usage
// ✅ Startup task completion
// Endpoints:
// GET /health/ready → Readiness probe
// GET /health/live → Liveness probe
// GET /health → Full health status
Response Example:
{
"status": "Healthy",
"checks": {
"Database": {
"status": "Healthy",
"description": "TimeOffContext"
},
"Kafka": {
"status": "Healthy",
"description": "Connected to kafka:9092"
},
"Redis": {
"status": "Healthy",
"description": "Connected to localhost:6379"
}
},
"totalDuration": "00:00:00.0123456"
}
CORS Policy
Automatically configured from settings:
{
"IronAlpine": {
"Web": {
"AspNetCore": {
"AllowedOrigins": [
"http://localhost:3000",
"https://app.example.com"
]
}
}
}
}
Used in controller:
[ApiController]
[Route("api/[controller]")]
[Authorize] // Only authenticated requests
public class TimeOffController : ControllerBase
{
// CORS headers automatically added if origin in AllowedOrigins
[HttpGet]
[AllowAnonymous] // Override authorization
public async Task<IActionResult> List()
{
// Returns with Access-Control-Allow-Origin header
}
}
Error Handling (ProblemDetails)
Automatic conversion of exceptions to RFC 7807 ProblemDetails:
// When handler throws
public async Task<TimeOffDetailsResponse> Handle(
GetTimeOffByIdQuery request,
CancellationToken ct)
{
var timeOff = await _repository.GetByIdAsync(request.TimeOffId, ct);
if (timeOff == null)
throw new DomainException("TimeOff not found"); // ← Thrown
return Map(timeOff);
}
// Client receives
{
"type": "https://api.example.com/errors/domain-error",
"title": "Domain Rule Violation",
"status": 400,
"detail": "TimeOff not found",
"traceId": "0HN48G5HMIB62:00000001"
}
Built-in error codes:
ValidationException→ 400 Bad RequestDomainException→ 400 Bad RequestNotFoundException→ 404 Not FoundForbiddenException→ 403 ForbiddenUnauthorizedException→ 401 UnauthorizedConflictException→ 409 ConflictUnhandled Exception→ 500 Internal Server Error
Middleware Stack
Automatically configured in UseIronAlpineWebDefaults():
Incoming Request
↓
Exception Handler
↓
Request Logging (Serilog)
↓
CORS Middleware
↓
Authentication
↓
Authorization
↓
Your Routes
↓
Outgoing Response
Custom Controllers
Simple Example
[ApiController]
[Route("api/[controller]")]
public class TimeOffController : ControllerBase
{
private readonly ISender _mediator;
public TimeOffController(ISender mediator)
{
_mediator = mediator;
}
[HttpPost("request")]
[Authorize(Policy = "TimeOff.Request")]
public async Task<IActionResult> Request(RequestTimeOffCommand command)
{
await _mediator.Send(command);
return CreatedAtAction(nameof(Get), new { id = command.TimeOffId });
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(Guid id)
{
var response = await _mediator.Send(new GetTimeOffByIdQuery { TimeOffId = id });
return Ok(response);
}
[HttpPost("{id}/approve")]
[Authorize(Policy = "TimeOff.Approve")]
public async Task<IActionResult> Approve(Guid id, ApproveTimeOffCommand command)
{
command.TimeOffId = id;
await _mediator.Send(command);
return Ok();
}
}
With Validation
[HttpPost("request")]
public async Task<IActionResult> Request(RequestTimeOffCommand command)
{
// Automatic validation via ValidationBehavior
// If invalid: 400 Bad Request with validation errors
var result = await _mediator.Send(command);
return Ok(result);
}
// Returns
{
"type": "https://api.example.com/errors/validation-error",
"title": "Validation Failed",
"status": 400,
"errors": {
"StartDate": ["Start date must be in the future"],
"EndDate": ["End date must be after start date"]
}
}
Advanced Configuration
Custom CORS Policy
services.AddCors(options =>
{
options.AddPolicy("CustomPolicy", builder =>
{
builder
.WithOrigins(configuration["AllowedOrigins"]?.Split(",") ?? Array.Empty<string>())
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithExposedHeaders("X-Pagination");
});
});
Custom Error Responses
// Extend error response format
public class CustomProblemDetails : ProblemDetails
{
public string? Code { get; set; }
public DateTime Timestamp { get; set; }
public Dictionary<string, string[]>? Errors { get; set; }
}
// Configure in DI
services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = context =>
{
var details = new CustomProblemDetails
{
Code = $"IA-{context.HttpContext.Response.StatusCode}",
Timestamp = DateTime.UtcNow,
TraceId = context.HttpContext.TraceIdentifier
};
};
});
Best Practices
✅ DO
// 1. Use controller tags for Swagger grouping
[Tags("TimeOff")]
public class TimeOffController : ControllerBase { }
// 2. Use XML comments for documentation
/// <summary>
/// Approve a time off request
/// </summary>
/// <remarks>
/// Only managers can approve requests
/// </remarks>
[HttpPost("{id}/approve")]
public async Task<IActionResult> Approve(Guid id) { }
// 3. Specify response types
[ProduceResponseType(StatusCodes.Status200OK)]
[ProduceResponseType(StatusCodes.Status400BadRequest)]
[ProduceResponseType(StatusCodes.Status404NotFound)]
[HttpGet("{id}")]
public async Task<IActionResult> Get(Guid id) { }
// 4. Use consistent naming
[Route("api/[controller]")] // Plural, lowercase
public class TimeOffController : ControllerBase { }
❌ DON'T
// 1. Don't return raw exceptions
throw new Exception("Something bad"); // Returns 500
// 2. Don't forget response types
[HttpPost]
public async Task<IActionResult> Create(TimeOff timeOff) { } // Type unclear
// 3. Don't skip authorization
[HttpPost("{id}/delete")]
public async Task<IActionResult> Delete(Guid id) { } // Anyone can delete!
// 4. Don't use action names in routes
[Route("TimeOff/RequestTimeOff")] // Use [controller] placeholder
public class TimeOffController : ControllerBase { }
Troubleshooting
Q: Swagger not appearing?
A: Ensure (1) EnableSwagger: true in config, (2) MapOpenApi() called, (3) access /api-docs.
Q: Health checks not working?
A: Check (1) AddIronAlpineHealthChecks() called, (2) dependencies (DB, Kafka) accessible, (3) /health endpoint accessible.
Q: CORS errors in frontend?
A: Verify (1) frontend origin in AllowedOrigins, (2) credentials: true if needed, (3) correct HTTP method.
Q: Validation errors not in response?
A: Ensure (1) ValidationBehavior enabled, (2) validators defined, (3) check request content type.
Examples
See IRONALPINE_V3_DOCUMENTATION.md for detailed examples.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- IronAlpine.Kernel (>= 3.0.0)
- IronAlpine.Observability (>= 3.0.0)
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer (>= 5.1.0)
- Microsoft.EntityFrameworkCore (>= 9.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.7)
- Swashbuckle.AspNetCore (>= 9.0.3)
- Yarp.ReverseProxy (>= 2.2.0)
-
net9.0
- IronAlpine.Kernel (>= 3.0.0)
- IronAlpine.Observability (>= 3.0.0)
- Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer (>= 5.1.0)
- Microsoft.EntityFrameworkCore (>= 9.0.7)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 9.0.7)
- Microsoft.Extensions.Hosting.Abstractions (>= 9.0.7)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.7)
- Swashbuckle.AspNetCore (>= 9.0.3)
- Yarp.ReverseProxy (>= 2.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 131 | 7/1/2026 |
Stable mediator release with request/response, notification publish strategies, streaming, and dependency injection integration.