[Fixes #5212] Added a cookie based ITempDataProvider

This commit is contained in:
Kiran Challa 2016-09-07 11:54:41 -07:00
parent 4cca6b09f0
commit 89bd6dc1cd
21 changed files with 1730 additions and 481 deletions

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.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Provides programmatic configuration for cookies set by <see cref="CookieTempDataProvider"/>
/// </summary>
public class CookieTempDataProviderOptions
{
/// <summary>
/// The path set for a cookie. If not specified, current request's <see cref="HttpRequest.PathBase"/> value is used.
/// </summary>
public string Path { get; set; }
/// <summary>
/// The domain set on a cookie. Defaults to <c>null</c>.
/// </summary>
public string Domain { get; set; }
}
}

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
@ -10,6 +12,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
/// </summary>
public class SaveTempDataFilter : IResourceFilter, IResultFilter
{
// Internal for unit testing
internal static readonly object TempDataSavedKey = new object();
private readonly ITempDataDictionaryFactory _factory;
/// <summary>
@ -29,21 +34,68 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
/// <inheritdoc />
public void OnResourceExecuted(ResourceExecutedContext context)
{
_factory.GetTempData(context.HttpContext).Save();
}
/// <inheritdoc />
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
});
}
/// <inheritdoc />
public void OnResultExecuted(ResultExecutedContext context)
{
if (context.Result is IKeepTempDataResult)
// We are doing this here again because the OnStarting delegate above might get fired too late in scenarios
// where the action result doesn't write anything to the body. This causes the delegate to be executed
// late in the pipeline at which point SessionFeature would not be available.
if (!context.HttpContext.Response.HasStarted)
{
_factory.GetTempData(context.HttpContext).Keep();
SaveTempData(context.Result, _factory, context.HttpContext);
context.HttpContext.Items.Add(TempDataSavedKey, true);
}
}
private static void SaveTempData(IActionResult result, ITempDataDictionaryFactory factory, HttpContext httpContext)
{
if (result is IKeepTempDataResult)
{
factory.GetTempData(httpContext).Keep();
}
factory.GetTempData(httpContext).Save();
}
private class SaveTempDataContext
{
public HttpContext HttpContext { get; set; }
public IActionResult ActionResult { get; set; }
public ITempDataDictionaryFactory TempDataDictionaryFactory { get; set; }
}
}
}

View File

@ -0,0 +1,239 @@
// 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 System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public class TempDataSerializer
{
private readonly JsonSerializer _jsonSerializer =
JsonSerializer.Create(JsonSerializerSettingsProvider.CreateSerializerSettings());
private static readonly MethodInfo _convertArrayMethodInfo = typeof(TempDataSerializer).GetMethod(
nameof(ConvertArray), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo _convertDictionaryMethodInfo = typeof(TempDataSerializer).GetMethod(
nameof(ConvertDictionary), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly ConcurrentDictionary<Type, Func<JArray, object>> _arrayConverters =
new ConcurrentDictionary<Type, Func<JArray, object>>();
private static readonly ConcurrentDictionary<Type, Func<JObject, object>> _dictionaryConverters =
new ConcurrentDictionary<Type, Func<JObject, object>>();
private static readonly Dictionary<JTokenType, Type> _tokenTypeLookup = new Dictionary<JTokenType, Type>
{
{ JTokenType.String, typeof(string) },
{ JTokenType.Integer, typeof(int) },
{ JTokenType.Boolean, typeof(bool) },
{ JTokenType.Float, typeof(float) },
{ JTokenType.Guid, typeof(Guid) },
{ JTokenType.Date, typeof(DateTime) },
{ JTokenType.TimeSpan, typeof(TimeSpan) },
{ JTokenType.Uri, typeof(Uri) },
};
public IDictionary<string, object> Deserialize(byte[] value)
{
Dictionary<string, object> tempDataDictionary = null;
using (var memoryStream = new MemoryStream(value))
using (var writer = new BsonReader(memoryStream))
{
tempDataDictionary = _jsonSerializer.Deserialize<Dictionary<string, object>>(writer);
if (tempDataDictionary == null)
{
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
}
var convertedDictionary = new Dictionary<string, object>(
tempDataDictionary,
StringComparer.OrdinalIgnoreCase);
foreach (var item in tempDataDictionary)
{
var jArrayValue = item.Value as JArray;
var jObjectValue = item.Value as JObject;
if (jArrayValue != null && jArrayValue.Count > 0)
{
var arrayType = jArrayValue[0].Type;
Type returnType;
if (_tokenTypeLookup.TryGetValue(arrayType, out returnType))
{
var arrayConverter = _arrayConverters.GetOrAdd(returnType, type =>
{
return (Func<JArray, object>)_convertArrayMethodInfo
.MakeGenericMethod(type)
.CreateDelegate(typeof(Func<JArray, object>));
});
var result = arrayConverter(jArrayValue);
convertedDictionary[item.Key] = result;
}
else
{
var message = Resources.FormatTempData_CannotDeserializeToken(nameof(JToken), arrayType);
throw new InvalidOperationException(message);
}
}
else if (jObjectValue != null)
{
if (!jObjectValue.HasValues)
{
convertedDictionary[item.Key] = null;
continue;
}
var jTokenType = jObjectValue.Properties().First().Value.Type;
Type valueType;
if (_tokenTypeLookup.TryGetValue(jTokenType, out valueType))
{
var dictionaryConverter = _dictionaryConverters.GetOrAdd(valueType, type =>
{
return (Func<JObject, object>)_convertDictionaryMethodInfo
.MakeGenericMethod(type)
.CreateDelegate(typeof(Func<JObject, object>));
});
var result = dictionaryConverter(jObjectValue);
convertedDictionary[item.Key] = result;
}
else
{
var message = Resources.FormatTempData_CannotDeserializeToken(nameof(JToken), jTokenType);
throw new InvalidOperationException(message);
}
}
else if (item.Value is long)
{
var longValue = (long)item.Value;
if (longValue >= int.MinValue && longValue <= int.MaxValue)
{
// BsonReader casts all ints to longs. We'll attempt to work around this by force converting
// longs to ints when there's no loss of precision.
convertedDictionary[item.Key] = (int)longValue;
}
}
}
return convertedDictionary ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public byte[] Serialize(IDictionary<string, object> values)
{
var hasValues = (values != null && values.Count > 0);
if (hasValues)
{
foreach (var item in values.Values)
{
if (item != null)
{
// We want to allow only simple types to be serialized.
EnsureObjectCanBeSerialized(item);
}
}
using (var memoryStream = new MemoryStream())
{
using (var writer = new BsonWriter(memoryStream))
{
_jsonSerializer.Serialize(writer, values);
return memoryStream.ToArray();
}
}
}
else
{
return new byte[0];
}
}
public void EnsureObjectCanBeSerialized(object item)
{
var itemType = item.GetType();
Type actualType = null;
if (itemType.IsArray)
{
itemType = itemType.GetElementType();
}
else if (itemType.GetTypeInfo().IsGenericType)
{
if (ClosedGenericMatcher.ExtractGenericInterface(itemType, typeof(IList<>)) != null)
{
var genericTypeArguments = itemType.GenericTypeArguments;
Debug.Assert(genericTypeArguments.Length == 1, "IList<T> has one generic argument");
actualType = genericTypeArguments[0];
}
else if (ClosedGenericMatcher.ExtractGenericInterface(itemType, typeof(IDictionary<,>)) != null)
{
var genericTypeArguments = itemType.GenericTypeArguments;
Debug.Assert(
genericTypeArguments.Length == 2,
"IDictionary<TKey, TValue> has two generic arguments");
// Throw if the key type of the dictionary is not string.
if (genericTypeArguments[0] != typeof(string))
{
var message = Resources.FormatTempData_CannotSerializeDictionary(
typeof(TempDataSerializer).FullName, genericTypeArguments[0]);
throw new InvalidOperationException(message);
}
else
{
actualType = genericTypeArguments[1];
}
}
}
actualType = actualType ?? itemType;
if (!IsSimpleType(actualType))
{
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
var message = Resources.FormatTempData_CannotSerializeType(
typeof(TempDataSerializer).FullName, underlyingType);
throw new InvalidOperationException(message);
}
}
private static IList<TVal> ConvertArray<TVal>(JArray array)
{
return array.Values<TVal>().ToArray();
}
private static IDictionary<string, TVal> ConvertDictionary<TVal>(JObject jObject)
{
var convertedDictionary = new Dictionary<string, TVal>(StringComparer.Ordinal);
foreach (var item in jObject)
{
convertedDictionary.Add(item.Key, jObject.Value<TVal>(item.Key));
}
return convertedDictionary;
}
private static bool IsSimpleType(Type type)
{
var typeInfo = type.GetTypeInfo();
return typeInfo.IsPrimitive ||
typeInfo.IsEnum ||
type.Equals(typeof(decimal)) ||
type.Equals(typeof(string)) ||
type.Equals(typeof(DateTime)) ||
type.Equals(typeof(Guid)) ||
type.Equals(typeof(DateTimeOffset)) ||
type.Equals(typeof(TimeSpan)) ||
type.Equals(typeof(Uri));
}
}
}

View File

@ -779,7 +779,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
/// <summary>
/// The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.
/// The '{0}' cannot serialize a dictionary with a key of type '{1}'.
/// </summary>
internal static string TempData_CannotSerializeDictionary
{
@ -787,7 +787,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
/// <summary>
/// The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.
/// The '{0}' cannot serialize a dictionary with a key of type '{1}'.
/// </summary>
internal static string FormatTempData_CannotSerializeDictionary(object p0, object p1)
{
@ -795,19 +795,19 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}' to session state.
/// The '{0}' cannot serialize an object of type '{1}'.
/// </summary>
internal static string TempData_CannotSerializeToSession
internal static string TempData_CannotSerializeType
{
get { return GetString("TempData_CannotSerializeToSession"); }
get { return GetString("TempData_CannotSerializeType"); }
}
/// <summary>
/// The '{0}' cannot serialize an object of type '{1}' to session state.
/// The '{0}' cannot serialize an object of type '{1}'.
/// </summary>
internal static string FormatTempData_CannotSerializeToSession(object p0, object p1)
internal static string FormatTempData_CannotSerializeType(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeToSession"), p0, p1);
return string.Format(CultureInfo.CurrentCulture, GetString("TempData_CannotSerializeType"), p0, p1);
}
/// <summary>

View File

@ -1,17 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
@ -26,36 +26,36 @@
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
@ -263,10 +263,10 @@
<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>
<value>The '{0}' cannot serialize a dictionary with a key of 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 name="TempData_CannotSerializeType" xml:space="preserve">
<value>The '{0}' cannot serialize an object of type '{1}'.</value>
</data>
<data name="Dictionary_DuplicateKey" xml:space="preserve">
<value>The collection already contains an entry with key '{0}'.</value>

View File

@ -0,0 +1,85 @@
// 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 System.Collections.Generic;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
/// <summary>
/// Provides data from cookie to the current <see cref="ITempDataDictionary"/> object.
/// </summary>
public class CookieTempDataProvider : ITempDataProvider
{
public static readonly string CookieName = ".AspNetCore.Mvc.CookieTempDataProvider";
private static readonly string Purpose = "Microsoft.AspNetCore.Mvc.CookieTempDataProviderToken.v1";
private const byte TokenVersion = 0x01;
private readonly IDataProtector _dataProtector;
private readonly TempDataSerializer _tempDataSerializer;
private readonly ChunkingCookieManager _chunkingCookieManager;
private readonly IOptions<CookieTempDataProviderOptions> _options;
public CookieTempDataProvider(IDataProtectionProvider dataProtectionProvider, IOptions<CookieTempDataProviderOptions> options)
{
_dataProtector = dataProtectionProvider.CreateProtector(Purpose);
_tempDataSerializer = new TempDataSerializer();
_chunkingCookieManager = new ChunkingCookieManager();
_options = options;
}
public IDictionary<string, object> LoadTempData(HttpContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Request.Cookies.ContainsKey(CookieName))
{
var base64EncodedValue = _chunkingCookieManager.GetRequestCookie(context, CookieName);
if (!string.IsNullOrEmpty(base64EncodedValue))
{
var protectedData = Convert.FromBase64String(base64EncodedValue);
var unprotectedData = _dataProtector.Unprotect(protectedData);
return _tempDataSerializer.Deserialize(unprotectedData);
}
}
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
public void SaveTempData(HttpContext context, IDictionary<string, object> values)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var cookieOptions = new CookieOptions()
{
Path = string.IsNullOrEmpty(_options.Value.Path) ? context.Request.PathBase.ToString() : _options.Value.Path,
Domain = string.IsNullOrEmpty(_options.Value.Domain) ? null : _options.Value.Domain,
HttpOnly = true,
Secure = true
};
var hasValues = (values != null && values.Count > 0);
if (hasValues)
{
var bytes = _tempDataSerializer.Serialize(values);
bytes = _dataProtector.Protect(bytes);
var base64EncodedValue = Convert.ToBase64String(bytes);
_chunkingCookieManager.AppendResponseCookie(context, CookieName, base64EncodedValue, cookieOptions);
}
else
{
_chunkingCookieManager.DeleteCookie(context, CookieName, cookieOptions);
}
}
}
}

View File

@ -14,7 +14,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class SaveTempDataAttribute : Attribute, IFilterFactory, IOrderedFilter
{
/// <inheritdoc />
public SaveTempDataAttribute()
{
// Since SaveTempDataFilter registers for a response's OnStarting callback, we want this filter to run
// as early as possible to get the oppurtunity to register the call back before any other result filter
// starts writing to the response stream.
Order = int.MinValue + 100;
}
// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />

View File

@ -2,18 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.Extensions.Internal;
using Newtonsoft.Json;
using Newtonsoft.Json.Bson;
using Newtonsoft.Json.Linq;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
@ -24,34 +15,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
// Internal for testing
internal const string TempDataSessionStateKey = "__ControllerTempData";
private readonly TempDataSerializer _tempDataSerializer;
private readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(
new JsonSerializerSettings()
{
TypeNameHandling = TypeNameHandling.None
});
private static readonly MethodInfo _convertArrayMethodInfo = typeof(SessionStateTempDataProvider).GetMethod(
nameof(ConvertArray), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly MethodInfo _convertDictMethodInfo = typeof(SessionStateTempDataProvider).GetMethod(
nameof(ConvertDictionary), BindingFlags.Static | BindingFlags.NonPublic);
private static readonly ConcurrentDictionary<Type, Func<JArray, object>> _arrayConverters =
new ConcurrentDictionary<Type, Func<JArray, object>>();
private static readonly ConcurrentDictionary<Type, Func<JObject, object>> _dictionaryConverters =
new ConcurrentDictionary<Type, Func<JObject, object>>();
private static readonly Dictionary<JTokenType, Type> _tokenTypeLookup = new Dictionary<JTokenType, Type>
public SessionStateTempDataProvider()
{
{ JTokenType.String, typeof(string) },
{ JTokenType.Integer, typeof(int) },
{ JTokenType.Boolean, typeof(bool) },
{ JTokenType.Float, typeof(float) },
{ JTokenType.Guid, typeof(Guid) },
{ JTokenType.Date, typeof(DateTime) },
{ JTokenType.TimeSpan, typeof(TimeSpan) },
{ JTokenType.Uri, typeof(Uri) },
};
_tempDataSerializer = new TempDataSerializer();
}
/// <inheritdoc />
public virtual IDictionary<string, object> LoadTempData(HttpContext context)
@ -64,103 +33,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
// Accessing Session property will throw if the session middleware is not enabled.
var session = context.Session;
Dictionary<string, object> tempDataDictionary = null;
byte[] value;
if (session.TryGetValue(TempDataSessionStateKey, out value))
{
// If we got it from Session, remove it so that no other request gets it
session.Remove(TempDataSessionStateKey);
using (var memoryStream = new MemoryStream(value))
using (var writer = new BsonReader(memoryStream))
{
tempDataDictionary = _jsonSerializer.Deserialize<Dictionary<string, object>>(writer);
if (tempDataDictionary == null)
{
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
}
var convertedDictionary = new Dictionary<string, object>(
tempDataDictionary,
StringComparer.OrdinalIgnoreCase);
foreach (var item in tempDataDictionary)
{
var jArrayValue = item.Value as JArray;
var jObjectValue = item.Value as JObject;
if (jArrayValue != null && jArrayValue.Count > 0)
{
var arrayType = jArrayValue[0].Type;
Type returnType;
if (_tokenTypeLookup.TryGetValue(arrayType, out returnType))
{
var arrayConverter = _arrayConverters.GetOrAdd(returnType, type =>
{
return (Func<JArray, object>)_convertArrayMethodInfo
.MakeGenericMethod(type)
.CreateDelegate(typeof(Func<JArray, object>));
});
var result = arrayConverter(jArrayValue);
convertedDictionary[item.Key] = result;
}
else
{
var message = Resources.FormatTempData_CannotDeserializeToken(nameof(JToken), arrayType);
throw new InvalidOperationException(message);
}
}
else if (jObjectValue != null)
{
if (!jObjectValue.HasValues)
{
convertedDictionary[item.Key] = null;
continue;
}
var jTokenType = jObjectValue.Properties().First().Value.Type;
Type valueType;
if (_tokenTypeLookup.TryGetValue(jTokenType, out valueType))
{
var dictionaryConverter = _dictionaryConverters.GetOrAdd(valueType, type =>
{
return (Func<JObject, object>)_convertDictMethodInfo
.MakeGenericMethod(type)
.CreateDelegate(typeof(Func<JObject, object>));
});
var result = dictionaryConverter(jObjectValue);
convertedDictionary[item.Key] = result;
}
else
{
var message = Resources.FormatTempData_CannotDeserializeToken(nameof(JToken), jTokenType);
throw new InvalidOperationException(message);
}
}
else if (item.Value is long)
{
var longValue = (long)item.Value;
if (longValue >= int.MinValue && longValue <= int.MaxValue)
{
// BsonReader casts all ints to longs. We'll attempt to work around this by force converting
// longs to ints when there's no loss of precision.
convertedDictionary[item.Key] = (int)longValue;
}
}
}
tempDataDictionary = convertedDictionary;
}
else
{
// Since we call Save() after the response has been sent, we need to initialize an empty session
// so that it is established before the headers are sent.
session.Set(TempDataSessionStateKey, EmptyArray<byte>.Instance);
return _tempDataSerializer.Deserialize(value);
}
return tempDataDictionary ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}
/// <inheritdoc />
@ -177,106 +59,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
var hasValues = (values != null && values.Count > 0);
if (hasValues)
{
foreach (var item in values.Values)
{
if (item != null)
{
// We want to allow only simple types to be serialized in session.
EnsureObjectCanBeSerialized(item);
}
}
using (var memoryStream = new MemoryStream())
{
using (var writer = new BsonWriter(memoryStream))
{
_jsonSerializer.Serialize(writer, values);
session.Set(TempDataSessionStateKey, memoryStream.ToArray());
}
}
var bytes = _tempDataSerializer.Serialize(values);
session.Set(TempDataSessionStateKey, bytes);
}
else
{
session.Remove(TempDataSessionStateKey);
}
}
internal void EnsureObjectCanBeSerialized(object item)
{
var itemType = item.GetType();
Type actualType = null;
if (itemType.IsArray)
{
itemType = itemType.GetElementType();
}
else if (itemType.GetTypeInfo().IsGenericType)
{
if (ClosedGenericMatcher.ExtractGenericInterface(itemType, typeof(IList<>)) != null)
{
var genericTypeArguments = itemType.GenericTypeArguments;
Debug.Assert(genericTypeArguments.Length == 1, "IList<T> has one generic argument");
actualType = genericTypeArguments[0];
}
else if (ClosedGenericMatcher.ExtractGenericInterface(itemType, typeof(IDictionary<,>)) != null)
{
var genericTypeArguments = itemType.GenericTypeArguments;
Debug.Assert(
genericTypeArguments.Length == 2,
"IDictionary<TKey, TValue> has two generic arguments");
// Throw if the key type of the dictionary is not string.
if (genericTypeArguments[0] != typeof(string))
{
var message = Resources.FormatTempData_CannotSerializeDictionary(
typeof(SessionStateTempDataProvider).FullName, genericTypeArguments[0]);
throw new InvalidOperationException(message);
}
else
{
actualType = genericTypeArguments[1];
}
}
}
actualType = actualType ?? itemType;
if (!IsSimpleType(actualType))
{
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
var message = Resources.FormatTempData_CannotSerializeToSession(
typeof(SessionStateTempDataProvider).FullName, underlyingType);
throw new InvalidOperationException(message);
}
}
private static IList<TVal> ConvertArray<TVal>(JArray array)
{
return array.Values<TVal>().ToArray();
}
private static IDictionary<string, TVal> ConvertDictionary<TVal>(JObject jObject)
{
var convertedDictionary = new Dictionary<string, TVal>(StringComparer.Ordinal);
foreach (var item in jObject)
{
convertedDictionary.Add(item.Key, jObject.Value<TVal>(item.Key));
}
return convertedDictionary;
}
private static bool IsSimpleType(Type type)
{
var typeInfo = type.GetTypeInfo();
return typeInfo.IsPrimitive ||
typeInfo.IsEnum ||
type.Equals(typeof(decimal)) ||
type.Equals(typeof(string)) ||
type.Equals(typeof(DateTime)) ||
type.Equals(typeof(Guid)) ||
type.Equals(typeof(DateTimeOffset)) ||
type.Equals(typeof(TimeSpan)) ||
type.Equals(typeof(Uri));
}
}
}

View File

@ -22,6 +22,10 @@
},
"dependencies": {
"Microsoft.AspNetCore.Antiforgery": "1.1.0-*",
"Microsoft.AspNetCore.ChunkingCookieManager.Sources": {
"version": "1.1.0-*",
"type": "build"
},
"Microsoft.AspNetCore.Diagnostics.Abstractions": "1.1.0-*",
"Microsoft.AspNetCore.Html.Abstractions": "1.1.0-*",
"Microsoft.AspNetCore.Mvc.Core": "1.1.0-*",
@ -35,6 +39,10 @@
"version": "1.1.0-*",
"type": "build"
},
"Microsoft.Extensions.HashCodeCombiner.Sources": {
"version": "1.1.0-*",
"type": "build"
},
"Microsoft.Extensions.PropertyActivator.Sources": {
"version": "1.1.0-*",
"type": "build"
@ -43,10 +51,6 @@
"version": "1.1.0-*",
"type": "build"
},
"Microsoft.Extensions.HashCodeCombiner.Sources": {
"version": "1.1.0-*",
"type": "build"
},
"Microsoft.Extensions.WebEncoders": "1.1.0-*",
"Newtonsoft.Json": "9.0.1",
"System.Buffers": "4.0.0-*"

View File

@ -0,0 +1,54 @@
// 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.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class TempDataInCookiesTest : TempDataTestBase, IClassFixture<MvcTestFixture<BasicWebSite.StartupWithCookieTempDataProvider>>
{
private const int DefaultChunkSize = 4070;
public TempDataInCookiesTest(MvcTestFixture<BasicWebSite.StartupWithCookieTempDataProvider> fixture)
{
Client = fixture.Client;
}
protected override HttpClient Client { get; }
[Theory]
[InlineData(DefaultChunkSize)]
[InlineData(DefaultChunkSize * 1.5)]
[InlineData(DefaultChunkSize * 2)]
[InlineData(DefaultChunkSize * 3)]
public async Task RoundTripLargeData_WorksWithChunkingCookies(int size)
{
// Arrange
var character = 'a';
var expected = new string(character, size);
// Act 1
var response = await Client.GetAsync($"/TempData/SetLargeValueInTempData?size={size}&character={character}");
// Assert 1
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Act 2
response = await Client.SendAsync(GetRequest("/TempData/GetLargeValueFromTempData", response));
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal(expected, body);
// Act 3
response = await Client.SendAsync(GetRequest("/TempData/GetLargeValueFromTempData", response));
// Assert 3
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
}
}

View File

@ -0,0 +1,18 @@
// 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.Net.Http;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class TempDataInSessionTest : TempDataTestBase, IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
{
public TempDataInSessionTest(MvcTestFixture<BasicWebSite.Startup> fixture)
{
Client = fixture.Client;
}
protected override HttpClient Client { get; }
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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;
@ -7,24 +7,17 @@ using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Testing.xunit;
using Microsoft.Net.Http.Headers;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
{
public class TempDataTest : IClassFixture<MvcTestFixture<BasicWebSite.Startup>>
public abstract class TempDataTestBase
{
public TempDataTest(MvcTestFixture<BasicWebSite.Startup> fixture)
{
Client = fixture.Client;
}
public HttpClient Client { get; }
protected abstract HttpClient Client { get; }
[Fact]
public async Task TempData_PersistsJustForNextRequest()
public async Task PersistsJustForNextRequest()
{
// Arrange
var nameValueCollection = new List<KeyValuePair<string, string>>
@ -40,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Act 2
response = await Client.SendAsync(GetRequest("TempData/GetTempData", response));
response = await Client.SendAsync(GetRequest("/TempData/GetTempData", response));
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@ -48,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("Foo", body);
// Act 3
response = await Client.SendAsync(GetRequest("TempData/GetTempData", response));
response = await Client.SendAsync(GetRequest("/TempData/GetTempData", response));
// Assert 3
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
@ -65,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
var content = new FormUrlEncodedContent(nameValueCollection);
// Act
var response = await Client.PostAsync("http://localhost/TempData/DisplayTempData", content);
var response = await Client.PostAsync("/TempData/DisplayTempData", content);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@ -73,9 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("Foo", body);
}
[ConditionalFact]
// Mono issue - https://github.com/aspnet/External/issues/21
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
[Fact]
public async Task Redirect_RetainsTempData_EvenIfAccessed()
{
// Arrange
@ -139,10 +130,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal("Foo", body);
}
[ConditionalFact]
// Mono issue - https://github.com/aspnet/External/issues/21
[FrameworkSkipCondition(RuntimeFrameworks.Mono)]
public async Task TempData_ValidTypes_RoundTripProperly()
[Fact]
public async Task ValidTypes_RoundTripProperly()
{
// Arrange
var testGuid = Guid.NewGuid();
@ -173,17 +162,52 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal($"Foo 10 3 10/10/2010 00:00:00 {testGuid.ToString()}", body);
}
private HttpRequestMessage GetRequest(string path, HttpResponseMessage response)
[Fact]
public async Task SetInActionResultExecution_AvailableForNextRequest()
{
// Arrange
var nameValueCollection = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("Name", "Jordan"),
};
var content = new FormUrlEncodedContent(nameValueCollection);
// Act 1
var response = await Client.GetAsync("/TempData/SetTempDataInActionResult");
// Assert 1
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Act 2
response = await Client.SendAsync(GetRequest("/TempData/GetTempDataSetInActionResult", response));
// Assert 2
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadAsStringAsync();
Assert.Equal("Michael", body);
// Act 3
response = await Client.SendAsync(GetRequest("/TempData/GetTempDataSetInActionResult", response));
// Assert 3
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
public HttpRequestMessage GetRequest(string path, HttpResponseMessage response)
{
var request = new HttpRequestMessage(HttpMethod.Get, path);
IEnumerable<string> values;
if (response.Headers.TryGetValues("Set-Cookie", out values))
{
var cookie = SetCookieHeaderValue.ParseList(values.ToList()).First();
request.Headers.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
foreach (var cookie in SetCookieHeaderValue.ParseList(values.ToList()))
{
if (cookie.Expires == null || cookie.Expires >= DateTimeOffset.UtcNow)
{
request.Headers.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
}
}
}
return request;
}
}
}
}

View File

@ -1,7 +1,10 @@
// 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 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.Routing;
@ -12,85 +15,335 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public class SaveTempDataFilterTest
{
[Fact]
public void SaveTempDataFilter_OnResourceExecuted_SavesTempData()
public static TheoryData<IActionResult> ActionResultsData
{
// Arrange
var tempData = new Mock<ITempDataDictionary>(MockBehavior.Strict);
tempData
.Setup(m => m.Save())
.Verifiable();
var tempDataFactory = new Mock<ITempDataDictionaryFactory>(MockBehavior.Strict);
tempDataFactory
.Setup(f => f.GetTempData(It.IsAny<HttpContext>()))
.Returns(tempData.Object);
var filter = new SaveTempDataFilter(tempDataFactory.Object);
var context = new ResourceExecutedContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
new IFilterMetadata[] { });
// Act
filter.OnResourceExecuted(context);
// Assert
tempData.Verify();
get
{
return new TheoryData<IActionResult>()
{
new TestActionResult(),
new TestKeepTempDataActionResult()
};
}
}
[Fact]
public void SaveTempDataFilter_OnResultExecuted_KeepsTempData_ForIKeepTempDataResult()
public void OnResultExecuting_RegistersOnStartingCallback()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>(MockBehavior.Strict);
tempData
.Setup(m => m.Keep())
var responseFeature = new Mock<IHttpResponseFeature>(MockBehavior.Strict);
responseFeature
.Setup(rf => rf.OnStarting(It.IsAny<System.Func<object, Task>>(), It.IsAny<object>()))
.Verifiable();
var tempDataFactory = new Mock<ITempDataDictionaryFactory>(MockBehavior.Strict);
tempDataFactory
.Setup(f => f.GetTempData(It.IsAny<HttpContext>()))
.Returns(tempData.Object);
.Verifiable();
var filter = new SaveTempDataFilter(tempDataFactory.Object);
var httpContext = GetHttpContext(responseFeature.Object);
var context = GetResultExecutingContext(httpContext);
var context = new ResultExecutedContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
new IFilterMetadata[] { },
new Mock<IKeepTempDataResult>().Object,
new object());
// Act
filter.OnResultExecuting(context);
// Assert
responseFeature.Verify();
tempDataFactory.Verify(tdf => tdf.GetTempData(It.IsAny<HttpContext>()), Times.Never());
}
[Fact]
public async Task OnResultExecuting_DoesNotSaveTempData_WhenTempDataAlreadySaved()
{
// Arrange
var responseFeature = new TestResponseFeature();
var httpContext = GetHttpContext(responseFeature);
httpContext.Items[SaveTempDataFilter.TempDataSavedKey] = true; // indicate that tempdata was already saved
var tempDataFactory = new Mock<ITempDataDictionaryFactory>(MockBehavior.Strict);
tempDataFactory
.Setup(f => f.GetTempData(It.IsAny<HttpContext>()))
.Verifiable();
var filter = new SaveTempDataFilter(tempDataFactory.Object);
var context = GetResultExecutingContext(httpContext);
filter.OnResultExecuting(context); // registers callback
// Act
await responseFeature.FireOnSendingHeadersAsync();
// Assert
tempDataFactory.Verify(tdf => tdf.GetTempData(It.IsAny<HttpContext>()), Times.Never());
}
[Theory]
[MemberData(nameof(ActionResultsData))]
public async Task OnResultExecuting_SavesTempData_WhenTempData_NotSavedAlready(IActionResult result)
{
// Arrange
var tempDataDictionary = GetTempDataDictionary();
var filter = GetFilter(tempDataDictionary.Object);
var responseFeature = new TestResponseFeature();
var actionContext = GetActionContext(GetHttpContext(responseFeature));
var context = GetResultExecutingContext(actionContext, result);
filter.OnResultExecuting(context); // registers callback
// Act
await responseFeature.FireOnSendingHeadersAsync();
// Assert
tempDataDictionary.Verify(tdd => tdd.Save(), Times.Once());
}
[Fact]
public async Task OnResultExecuting_KeepsTempData_ForIKeepTempDataResult()
{
// Arrange
var tempDataDictionary = GetTempDataDictionary();
var filter = GetFilter(tempDataDictionary.Object);
var responseFeature = new TestResponseFeature();
var actionContext = GetActionContext(GetHttpContext(responseFeature));
var context = GetResultExecutingContext(actionContext, new TestKeepTempDataActionResult());
filter.OnResultExecuting(context); // registers callback
// Act
await responseFeature.FireOnSendingHeadersAsync();
// Assert
tempDataDictionary.Verify(tdf => tdf.Keep(), Times.Once());
tempDataDictionary.Verify(tdf => tdf.Save(), Times.Once());
}
[Fact]
public async Task OnResultExecuting_DoesNotKeepTempData_ForNonIKeepTempDataResult()
{
// Arrange
var tempDataDictionary = GetTempDataDictionary();
var filter = GetFilter(tempDataDictionary.Object);
var responseFeature = new TestResponseFeature();
var actionContext = GetActionContext(GetHttpContext(responseFeature));
var context = GetResultExecutingContext(actionContext, new TestActionResult());
filter.OnResultExecuting(context); // registers callback
// Act
await responseFeature.FireOnSendingHeadersAsync();
// Assert
tempDataDictionary.Verify(tdf => tdf.Keep(), Times.Never());
tempDataDictionary.Verify(tdf => tdf.Save(), Times.Once());
}
[Fact]
public void OnResultExecuted_DoesNotSaveTempData_WhenResponseHas_AlreadyStarted()
{
// Arrange
var tempDataFactory = new Mock<ITempDataDictionaryFactory>(MockBehavior.Strict);
tempDataFactory
.Setup(f => f.GetTempData(It.IsAny<HttpContext>()))
.Verifiable();
var filter = new SaveTempDataFilter(tempDataFactory.Object);
var httpContext = GetHttpContext(new TestResponseFeature(hasStarted: true));
var context = GetResultExecutedContext(httpContext);
// Act
filter.OnResultExecuted(context);
// Assert
tempData.Verify();
tempDataFactory.Verify(tdf => tdf.GetTempData(It.IsAny<HttpContext>()), Times.Never());
}
[Theory]
[MemberData(nameof(ActionResultsData))]
public void OnResultExecuted_SavesTempData_WhenResponseHas_NotStarted(IActionResult result)
{
// Arrange
var tempDataDictionary = GetTempDataDictionary();
var filter = GetFilter(tempDataDictionary.Object);
var context = GetResultExecutedContext(actionResult: result);
// Act
filter.OnResultExecuted(context);
// Assert
tempDataDictionary.Verify(tdf => tdf.Save(), Times.Once());
}
[Fact]
public void SaveTempDataFilter_OnResultExecuted_DoesNotKeepTempData_ForNonIKeepTempDataResult()
public void OnResultExecuted_KeepsTempData_ForIKeepTempDataResult()
{
// Arrange
var tempData = new Mock<ITempDataDictionary>(MockBehavior.Strict);
var tempDataDictionary = GetTempDataDictionary();
var filter = GetFilter(tempDataDictionary.Object);
var context = GetResultExecutedContext(actionResult: new TestKeepTempDataActionResult());
// Act
filter.OnResultExecuted(context);
// Assert
tempDataDictionary.Verify(tdf => tdf.Keep(), Times.Once());
tempDataDictionary.Verify(tdf => tdf.Save(), Times.Once());
}
[Fact]
public void OnResultExecuted_DoesNotKeepTempData_ForNonIKeepTempDataResult()
{
// Arrange
var tempDataDictionary = GetTempDataDictionary();
var filter = GetFilter(tempDataDictionary.Object);
var context = GetResultExecutedContext(actionResult: new TestActionResult());
// Act
filter.OnResultExecuted(context);
// Assert
tempDataDictionary.Verify(tdf => tdf.Keep(), Times.Never());
tempDataDictionary.Verify(tdf => tdf.Save(), Times.Once());
}
private SaveTempDataFilter GetFilter(ITempDataDictionary tempDataDictionary)
{
var tempDataFactory = GetTempDataDictionaryFactory(tempDataDictionary);
return new SaveTempDataFilter(tempDataFactory.Object);
}
private Mock<ITempDataDictionaryFactory> GetTempDataDictionaryFactory(ITempDataDictionary tempDataDictionary)
{
var tempDataFactory = new Mock<ITempDataDictionaryFactory>(MockBehavior.Strict);
tempDataFactory
.Setup(f => f.GetTempData(It.IsAny<HttpContext>()))
.Returns(tempData.Object);
.Returns(tempDataDictionary);
return tempDataFactory;
}
var filter = new SaveTempDataFilter(tempDataFactory.Object);
private Mock<ITempDataDictionary> GetTempDataDictionary()
{
var tempDataDictionary = new Mock<ITempDataDictionary>(MockBehavior.Strict);
tempDataDictionary
.Setup(tdd => tdd.Keep())
.Verifiable();
tempDataDictionary
.Setup(tdd => tdd.Save())
.Verifiable();
return tempDataDictionary;
}
var context = new ResultExecutedContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()),
private ResultExecutedContext GetResultExecutedContext(HttpContext httpContext = null, IActionResult actionResult = null)
{
if (httpContext == null)
{
httpContext = GetHttpContext();
}
if (actionResult == null)
{
actionResult = new TestActionResult();
}
return new ResultExecutedContext(
new ActionContext(httpContext, new RouteData(), new ActionDescriptor()),
new IFilterMetadata[] { },
actionResult,
new TestController());
}
private ResultExecutingContext GetResultExecutingContext(ActionContext actionContext, IActionResult actionResult = null)
{
if (actionResult == null)
{
actionResult = new TestActionResult();
}
return new ResultExecutingContext(
actionContext,
new IFilterMetadata[] { },
actionResult,
new TestController());
}
private ResultExecutingContext GetResultExecutingContext(HttpContext httpContext = null, IActionResult actionResult = null)
{
if (httpContext == null)
{
httpContext = new DefaultHttpContext();
}
if (actionResult == null)
{
actionResult = new TestActionResult();
}
return new ResultExecutingContext(
new ActionContext(httpContext, new RouteData(), new ActionDescriptor()),
new IFilterMetadata[] { },
new Mock<IActionResult>().Object,
new object());
new TestController());
}
// Act
filter.OnResultExecuted(context);
private HttpContext GetHttpContext(IHttpResponseFeature responseFeature = null)
{
if (responseFeature == null)
{
responseFeature = new TestResponseFeature();
}
var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IHttpResponseFeature>(responseFeature);
return httpContext;
}
// Assert - The mock will throw if we do the wrong thing.
private ActionContext GetActionContext(HttpContext httpContext = null)
{
if (httpContext == null)
{
httpContext = GetHttpContext();
}
return new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
}
private class TestController : Controller
{
}
private class TestActionResult : IActionResult
{
public Task ExecuteResultAsync(ActionContext context)
{
return context.HttpContext.Response.WriteAsync($"Hello from {nameof(TestActionResult)}");
}
}
private class TestKeepTempDataActionResult : IActionResult, IKeepTempDataResult
{
public Task ExecuteResultAsync(ActionContext context)
{
return context.HttpContext.Response.WriteAsync($"Hello from {nameof(TestKeepTempDataActionResult)}");
}
}
private class TestResponseFeature : HttpResponseFeature
{
private bool _hasStarted;
private Func<Task> _responseStartingAsync = () => Task.FromResult(true);
public TestResponseFeature(bool hasStarted = false)
{
_hasStarted = hasStarted;
}
public override bool HasStarted
{
get
{
return _hasStarted;
}
}
public override void OnStarting(Func<object, Task> callback, object state)
{
var prior = _responseStartingAsync;
_responseStartingAsync = async () =>
{
await callback(state);
await prior();
};
}
public async Task FireOnSendingHeadersAsync()
{
await _responseStartingAsync();
_hasStarted = true;
}
}
}
}

View File

@ -0,0 +1,146 @@
// 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 System.Collections.Generic;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public class TempDataSerializerTest
{
public static TheoryData<object, Type> InvalidTypes
{
get
{
return new TheoryData<object, Type>
{
{ new object(), typeof(object) },
{ new object[3], typeof(object) },
{ new TestItem(), typeof(TestItem) },
{ new List<TestItem>(), typeof(TestItem) },
{ new Dictionary<string, TestItem>(), typeof(TestItem) },
};
}
}
[Theory]
[MemberData(nameof(InvalidTypes))]
public void EnsureObjectCanBeSerialized_ThrowsException_OnInvalidType(object value, Type type)
{
// Arrange
var testProvider = new TempDataSerializer();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
{
testProvider.EnsureObjectCanBeSerialized(value);
});
Assert.Equal($"The '{typeof(TempDataSerializer).FullName}' cannot serialize " +
$"an object of type '{type}'.",
exception.Message);
}
public static TheoryData<object, Type> InvalidDictionaryTypes
{
get
{
return new TheoryData<object, Type>
{
{ new Dictionary<int, string>(), typeof(int) },
{ new Dictionary<Uri, Guid>(), typeof(Uri) },
{ new Dictionary<object, string>(), typeof(object) },
{ new Dictionary<TestItem, TestItem>(), typeof(TestItem) }
};
}
}
[Theory]
[MemberData(nameof(InvalidDictionaryTypes))]
public void EnsureObjectCanBeSerialized_ThrowsException_OnInvalidDictionaryType(object value, Type type)
{
// Arrange
var testProvider = new TempDataSerializer();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
{
testProvider.EnsureObjectCanBeSerialized(value);
});
Assert.Equal($"The '{typeof(TempDataSerializer).FullName}' cannot serialize a dictionary " +
$"with a key of type '{type}'.",
exception.Message);
}
public static TheoryData<object> ValidTypes
{
get
{
return new TheoryData<object>
{
{ 10 },
{ new int[]{ 10, 20 } },
{ "FooValue" },
{ new Uri("http://Foo") },
{ Guid.NewGuid() },
{ new List<string> { "foo", "bar" } },
{ new DateTimeOffset() },
{ 100.1m },
{ new Dictionary<string, int>() },
{ new Uri[] { new Uri("http://Foo"), new Uri("http://Bar") } },
{ DayOfWeek.Sunday },
};
}
}
[Theory]
[MemberData(nameof(ValidTypes))]
public void EnsureObjectCanBeSerialized_DoesNotThrow_OnValidType(object value)
{
// Arrange
var testProvider = new TempDataSerializer();
// Act & Assert (Does not throw)
testProvider.EnsureObjectCanBeSerialized(value);
}
[Fact]
public void DeserializeTempData_ReturnsEmptyDictionary_DataIsEmpty()
{
// Arrange
var serializer = new TempDataSerializer();
// Act
var tempDataDictionary = serializer.Deserialize(new byte[0]);
// Assert
Assert.NotNull(tempDataDictionary);
Assert.Empty(tempDataDictionary);
}
[Fact]
public void SerializeAndDeserialize_NullValue_RoundTripsSuccessfully()
{
// Arrange
var key = "NullKey";
var testProvider = new TempDataSerializer();
var input = new Dictionary<string, object>
{
{ key, null }
};
// Act
var bytes = testProvider.Serialize(input);
var values = testProvider.Deserialize(bytes);
// Assert
Assert.True(values.ContainsKey(key));
Assert.Null(values[key]);
}
private class TestItem
{
public int DummyInt { get; set; }
}
}
}

View File

@ -0,0 +1,596 @@
// 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 System.Collections;
using System.Collections.Generic;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
{
public class CookieTempDataProviderTest
{
[Fact]
public void LoadTempData_ReturnsEmptyDictionary_WhenNoCookieDataIsAvailable()
{
// Arrange
var tempDataProvider = GetProvider();
// Act
var tempDataDictionary = tempDataProvider.LoadTempData(new DefaultHttpContext());
// Assert
Assert.NotNull(tempDataDictionary);
Assert.Empty(tempDataDictionary);
}
[Fact]
public void LoadTempData_Base64DecodesAnd_UnprotectsData_FromCookie()
{
// Arrange
var expectedValues = new Dictionary<string, object>();
expectedValues.Add("int", 10);
var tempDataProviderSerializer = new TempDataSerializer();
var expectedDataToUnprotect = tempDataProviderSerializer.Serialize(expectedValues);
var base64EncodedDataInCookie = Convert.ToBase64String(expectedDataToUnprotect);
var dataProtector = new PassThroughDataProtector();
var tempDataProvider = GetProvider(dataProtector);
var requestCookies = new RequestCookieCollection(new Dictionary<string, string>()
{
{ CookieTempDataProvider.CookieName, base64EncodedDataInCookie }
});
var httpContext = new Mock<HttpContext>();
httpContext
.Setup(hc => hc.Request.Cookies)
.Returns(requestCookies);
// Act
var actualValues = tempDataProvider.LoadTempData(httpContext.Object);
// Assert
Assert.Equal(expectedDataToUnprotect, dataProtector.DataToUnprotect);
Assert.Equal(expectedValues, actualValues);
}
[Fact]
public void SaveTempData_ProtectsAnd_Base64EncodesDataAnd_SetsCookie()
{
// Arrange
var values = new Dictionary<string, object>();
values.Add("int", 10);
var tempDataProviderStore = new TempDataSerializer();
var expectedDataToProtect = tempDataProviderStore.Serialize(values);
var expectedDataInCookie = Convert.ToBase64String(expectedDataToProtect);
var dataProtector = new PassThroughDataProtector();
var tempDataProvider = GetProvider(dataProtector);
var responseCookies = new MockResponseCookieCollection();
var httpContext = new Mock<HttpContext>();
httpContext
.SetupGet(hc => hc.Request.PathBase)
.Returns("/");
httpContext
.Setup(hc => hc.Response.Cookies)
.Returns(responseCookies);
// Act
tempDataProvider.SaveTempData(httpContext.Object, values);
// Assert
Assert.Equal(1, responseCookies.Count);
var cookieInfo = responseCookies[CookieTempDataProvider.CookieName];
Assert.NotNull(cookieInfo);
Assert.Equal(expectedDataInCookie, cookieInfo.Value);
Assert.Equal(expectedDataToProtect, dataProtector.PlainTextToProtect);
}
[Theory]
[InlineData("/")]
[InlineData("/vdir1")]
public void SaveTempData_DefaultProviderOptions_SetsCookie_WithAppropriateCookieOptions(string pathBase)
{
// Arrange
var values = new Dictionary<string, object>();
values.Add("int", 10);
var tempDataProviderStore = new TempDataSerializer();
var expectedDataToProtect = tempDataProviderStore.Serialize(values);
var expectedDataInCookie = Convert.ToBase64String(expectedDataToProtect);
var dataProtector = new PassThroughDataProtector();
var tempDataProvider = GetProvider(dataProtector);
var responseCookies = new MockResponseCookieCollection();
var httpContext = new Mock<HttpContext>();
httpContext
.SetupGet(hc => hc.Request.PathBase)
.Returns(pathBase);
httpContext
.Setup(hc => hc.Response.Cookies)
.Returns(responseCookies);
// Act
tempDataProvider.SaveTempData(httpContext.Object, values);
// Assert
Assert.Equal(1, responseCookies.Count);
var cookieInfo = responseCookies[CookieTempDataProvider.CookieName];
Assert.NotNull(cookieInfo);
Assert.Equal(expectedDataInCookie, cookieInfo.Value);
Assert.Equal(expectedDataToProtect, dataProtector.PlainTextToProtect);
Assert.Equal(pathBase, cookieInfo.Options.Path);
Assert.True(cookieInfo.Options.Secure);
Assert.True(cookieInfo.Options.HttpOnly);
Assert.Null(cookieInfo.Options.Expires);
Assert.Null(cookieInfo.Options.Domain);
}
[Theory]
[InlineData("/", null, null, "/", null)]
[InlineData("/", "/vdir1", null, "/vdir1", null)]
[InlineData("/", "/vdir1", ".abc.com", "/vdir1", ".abc.com")]
[InlineData("/vdir1", "/", ".abc.com", "/", ".abc.com")]
public void SaveTempData_CustomProviderOptions_SetsCookie_WithAppropriateCookieOptions(
string requestPathBase, string optionsPath, string optionsDomain, string expectedCookiePath, string expectedDomain)
{
// Arrange
var values = new Dictionary<string, object>();
values.Add("int", 10);
var tempDataProviderStore = new TempDataSerializer();
var expectedDataToProtect = tempDataProviderStore.Serialize(values);
var expectedDataInCookie = Convert.ToBase64String(expectedDataToProtect);
var dataProtector = new PassThroughDataProtector();
var tempDataProvider = GetProvider(
dataProtector,
new CookieTempDataProviderOptions() { Path = optionsPath, Domain = optionsDomain });
var responseCookies = new MockResponseCookieCollection();
var httpContext = new Mock<HttpContext>();
httpContext
.SetupGet(hc => hc.Request.PathBase)
.Returns(requestPathBase);
httpContext
.Setup(hc => hc.Response.Cookies)
.Returns(responseCookies);
// Act
tempDataProvider.SaveTempData(httpContext.Object, values);
// Assert
Assert.Equal(1, responseCookies.Count);
var cookieInfo = responseCookies[CookieTempDataProvider.CookieName];
Assert.NotNull(cookieInfo);
Assert.Equal(expectedDataInCookie, cookieInfo.Value);
Assert.Equal(expectedDataToProtect, dataProtector.PlainTextToProtect);
Assert.Equal(expectedCookiePath, cookieInfo.Options.Path);
Assert.Equal(expectedDomain, cookieInfo.Options.Domain);
Assert.True(cookieInfo.Options.Secure);
Assert.True(cookieInfo.Options.HttpOnly);
Assert.Null(cookieInfo.Options.Expires);
}
[Fact]
public void SaveTempData_RemovesCookie_WhenNoDataToSave()
{
// Arrange
var values = new Dictionary<string, object>();
values.Add("int", 10);
var tempDataProviderStore = new TempDataSerializer();
var serializedData = tempDataProviderStore.Serialize(values);
var base64EncodedData = Convert.ToBase64String(serializedData);
var dataProtector = new PassThroughDataProtector();
var tempDataProvider = GetProvider(dataProtector);
var requestCookies = new RequestCookieCollection(new Dictionary<string, string>()
{
{ CookieTempDataProvider.CookieName, base64EncodedData }
});
var responseCookies = new MockResponseCookieCollection();
var httpContext = new Mock<HttpContext>();
httpContext
.SetupGet(hc => hc.Request.PathBase)
.Returns("/");
httpContext
.Setup(hc => hc.Request.Cookies)
.Returns(requestCookies);
httpContext
.Setup(hc => hc.Response.Cookies)
.Returns(responseCookies);
httpContext
.Setup(hc => hc.Response.Headers)
.Returns(new HeaderDictionary());
// Act
tempDataProvider.SaveTempData(httpContext.Object, new Dictionary<string, object>());
// Assert
Assert.Equal(1, responseCookies.Count);
var cookie = responseCookies[CookieTempDataProvider.CookieName];
Assert.NotNull(cookie);
Assert.Equal(string.Empty, cookie.Value);
Assert.NotNull(cookie.Options.Expires);
Assert.True(cookie.Options.Expires.Value < DateTimeOffset.Now); // expired cookie
}
[Fact]
public void SaveAndLoad_StringCanBeStoredAndLoaded()
{
// Arrange
var testProvider = GetProvider();
var input = new Dictionary<string, object>
{
{ "string", "value" }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var stringVal = Assert.IsType<string>(TempData["string"]);
Assert.Equal("value", stringVal);
}
[Theory]
[InlineData(10)]
[InlineData(int.MaxValue)]
[InlineData(int.MinValue)]
public void SaveAndLoad_IntCanBeStoredAndLoaded(int expected)
{
// Arrange
var testProvider = GetProvider();
var input = new Dictionary<string, object>
{
{ "int", expected }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var intVal = Assert.IsType<int>(TempData["int"]);
Assert.Equal(expected, intVal);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void SaveAndLoad_BoolCanBeStoredAndLoaded(bool value)
{
// Arrange
var testProvider = GetProvider();
var input = new Dictionary<string, object>
{
{ "bool", value }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var boolVal = Assert.IsType<bool>(TempData["bool"]);
Assert.Equal(value, boolVal);
}
[Fact]
public void SaveAndLoad_DateTimeCanBeStoredAndLoaded()
{
// Arrange
var testProvider = GetProvider();
var inputDatetime = new DateTime(2010, 12, 12, 1, 2, 3, DateTimeKind.Local);
var input = new Dictionary<string, object>
{
{ "DateTime", inputDatetime }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var datetime = Assert.IsType<DateTime>(TempData["DateTime"]);
Assert.Equal(inputDatetime, datetime);
}
[Fact]
public void SaveAndLoad_GuidCanBeStoredAndLoaded()
{
// Arrange
var testProvider = GetProvider();
var inputGuid = Guid.NewGuid();
var input = new Dictionary<string, object>
{
{ "Guid", inputGuid }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var guidVal = Assert.IsType<Guid>(TempData["Guid"]);
Assert.Equal(inputGuid, guidVal);
}
[Fact]
public void SaveAndLoad_EnumCanBeSavedAndLoaded()
{
// Arrange
var key = "EnumValue";
var testProvider = GetProvider();
var expected = DayOfWeek.Friday;
var input = new Dictionary<string, object>
{
{ key, expected }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
var result = TempData[key];
// Assert
var actual = (DayOfWeek)result;
Assert.Equal(expected, actual);
}
[Theory]
[InlineData(3100000000L)]
[InlineData(-3100000000L)]
public void SaveAndLoad_LongCanBeSavedAndLoaded(long expected)
{
// Arrange
var key = "LongValue";
var testProvider = GetProvider();
var input = new Dictionary<string, object>
{
{ key, expected }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
var result = TempData[key];
// Assert
var actual = Assert.IsType<long>(result);
Assert.Equal(expected, actual);
}
[Fact]
public void SaveAndLoad_ListCanBeStoredAndLoaded()
{
// Arrange
var testProvider = GetProvider();
var input = new Dictionary<string, object>
{
{ "List`string", new List<string> { "one", "two" } }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var list = (IList<string>)TempData["List`string"];
Assert.Equal(2, list.Count);
Assert.Equal("one", list[0]);
Assert.Equal("two", list[1]);
}
[Fact]
public void SaveAndLoad_DictionaryCanBeStoredAndLoaded()
{
// Arrange
var testProvider = GetProvider();
var inputDictionary = new Dictionary<string, string>
{
{ "Hello", "World" },
};
var input = new Dictionary<string, object>
{
{ "Dictionary", inputDictionary }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var dictionary = Assert.IsType<Dictionary<string, string>>(TempData["Dictionary"]);
Assert.Equal("World", dictionary["Hello"]);
}
[Fact]
public void SaveAndLoad_EmptyDictionary_RoundTripsAsNull()
{
// Arrange
var testProvider = GetProvider();
var input = new Dictionary<string, object>
{
{ "EmptyDictionary", new Dictionary<string, int>() }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
UpdateRequestWithCookies(context);
var TempData = testProvider.LoadTempData(context);
// Assert
var emptyDictionary = (IDictionary<string, int>)TempData["EmptyDictionary"];
Assert.Null(emptyDictionary);
}
private static HttpContext GetHttpContext()
{
var context = new Mock<HttpContext>();
context
.SetupGet(hc => hc.Request.PathBase)
.Returns("/");
context
.SetupGet(hc => hc.Response.Cookies)
.Returns(new MockResponseCookieCollection());
return context.Object;
}
private void UpdateRequestWithCookies(HttpContext httpContext)
{
var responseCookies = (MockResponseCookieCollection)httpContext.Response.Cookies;
var values = new Dictionary<string, string>();
foreach (var responseCookie in responseCookies)
{
values.Add(responseCookie.Key, responseCookie.Value);
}
if (values.Count > 0)
{
httpContext.Request.Cookies = new RequestCookieCollection(values);
}
}
private class MockResponseCookieCollection : IResponseCookies, IEnumerable<CookieInfo>
{
private Dictionary<string, CookieInfo> _cookies = new Dictionary<string, CookieInfo>(StringComparer.OrdinalIgnoreCase);
public int Count
{
get
{
return _cookies.Count;
}
}
public CookieInfo this[string key]
{
get
{
return _cookies[key];
}
}
public void Append(string key, string value, CookieOptions options)
{
_cookies[key] = new CookieInfo()
{
Key = key,
Value = value,
Options = options
};
}
public void Append(string key, string value)
{
Append(key, value, new CookieOptions());
}
public void Delete(string key, CookieOptions options)
{
_cookies.Remove(key);
}
public void Delete(string key)
{
_cookies.Remove(key);
}
public IEnumerator<CookieInfo> GetEnumerator()
{
return _cookies.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private CookieTempDataProvider GetProvider(IDataProtector dataProtector = null, CookieTempDataProviderOptions options = null)
{
if(dataProtector == null)
{
dataProtector = new PassThroughDataProtector();
}
if(options == null)
{
options = new CookieTempDataProviderOptions();
}
var testOptions = new Mock<IOptions<CookieTempDataProviderOptions>>();
testOptions.SetupGet(o => o.Value).Returns(options);
return new CookieTempDataProvider(new PassThroughDataProtectionProvider(dataProtector), testOptions.Object);
}
private class PassThroughDataProtectionProvider : IDataProtectionProvider
{
private readonly IDataProtector _dataProtector;
public PassThroughDataProtectionProvider(IDataProtector dataProtector)
{
_dataProtector = dataProtector;
}
public IDataProtector CreateProtector(string purpose)
{
return _dataProtector;
}
}
private class PassThroughDataProtector : IDataProtector
{
public byte[] DataToUnprotect { get; private set; }
public byte[] PlainTextToProtect { get; private set; }
public string Purpose { get; private set; }
public IDataProtector CreateProtector(string purpose)
{
Purpose = purpose;
return this;
}
public byte[] Protect(byte[] plaintext)
{
PlainTextToProtect = plaintext;
return PlainTextToProtect;
}
public byte[] Unprotect(byte[] protectedData)
{
DataToUnprotect = protectedData;
return DataToUnprotect;
}
}
private class CookieInfo
{
public string Key { get; set; }
public string Value { get; set; }
public CookieOptions Options { get; set; }
}
}
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures
@ -54,116 +55,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
Assert.Empty(tempDataDictionary);
}
[Fact]
public void Load_ReturnsEmptyDictionary_WhenSessionDataIsEmpty()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
var httpContext = GetHttpContext();
httpContext.Session.Set(SessionStateTempDataProvider.TempDataSessionStateKey, new byte[] { });
// Act
var tempDataDictionary = testProvider.LoadTempData(httpContext);
// Assert
Assert.Empty(tempDataDictionary);
}
public static TheoryData<object, Type> InvalidTypes
{
get
{
return new TheoryData<object, Type>
{
{ new object(), typeof(object) },
{ new object[3], typeof(object) },
{ new TestItem(), typeof(TestItem) },
{ new List<TestItem>(), typeof(TestItem) },
{ new Dictionary<string, TestItem>(), typeof(TestItem) },
};
}
}
[Theory]
[MemberData(nameof(InvalidTypes))]
public void EnsureObjectCanBeSerialized_ThrowsException_OnInvalidType(object value, Type type)
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
{
testProvider.EnsureObjectCanBeSerialized(value);
});
Assert.Equal($"The '{typeof(SessionStateTempDataProvider).FullName}' cannot serialize " +
$"an object of type '{type}' to session state.",
exception.Message);
}
public static TheoryData<object, Type> InvalidDictionaryTypes
{
get
{
return new TheoryData<object, Type>
{
{ new Dictionary<int, string>(), typeof(int) },
{ new Dictionary<Uri, Guid>(), typeof(Uri) },
{ new Dictionary<object, string>(), typeof(object) },
{ new Dictionary<TestItem, TestItem>(), typeof(TestItem) }
};
}
}
[Theory]
[MemberData(nameof(InvalidDictionaryTypes))]
public void EnsureObjectCanBeSerialized_ThrowsException_OnInvalidDictionaryType(object value, Type type)
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() =>
{
testProvider.EnsureObjectCanBeSerialized(value);
});
Assert.Equal($"The '{typeof(SessionStateTempDataProvider).FullName}' cannot serialize a dictionary " +
$"with a key of type '{type}' to session state.",
exception.Message);
}
public static TheoryData<object> ValidTypes
{
get
{
return new TheoryData<object>
{
{ 10 },
{ new int[]{ 10, 20 } },
{ "FooValue" },
{ new Uri("http://Foo") },
{ Guid.NewGuid() },
{ new List<string> { "foo", "bar" } },
{ new DateTimeOffset() },
{ 100.1m },
{ new Dictionary<string, int>() },
{ new Uri[] { new Uri("http://Foo"), new Uri("http://Bar") } },
{ DayOfWeek.Sunday },
};
}
}
[Theory]
[MemberData(nameof(ValidTypes))]
public void EnsureObjectCanBeSerialized_DoesNotThrow_OnValidType(object value)
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
// Act & Assert (Does not throw)
testProvider.EnsureObjectCanBeSerialized(value);
}
[Fact]
public void SaveAndLoad_StringCanBeStoredAndLoaded()
{
@ -384,26 +275,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures
Assert.Null(emptyDictionary);
}
[Fact]
public void SaveAndLoad_NullValue_RoundTripsSuccessfully()
{
// Arrange
var testProvider = new SessionStateTempDataProvider();
var input = new Dictionary<string, object>
{
{ "NullKey", null }
};
var context = GetHttpContext();
// Act
testProvider.SaveTempData(context, input);
var TempData = testProvider.LoadTempData(context);
// Assert
Assert.True(TempData.ContainsKey("NullKey"));
Assert.Null(TempData["NullKey"]);
}
private class TestItem
{
public int DummyInt { get; set; }

View File

@ -68,5 +68,30 @@ namespace BasicWebSite.Controllers
var value5 = (Guid)TempData["key5"];
return $"{value1} {value2.ToString()} {value3.Count.ToString()} {value4.ToString()} {value5.ToString()}";
}
[HttpGet]
public IActionResult SetTempDataInActionResult()
{
return new StoreIntoTempDataActionResult();
}
[HttpGet]
public string GetTempDataSetInActionResult()
{
return TempData["Name"]?.ToString();
}
[HttpGet]
public IActionResult SetLargeValueInTempData(int size, char character)
{
TempData["LargeValue"] = new string(character, size);
return Ok();
}
[HttpGet]
public string GetLargeValueFromTempData()
{
return TempData["LargeValue"]?.ToString();
}
}
}

View File

@ -0,0 +1,23 @@
// 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.IO;
using Microsoft.AspNetCore.Hosting;
namespace BasicWebSite
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseKestrel()
.UseIISIntegration()
.Build();
host.Run();
}
}
}

View File

@ -53,18 +53,6 @@ namespace BasicWebSite
});
}
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseKestrel()
.UseIISIntegration()
.Build();
host.Run();
}
}
}

View File

@ -0,0 +1,25 @@
// 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.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
namespace BasicWebSite
{
public class StartupWithCookieTempDataProvider
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
}
public void Configure(IApplicationBuilder app)
{
app.UseCultureReplacer();
app.UseMvcWithDefaultRoute();
}
}
}

View File

@ -0,0 +1,25 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace BasicWebSite
{
public class StoreIntoTempDataActionResult : IActionResult
{
public Task ExecuteResultAsync(ActionContext context)
{
// store information in temp data
var httpContext = context.HttpContext;
var tempDataDictionaryFactory = httpContext.RequestServices.GetRequiredService<ITempDataDictionaryFactory>();
var tempDataDictionary = tempDataDictionaryFactory.GetTempData(httpContext);
tempDataDictionary["Name"] = "Michael";
return httpContext.Response.WriteAsync($"Hello from {nameof(StoreIntoTempDataActionResult)}");
}
}
}