IronAlpine.Web 3.0.0

dotnet add package IronAlpine.Web --version 3.0.0
                    
NuGet\Install-Package IronAlpine.Web -Version 3.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="IronAlpine.Web" Version="3.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="IronAlpine.Web" Version="3.0.0" />
                    
Directory.Packages.props
<PackageReference Include="IronAlpine.Web" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add IronAlpine.Web --version 3.0.0
                    
#r "nuget: IronAlpine.Web, 3.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package IronAlpine.Web@3.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=IronAlpine.Web&version=3.0.0
                    
Install as a Cake Addin
#tool nuget:?package=IronAlpine.Web&version=3.0.0
                    
Install as a Cake Tool

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 Request
  • DomainException → 400 Bad Request
  • NotFoundException → 404 Not Found
  • ForbiddenException → 403 Forbidden
  • UnauthorizedException → 401 Unauthorized
  • ConflictException → 409 Conflict
  • Unhandled 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.