Mvc: BUG? Accessing TempData prevent response to have content on error.

Created on 28 Jul 2017  路  10Comments  路  Source: aspnet/Mvc

While I was debugging an issue on my app, I found a really weird behavior.

Suppose this code (with DeveloperErrorPage enabled):

[HttpGet("/wtf")]
public async Task<IActionResult> WTF()
{
    // var x = TempData.ContainsKey("SomeKey");
    await Task.Run(() => throw new Exception("error"));
    return Content("this will never hit.");
}

The access to TempData is commented out, so when hitting the /wtf endpoint you get a nice error page.
If you uncomment the TempData.ContainsKey line, the response will still be error 500 but with no content at all.

image

I would love to know why is this happening, something is writing to the response? and if it's indeed a bug well, just wanted to let you know :)

3 - Done bug

Most helpful comment

Hey @Bartmax thanks for the report. This is definitely an issue on our end.

Your workaround is a fine alternative for the time being:
```C#
app.UseSession();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

--------------------------------------------

#### Details
I've investigated and this repros when using the session temp data provider in both 1.1 and 2.0 (note that 2.0 defaults to cookie temp data provider so less of a problem there). The reason is as follows:

Typically a user will have an app in the following form:
```C#
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseMvc();

The issue that happens is our SaveTempDataFilter ends up exploding the request due to its configuration of context.Response.OnStarting. The broken flow for this issue is as follows:

  1. Request comes in and TempData is queried
  2. Exception is thrown (nothing has been written to the response body)
  3. UseMvc unwinds
  4. UseSession unwinds
  5. UseDeveloperExceptionPage catches the Exception created in MVC and attempts to render a View; this starts writing to the response and triggers SaveTempDataFilters saving of temp data.
  6. SessionStateTempDataProvider attempts to access session in order to save temp data. This explodes because UseSession has already unwound.
  7. Developer exception page fails to render the error page because another exception occurred in the process.

All 10 comments

Just tried this out with a default Mvc web application and I'm unable to reproduce. Both scenarios in screenshot result in the dev exception page.

image

If you could provide a simple repro I'd be more than happy to take a look.

@NTaylorMullen that's interesting! This is happening on a large project, so I will try to isolate and make a repro.

@NTaylorMullen ok, I could repro easily.

This repro the described behavior (error 500 with no content)

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
app.UseSession();

this works fine, error page is shown.

app.UseSession();
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}

so if app.UseSession() is after app.UseDeveloperExceptionPage(); you get empty error 500

Can you clarify why this happens? It's really hard to get correct middleware order. (I'm using 15+ middleware) and this side effects are non trivial to debug.

Uploaded the project to github (just in case)
https://github.com/Bartmax/MvcBugSessionRepro

Hey @Bartmax thanks for the report. This is definitely an issue on our end.

Your workaround is a fine alternative for the time being:
```C#
app.UseSession();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}

--------------------------------------------

#### Details
I've investigated and this repros when using the session temp data provider in both 1.1 and 2.0 (note that 2.0 defaults to cookie temp data provider so less of a problem there). The reason is as follows:

Typically a user will have an app in the following form:
```C#
if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
app.UseSession();
app.UseMvc();

The issue that happens is our SaveTempDataFilter ends up exploding the request due to its configuration of context.Response.OnStarting. The broken flow for this issue is as follows:

  1. Request comes in and TempData is queried
  2. Exception is thrown (nothing has been written to the response body)
  3. UseMvc unwinds
  4. UseSession unwinds
  5. UseDeveloperExceptionPage catches the Exception created in MVC and attempts to render a View; this starts writing to the response and triggers SaveTempDataFilters saving of temp data.
  6. SessionStateTempDataProvider attempts to access session in order to save temp data. This explodes because UseSession has already unwound.
  7. Developer exception page fails to render the error page because another exception occurred in the process.

Thanks for the detailed explanation @NTaylorMullen glad it helped!

@NTaylorMullen - any idea if this is an issue with the error handling middleware? Or is it more specific to the developer exception page?

Not an issue with either. This is an underlying design issue with how session registers itself as a feature and how our SaveTempDataFilter (when using a session temp data provider) ties into Response.OnStarting.

You could encounter this issue without having any error thrown as well. Basically, if nothing has been written to the response by the time the app.UseSession scope has ended our SaveTempDataFilter will end up exploding when Kestrel attempts to write headers.

@kichalla can you work on the fix for this? I recall that @NTaylorMullen has some ideas on how to fix.

Was this page helpful?
0 / 5 - 0 ratings