Make saving TempData operate via a filter

This change moves the responsibility for saving TempData into a filter,
which is registered with other View features. This moves TempData into the
ViewFeatures package.

ActionResults which require TempData to be kept use a new marker interface
which is handled by the filter.
This commit is contained in:
Ryan Nowak 2015-07-29 09:43:31 -07:00
parent b5237b29b5
commit ff6cbfd7cf
28 changed files with 257 additions and 206 deletions

View File

@ -18,7 +18,6 @@ namespace Microsoft.AspNet.Mvc.Core
private readonly ControllerActionDescriptor _descriptor;
private readonly IControllerFactory _controllerFactory;
private readonly IControllerActionArgumentBinder _argumentBinder;
private readonly ITempDataDictionary _tempData;
public ControllerActionInvoker(
[NotNull] ActionContext actionContext,
@ -32,7 +31,6 @@ namespace Microsoft.AspNet.Mvc.Core
[NotNull] IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
[NotNull] IReadOnlyList<IValueProviderFactory> valueProviderFactories,
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
[NotNull] ITempDataDictionary tempData,
[NotNull] ILoggerFactory loggerFactory,
int maxModelValidationErrors)
: base(
@ -50,7 +48,6 @@ namespace Microsoft.AspNet.Mvc.Core
_descriptor = descriptor;
_controllerFactory = controllerFactory;
_argumentBinder = controllerActionArgumentBinder;
_tempData = tempData;
if (descriptor.MethodInfo == null)
{
@ -70,7 +67,6 @@ namespace Microsoft.AspNet.Mvc.Core
protected override void ReleaseInstance(object instance)
{
_tempData.Save();
_controllerFactory.ReleaseController(instance);
}

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc.Core
private readonly IReadOnlyList<IModelValidatorProvider> _modelValidatorProviders;
private readonly IReadOnlyList<IValueProviderFactory> _valueProviderFactories;
private readonly IScopedInstance<ActionBindingContext> _actionBindingContextAccessor;
private readonly ITempDataDictionary _tempData;
private readonly int _maxModelValidationErrors;
private readonly ILoggerFactory _loggerFactory;
@ -32,7 +31,6 @@ namespace Microsoft.AspNet.Mvc.Core
IControllerActionArgumentBinder argumentBinder,
IOptions<MvcOptions> optionsAccessor,
IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
ITempDataDictionary tempData,
ILoggerFactory loggerFactory)
{
_controllerFactory = controllerFactory;
@ -44,7 +42,6 @@ namespace Microsoft.AspNet.Mvc.Core
_modelValidatorProviders = optionsAccessor.Options.ModelValidatorProviders.ToArray();
_valueProviderFactories = optionsAccessor.Options.ValueProviderFactories.ToArray();
_actionBindingContextAccessor = actionBindingContextAccessor;
_tempData = tempData;
_maxModelValidationErrors = optionsAccessor.Options.MaxModelValidationErrors;
_loggerFactory = loggerFactory;
}
@ -73,7 +70,6 @@ namespace Microsoft.AspNet.Mvc.Core
_modelValidatorProviders,
_valueProviderFactories,
_actionBindingContextAccessor,
_tempData,
_loggerFactory,
_maxModelValidationErrors);
}

View File

@ -130,15 +130,6 @@ namespace Microsoft.Framework.DependencyInjection
return new DefaultObjectValidator(options.ValidationExcludeFilters, modelMetadataProvider);
}));
//
// Temp Data
//
// Holds per-request data so it should be scoped
services.TryAddScoped<ITempDataDictionary, TempDataDictionary>();
// This does caching so it should stay singleton
services.TryAddSingleton<ITempDataProvider, SessionStateTempDataProvider>();
//
// Random Infrastructure
//

View File

@ -0,0 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// A marker interface for <see cref="IActionResult"/> types which need to have temp data saved.
/// </summary>
public interface IKeepTempDataResult : IActionResult
{
}
}

View File

@ -874,54 +874,6 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("ModelType_WrongType"), p0, p1);
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}' to session state.
/// </summary>
internal static string TempData_CannotSerializeToSession
{
get { return GetString("TempData_CannotSerializeToSession"); }
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}' to session state.
/// </summary>
internal static string FormatTempData_CannotSerializeToSession(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeToSession"), p0, p1);
}
/// <summary>
/// Cannot deserialize {0} of type '{1}'.
/// </summary>
internal static string TempData_CannotDeserializeToken
{
get { return GetString("TempData_CannotDeserializeToken"); }
}
/// <summary>
/// Cannot deserialize {0} of type '{1}'.
/// </summary>
internal static string FormatTempData_CannotDeserializeToken(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotDeserializeToken"), p0, p1);
}
/// <summary>
/// The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.
/// </summary>
internal static string TempData_CannotSerializeDictionary
{
get { return GetString("TempData_CannotSerializeDictionary"); }
}
/// <summary>
/// The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.
/// </summary>
internal static string FormatTempData_CannotSerializeDictionary(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeDictionary"), p0, p1);
}
/// <summary>
/// The type '{0}' cannot be activated by '{1}' because it is either a value type, an interface, an abstract class or an open generic type.
/// </summary>

View File

@ -8,7 +8,7 @@ using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc
{
public class RedirectResult : ActionResult
public class RedirectResult : ActionResult, IKeepTempDataResult
{
private string _url;
@ -60,8 +60,6 @@ namespace Microsoft.AspNet.Mvc
destinationUrl = urlHelper.Content(Url);
}
var tempData = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionary>();
tempData.Keep();
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}

View File

@ -9,7 +9,7 @@ using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc
{
public class RedirectToActionResult : ActionResult
public class RedirectToActionResult : ActionResult, IKeepTempDataResult
{
public RedirectToActionResult(
string actionName,
@ -51,8 +51,6 @@ namespace Microsoft.AspNet.Mvc
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
var tempData = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionary>();
tempData.Keep();
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}

View File

@ -9,7 +9,7 @@ using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc
{
public class RedirectToRouteResult : ActionResult
public class RedirectToRouteResult : ActionResult, IKeepTempDataResult
{
public RedirectToRouteResult(object routeValues)
: this(routeName: null, routeValues: routeValues)
@ -51,8 +51,6 @@ namespace Microsoft.AspNet.Mvc
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
var tempData = context.HttpContext.RequestServices.GetRequiredService<ITempDataDictionary>();
tempData.Keep();
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}

View File

@ -288,15 +288,6 @@
<data name="ModelType_WrongType" xml:space="preserve">
<value>The model's runtime type '{0}' is not assignable to the type '{1}'.</value>
</data>
<data name="TempData_CannotSerializeToSession" xml:space="preserve">
<value>The '{0}' cannot serialize an object of type '{1}' to session state.</value>
</data>
<data name="TempData_CannotDeserializeToken" xml:space="preserve">
<value>Cannot deserialize {0} of type '{1}'.</value>
</data>
<data name="TempData_CannotSerializeDictionary" xml:space="preserve">
<value>The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.</value>
</data>
<data name="ValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated" xml:space="preserve">
<value>The type '{0}' cannot be activated by '{1}' because it is either a value type, an interface, an abstract class or an open generic type.</value>
</data>

View File

@ -14,15 +14,15 @@
"Microsoft.AspNet.Hosting.Abstractions": "1.0.0-*",
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Mvc.Abstractions": "6.0.0-*",
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
"Microsoft.Framework.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.Notification": "1.0.0-*",
"Microsoft.Framework.NotNullAttribute.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.PropertyActivator.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.PropertyHelper.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.SecurityHelper.Sources": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Dnx.Compilation.Abstractions": "1.0.0-*",
"Newtonsoft.Json": "6.0.6"
"Microsoft.Framework.SecurityHelper.Sources": { "version": "1.0.0-*", "type": "build" }
},
"frameworks": {
"dnx451": { },

View File

@ -51,6 +51,8 @@ namespace Microsoft.Framework.DependencyInjection
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcViewOptions>, MvcViewOptionsSetup>());
services.TryAddEnumerable(
ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, TempDataMvcOptionsSetup>());
//
// View Engine and related infrastructure
@ -95,6 +97,16 @@ namespace Microsoft.Framework.DependencyInjection
services.TryAddTransient<IViewComponentDescriptorProvider, DefaultViewComponentDescriptorProvider>();
services.TryAddSingleton<IViewComponentInvokerFactory, DefaultViewComponentInvokerFactory>();
services.TryAddTransient<IViewComponentHelper, DefaultViewComponentHelper>();
//
// Temp Data
//
// Holds per-request data so it should be scoped
services.TryAddScoped<ITempDataDictionary, TempDataDictionary>();
services.TryAddScoped<SaveTempDataFilter>();
// This does caching so it should stay singleton
services.TryAddSingleton<ITempDataProvider, SessionStateTempDataProvider>();
}
}
}

View File

@ -794,6 +794,54 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponentResult_NameOrTypeMustBeSet"), p0, p1);
}
/// <summary>
/// Cannot deserialize {0} of type '{1}'.
/// </summary>
internal static string TempData_CannotDeserializeToken
{
get { return GetString("TempData_CannotDeserializeToken"); }
}
/// <summary>
/// Cannot deserialize {0} of type '{1}'.
/// </summary>
internal static string FormatTempData_CannotDeserializeToken(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotDeserializeToken"), p0, p1);
}
/// <summary>
/// The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.
/// </summary>
internal static string TempData_CannotSerializeDictionary
{
get { return GetString("TempData_CannotSerializeDictionary"); }
}
/// <summary>
/// The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.
/// </summary>
internal static string FormatTempData_CannotSerializeDictionary(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeDictionary"), p0, p1);
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}' to session state.
/// </summary>
internal static string TempData_CannotSerializeToSession
{
get { return GetString("TempData_CannotSerializeToSession"); }
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}' to session state.
/// </summary>
internal static string FormatTempData_CannotSerializeToSession(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeToSession"), p0, p1);
}
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -265,4 +265,13 @@
<data name="ViewComponentResult_NameOrTypeMustBeSet" xml:space="preserve">
<value>Either the '{0}' or '{1}' property must be set in order to invoke a view component.</value>
</data>
<data name="TempData_CannotDeserializeToken" xml:space="preserve">
<value>Cannot deserialize {0} of type '{1}'.</value>
</data>
<data name="TempData_CannotSerializeDictionary" xml:space="preserve">
<value>The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.</value>
</data>
<data name="TempData_CannotSerializeToSession" xml:space="preserve">
<value>The '{0}' cannot serialize an object of type '{1}' to session state.</value>
</data>
</root>

View File

@ -0,0 +1,21 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Adds a filter which will save the <see cref="ITempDataDictionary"/> for a request.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SaveTempDataAttribute : Attribute, IFilterFactory
{
/// <inheritdoc />
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<SaveTempDataFilter>();
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// A filter which saves temp data.
/// </summary>
public class SaveTempDataFilter : IResourceFilter, IResultFilter
{
private readonly ITempDataDictionary _tempData;
/// <summary>
/// Creates a new instance of <see cref="SaveTempDataFilter"/>.
/// </summary>
/// <param name="tempData">The <see cref="ITempDataDictionary"/> for the current request.</param>
public SaveTempDataFilter(ITempDataDictionary tempData)
{
_tempData = tempData;
}
/// <inheritdoc />
public void OnResourceExecuting(ResourceExecutingContext context)
{
}
/// <inheritdoc />
public void OnResourceExecuted(ResourceExecutedContext context)
{
_tempData.Save();
}
/// <inheritdoc />
public void OnResultExecuting(ResultExecutingContext context)
{
}
/// <inheritdoc />
public void OnResultExecuted(ResultExecutedContext context)
{
if (context.Result is IKeepTempDataResult)
{
_tempData.Keep();
}
}
}
}

View File

@ -10,7 +10,7 @@ using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Features;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ViewFeatures;
using Microsoft.Framework.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;

View File

@ -0,0 +1,24 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Sets up default options for <see cref="MvcOptions"/>.
/// </summary>
public class TempDataMvcOptionsSetup : ConfigureOptions<MvcOptions>
{
public TempDataMvcOptionsSetup()
: base(ConfigureMvc)
{
Order = DefaultOrder.DefaultFrameworkSortOrder;
}
public static void ConfigureMvc(MvcOptions options)
{
options.Filters.Add(new SaveTempDataAttribute());
}
}
}

View File

@ -54,36 +54,6 @@ namespace Microsoft.AspNet.Mvc
filter.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Never());
}
[Fact]
public async Task InvokeAction_SavesTempData_WhenActionDoesNotThrow()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>();
tempData.Setup(t => t.Save()).Verifiable();
var invoker = CreateInvoker(Mock.Of<IFilterMetadata>(), actionThrows: false, tempData: tempData.Object);
// Act
await invoker.InvokeAsync();
// Assert
tempData.Verify(t => t.Save(), Times.Once());
}
[Fact]
public async Task InvokeAction_SavesTempData_WhenActionThrows()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>();
tempData.Setup(t => t.Save()).Verifiable();
var invoker = CreateInvoker(Mock.Of<IFilterMetadata>(), actionThrows: true, tempData: tempData.Object);
// Act & Assert
await Assert.ThrowsAsync(_actionException.GetType(), async () => await invoker.InvokeAsync());
tempData.Verify(t => t.Save(), Times.Once());
}
[Fact]
public async Task InvokeAction_DoesNotAsyncInvokeExceptionFilter_WhenActionDoesNotThrow()
{
@ -1967,7 +1937,6 @@ namespace Microsoft.AspNet.Mvc
var invoker = CreateInvoker(
filter,
actionThrows: false,
tempData: null,
maxAllowedErrorsInModelState: expected);
// Act & Assert
@ -1978,16 +1947,14 @@ namespace Microsoft.AspNet.Mvc
private TestControllerActionInvoker CreateInvoker(
IFilterMetadata filter,
bool actionThrows = false,
ITempDataDictionary tempData = null,
int maxAllowedErrorsInModelState = 200)
{
return CreateInvoker(new[] { filter }, actionThrows, tempData, maxAllowedErrorsInModelState);
return CreateInvoker(new[] { filter }, actionThrows, maxAllowedErrorsInModelState);
}
private TestControllerActionInvoker CreateInvoker(
IFilterMetadata[] filters,
bool actionThrows = false,
ITempDataDictionary tempData = null,
int maxAllowedErrorsInModelState = 200)
{
var actionDescriptor = new ControllerActionDescriptor()
@ -2004,16 +1971,13 @@ namespace Microsoft.AspNet.Mvc
{
actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod");
}
tempData = tempData ?? new Mock<ITempDataDictionary>().Object;
var httpContext = new Mock<HttpContext>(MockBehavior.Loose);
var httpRequest = new DefaultHttpContext().Request;
var httpResponse = new DefaultHttpContext().Response;
httpContext.SetupGet(c => c.Request).Returns(httpRequest);
httpContext.SetupGet(c => c.Response).Returns(httpResponse);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary)))
.Returns(tempData);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ILogger<ObjectResult>)))
.Returns(new Mock<ILogger<ObjectResult>>().Object);
@ -2077,7 +2041,6 @@ namespace Microsoft.AspNet.Mvc
new IModelValidatorProvider[0],
new IValueProviderFactory[0],
new MockScopedInstance<ActionBindingContext>(),
tempData,
new NullLoggerFactory(),
maxAllowedErrorsInModelState);
@ -2140,7 +2103,6 @@ namespace Microsoft.AspNet.Mvc
new IModelValidatorProvider[0],
new IValueProviderFactory[0],
new MockScopedInstance<ActionBindingContext>(),
Mock.Of<ITempDataDictionary>(),
new NullLoggerFactory(),
200);
@ -2241,7 +2203,6 @@ namespace Microsoft.AspNet.Mvc
IReadOnlyList<IModelValidatorProvider> modelValidatorProviders,
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
IScopedInstance<ActionBindingContext> actionBindingContext,
ITempDataDictionary tempData,
ILoggerFactory loggerFactory,
int maxAllowedErrorsInModelState)
: base(
@ -2256,7 +2217,6 @@ namespace Microsoft.AspNet.Mvc
modelValidatorProviders,
valueProviderFactories,
actionBindingContext,
tempData,
loggerFactory,
maxAllowedErrorsInModelState)
{

View File

@ -91,30 +91,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
httpResponse.Verify();
}
[Fact]
public void Execute_Calls_TempDataKeep()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>();
tempData.Setup(t => t.Keep()).Verifiable();
var httpContext = new Mock<HttpContext>();
httpContext.Setup(o => o.Response).Returns(new Mock<HttpResponse>().Object);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary))).Returns(tempData.Object);
var actionContext = GetActionContext(httpContext.Object);
var result = new RedirectResult("url")
{
UrlHelper = Mock.Of<IUrlHelper>()
};
// Act
result.ExecuteResult(actionContext);
// Assert
tempData.Verify(t => t.Keep(), Times.Once());
}
private static ActionContext GetActionContext(HttpContext httpContext)
{
var routeData = new RouteData();
@ -129,7 +105,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddInstance<IUrlHelper>(urlHelper);
serviceCollection.AddInstance<ITempDataDictionary>(Mock.Of<ITempDataDictionary>());
return serviceCollection.BuildServiceProvider();
}

View File

@ -68,32 +68,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
"No route matches the supplied values.");
}
[Fact]
public void RedirectToAction_Execute_Calls_TempDataKeep()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>();
tempData.Setup(t => t.Keep()).Verifiable();
var httpContext = new Mock<HttpContext>();
httpContext.Setup(o => o.Response).Returns(new Mock<HttpResponse>().Object);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary))).Returns(tempData.Object);
var actionContext = new ActionContext(httpContext.Object,
new RouteData(),
new ActionDescriptor());
var result = new RedirectToActionResult("SampleAction", null, null)
{
UrlHelper = GetMockUrlHelper("SampleAction")
};
// Act
result.ExecuteResult(actionContext);
// Assert
tempData.Verify(t => t.Keep(), Times.Once());
}
private static IUrlHelper GetMockUrlHelper(string returnValue)
{
var urlHelper = new Mock<IUrlHelper>();

View File

@ -74,32 +74,6 @@ namespace Microsoft.AspNet.Mvc.Core
"No route matches the supplied values.");
}
[Fact]
public void RedirectToRoute_Execute_Calls_TempDataKeep()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>();
tempData.Setup(t => t.Keep()).Verifiable();
var httpContext = new Mock<HttpContext>();
httpContext.Setup(o => o.Response).Returns(new Mock<HttpResponse>().Object);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary))).Returns(tempData.Object);
var actionContext = new ActionContext(httpContext.Object,
new RouteData(),
new ActionDescriptor());
var result = new RedirectToRouteResult("SampleRoute", null)
{
UrlHelper = GetMockUrlHelper("SampleRoute")
};
// Act
result.ExecuteResult(actionContext);
// Assert
tempData.Verify(t => t.Keep(), Times.Once());
}
[Fact]
public async Task ExecuteResultAsync_UsesRouteName_ToGenerateLocationHeader()
{

View File

@ -205,6 +205,7 @@ namespace Microsoft.AspNet.Mvc
typeof(MvcCoreMvcOptionsSetup),
typeof(MvcDataAnnotationsMvcOptionsSetup),
typeof(MvcJsonMvcOptionsSetup),
typeof(TempDataMvcOptionsSetup),
}
},
{

View File

@ -0,0 +1,74 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class SaveTempDataFilterTest
{
[Fact]
public void SaveTempDataFilter_OnResourceExecuted_SavesTempData()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>(MockBehavior.Strict);
tempData
.Setup(m => m.Save())
.Verifiable();
var filter = new SaveTempDataFilter(tempData.Object);
var context = new ResourceExecutedContext(new ActionContext(), new IFilterMetadata[] { });
// Act
filter.OnResourceExecuted(context);
// Assert
tempData.Verify();
}
[Fact]
public void SaveTempDataFilter_OnResultExecuted_KeepsTempData_ForIKeepTempDataResult()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>(MockBehavior.Strict);
tempData
.Setup(m => m.Keep())
.Verifiable();
var filter = new SaveTempDataFilter(tempData.Object);
var context = new ResultExecutedContext(
new ActionContext(),
new IFilterMetadata[] { },
new Mock<IKeepTempDataResult>().Object,
new object());
// Act
filter.OnResultExecuted(context);
// Assert
tempData.Verify();
}
[Fact]
public void SaveTempDataFilter_OnResultExecuted_DoesNotKeepTempData_ForNonIKeepTempDataResult()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>(MockBehavior.Strict);
var filter = new SaveTempDataFilter(tempData.Object);
var context = new ResultExecutedContext(
new ActionContext(),
new IFilterMetadata[] { },
new Mock<IActionResult>().Object,
new object());
// Act
filter.OnResultExecuted(context);
// Assert - The mock will throw if we do the wrong thing.
}
}
}