This repository has been archived by the owner on Dec 14, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Fixes #5212] Added a cookie based ITempDataProvider
- Loading branch information
Showing
15 changed files
with
1,278 additions
and
379 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
233 changes: 233 additions & 0 deletions
233
src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataSerializer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,233 @@ | ||
// 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.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( | ||
new JsonSerializerSettings() | ||
{ | ||
TypeNameHandling = TypeNameHandling.None | ||
}); | ||
|
||
private static readonly MethodInfo _convertArrayMethodInfo = typeof(TempDataSerializer).GetMethod( | ||
nameof(ConvertArray), BindingFlags.Static | BindingFlags.NonPublic); | ||
private static readonly MethodInfo _convertDictMethodInfo = 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> DeserializeTempData(byte[] value) | ||
{ | ||
var tempDataDictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); | ||
using (var memoryStream = new MemoryStream(value)) | ||
using (var writer = new BsonReader(memoryStream)) | ||
{ | ||
tempDataDictionary = _jsonSerializer.Deserialize<Dictionary<string, object>>(writer); | ||
} | ||
|
||
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; | ||
} | ||
} | ||
} | ||
|
||
return convertedDictionary; | ||
} | ||
|
||
public byte[] SerializeTempData(IDictionary<string, object> values) | ||
{ | ||
var hasValues = (values != null && values.Count > 0); | ||
if (hasValues) | ||
{ | ||
foreach (var item in values.Values) | ||
{ | ||
// 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)); | ||
} | ||
} | ||
} |
16 changes: 8 additions & 8 deletions
16
src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.