IConfiguration vs IOptions NET
Synchronous and Asynchronous in .NET Core
Model Binding and Validation in ASP.NET Core
ControllerBase vs Controller in ASP.NET Core
ConfigureServices and Configure methods
IHostedService interface in .NET Core
ASP.NET Core request processing
| YARP (Yet Another Reverse Proxy) :👈 | 👉:Build taxi-booking application |
Production Ready YARP Setup |
A production-ready YARP setup is usually much more than
MapReverseProxy() and a couple of routes. In enterprise .NET
environments, YARP often acts as an API Gateway responsible for:
Gateway β βββ Program.cs βββ appsettings.json βββ Middleware β βββ CorrelationIdMiddleware.cs β βββ Extensions β βββ ServiceCollectionExtensions.cs β βββ Controllers
dotnet add package Yarp.ReverseProxy dotnet add package Microsoft.AspNetCore.RateLimiting dotnet add package AspNetCore.HealthChecks.UI.Client dotnet add package OpenTelemetry.Extensions.Hosting dotnet add package OpenTelemetry.Exporter.Console
This example has:
{
"ReverseProxy": {
"Routes": {
"users-route": {
"ClusterId": "users-cluster",
"Match": {
"Path": "/api/users/{**catch-all}"
}
},
"products-route": {
"ClusterId": "products-cluster",
"Match": {
"Path": "/api/products/{**catch-all}"
}
}
},
"Clusters": {
"users-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api1": {
"Address": "https://users-api-1/"
},
"api2": {
"Address": "https://users-api-2/"
}
}
},
"products-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"api1": {
"Address": "https://products-api-1/"
},
"api2": {
"Address": "https://products-api-2/"
}
}
}
}
}
}
A production startup configuration.
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(
builder.Configuration.GetSection("ReverseProxy"));
builder.Services.AddHealthChecks();
builder.Services.AddHttpContextAccessor();
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(
"api",
limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
});
});
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders =
ForwardedHeaders.XForwardedFor |
ForwardedHeaders.XForwardedProto;
});
var app = builder.Build();
app.UseForwardedHeaders();
app.UseRateLimiter();
app.UseHttpsRedirection();
app.MapHealthChecks("/health");
app.MapReverseProxy();
app.Run();
One of the most common production patterns.
Instead of every microservice validating JWT tokens:
Client | JWT | YARP Gateway | Validated | Microservices
Configure JWT:
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://identity.company.com";
options.Audience = "gateway-api";
});
builder.Services.AddAuthorization();
Enable:
app.UseAuthentication(); app.UseAuthorization();
Routes:
{
"Routes": {
"users-route": {
"ClusterId": "users-cluster",
"AuthorizationPolicy": "default",
"Match": {
"Path": "/api/users/{**catch-all}"
}
}
}
}
Critical for troubleshooting across services.
Request:
RequestId=ABC123
Must follow every API call.
Middleware:
public class CorrelationIdMiddleware
{
private readonly RequestDelegate _next;
public CorrelationIdMiddleware(
RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var correlationId =
context.Request.Headers["X-Correlation-ID"]
.FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Response.Headers["X-Correlation-ID"]
= correlationId;
await _next(context);
}
}
Register:
app.UseMiddleware<CorrelationIdMiddleware>();
Never log only:
logger.LogInformation("Request received");
Instead:
logger.LogInformation(
"Path:{Path} Correlation:{CorrelationId}",
context.Request.Path,
correlationId);
Good logs answer:
Modern production systems use distributed tracing.
builder.Services.AddOpenTelemetry()
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation();
tracing.AddHttpClientInstrumentation();
tracing.AddConsoleExporter();
});
Flow:
Gateway | | TraceId | User Service | Order Service | Payment Service
β Single trace across all services.
Backend services occasionally fail.
Use HttpClient resilience.
builder.Services
.AddHttpClient("proxy")
.AddStandardResilienceHandler();
Handles:
Every service should expose:
/health
Example:
app.MapHealthChecks("/health");
Response:
{
"status": "Healthy"
}
β Load balancers can remove unhealthy instances automatically.
Prevent abuse.
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter(
"gateway",
config =>
{
config.PermitLimit = 100;
config.Window = TimeSpan.FromMinutes(1);
});
});
Example:
100 requests/minute
After that:
429 Too Many Requests
Add security headers.
app.Use(async (context, next) =>
{
context.Response.Headers
.Append("X-Content-Type-Options", "nosniff");
context.Response.Headers
.Append("X-Frame-Options", "DENY");
context.Response.Headers
.Append("Referrer-Policy", "strict-origin");
await next();
});
Instead of hardcoding servers:
"Address": "https://10.1.5.23:5000"
Use service DNS:
"Address": "http://user-service/"
Architecture:
YARP | +--> user-service +--> order-service +--> payment-service
β Kubernetes resolves service addresses automatically.
Bad:
Gateway | App1 App2
with:
AddSession()
using in-memory store. User may hit different servers.
Use:
Redis SQL Distributed Cache
Wrong client IP.
app.UseForwardedHeaders();
is mandatory behind proxies.
Bad:
YARP | + Calculate Taxes + Create Orders
Gateway should focus on:
Business logic belongs to services.
A slow downstream service can consume all gateway threads.
Configure:
{
"HttpRequest": {
"ActivityTimeout": "00:00:30"
}
}
This gives you centralized security, routing, observability, and scalability while keeping individual microservices simple and focused on business logic.
| YARP (Yet Another Reverse Proxy) :👈 | 👉:Build taxi-booking application |