| name | ocelot |
| description | USE FOR: Building API gateways in .NET using Ocelot for microservices architectures. Use when you need request routing, load balancing, rate limiting, request aggregation, authentication forwarding, and service discovery across multiple downstream services behind a unified entry point.
DO NOT USE FOR: Simple single-service reverse proxying (use YARP), high-throughput proxying requiring custom transforms and session affinity (use YARP), or service mesh features like mTLS and distributed tracing injection (use Istio, Linkerd, or Dapr).
|
| license | MIT |
| metadata | {"displayName":"Ocelot","author":"Tyler-R-Kendrick","version":"1.0.0"} |
| compatibility | ["claude","copilot","cursor"] |
| references | [{"title":"Ocelot Documentation","url":"https://ocelot.readthedocs.io/"},{"title":"Ocelot GitHub Repository","url":"https://github.com/ThreeMammals/Ocelot"},{"title":"Ocelot NuGet Package","url":"https://www.nuget.org/packages/Ocelot"}] |
Ocelot
Overview
Ocelot is an open-source API gateway for .NET that sits between client applications and downstream microservices, providing unified routing, load balancing, authentication, rate limiting, caching, request aggregation, and service discovery. Ocelot is configured primarily through JSON, mapping upstream (client-facing) routes to downstream (service) endpoints. It integrates with ASP.NET Core's middleware pipeline, supports Consul and Eureka for service discovery, and can forward authentication tokens from the gateway to downstream services. Ocelot is well suited for microservices architectures that need a lightweight gateway without deploying a separate infrastructure component like Kong or AWS API Gateway.
Basic Gateway Setup
Configure Ocelot in an ASP.NET Core application with route definitions.
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
builder.Services.AddOcelot(builder.Configuration);
var app = builder.Build();
await app.UseOcelot();
app.Run();
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products/{everything}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{ "Host": "product-service", "Port": 443 }
],
"UpstreamPathTemplate": "/products/{everything}",
"UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer"
},
"RateLimitOptions": {
Authentication and Authorization
Forward JWT tokens to downstream services and enforce policies at the gateway.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("Bearer", options =>
{
options.Authority = "https://identity.mysite.com";
options.Audience = "api-gateway";
options.RequireHttpsMetadata = true;
});
builder.Services.AddOcelot(builder.Configuration);
var app = builder.Build();
app.UseAuthentication();
await app.UseOcelot();
app.Run();
{
"DownstreamPathTemplate": "/api/admin/{everything}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{ "Host": "admin-service", "Port": 443 }
],
"UpstreamPathTemplate": "/admin/{everything}",
"UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],
"AuthenticationOptions": {
"AuthenticationProviderKey": "Bearer"
},
"RouteClaimsRequirement": {
"role": "admin"
}
}
Request Aggregation
Combine responses from multiple downstream services into a single response.
{
"Routes": [
{
"DownstreamPathTemplate": "/api/users/{userId}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{ "Host": "user-service", "Port": 443 }
],
"UpstreamPathTemplate": "/users/{userId}",
"UpstreamHttpMethod": [ "Get" ],
"Key": "user"
},
{
"DownstreamPathTemplate": "/api/orders?userId={userId}",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
using Ocelot.Middleware;
using Ocelot.Multiplexer;
public class UserDashboardAggregator : IDefinedAggregator
{
public async Task<DownstreamResponse> Aggregate(List<HttpContext> responses)
{
var userResponse = await responses[0].Items
.DownstreamResponse().Content.ReadAsStringAsync();
var ordersResponse = await responses[1].Items
.DownstreamResponse().Content.ReadAsStringAsync();
var combined = $"{{\"user\": {userResponse}, \"orders\": {ordersResponse}}}";
var headers = new List<Header>
{
new("Content-Type", new[] { "application/json" })
};
return new DownstreamResponse(
new StringContent(combined),
System.Net.HttpStatusCode.OK,
headers,
"OK");
}
}
Service Discovery with Consul
Use Consul for dynamic service discovery instead of hardcoded hosts.
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using Ocelot.Provider.Consul;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);
builder.Services.AddOcelot(builder.Configuration)
.AddConsul();
var app = builder.Build();
await app.UseOcelot();
app.Run();
{
"Routes": [
{
"DownstreamPathTemplate": "/api/products/{everything}",
"DownstreamScheme": "https",
"UpstreamPathTemplate": "/products/{everything}",
"UpstreamHttpMethod": [ "Get" ],
"ServiceName": "product-service",
"LoadBalancerOptions": {
"Type": "LeastConnection"
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Scheme": "http",
"Host": "consul-server",
"Port": 8500
Ocelot vs Other API Gateways
| Feature | Ocelot | YARP | Kong | AWS API Gateway |
|---|
| Language | C# / .NET | C# / .NET | Lua / Go | Managed service |
| Configuration | JSON file | JSON / code | Admin API / YAML | Console / CloudFormation |
| Load balancing | Round-robin, least-conn | Round-robin, random, power-of-two | Ring balancer | ALB/NLB integration |
| Rate limiting | Built-in per-route | Custom middleware | Plugin-based | Built-in per-stage |
| Service discovery | Consul, Eureka | Custom providers | DNS, Consul | VPC Link, Cloud Map |
| Auth forwarding | JWT pass-through | Header transforms | Plugin-based | Lambda authorizer |
| Request aggregation | Built-in | Custom middleware | Not built-in | Not built-in |
| Caching | Built-in | Custom middleware | Plugin-based | Built-in |
| Complexity | Low | Low-medium | Medium-high | Medium |
Best Practices
-
Split route configuration into environment-specific files using AddJsonFile($"ocelot.{env.EnvironmentName}.json") so that downstream host addresses differ between development (localhost), staging (internal DNS), and production (service mesh addresses) without modifying shared route definitions.
-
Assign a unique Key property to routes used in aggregation and define aggregates referencing those keys, rather than building a custom controller that calls multiple services, because Ocelot executes aggregated requests in parallel and combines results with a single upstream response.
-
Enable rate limiting per route with RateLimitOptions specifying Period, Limit, and PeriodTimespan (the retry-after seconds), and set HttpStatusCode to 429 in GlobalConfiguration.RateLimitOptions, so that abusive clients receive standard rate-limit headers without downstream services being overwhelmed.
-
Use LoadBalancerOptions with LeastConnection for stateless services that have variable response times, and RoundRobin for services with consistent latency, listing multiple DownstreamHostAndPorts entries per route to distribute traffic without an external load balancer.
-
Configure AuthenticationOptions.AuthenticationProviderKey on routes that require authentication and register the corresponding JWT bearer scheme in Program.cs, so that Ocelot validates tokens at the gateway before forwarding requests, reducing load on downstream services that would otherwise validate tokens independently.
-
Use RouteClaimsRequirement on routes that need role-based access control (e.g., "role": "admin") to reject unauthorized requests at the gateway rather than in downstream services, centralizing authorization policy and preventing unauthenticated traffic from reaching internal services.
-
Enable Consul or Eureka service discovery for dynamic environments (Kubernetes, Docker Swarm) where downstream service addresses change frequently, setting ServiceName on each route instead of hardcoding DownstreamHostAndPorts, so the gateway resolves healthy instances from the service registry at request time.
-