ASP.NET Core middleware that lets a client send many API calls in one HTTP request.
Mobile and game clients often need to fire several calls at once (load profile, inventory, settings…). Sent one by one, they cost a round trip each and can overlap on the server. With this middleware the client posts a single JSON array to /api/batch; the server runs each sub-request through the normal ASP.NET Core pipeline, one after the other, and returns all the responses together.
Each sub-request goes through the same routing, controllers, filters and authorization as a normal request, so existing endpoints need no changes.
var app = builder.Build();
app.UseBatchMiddleware(); // POST /api/batch
// or: app.UseBatchMiddleware(o => o.BatchPath = "/custom/batch");
app.MapControllers();
app.Run();Register it before routing, so that sub-requests run through the rest of the pipeline.
POST /api/batch
Content-Type: application/json
[
{ "method": "GET", "relativeUrl": "/api/profile?id=42" },
{ "method": "POST", "relativeUrl": "/api/inventory",
"body": { "itemId": 7 },
"additionalHeaders": [ { "key": "X-Request-Source", "value": "batch" } ] }
]- Headers of the outer request (for example
Authorization) are forwarded to every sub-request. additionalHeadersadds or overrides headers for a single sub-request.
[
{ "statusCode": 200, "headers": [ ... ], "body": { "id": 42, "name": "..." } },
{ "statusCode": 201, "headers": [ ... ], "body": { "itemId": 7 } }
]- JSON bodies are returned as objects, other content types as strings.
- If a sub-request throws, its entry is a
500and the rest of the batch still runs.
| Project | What it is |
|---|---|
BatchRequests.Middleware |
The middleware library (.NET 10) |
BatchRequests.Middleware.Tests |
xUnit integration tests on TestServer |
BatchRequests.Sample |
Minimal Web API using the middleware |
dotnet test- Sub-requests run sequentially, by design: it keeps their order and prevents overlapping writes.
- No limit on batch size: add one before exposing it publicly.
- Request bodies are sent as JSON.
The idea comes from a production need on a game backend, where clients sent bursts of concurrent calls that overlapped on the server. Batching them into one ordered request removed those conflicts.