diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs
index f430ba7b08..912c7c7131 100644
--- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs
+++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs
@@ -29,6 +29,36 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
///
public void OnResourceExecuting(ResourceExecutingContext context)
{
+ if (!context.HttpContext.Response.HasStarted)
+ {
+ context.HttpContext.Response.OnStarting((state) =>
+ {
+ var saveTempDataContext = (SaveTempDataContext)state;
+
+ // If temp data was already saved, skip trying to save again as the calls here would potentially fail
+ // because the session feature might not be available at this point.
+ // Example: An action returns NoContentResult and since NoContentResult does not write anything to
+ // the body of the response, this delegate would get executed way late in the pipeline at which point
+ // the session feature would have been removed.
+ object obj;
+ if (saveTempDataContext.HttpContext.Items.TryGetValue(TempDataSavedKey, out obj))
+ {
+ return TaskCache.CompletedTask;
+ }
+
+ SaveTempData(
+ result: null,
+ factory: saveTempDataContext.TempDataDictionaryFactory,
+ httpContext: saveTempDataContext.HttpContext);
+
+ return TaskCache.CompletedTask;
+ },
+ state: new SaveTempDataContext()
+ {
+ HttpContext = context.HttpContext,
+ TempDataDictionaryFactory = _factory
+ });
+ }
}
///
@@ -39,34 +69,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
///
public void OnResultExecuting(ResultExecutingContext context)
{
- context.HttpContext.Response.OnStarting((state) =>
- {
- var saveTempDataContext = (SaveTempDataContext)state;
-
- // If temp data was already saved, skip trying to save again as the calls here would potentially fail
- // because the session feature might not be available at this point.
- // Example: An action returns NoContentResult and since NoContentResult does not write anything to
- // the body of the response, this delegate would get executed way late in the pipeline at which point
- // the session feature would have been removed.
- object obj;
- if (saveTempDataContext.HttpContext.Items.TryGetValue(TempDataSavedKey, out obj))
- {
- return TaskCache.CompletedTask;
- }
-
- SaveTempData(
- saveTempDataContext.ActionResult,
- saveTempDataContext.TempDataDictionaryFactory,
- saveTempDataContext.HttpContext);
-
- return TaskCache.CompletedTask;
- },
- state: new SaveTempDataContext()
- {
- HttpContext = context.HttpContext,
- ActionResult = context.Result,
- TempDataDictionaryFactory = _factory
- });
}
///
@@ -78,7 +80,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
if (!context.HttpContext.Response.HasStarted)
{
SaveTempData(context.Result, _factory, context.HttpContext);
- context.HttpContext.Items.Add(TempDataSavedKey, true);
+ // If SaveTempDataFilter got added twice this might already be in there.
+ if (!context.HttpContext.Items.ContainsKey(TempDataSavedKey))
+ {
+ context.HttpContext.Items.Add(TempDataSavedKey, true);
+ }
}
}
@@ -94,7 +100,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
private class SaveTempDataContext
{
public HttpContext HttpContext { get; set; }
- public IActionResult ActionResult { get; set; }
public ITempDataDictionaryFactory TempDataDictionaryFactory { get; set; }
}
}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs
index 0f88652357..ab9c284483 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs
@@ -162,6 +162,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal($"Foo 10 3 10/10/2010 00:00:00 {testGuid.ToString()}", body);
}
+ [Fact]
+ public async Task ResponseWrite_DoesNotCrashSaveTempDataFilter()
+ {
+ // Arrange
+ var nameValueCollection = new List>
+ {
+ new KeyValuePair("Name", "Jordan"),
+ };
+ var content = new FormUrlEncodedContent(nameValueCollection);
+
+ // Act, checking it didn't throw
+ var response = await Client.GetAsync("/TempData/SetTempDataResponseWrite");
+ }
+
[Fact]
public async Task SetInActionResultExecution_AvailableForNextRequest()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs
index 00f7fd061d..fe7d6f4b9e 100644
--- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs
@@ -2,11 +2,13 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing;
using Moq;
using Xunit;
@@ -28,23 +30,46 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
}
[Fact]
- public void OnResultExecuting_RegistersOnStartingCallback()
+ public async Task OnResultExecuting_DoesntThrowIfResponseStarted()
+ {
+ // Arrange
+ var responseFeature = new TestResponseFeature(hasStarted: true);
+ var httpContext = GetHttpContext(responseFeature);
+ var tempDataFactory = new Mock(MockBehavior.Loose);
+ tempDataFactory
+ .Setup(f => f.GetTempData(It.IsAny()))
+ .Verifiable();
+ var filter = new SaveTempDataFilter(tempDataFactory.Object);
+ var context = GetResultExecutingContext(httpContext);
+ filter.OnResultExecuting(context);
+
+ // Act
+ // Checking it doesn't throw
+ await responseFeature.FireOnSendingHeadersAsync();
+ }
+
+ [Fact]
+ public void OnResourceExecuting_RegistersOnStartingCallback()
{
// Arrange
var responseFeature = new Mock(MockBehavior.Strict);
responseFeature
- .Setup(rf => rf.OnStarting(It.IsAny>(), It.IsAny