33

Related: Modify static file response in ASP.NET Core

However, I do not understand why the following code works when my business logic throws one of my custom exceptions like UnprocessableException:

try
{
    await next.Invoke(context);
}
catch (UnprocessableException uex)
{
    Logger.Warn(uex);
    context.Response.StatusCode = 422;
    var responseContent = JsonConvert.SerializeObject(new { uex.Message });
    await context.Response.WriteAsync(responseContent);
}
// more specific exceptions resulting in HTTP 4xx status

but when a totally unexpected IndexOutOfRangeException is caught by the last catch block in the chain

catch (Exception ex)
{
    Logger.Error(ex);
    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
    var responseContent = env.IsDevelopment()
                              ? JsonConvert.SerializeObject(new { ex.Message, ex.StackTrace })
                              : JsonConvert.SerializeObject(new { Message = "An internal error occured" });
    await context.Response.WriteAsync(responseContent);
}

this exception is thrown when trying to set the status code:

System.InvalidOperationException: StatusCode cannot be set, response has already started.
   bei Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.ThrowResponseAlreadyStartedException(String value)
   bei Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.set_StatusCode(Int32 value)
   bei Microsoft.AspNetCore.Server.Kestrel.Internal.Http.Frame.Microsoft.AspNetCore.Http.Features.IHttpResponseFeature.set_StatusCode(Int32 value)
   bei Microsoft.AspNetCore.Http.Internal.DefaultHttpResponse.set_StatusCode(Int32 value)
   bei Anicors.Infrastructure.Middlewares.ScopeMiddleware.<Invoke>d__5.MoveNext()
Marc Wittke
  • 2,991
  • 2
  • 30
  • 45
  • Does https://github.com/wesamco/ASP.Net-Core-GraphQL-Middleware/issues/1 or https://github.com/aspnet/HttpAbstractions/issues/686 or http://cknaap.blogspot.com.au/2016/07/aspnet-core-middleware-pipeline-status.html help? – mjwills Aug 14 '17 at 13:02
  • 1
    Please learn how to format your code. The triple-backslash Markdown syntax doesn't give you proper code block formatting. – DavidG Aug 14 '17 at 13:06
  • @DavidG looks perfect on my side: https://imgur.com/a/PLO7u. What do you mean by triple backslash? – Marc Wittke Aug 14 '17 at 13:11
  • @mjwills: don't think so, it's the last middleware in the pipeline, no other "Next" in play – Marc Wittke Aug 14 '17 at 13:11
  • @MarcWittke It looks perfect because I've just fixed it! – DavidG Aug 14 '17 at 13:13
  • @DavidG, sounds reasonable :) now I know what you mean, thanks! – Marc Wittke Aug 14 '17 at 13:14
  • If the first piece of code works, why are you getting an exception? Don't focus on the fact that it fails because you try to set the status code (twice), focus on which exception you got, from where, and why, that made it end up in that exception handler. If your program has already started serving the response before it throws that exception then the exception you're looking at here is correct, you can no longer set the status code, but the exception that triggered this is what you should look into. Since you're logging, look in your logs. – Lasse V. Karlsen Aug 14 '17 at 14:44
  • @Lasse see my updated description. It's all about Exception handling, those two catch blocks are for the same try-block. My custom exceptions are handled fine, resulting in HTTP 422 (or respective), but catching a bare Exception at the end won't let me set the status to 500. I'm not setting the status twice. It's either...or. – Marc Wittke Aug 14 '17 at 15:52
  • If these two are for the same try block then only the last one will execute so in this case the code in the first catch block shouldn't be involved. In any case, the framework object says the response has started so something somewhere is doing it. – Lasse V. Karlsen Aug 14 '17 at 15:54
  • Exactly that's my question. Who is doing this where? And why is there difference when it comes to different exceptions? – Marc Wittke Aug 14 '17 at 15:56

8 Answers8

58

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()

CularBytes
  • 9,924
  • 8
  • 76
  • 101
  • 1
    Thank you for the hint! I returned the same EmptyResult, and that showed my error. I was trying to print to the console to debug some stuff, and found Response.WriteAsync(); from somewhere on the internet. I pasted it to the start of my method, and that's why I was getting the error "System.InvalidOperationException: StatusCode cannot be set because the response has already started." – Dan Bitter May 11 '19 at 16:03
  • Another upvote for `return new EmptyResult()`. I had the following throw the same exception: `dataStream.WriteToStream(Response.Body); return Ok();` Replaced the final `return Ok()` with `return new EmptyResult()`, all good now. – abkonsta Apr 02 '20 at 12:58
  • 3
    EmptyResult really just saved me a whole lot of trouble! My sincere gratitude. – Michael K Jul 30 '20 at 23:02
  • This answer helped me to solve this problem when handing WebSocket connections on my controller action. – Gerardo Contijoch May 03 '22 at 19:10
14

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);
        }
    }
Hypenate
  • 1,907
  • 3
  • 22
  • 38
  • I noticed that neither the method is async nor it awaits. I am using similar approach but with async and await without returning anything. Does it make any difference? – devanalyst Oct 16 '21 at 15:54
8

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.

J.P.
  • 5,567
  • 3
  • 25
  • 39
3

Oh, well, I was investigating further and while trying to reproduce the case more isolated I found the root cause.

But first some history: I've seen these errors then and when in production, but never was able to reproduce it. Now I am developing another feature and due to an error in my database structure on my development machine this error happens on every request using a decently joined query. So I thought, hey, that's the moment to resolve this issue... but it ended up here.

However, trying to isolate it more, I made an action just throwing a NotImplementedException in my face. And guess what: it works as expected. HTTP 500, no "StatusCode cannot be set, response has already started".

What's the difference? The difference is, that my other failing controller returns this:

IQueryable<MySearchRecords> searchResult = service.Search(/*snipped boring stuff*/);
var result = DataSourceLoader.Load(searchResult, loadOptions);
return Ok(result);

while DataSourceLoader is a .net class to support DevExpress' DevExtreme JS Framework. It turns out, that result is object, because it returns either a plain array or a wrapping type that also provides some metadata (e.g. for paging and stuff). In my case it applies some Take and Skip but: does not enumerate the search result but returns an IQueryable<>! So enumerating is not done earlier than during rendering the result to JSON. That's why I see the InvalidOperationException above in this special case, but not when throwing it directly from the controller.

Nevertheless, it shows that my exception handling is not working as expected in all cases. I've read that you can replace the whole response stream to avoid this issue, but this has some downsides. So what would be the right way of handling such a situation? I'd like to have the HTTP 500 with my custom JSON content anyway.

Marc Wittke
  • 2,991
  • 2
  • 30
  • 45
  • 1
    as you are using IQueryable not only enumerating but fetching data is made while rendering – Eugen Kotov Mar 03 '18 at 10:59
  • @EugenKotov you should add as an answer, as passing anything deferred (that could throw) into any ActionResult factory will exhibit this issue. Worse still it will return a success code with an unreadable body as far as I can see. – Yoztastic Oct 12 '18 at 12:26
1

The solution is quite simple. Once you write a response you should not pass the context to the request delegate; as shown in the below example.

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                string header = "X-API-KEY";

                if(!context.Request.Headers.TryGetValue(header, out var extractedApiKey))
                {
                    await HandleError(context, "Missing X-API-KEY Header");
                }
                else
                {
                    string key = context.Request.Headers[header];
                    var appSettings = context.RequestServices.GetRequiredService<IConfiguration>();
                    string apiKey = appSettings.GetValue<string>("YUi:APIKEY");

                    if (!key.Equals(apiKey))
                    {
                        await HandleError(context, "API-KEYs Don't Match");
                    }
                    else
                    {
                        await _next(context);
                    }
                }

                
            }
            catch(Exception ex)
            {
                await HandleError(context, ex.Message);
            }

        }


        private static Task HandleError(HttpContext context, string ex)
        {
            
            HttpStatusCode code = HttpStatusCode.InternalServerError; // 500 if unexpected


            string result = JsonConvert.SerializeObject(new { error = ex });

            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)code;

            return context.Response.WriteAsync(result);

        }

Ensure that RequestDelegate doesn't get the context in response status.

NOTE:

As soon as you set the StatusCode the context changes to Response.

John Nyingi
  • 951
  • 1
  • 16
  • 36
  • 1
    not sure if what u provided is what to do or what not to do? Also I don't see any delegate there – Markus Sep 06 '21 at 13:27
1

I was able to resolve this error by taking code that was creating problems and moving it to inside app.UseStatusCodePages; see longer answer here: https://stackoverflow.com/a/71652771/4009972

app.UseStatusCodePages((StatusCodeContext statusCodeContext) =>
{
    var context = statusCodeContext.HttpContext;
    if (context.Response.StatusCode == 401)
    {
        context.Response.ContentType = _applicationJsonMediaHeader;
        return context.Response.Body.WriteAsync(_serializedUnauthorizedError).AsTask();
    }

    return Task.CompletedTask;
});
codeMonkey
  • 4,134
  • 2
  • 31
  • 50
0

In my case, I was trying to print to the console to debug some stuff, and found Response.WriteAsync(); from somewhere on the internet. I pasted it to the top of my method, and that's why I was getting the error "System.InvalidOperationException: StatusCode cannot be set because the response has already started."

Removing the Response.WriteAsync("test") method solved my issue! The simplest things ¯_(ツ)_/¯

Thanks to CularBytes' suggestion of return new EmptyResult(), which printed my WriteAsync("test") and exposed my mistake.

Dan Bitter
  • 229
  • 4
  • 12
0

That would depend on your situation, but for me, as I was implementing a IAsyncExceptionFilter, setting

context.ExceptionHandled = true;

did the trick.

The whole code would look like:

 context.HttpContext.Response.Clear();
 context.HttpContext.Response.StatusCode = 500;
 context.HttpContext.Response.ContentType = MediaTypeNames.Application.Json;
 await context.HttpContext.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(
        JsonConvert.SerializeObject(new
        {
            Error = "ERROR",
            Message =  "MESSAGE",
            OperationId = Guid.NewGuid()
        })));
 context.ExceptionHandled = true;

Hope it helps.

Tiaraju
  • 111
  • 1
  • 2