System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead. means your code read the request body with a blocking call, typically new StreamReader(request.Body).ReadToEnd(). Kestrel refuses blocking reads by default. Await ReadToEndAsync() instead and the error is gone.

Make the handler async, put the reader in a using, and call await reader.ReadToEndAsync(). Turning on AllowSynchronousIO also silences the exception, but it is an escape hatch for libraries that cannot read asynchronously, not a fix for your own code.

The error

fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
      System.InvalidOperationException: Synchronous operations are disallowed. Call ReadAsync or set AllowSynchronousIO to true instead.
         at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpRequestStream.Read(Byte[] buffer, Int32 offset, Int32 count)
         at System.IO.StreamReader.ReadBuffer()
         at System.IO.StreamReader.ReadToEnd()
         at Program.<>c.<<Main>$>b__0_0(HttpRequest request) in ...\SyncLab\Program.cs:line 6
         at lambda_method1(Closure, Object, HttpContext)
         at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

The path was shortened; nothing else changed. The client received HTTP/1.1 500 Internal Server Error with the same stack trace as plain text, because the app ran in the Development environment.

Why it happens

This was the endpoint, a webhook that wants the raw body as a string:

app.MapPost("/webhook", (HttpRequest request) =>
{
    var body = new StreamReader(request.Body).ReadToEnd();
    return Results.Ok(new { received = body.Length });
});

ReadToEnd() calls the synchronous Stream.Read on Kestrel's request stream, and the stack trace shows that stream throwing on the first read. The bytes of a request body arrive over the network; a synchronous read would park a thread-pool thread until they come. Kestrel's AllowSynchronousIO option decides whether that is allowed, it covers both the request and the response, and it is off by default. The reason is spelled out on the Kestrel options page: "A large number of blocking synchronous I/O operations can lead to thread pool starvation, which makes the app unresponsive." Every request that blocks holds a thread that the other requests needed.

The fix

app.MapPost("/webhook", async (HttpRequest request) =>
{
    using var reader = new StreamReader(request.Body);
    var body = await reader.ReadToEndAsync();
    return Results.Ok(new { received = body.Length });
});

The same request now succeeds:

curl.exe -s -i -X POST -H 'Content-Type: application/json' --data-binary '@body.json' http://127.0.0.1:15610/webhook
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"received":56}

The escape hatch is one line in Program.cs, builder.WebHost.ConfigureKestrel(options => options.AllowSynchronousIO = true);. With it, the original synchronous endpoint returned the same 200. Microsoft's advice is to enable it only when a library you cannot change does not support asynchronous I/O, and that is the right bar: if the blocking call is in your own code, make it asynchronous. The option is server-wide, so one convenient line also permits every other blocking read and write in the app.

How it was reproduced

An empty project from dotnet new web -n SyncLab on .NET SDK 10.0.402 with the ASP.NET Core 10.0.13 runtime, no packages. The endpoint above was started with dotnet run --project SyncLab --urls http://127.0.0.1:15610 and called with curl.exe 8.21.0, posting a 56-byte JSON file. The first run produced the exception and a 500; the AllowSynchronousIO run and the ReadToEndAsync run each returned 200 with {"received":56}.

Frequently asked

How do I fix Synchronous operations are disallowed in ASP.NET Core?
Replace the blocking call with its async version and await it. For a request body read with StreamReader, make the endpoint async and call await reader.ReadToEndAsync() instead of ReadToEnd().
Is it safe to set AllowSynchronousIO to true?
It works, but Microsoft warns that many blocking synchronous I/O operations can starve the thread pool and make the app unresponsive. Enable it only for a library that cannot do asynchronous I/O, and prefer fixing your own code.
Why does ReadToEnd work in a console app but not in ASP.NET Core?
A file or memory stream allows synchronous reads. Kestrel's request body stream does not, because the bytes come from the network and a blocking read would hold a thread-pool thread until they arrive.

More decoded errors in the Fixes category. For why a blocked thread matters at all, see async/await Explained with a Coffee Shop.