Introducing TempData

- Issue #455
 - Updated MVC sample
 - Added relevant tests
This commit is contained in:
Ajay Bhargav Baaskaran 2015-02-10 19:09:06 -08:00
parent 4e9e33fc07
commit db728cd386
64 changed files with 1248 additions and 38 deletions

17
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22602.0
VisualStudioVersion = 14.0.22609.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -148,6 +148,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "BestEffortLinkGenerationWeb
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LowercaseUrlsWebSite", "test\WebSites\LowercaseUrlsWebSite\LowercaseUrlsWebSite.kproj", "{BCDB13A6-7D6E-485E-8424-A156432B71AC}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "TempDataWebSite", "test\WebSites\TempDataWebSite\TempDataWebSite.kproj", "{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -866,6 +868,18 @@ Global
{BCDB13A6-7D6E-485E-8424-A156432B71AC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{BCDB13A6-7D6E-485E-8424-A156432B71AC}.Release|x86.ActiveCfg = Release|Any CPU
{BCDB13A6-7D6E-485E-8424-A156432B71AC}.Release|x86.Build.0 = Release|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Debug|x86.ActiveCfg = Debug|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Debug|x86.Build.0 = Debug|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Release|Any CPU.Build.0 = Release|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Release|x86.ActiveCfg = Release|Any CPU
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -938,5 +952,6 @@ Global
{A19022EF-9BA3-4349-94E4-F48E13E1C8AE} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{B11C99C9-E577-4CA2-AC53-4F20EA71AD34} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{BCDB13A6-7D6E-485E-8424-A156432B71AC} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{8AEB631E-AB74-4D2E-83FB-8931EE10D9D3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
EndGlobalSection
EndGlobal

View File

@ -113,6 +113,21 @@ namespace MvcSample.Web
return View();
}
public ActionResult AddTempData()
{
TempData["controllerData"] = "Temporary data from controller through ViewBag.";
TempData["tempData"] = "Temporary data directly from TempData.";
return RedirectToAction("UseTempData");
}
public ActionResult UseTempData()
{
var data = TempData["controllerData"];
ViewBag.TempData = data;
return View("MyView", CreateUser());
}
/// <summary>
/// Action that exercises input formatter
/// </summary>

View File

@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Security.Claims;
using Microsoft.AspNet.Authentication;
using Microsoft.AspNet.Authorization;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
@ -41,6 +38,9 @@ namespace MvcSample.Web
app.UseServices(services =>
{
services.AddCachingServices();
services.AddSessionServices();
services.AddMvc();
services.AddSingleton<PassThroughAttribute>();
services.AddSingleton<UserNameService>();
@ -83,6 +83,9 @@ namespace MvcSample.Web
{
app.UseServices(services =>
{
services.AddCachingServices();
services.AddSessionServices();
services.AddMvc();
services.AddSingleton<PassThroughAttribute>();
services.AddSingleton<UserNameService>();
@ -102,6 +105,7 @@ namespace MvcSample.Web
});
}
app.UseInMemorySession();
app.UseMvc(routes =>
{
routes.MapRoute("areaRoute", "{area:exists}/{controller}/{action}");

View File

@ -51,6 +51,7 @@
<h1>ASP.NET</h1>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a href="http://asp.net" class="btn btn-primary btn-large">Learn more &raquo;</a></p>
<h3 style="color: red">@ViewBag.TempData<br/>@TempData["tempData"]</h3>
File Upload Demo: <br/>
<form action="Home/PostFile" method="post" enctype="multipart/form-data">
<input type="file" name="files" id="file1" />

View File

@ -14,6 +14,7 @@
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.Session": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
"Microsoft.Framework.PropertyHelper.Internal": { "version": "1.0.0-*", "type": "build" },
"Microsoft.Framework.NotNullAttribute.Internal": { "type": "build", "version": "1.0.0-*" }

View File

@ -32,6 +32,11 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public ViewDataDictionary ViewData { get; set; }
/// <summary>
/// Gets or sets the <see cref="ITempDataDictionary"/> used for rendering the view for this result.
/// </summary>
public ITempDataDictionary TempData { get; set; }
/// <summary>
/// Gets or sets the <see cref="IViewEngine"/> used to locate views.
/// </summary>
@ -57,7 +62,7 @@ namespace Microsoft.AspNet.Mvc
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null);
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, contentType: null);
}
}
}

View File

@ -60,6 +60,8 @@ 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

@ -51,6 +51,8 @@ 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

@ -51,6 +51,8 @@ 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

@ -22,10 +22,12 @@ namespace Microsoft.AspNet.Mvc
/// <param name="view">The <see cref="IView"/> to render.</param>
/// <param name="actionContext">The <see cref="ActionContext"/> for the current executing action.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/> for the view being rendered.</param>
/// <param name="tempData">The <see cref="ITempDataDictionary"/> for the view being rendered.</param>
/// <returns>A <see cref="Task"/> that represents the asychronous rendering.</returns>
public static async Task ExecuteAsync([NotNull] IView view,
[NotNull] ActionContext actionContext,
[NotNull] ViewDataDictionary viewData,
[NotNull] ITempDataDictionary tempData,
string contentType)
{
if (string.IsNullOrEmpty(contentType))
@ -40,7 +42,7 @@ namespace Microsoft.AspNet.Mvc
{
try
{
var viewContext = new ViewContext(actionContext, view, viewData, writer);
var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
await view.RenderAsync(viewContext);
}
catch

View File

@ -32,6 +32,11 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public ViewDataDictionary ViewData { get; set; }
/// <summary>
/// Gets or sets the <see cref="ITempDataDictionary"/> for this result.
/// </summary>
public ITempDataDictionary TempData { get; set; }
/// <summary>
/// Gets or sets the <see cref="IViewEngine"/> used to locate views.
/// </summary>
@ -57,7 +62,7 @@ namespace Microsoft.AspNet.Mvc
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null);
await ViewExecutor.ExecuteAsync(view, context, ViewData, TempData, contentType: null);
}
}
}

View File

@ -185,6 +185,12 @@ namespace Microsoft.AspNet.Mvc
}
}
/// <summary>
/// Gets or sets <see cref="ITempDataDictionary"/> used by <see cref="ViewResult"/>.
/// </summary>
[FromServices]
public ITempDataDictionary TempData { get; set; }
/// <summary>
/// Gets the dynamic view bag.
/// </summary>
@ -254,6 +260,7 @@ namespace Microsoft.AspNet.Mvc
{
ViewName = viewName,
ViewData = ViewData,
TempData = TempData
};
}
@ -310,6 +317,7 @@ namespace Microsoft.AspNet.Mvc
{
ViewName = viewName,
ViewData = ViewData,
TempData = TempData
};
}

View File

@ -16,6 +16,7 @@ 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,
@ -27,7 +28,8 @@ namespace Microsoft.AspNet.Mvc.Core
[NotNull] IModelBinderProvider modelBinderProvider,
[NotNull] IModelValidatorProviderProvider modelValidatorProviderProvider,
[NotNull] IValueProviderFactoryProvider valueProviderFactoryProvider,
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor)
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
[NotNull] ITempDataDictionary tempData)
: base(
actionContext,
filterProviders,
@ -40,6 +42,7 @@ namespace Microsoft.AspNet.Mvc.Core
_descriptor = descriptor;
_controllerFactory = controllerFactory;
_argumentBinder = controllerActionArgumentBinder;
_tempData = tempData;
if (descriptor.MethodInfo == null)
{
@ -59,6 +62,7 @@ namespace Microsoft.AspNet.Mvc.Core
protected override void ReleaseInstance(object instance)
{
_tempData.Save();
_controllerFactory.ReleaseController(instance);
}

View File

@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.Core
private readonly IModelValidatorProviderProvider _modelValidationProviderProvider;
private readonly IValueProviderFactoryProvider _valueProviderFactoryProvider;
private readonly IScopedInstance<ActionBindingContext> _actionBindingContextAccessor;
private readonly ITempDataDictionary _tempData;
public ControllerActionInvokerProvider(
IControllerFactory controllerFactory,
@ -29,7 +30,8 @@ namespace Microsoft.AspNet.Mvc.Core
IModelBinderProvider modelBinderProvider,
IModelValidatorProviderProvider modelValidationProviderProvider,
IValueProviderFactoryProvider valueProviderFactoryProvider,
IScopedInstance<ActionBindingContext> actionBindingContextAccessor)
IScopedInstance<ActionBindingContext> actionBindingContextAccessor,
ITempDataDictionary tempData)
{
_controllerFactory = controllerFactory;
_inputFormattersProvider = inputFormattersProvider;
@ -39,6 +41,7 @@ namespace Microsoft.AspNet.Mvc.Core
_modelValidationProviderProvider = modelValidationProviderProvider;
_valueProviderFactoryProvider = valueProviderFactoryProvider;
_actionBindingContextAccessor = actionBindingContextAccessor;
_tempData = tempData;
}
public int Order
@ -63,7 +66,8 @@ namespace Microsoft.AspNet.Mvc.Core
_modelBinderProvider,
_modelValidationProviderProvider,
_valueProviderFactoryProvider,
_actionBindingContextAccessor);
_actionBindingContextAccessor,
_tempData);
}
}

View File

@ -0,0 +1,42 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Represents a set of data that persists only from one request to the next.
/// </summary>
public interface ITempDataDictionary : IDictionary<string, object>
{
/// <summary>
/// Loads the dictionary by using the registered <see cref="ITempDataProvider"/>.
/// </summary>
void Load();
/// <summary>
/// Saves the dictionary by using the registered <see cref="ITempDataProvider"/>.
/// </summary>
void Save();
/// <summary>
/// Marks all keys in the dictionary for retention.
/// </summary>
void Keep();
/// <summary>
/// Marks the specified key in the dictionary for retention.
/// </summary>
/// <param name="key">The key to retain in the dictionary.</param>
void Keep(string key);
/// <summary>
/// Returns an object that contains the element that is associated with the specified key,
/// without marking the key for deletion.
/// </summary>
/// <param name="key">The key of the element to return.</param>
/// <returns>An object that contains the element that is associated with the specified key.</returns>
object Peek(string key);
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Defines the contract for temporary-data providers that store data that is viewed on the next request.
/// </summary>
public interface ITempDataProvider
{
/// <summary>
/// Loads the temporary data.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <returns>The temporary data.</returns>
IDictionary<string, object> LoadTempData([NotNull] HttpContext context);
/// <summary>
/// Saves the temporary data.
/// </summary>
/// <param name="context">The <see cref="HttpContext"/>.</param>
/// <param name="values">The values to save.</param>
void SaveTempData([NotNull] HttpContext context, IDictionary<string, object> values);
}
}

View File

@ -113,6 +113,15 @@ namespace Microsoft.AspNet.Mvc.Rendering
}
}
/// <inheritdoc />
public ITempDataDictionary TempData
{
get
{
return ViewContext.TempData;
}
}
/// <inheritdoc />
public IHtmlEncoder HtmlEncoder { get; }
@ -450,7 +459,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
var view = viewEngineResult.View;
using (view as IDisposable)
{
var viewContext = new ViewContext(ViewContext, view, newViewData, writer);
var viewContext = new ViewContext(ViewContext, view, newViewData, TempData, writer);
await viewEngineResult.View.RenderAsync(viewContext);
}
}

View File

@ -46,6 +46,11 @@ namespace Microsoft.AspNet.Mvc.Rendering
/// </summary>
ViewDataDictionary ViewData { get; }
/// <summary>
/// Gets the current <see cref="ITempDataDictionary"/> instance.
/// </summary>
ITempDataDictionary TempData { get; }
/// <summary>
/// Gets the <see cref="IHtmlEncoder"/> to be used for encoding HTML.
/// </summary>

View File

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Provides session-state data to the current <see cref="ITempDataDictionary"/> object.
/// </summary>
public class SessionStateTempDataProvider : ITempDataProvider
{
private static JsonSerializer jsonSerializer = new JsonSerializer();
private static string TempDataSessionStateKey = "__ControllerTempData";
/// <inheritdoc />
public virtual IDictionary<string, object> LoadTempData([NotNull] HttpContext context)
{
if (!IsSessionEnabled(context))
{
// Session middleware is not enabled. No-op
return null;
}
var tempDataDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
var session = context.Session;
byte[] value;
if (session != null && session.TryGetValue(TempDataSessionStateKey, out value))
{
using (var memoryStream = new MemoryStream(value))
using (var writer = new BsonReader(memoryStream))
{
tempDataDictionary = jsonSerializer.Deserialize<Dictionary<string, object>>(writer);
}
// If we got it from Session, remove it so that no other request gets it
session.Remove(TempDataSessionStateKey);
}
return tempDataDictionary;
}
/// <inheritdoc />
public virtual void SaveTempData([NotNull] HttpContext context, IDictionary<string, object> values)
{
var hasValues = (values != null && values.Count > 0);
if (hasValues)
{
// Accessing Session property will throw if the session middleware is not enabled.
var session = context.Session;
using (var memoryStream = new MemoryStream())
using (var writer = new BsonWriter(memoryStream))
{
jsonSerializer.Serialize(writer, values);
session[TempDataSessionStateKey] = memoryStream.ToArray();
}
}
else if (IsSessionEnabled(context))
{
var session = context.Session;
session.Remove(TempDataSessionStateKey);
}
}
private static bool IsSessionEnabled(HttpContext context)
{
return context.GetFeature<ISessionFeature>() != null;
}
}
}

View File

@ -0,0 +1,285 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.AspNet.Hosting;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc
{
/// <inheritdoc />
public class TempDataDictionary : ITempDataDictionary
{
private Dictionary<string, object> _data;
private bool _loaded;
private readonly ITempDataProvider _provider;
private readonly IHttpContextAccessor _contextAccessor;
private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Initializes a new instance of the <see cref="TempDataDictionary"/> class.
/// </summary>
/// <param name="context">The <see cref="IHttpContextAccessor"/> that provides the HttpContext.</param>
/// <param name="provider">The <see cref="ITempDataProvider"/> used to Load and Save data.</param>
public TempDataDictionary([NotNull] IHttpContextAccessor context, [NotNull] ITempDataProvider provider)
{
_provider = provider;
_loaded = false;
_contextAccessor = context;
}
public int Count
{
get
{
Load();
return _data.Count;
}
}
public ICollection<string> Keys
{
get
{
Load();
return _data.Keys;
}
}
public ICollection<object> Values
{
get
{
Load();
return _data.Values;
}
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get
{
Load();
return ((ICollection<KeyValuePair<string, object>>)_data).IsReadOnly;
}
}
public object this[string key]
{
get
{
Load();
object value;
if (TryGetValue(key, out value))
{
// Mark the key for deletion since it is read.
_initialKeys.Remove(key);
return value;
}
return null;
}
set
{
Load();
_data[key] = value;
_initialKeys.Add(key);
}
}
/// <inheritdoc />
public void Keep()
{
Load();
_retainedKeys.Clear();
_retainedKeys.UnionWith(_data.Keys);
}
/// <inheritdoc />
public void Keep(string key)
{
Load();
_retainedKeys.Add(key);
}
/// <inheritdoc />
public void Load()
{
if (_loaded)
{
return;
}
var providerDictionary = _provider.LoadTempData(_contextAccessor.HttpContext);
_data = (providerDictionary != null)
? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase)
: new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
_initialKeys = new HashSet<string>(_data.Keys, StringComparer.OrdinalIgnoreCase);
_retainedKeys.Clear();
_loaded = true;
}
/// <inheritdoc />
public void Save()
{
if (!_loaded)
{
return;
}
// Because it is not possible to delete while enumerating, a copy of the keys must be taken.
// Use the size of the dictionary as an upper bound to avoid creating more than one copy of the keys.
var removeCount = 0;
var keys = new string[_data.Count];
foreach (var entry in _data)
{
if (!_initialKeys.Contains(entry.Key) && !_retainedKeys.Contains(entry.Key))
{
keys[removeCount] = entry.Key;
removeCount++;
}
}
for (var i = 0; i < removeCount; i++)
{
_data.Remove(keys[i]);
}
_provider.SaveTempData(_contextAccessor.HttpContext, _data);
}
/// <inheritdoc />
public object Peek(string key)
{
Load();
object value;
_data.TryGetValue(key, out value);
return value;
}
public void Add(string key, object value)
{
Load();
_data.Add(key, value);
_initialKeys.Add(key);
}
public void Clear()
{
Load();
_data.Clear();
_retainedKeys.Clear();
_initialKeys.Clear();
}
public bool ContainsKey(string key)
{
Load();
return _data.ContainsKey(key);
}
public bool ContainsValue(object value)
{
Load();
return _data.ContainsValue(value);
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
Load();
return new TempDataDictionaryEnumerator(this);
}
public bool Remove(string key)
{
Load();
_retainedKeys.Remove(key);
_initialKeys.Remove(key);
return _data.Remove(key);
}
public bool TryGetValue(string key, out object value)
{
Load();
// Mark the key for deletion since it is read.
_initialKeys.Remove(key);
return _data.TryGetValue(key, out value);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int index)
{
Load();
((ICollection<KeyValuePair<string, object>>)_data).CopyTo(array, index);
}
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> keyValuePair)
{
Load();
_initialKeys.Add(keyValuePair.Key);
((ICollection<KeyValuePair<string, object>>)_data).Add(keyValuePair);
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> keyValuePair)
{
Load();
return ((ICollection<KeyValuePair<string, object>>)_data).Contains(keyValuePair);
}
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> keyValuePair)
{
Load();
_initialKeys.Remove(keyValuePair.Key);
return ((ICollection<KeyValuePair<string, object>>)_data).Remove(keyValuePair);
}
IEnumerator IEnumerable.GetEnumerator()
{
Load();
return new TempDataDictionaryEnumerator(this);
}
private sealed class TempDataDictionaryEnumerator : IEnumerator<KeyValuePair<string, object>>
{
private IEnumerator<KeyValuePair<string, object>> _enumerator;
private TempDataDictionary _tempData;
public TempDataDictionaryEnumerator(TempDataDictionary tempData)
{
_tempData = tempData;
_enumerator = _tempData._data.GetEnumerator();
}
public KeyValuePair<string, object> Current
{
get
{
var kvp = _enumerator.Current;
// Mark the key for deletion since it is read.
_tempData._initialKeys.Remove(kvp.Key);
return kvp;
}
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
return _enumerator.MoveNext();
}
public void Reset()
{
_enumerator.Reset();
}
void IDisposable.Dispose()
{
_enumerator.Dispose();
}
}
}
}

View File

@ -28,6 +28,11 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public ViewDataDictionary ViewData { get; set; }
/// <summary>
/// Gets or sets the <see cref="ITempDataDictionary"/> instance.
/// </summary>
public ITempDataDictionary TempData { get; set; }
/// <summary>
/// Gets or sets the <see cref="ViewEngine"/>.
/// </summary>
@ -90,6 +95,7 @@ namespace Microsoft.AspNet.Mvc
context.ViewContext,
view,
ViewData ?? context.ViewContext.ViewData,
TempData ?? context.ViewContext.TempData,
context.Writer);
using (view as IDisposable)

View File

@ -24,16 +24,19 @@ namespace Microsoft.AspNet.Mvc
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="view">The <see cref="IView"/> being rendered.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param>
/// <param name="tempData">The <see cref="ITempDataDictionary"/>.</param>
/// <param name="writer">The <see cref="TextWriter"/> to render output to.</param>
public ViewContext(
[NotNull] ActionContext actionContext,
[NotNull] IView view,
[NotNull] ViewDataDictionary viewData,
[NotNull] ITempDataDictionary tempData,
[NotNull] TextWriter writer)
: base(actionContext)
{
View = view;
ViewData = viewData;
TempData = tempData;
Writer = writer;
_formContext = _defaultFormContext;
@ -64,6 +67,7 @@ namespace Microsoft.AspNet.Mvc
View = view;
ViewData = viewData;
TempData = viewContext.TempData;
Writer = writer;
}
@ -135,6 +139,11 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public ViewDataDictionary ViewData { get; set; }
/// <summary>
/// Gets or sets the <see cref="ITempDataDictionary"/> instance.
/// </summary>
public ITempDataDictionary TempData { get; set; }
/// <summary>
/// Gets or sets the <see cref="TextWriter"/> used to write the output.
/// </summary>

View File

@ -105,7 +105,19 @@ namespace Microsoft.AspNet.Mvc.Razor
{
get
{
return (ViewContext == null) ? null : ViewContext.ViewBag;
return ViewContext?.ViewBag;
}
}
/// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="ViewContext"/>.
/// </summary>
/// <remarks>Returns null if <see cref="ViewContext"/> is null.</remarks>
public ITempDataDictionary TempData
{
get
{
return ViewContext?.TempData;
}
}

View File

@ -177,6 +177,10 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Singleton<IApiDescriptionGroupCollectionProvider,
ApiDescriptionGroupCollectionProvider>();
yield return describe.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>();
// Temp Data
yield return describe.Singleton<ITempDataProvider, SessionStateTempDataProvider>();
yield return describe.Scoped<ITempDataDictionary, TempDataDictionary>();
}
}
}
}

View File

@ -91,6 +91,30 @@ 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();
@ -105,6 +129,7 @@ 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

@ -21,6 +21,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults
var httpContext = new Mock<HttpContext>();
var httpResponse = new Mock<HttpResponse>();
httpContext.Setup(o => o.Response).Returns(httpResponse.Object);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary)))
.Returns(Mock.Of<ITempDataDictionary>());
var actionContext = new ActionContext(httpContext.Object,
new RouteData(),
@ -66,6 +68,32 @@ 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

@ -23,6 +23,8 @@ namespace Microsoft.AspNet.Mvc.Core
var httpContext = new Mock<HttpContext>();
var httpResponse = new Mock<HttpResponse>();
httpContext.Setup(o => o.Response).Returns(httpResponse.Object);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary)))
.Returns(Mock.Of<ITempDataDictionary>());
var actionContext = new ActionContext(httpContext.Object,
new RouteData(),
@ -69,6 +71,32 @@ 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());
}
public static IEnumerable<object[]> RedirectToRouteData
{
get

View File

@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
// Act
await ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, contentType: null);
await ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, null, contentType: null);
// Assert
Assert.Equal(expected, memoryStream.ToArray());
@ -67,7 +67,7 @@ namespace Microsoft.AspNet.Mvc
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
// Act
await ViewExecutor.ExecuteAsync(view, actionContext, viewData, contentType);
await ViewExecutor.ExecuteAsync(view, actionContext, viewData, null, contentType);
// Assert
Assert.Equal(contentType, context.Response.ContentType);
@ -120,7 +120,7 @@ namespace Microsoft.AspNet.Mvc
// Act
await Record.ExceptionAsync(
() => ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, contentType: null));
() => ViewExecutor.ExecuteAsync(view.Object, actionContext, viewData, null, contentType: null));
// Assert
Assert.Equal(expectedLength, memoryStream.Length);

View File

@ -51,6 +51,36 @@ 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<IFilter>(), 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<IFilter>(), 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()
{
@ -1929,12 +1959,18 @@ namespace Microsoft.AspNet.Mvc
Assert.Same(input, contentResult.Value);
}
private TestControllerActionInvoker CreateInvoker(IFilter filter, bool actionThrows = false)
private TestControllerActionInvoker CreateInvoker(
IFilter filter,
bool actionThrows = false,
ITempDataDictionary tempData = null)
{
return CreateInvoker(new[] { filter }, actionThrows);
return CreateInvoker(new[] { filter }, actionThrows, tempData);
}
private TestControllerActionInvoker CreateInvoker(IFilter[] filters, bool actionThrows = false)
private TestControllerActionInvoker CreateInvoker(
IFilter[] filters,
bool actionThrows = false,
ITempDataDictionary tempData = null)
{
var actionDescriptor = new ControllerActionDescriptor()
{
@ -1951,6 +1987,7 @@ 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;
@ -1965,6 +2002,8 @@ namespace Microsoft.AspNet.Mvc
httpContext.SetupGet(c => c.Response).Returns(httpResponse);
httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider)))
.Returns(mockFormattersProvider.Object);
httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary)))
.Returns(tempData);
httpResponse.Body = new MemoryStream();
var options = new Mock<IOptions<MvcOptions>>();
@ -2012,7 +2051,8 @@ namespace Microsoft.AspNet.Mvc
new MockModelBinderProvider(),
new MockModelValidatorProviderProvider(),
new MockValueProviderFactoryProvider(),
new MockScopedInstance<ActionBindingContext>());
new MockScopedInstance<ActionBindingContext>(),
tempData);
return invoker;
}
@ -2044,6 +2084,8 @@ namespace Microsoft.AspNet.Mvc
var context = new Mock<HttpContext>();
context.SetupGet(c => c.Items)
.Returns(new Dictionary<object, object>());
context.Setup(c => c.RequestServices.GetService(typeof(ITempDataDictionary)))
.Returns(new Mock<ITempDataDictionary>().Object);
var actionContext = new ActionContext(context.Object, new RouteData(), actionDescriptor);
@ -2067,7 +2109,8 @@ namespace Microsoft.AspNet.Mvc
new MockModelBinderProvider() { ModelBinders = new List<IModelBinder>() { binder.Object } },
new MockModelValidatorProviderProvider(),
new MockValueProviderFactoryProvider(),
new MockScopedInstance<ActionBindingContext>());
new MockScopedInstance<ActionBindingContext>(),
Mock.Of<ITempDataDictionary>());
// Act
await invoker.InvokeAsync();
@ -2164,7 +2207,8 @@ namespace Microsoft.AspNet.Mvc
IModelBinderProvider modelBinderProvider,
IModelValidatorProviderProvider modelValidatorProviderProvider,
IValueProviderFactoryProvider valueProviderFactoryProvider,
IScopedInstance<ActionBindingContext> actionBindingContext)
IScopedInstance<ActionBindingContext> actionBindingContext,
ITempDataDictionary tempData)
: base(
actionContext,
filterProvider,
@ -2175,7 +2219,8 @@ namespace Microsoft.AspNet.Mvc
modelBinderProvider,
modelValidatorProviderProvider,
valueProviderFactoryProvider,
actionBindingContext)
actionBindingContext,
tempData)
{
ControllerFactory = controllerFactory;
}

View File

@ -5,15 +5,16 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Testing;
using Microsoft.AspNet.WebUtilities;
using Microsoft.AspNet.Http.Core;
#if DNX451
using Moq;
#endif
@ -739,12 +740,13 @@ namespace Microsoft.AspNet.Mvc.Test
}
[Fact]
public void Controller_View_WithoutParameter_SetsResultNullViewNameAndNullViewDataModel()
public void Controller_View_WithoutParameter_SetsResultNullViewNameAndNullViewDataModelAndSameTempData()
{
// Arrange
var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = new TempDataDictionary(Mock.Of<IHttpContextAccessor>(), Mock.Of<ITempDataProvider>()),
};
// Act
@ -754,16 +756,18 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.IsType<ViewResult>(actualViewResult);
Assert.Null(actualViewResult.ViewName);
Assert.Same(controller.ViewData, actualViewResult.ViewData);
Assert.Same(controller.TempData, actualViewResult.TempData);
Assert.Null(actualViewResult.ViewData.Model);
}
[Fact]
public void Controller_View_WithParameterViewName_SetsResultViewNameAndNullViewDataModel()
public void Controller_View_WithParameterViewName_SetsResultViewNameAndNullViewDataModelAndSameTempData()
{
// Arrange
var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = new TempDataDictionary(Mock.Of<IHttpContextAccessor>(), Mock.Of<ITempDataProvider>()),
};
// Act
@ -773,16 +777,18 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.IsType<ViewResult>(actualViewResult);
Assert.Equal("CustomViewName", actualViewResult.ViewName);
Assert.Same(controller.ViewData, actualViewResult.ViewData);
Assert.Same(controller.TempData, actualViewResult.TempData);
Assert.Null(actualViewResult.ViewData.Model);
}
[Fact]
public void Controller_View_WithParameterViewModel_SetsResultNullViewNameAndViewDataModel()
public void Controller_View_WithParameterViewModel_SetsResultNullViewNameAndViewDataModelAndSameTempData()
{
// Arrange
var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = new TempDataDictionary(Mock.Of<IHttpContextAccessor>(), Mock.Of<ITempDataProvider>()),
};
var model = new object();
@ -793,16 +799,18 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.IsType<ViewResult>(actualViewResult);
Assert.Null(actualViewResult.ViewName);
Assert.Same(controller.ViewData, actualViewResult.ViewData);
Assert.Same(controller.TempData, actualViewResult.TempData);
Assert.Same(model, actualViewResult.ViewData.Model);
}
[Fact]
public void Controller_View_WithParameterViewNameAndViewModel_SetsResultViewNameAndViewDataModel()
public void Controller_View_WithParameterViewNameAndViewModel_SetsResultViewNameAndViewDataModelAndSameTempData()
{
// Arrange
var controller = new TestableController()
{
ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider()),
TempData = new TempDataDictionary(Mock.Of<IHttpContextAccessor>(), Mock.Of<ITempDataProvider>()),
};
var model = new object();
@ -813,6 +821,7 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.IsType<ViewResult>(actualViewResult);
Assert.Equal("CustomViewName", actualViewResult.ViewName);
Assert.Same(controller.ViewData, actualViewResult.ViewData);
Assert.Same(controller.TempData, actualViewResult.TempData);
Assert.Same(model, actualViewResult.ViewData.Model);
}
@ -1466,6 +1475,21 @@ namespace Microsoft.AspNet.Mvc.Test
Assert.Equal("The 'BindingContext' property of 'Microsoft.AspNet.Mvc.Controller' must not be null.", exception.Message);
}
[Fact]
public void TempData_CanSetAndGetValues()
{
// Arrange
var controller = GetController(null, null);
var input = "Foo";
// Act
controller.TempData["key"] = input;
var result = controller.TempData["key"];
// Assert
Assert.Equal(input, result);
}
private static Controller GetController(IModelBinder binder, IValueProvider provider)
{
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
@ -1473,6 +1497,7 @@ namespace Microsoft.AspNet.Mvc.Test
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
var tempData = new TempDataDictionary(Mock.Of<IHttpContextAccessor>(), Mock.Of<ITempDataProvider>());
var bindingContext = new ActionBindingContext()
{
@ -1487,6 +1512,7 @@ namespace Microsoft.AspNet.Mvc.Test
BindingContext = bindingContext,
MetadataProvider = metadataProvider,
ViewData = viewData,
TempData = tempData,
ObjectValidator = new DefaultObjectValidator(Mock.Of<IValidationExcludeFiltersProvider>(), metadataProvider)
};
}

View File

@ -35,6 +35,7 @@ namespace Microsoft.AspNet.Mvc
Assert.NotNull(viewResult.ViewData);
Assert.Same(model, viewResult.ViewData.Model);
Assert.Same(controller.ViewData, viewResult.ViewData);
Assert.Same(controller.TempData, viewResult.TempData);
if (model != null)
{
@ -61,6 +62,7 @@ namespace Microsoft.AspNet.Mvc
Assert.NotNull(viewResult.ViewData);
Assert.Same(model, viewResult.ViewData.Model);
Assert.Same(controller.ViewData, viewResult.ViewData);
Assert.Same(controller.TempData, viewResult.TempData);
if (model != null)
{

View File

@ -281,6 +281,8 @@ namespace Microsoft.AspNet.Mvc.Core
services
.Setup(s => s.GetService(typeof(IScopedInstance<ActionBindingContext>)))
.Returns(new MockScopedInstance<ActionBindingContext>());
services.Setup(s => s.GetService(typeof(ITempDataDictionary)))
.Returns(new Mock<ITempDataDictionary>().Object);
return services.Object;
}

View File

@ -109,6 +109,7 @@ namespace Microsoft.AspNet.Mvc
return new ViewContext(actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
null,
TextWriter.Null);
}

View File

@ -881,6 +881,11 @@ Environment.NewLine;
get { return _innerHelper.ViewData; }
}
public ITempDataDictionary TempData
{
get { return _innerHelper.TempData; }
}
public IHtmlEncoder HtmlEncoder
{
get { return _innerHelper.HtmlEncoder; }

View File

@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
new HtmlEncoder(),
new UrlEncoder(),
new JavaScriptStringEncoder());
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, new StringWriter());
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, null, new StringWriter());
htmlHelper.Contextualize(viewContext);
return htmlHelper;

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.Rendering
public void SettingViewData_AlsoUpdatesViewBag()
{
// Arrange (eventually passing null to these consturctors will throw)
var context = new ViewContext(new ActionContext(null, null, null), view: null, viewData: null, writer: null);
var context = new ViewContext(new ActionContext(null, null, null), view: null, viewData: null, tempData: null, writer: null);
var originalViewData = context.ViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());
var replacementViewData = new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider());

View File

@ -0,0 +1,97 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Http;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class SessionStateTempDataProviderTest
{
[Fact]
public void Load_NullSession_ReturnsEmptyDictionary()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act
var tempDataDictionary = testProvider.LoadTempData(
GetHttpContext(session: null, sessionEnabled: true));
// Assert
Assert.Empty(tempDataDictionary);
}
[Fact]
public void Load_NonNullSession_NoSessionData_ReturnsEmptyDictionary()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act
var tempDataDictionary = testProvider.LoadTempData(
GetHttpContext(Mock.Of<ISessionCollection>()));
// Assert
Assert.Empty(tempDataDictionary);
}
[Fact]
public void Save_NullSession_NullDictionary_DoesNotThrow()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act & Assert (does not throw)
testProvider.SaveTempData(GetHttpContext(session: null, sessionEnabled: false), null);
}
[Fact]
public void Save_NullSession_EmptyDictionary_DoesNotThrow()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act & Assert (does not throw)
testProvider.SaveTempData(
GetHttpContext(session: null, sessionEnabled: false), new Dictionary<string, object>());
}
[Fact]
public void Save_NullSession_NonEmptyDictionary_Throws()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act & Assert
Assert.Throws<InvalidOperationException>(() =>
{
testProvider.SaveTempData(
GetHttpContext(session: null, sessionEnabled: false),
new Dictionary<string, object> { { "foo", "bar" } }
);
});
}
private HttpContext GetHttpContext(ISessionCollection session, bool sessionEnabled=true)
{
var httpContext = new Mock<HttpContext>();
if (session != null)
{
httpContext.Setup(h => h.Session).Returns(session);
}
else if (!sessionEnabled)
{
httpContext.Setup(h => h.Session).Throws<InvalidOperationException>();
}
if (sessionEnabled)
{
httpContext.Setup(h => h.GetFeature<ISessionFeature>()).Returns(Mock.Of<ISessionFeature>());
}
return httpContext.Object;
}
}
}

View File

@ -0,0 +1,225 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.Framework.Internal;
using Moq;
using Xunit;
namespace Microsoft.AspNet.Mvc
{
public class TempDataDictionaryTest
{
[Fact]
public void TempData_Load_CreatesEmptyDictionaryIfProviderReturnsNull()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
// Act
tempData.Load();
// Assert
Assert.Empty(tempData);
}
[Fact]
public void TempData_Save_RemovesKeysThatWereRead()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
tempData["Foo"] = "Foo";
tempData["Bar"] = "Bar";
// Act
var value = tempData["Foo"];
tempData.Save();
// Assert
Assert.False(tempData.ContainsKey("Foo"));
Assert.True(tempData.ContainsKey("Bar"));
}
[Fact]
public void TempData_EnumeratingDictionary_MarksKeysForDeletion()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
tempData["Foo"] = "Foo";
tempData["Bar"] = "Bar";
// Act
var enumerator = tempData.GetEnumerator();
while (enumerator.MoveNext())
{
var value = enumerator.Current;
}
tempData.Save();
// Assert
Assert.False(tempData.ContainsKey("Foo"));
Assert.False(tempData.ContainsKey("Bar"));
}
[Fact]
public void TempData_TryGetValue_MarksKeyForDeletion()
{
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
object value;
tempData["Foo"] = "Foo";
// Act
tempData.TryGetValue("Foo", out value);
tempData.Save();
// Assert
Assert.False(tempData.ContainsKey("Foo"));
}
[Fact]
public void TempData_Keep_RetainsAllKeysWhenSavingDictionary()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
tempData["Foo"] = "Foo";
tempData["Bar"] = "Bar";
// Act
tempData.Keep();
tempData.Save();
// Assert
Assert.True(tempData.ContainsKey("Foo"));
Assert.True(tempData.ContainsKey("Bar"));
}
[Fact]
public void TempData_Keep_RetainsSpecificKeysWhenSavingDictionary()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
tempData["Foo"] = "Foo";
tempData["Bar"] = "Bar";
// Act
var foo = tempData["Foo"];
var bar = tempData["Bar"];
tempData.Keep("Foo");
tempData.Save();
// Assert
Assert.True(tempData.ContainsKey("Foo"));
Assert.False(tempData.ContainsKey("Bar"));
}
[Fact]
public void TempData_Peek_DoesNotMarkKeyForDeletion()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
tempData["Bar"] = "barValue";
// Act
var value = tempData.Peek("bar");
tempData.Save();
// Assert
Assert.Equal("barValue", value);
Assert.True(tempData.ContainsKey("Bar"));
}
[Fact]
public void TempData_CompareIsOrdinalIgnoreCase()
{
// Arrange
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
var item = new object();
// Act
tempData["Foo"] = item;
var value = tempData["FOO"];
// Assert
Assert.Same(item, value);
}
[Fact]
public void TempData_LoadAndSaveAreCaseInsensitive()
{
// Arrange
var data = new Dictionary<string, object>();
data["Foo"] = "Foo";
data["Bar"] = "Bar";
var provider = new TestTempDataProvider(data);
var tempData = new TempDataDictionary(GetHttpContextAccessor(), provider);
// Act
tempData.Load();
var value = tempData["FOO"];
tempData.Save();
// Assert
Assert.False(tempData.ContainsKey("foo"));
Assert.True(tempData.ContainsKey("bar"));
}
[Fact]
public void TempData_RemovalOfKeysAreCaseInsensitive()
{
var tempData = new TempDataDictionary(GetHttpContextAccessor(), new NullTempDataProvider());
object fooValue;
tempData["Foo"] = "Foo";
tempData["Bar"] = "Bar";
// Act
tempData.TryGetValue("foo", out fooValue);
var barValue = tempData["bar"];
tempData.Save();
// Assert
Assert.False(tempData.ContainsKey("Foo"));
Assert.False(tempData.ContainsKey("Boo"));
}
private class NullTempDataProvider : ITempDataProvider
{
public IDictionary<string, object> LoadTempData([NotNull]HttpContext context)
{
return null;
}
public void SaveTempData([NotNull]HttpContext context, IDictionary<string, object> values)
{
}
}
private class TestTempDataProvider : ITempDataProvider
{
private IDictionary<string, object> _data;
public TestTempDataProvider(IDictionary<string, object> data)
{
_data = data;
}
public IDictionary<string, object> LoadTempData([NotNull]HttpContext context)
{
return _data;
}
public void SaveTempData([NotNull]HttpContext context, IDictionary<string, object> values)
{
}
}
private static IHttpContextAccessor GetHttpContextAccessor()
{
var httpContext = new Mock<HttpContext>();
var httpContextAccessor = new Mock<IHttpContextAccessor>();
httpContextAccessor.Setup(h => h.HttpContext).Returns(httpContext.Object);
return httpContextAccessor.Object;
}
}
}

View File

@ -54,7 +54,7 @@ namespace Microsoft.AspNet.Mvc
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewContext = new ViewContext(actionContext, view, viewData, TextWriter.Null);
var viewContext = new ViewContext(actionContext, view, viewData, null, TextWriter.Null);
var writer = new StreamWriter(stream) { AutoFlush = true };
var viewComponentContext = new ViewComponentContext(typeof(object).GetTypeInfo(), viewContext, writer);
return viewComponentContext;

View File

@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider());
var viewContext = new ViewContext(actionContext, view, viewData, TextWriter.Null);
var viewContext = new ViewContext(actionContext, view, viewData, null, TextWriter.Null);
var writer = new StreamWriter(stream) { AutoFlush = true };
var viewComponentContext = new ViewComponentContext(typeof(object).GetTypeInfo(), viewContext, writer);
return viewComponentContext;

View File

@ -300,7 +300,7 @@ namespace Microsoft.AspNet.Mvc
private static ViewComponentContext GetViewComponentContext(IView view, ViewDataDictionary viewData)
{
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var viewContext = new ViewContext(actionContext, view, viewData, TextWriter.Null);
var viewContext = new ViewContext(actionContext, view, viewData, null, TextWriter.Null);
var viewComponentContext = new ViewComponentContext(typeof(object).GetTypeInfo(), viewContext, TextWriter.Null);
return viewComponentContext;
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost;
using Xunit;
namespace Microsoft.AspNet.Mvc.FunctionalTests
{
public class TempDataTest
{
private readonly IServiceProvider _services = TestHelper.CreateServices("TempDataWebSite");
private readonly Action<IApplicationBuilder> _app = new TempDataWebSite.Startup().Configure;
[Fact]
public async Task ViewRendersTempData()
{
// Arrange
var server = TestServer.Create(_services, _app);
var client = server.CreateClient();
var nameValueCollection = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string,string>("value", "Foo"),
};
var content = new FormUrlEncodedContent(nameValueCollection);
// Act
var response = await client.PostAsync("http://localhost/Home/DisplayTempData", content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Foo", body);
}
}
}

View File

@ -48,6 +48,7 @@
"RoutingWebSite": "1.0.0",
"TagHelperSample.Web": "1.0.0",
"TagHelpersWebSite": "1.0.0",
"TempDataWebSite": "1.0.0",
"UrlHelperWebSite": "1.0.0",
"ValidationWebSite": "1.0.0",
"ValueProvidersWebSite": "1.0.0",

View File

@ -43,6 +43,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
// Act
@ -73,6 +74,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
// Act and Assert
@ -114,6 +116,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
// Act
@ -151,6 +154,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
// Act
@ -185,6 +189,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
// Act

View File

@ -125,6 +125,7 @@ namespace Microsoft.AspNet.Mvc.Razor
actionContext,
view: Mock.Of<IView>(),
viewData: viewData,
tempData: Mock.Of<ITempDataDictionary>(),
writer: new StringWriter());
}

View File

@ -115,6 +115,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewContext = new ViewContext(actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
return new TestRazorPage

View File

@ -816,6 +816,7 @@ namespace Microsoft.AspNet.Mvc.Razor
actionContext,
Mock.Of<IView>(),
null,
Mock.Of<ITempDataDictionary>(),
writer);
}

View File

@ -1023,6 +1023,7 @@ namespace Microsoft.AspNet.Mvc.Razor
actionContext,
view,
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
new StringWriter());
}

View File

@ -756,6 +756,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
return new ViewContext(actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
}

View File

@ -441,6 +441,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new TestModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
}
}

View File

@ -371,7 +371,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var metadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(metadataProvider);
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, TextWriter.Null);
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
return viewContext;
}

View File

@ -462,7 +462,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
var metadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(metadataProvider);
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, TextWriter.Null);
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
return viewContext;
}

View File

@ -59,7 +59,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
{
Model = model,
};
var viewContext = new ViewContext(actionContext, Mock.Of<IView>(), viewData, TextWriter.Null);
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
return viewContext;
}

View File

@ -305,6 +305,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers
Mock.Of<IView>(),
new ViewDataDictionary(
new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
}
}

View File

@ -310,6 +310,7 @@ Parameter name: value",
Mock.Of<IView>(),
new ViewDataDictionary(
new EmptyModelMetadataProvider()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null);
}

View File

@ -24,7 +24,7 @@ namespace ModelBindingWebSite.Controllers
{
_activated = true;
var viewData = new ViewDataDictionary<Person>(ViewData);
var context = new ViewContext(ActionContext, new TestView(), viewData, TextWriter.Null);
var context = new ViewContext(ActionContext, new TestView(), viewData, null, TextWriter.Null);
((ICanHasViewContext)PersonHelper).Contextualize(context);
}

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace TempDataWebSite.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult DisplayTempData(string value)
{
TempData["key"] = value;
return View();
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder;
using Microsoft.Framework.DependencyInjection;
namespace TempDataWebSite
{
public class Startup
{
public void Configure(IApplicationBuilder app)
{
var configuration = app.GetTestConfiguration();
app.UseServices(services =>
{
services.AddCachingServices();
services.AddSessionServices();
services.AddMvc(configuration);
});
app.UseInMemorySession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.Props" Condition="'$(VSToolsPath)' != ''" />
<PropertyGroup Label="Globals">
<ProjectGuid>8aeb631e-ab74-4d2e-83fb-8931ee10d9d3</ProjectGuid>
<RootNamespace>TempDataWebSite</RootNamespace>
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
</PropertyGroup>
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
<DevelopmentServerPort>28690</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

View File

@ -0,0 +1 @@
@TempData["key"]

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Hello</title>
</head>
<body>
Hello
</body>
</html>

View File

@ -0,0 +1,19 @@
{
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
},
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Session": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*"
},
"frameworks": {
"dnx451": { },
"dnxcore50": { }
},
"webroot": "wwwroot"
}

View File

@ -0,0 +1,4 @@
TempDataWebSite
===
This web site illustrates use cases of TempData.

View File

@ -0,0 +1 @@
HelloWorld