ASP.Net Core middleware cannot set status code on exception because "response has already started"

Since this is the top search result on Google, I might as well tell new comers how I came up with this error. I was trying to use this answer by zipping files and downloading them (streaming) to the client. I returned return Ok() at the end of the actual controller action. I needed to return return new EmptyResult()


Just to weigh in here: I received this error from a controller that handled WebSocket connections. When the WebSocket connection was closed (user closes browser tab), this exception got thrown: System.InvalidOperationException: StatusCode cannot be set because the response has already started. Note also that that controller responsible for handling the WebSocket connection is nowhere to be found in the stacktrace:

System.InvalidOperationException: StatusCode cannot be set because the response has already started.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ThrowResponseAlreadyStartedException(String value)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_StatusCode(Int32 value)
   at Microsoft.AspNetCore.Mvc.StatusCodeResult.ExecuteResult(ActionContext context)
   at Microsoft.AspNetCore.Mvc.ActionResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context)
   at MyApp.Middleware.MyAppNotFoundHandlerMiddleware.Invoke(HttpContext context) in C:\Proj\MyApp\Middleware\MyAppNotFoundHandlerMiddleware.cs:line 24
   at MyApp.Middleware.MyAppExceptionHandlerMiddleware.Invoke(HttpContext context) in C:\Proj\MyApp\Middleware\MyAppExceptionHandlerMiddleware.cs:line 26
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.ProcessRequests[TContext](IHttpApplication`1 application)

Here's the controller action where it went wrong:

[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> Get()
{
    if (HttpContext.WebSockets.IsWebSocketRequest)
    {
        var socket = await HttpContext.WebSockets.AcceptWebSocketAsync();
        Clients.Add(socket);
        await WaitForClose(HttpContext, socket);
    }
    return Ok();
}

And as mentioned by the other answers, the culprit is the return Ok(). This statement is executed when the socket closes, but by then, the HTTP connection has long been closed.

I was using the NuGet package Microsoft.AspNetCore.WebSockets version 2.1.0.


I had this error thrown by my custom middleware, but you can check if the 'response has already started' by checking it:

    if (!context.Response.HasStarted)
        { ... }

Full code:

    private Task HandleExceptionAsync(HttpContext context, Exception ex)
    {
        if (!context.Response.HasStarted)
        {
            string result;

            context.Response.StatusCode = StatusCodes.Status500InternalServerError;
            result = JsonConvert.SerializeObject(new { error = "An error has occured" });
            _logger.LogError(ex, CreateErrorMessage(context));              

            context.Response.ContentType = "application/json";
            return context.Response.WriteAsync(result);
        }
        else
        {
            return context.Response.WriteAsync(string.Empty);
        }
    }