Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit a4fd517

Browse files
committed
[Fixes #2276] Serialize only simple types to session in TempData
1 parent fb451b5 commit a4fd517

File tree

8 files changed

+519
-7
lines changed

8 files changed

+519
-7
lines changed

src/Microsoft.AspNet.Mvc.Core/Formatters/JsonContractResolver.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4-
using System;
54
using System.ComponentModel.DataAnnotations;
65
using System.Reflection;
76
using Newtonsoft.Json;

src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.AspNet.Mvc.Core/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,4 +451,13 @@
451451
<data name="ModelType_WrongType" xml:space="preserve">
452452
<value>The model's runtime type '{0}' is not assignable to the type '{1}'.</value>
453453
</data>
454+
<data name="TempData_CannotSerializeToSession" xml:space="preserve">
455+
<value>The '{0}' cannot serialize an object of type '{1}' to session state.</value>
456+
</data>
457+
<data name="TempData_CannotDeserializeToken" xml:space="preserve">
458+
<value>Cannot deserialize {0} of type '{1}'.</value>
459+
</data>
460+
<data name="TempData_CannotSerializeDictionary" xml:space="preserve">
461+
<value>The '{0}' cannot serialize a dictionary with a key of type '{1}' to session state.</value>
462+
</data>
454463
</root>

src/Microsoft.AspNet.Mvc.Core/SessionStateTempDataProvider.cs

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Collections.Concurrent;
56
using System.Collections.Generic;
7+
using System.Diagnostics;
68
using System.IO;
9+
using System.Linq;
10+
using System.Reflection;
711
using Microsoft.AspNet.Http;
12+
using Microsoft.AspNet.Mvc.Core;
813
using Microsoft.Framework.Internal;
914
using Newtonsoft.Json;
1015
using Newtonsoft.Json.Bson;
16+
using Newtonsoft.Json.Linq;
1117

1218
namespace Microsoft.AspNet.Mvc
1319
{
@@ -16,8 +22,34 @@ namespace Microsoft.AspNet.Mvc
1622
/// </summary>
1723
public class SessionStateTempDataProvider : ITempDataProvider
1824
{
19-
private static JsonSerializer jsonSerializer = new JsonSerializer();
20-
private static string TempDataSessionStateKey = "__ControllerTempData";
25+
private const string TempDataSessionStateKey = "__ControllerTempData";
26+
private readonly JsonSerializer _jsonSerializer = JsonSerializer.Create(
27+
new JsonSerializerSettings()
28+
{
29+
TypeNameHandling = TypeNameHandling.None
30+
});
31+
32+
private static readonly MethodInfo _convertArrayMethodInfo = typeof(SessionStateTempDataProvider).GetMethod(
33+
nameof(ConvertArray), BindingFlags.Static | BindingFlags.NonPublic);
34+
private static readonly MethodInfo _convertDictMethodInfo = typeof(SessionStateTempDataProvider).GetMethod(
35+
nameof(ConvertDictionary), BindingFlags.Static | BindingFlags.NonPublic);
36+
37+
private static readonly ConcurrentDictionary<Type, Func<JArray, object>> _arrayConverters =
38+
new ConcurrentDictionary<Type, Func<JArray, object>>();
39+
private static readonly ConcurrentDictionary<Type, Func<JObject, object>> _dictionaryConverters =
40+
new ConcurrentDictionary<Type, Func<JObject, object>>();
41+
42+
private static readonly Dictionary<JTokenType, Type> _tokenTypeLookup = new Dictionary<JTokenType, Type>
43+
{
44+
{ JTokenType.String, typeof(string) },
45+
{ JTokenType.Integer, typeof(int) },
46+
{ JTokenType.Boolean, typeof(bool) },
47+
{ JTokenType.Float, typeof(float) },
48+
{ JTokenType.Guid, typeof(Guid) },
49+
{ JTokenType.Date, typeof(DateTime) },
50+
{ JTokenType.TimeSpan, typeof(TimeSpan) },
51+
{ JTokenType.Uri, typeof(Uri) },
52+
};
2153

2254
/// <inheritdoc />
2355
public virtual IDictionary<string, object> LoadTempData([NotNull] HttpContext context)
@@ -37,9 +69,68 @@ public virtual IDictionary<string, object> LoadTempData([NotNull] HttpContext co
3769
using (var memoryStream = new MemoryStream(value))
3870
using (var writer = new BsonReader(memoryStream))
3971
{
40-
tempDataDictionary = jsonSerializer.Deserialize<Dictionary<string, object>>(writer);
72+
tempDataDictionary = _jsonSerializer.Deserialize<Dictionary<string, object>>(writer);
4173
}
4274

75+
var convertedDictionary = new Dictionary<string, object>(tempDataDictionary, StringComparer.OrdinalIgnoreCase);
76+
foreach (var item in tempDataDictionary)
77+
{
78+
var jArrayValue = item.Value as JArray;
79+
if (jArrayValue != null && jArrayValue.Count > 0)
80+
{
81+
var arrayType = jArrayValue[0].Type;
82+
Type returnType;
83+
if (_tokenTypeLookup.TryGetValue(arrayType, out returnType))
84+
{
85+
var arrayConverter = _arrayConverters.GetOrAdd(returnType, type =>
86+
{
87+
return (Func<JArray, object>)_convertArrayMethodInfo.MakeGenericMethod(type).CreateDelegate(typeof(Func<JArray, object>));
88+
});
89+
var result = arrayConverter(jArrayValue);
90+
91+
convertedDictionary[item.Key] = result;
92+
}
93+
else
94+
{
95+
var message = Resources.FormatTempData_CannotDeserializeToken(nameof(JToken), arrayType);
96+
throw new InvalidOperationException(message);
97+
}
98+
}
99+
else
100+
{
101+
var jObjectValue = item.Value as JObject;
102+
if (jObjectValue == null)
103+
{
104+
continue;
105+
}
106+
else if (!jObjectValue.HasValues)
107+
{
108+
convertedDictionary[item.Key] = null;
109+
continue;
110+
}
111+
112+
var jTokenType = jObjectValue.Properties().First().Value.Type;
113+
Type valueType;
114+
if (_tokenTypeLookup.TryGetValue(jTokenType, out valueType))
115+
{
116+
var dictionaryConverter = _dictionaryConverters.GetOrAdd(valueType, type =>
117+
{
118+
return (Func<JObject, object>)_convertDictMethodInfo.MakeGenericMethod(type).CreateDelegate(typeof(Func<JObject, object>));
119+
});
120+
var result = dictionaryConverter(jObjectValue);
121+
122+
convertedDictionary[item.Key] = result;
123+
}
124+
else
125+
{
126+
var message = Resources.FormatTempData_CannotDeserializeToken(nameof(JToken), jTokenType);
127+
throw new InvalidOperationException(message);
128+
}
129+
}
130+
}
131+
132+
tempDataDictionary = convertedDictionary;
133+
43134
// If we got it from Session, remove it so that no other request gets it
44135
session.Remove(TempDataSessionStateKey);
45136
}
@@ -59,13 +150,19 @@ public virtual void SaveTempData([NotNull] HttpContext context, IDictionary<stri
59150
var hasValues = (values != null && values.Count > 0);
60151
if (hasValues)
61152
{
153+
foreach (var item in values.Values)
154+
{
155+
// We want to allow only simple types to be serialized in session.
156+
EnsureObjectCanBeSerialized(item);
157+
}
158+
62159
// Accessing Session property will throw if the session middleware is not enabled.
63160
var session = context.Session;
64161

65162
using (var memoryStream = new MemoryStream())
66163
using (var writer = new BsonWriter(memoryStream))
67164
{
68-
jsonSerializer.Serialize(writer, values);
165+
_jsonSerializer.Serialize(writer, values);
69166
session[TempDataSessionStateKey] = memoryStream.ToArray();
70167
}
71168
}
@@ -80,5 +177,65 @@ private static bool IsSessionEnabled(HttpContext context)
80177
{
81178
return context.GetFeature<ISessionFeature>() != null;
82179
}
180+
181+
internal void EnsureObjectCanBeSerialized(object item)
182+
{
183+
var itemType = item.GetType();
184+
Type actualType = null;
185+
186+
if (itemType.IsArray)
187+
{
188+
itemType = itemType.GetElementType();
189+
}
190+
else if (itemType.GetTypeInfo().IsGenericType)
191+
{
192+
if (itemType.ExtractGenericInterface(typeof(IList<>)) != null)
193+
{
194+
var genericTypeArguments = itemType.GetGenericArguments();
195+
Debug.Assert(genericTypeArguments.Length == 1, "IList<T> has one generic argument");
196+
actualType = genericTypeArguments[0];
197+
}
198+
else if (itemType.ExtractGenericInterface(typeof(IDictionary<,>)) != null)
199+
{
200+
var genericTypeArguments = itemType.GetGenericArguments();
201+
Debug.Assert(genericTypeArguments.Length == 2, "IDictionary<TKey, TValue> has two generic arguments");
202+
// Throw if the key type of the dictionary is not string.
203+
if (genericTypeArguments[0] != typeof(string))
204+
{
205+
var message = Resources.FormatTempData_CannotSerializeDictionary(
206+
typeof(SessionStateTempDataProvider).FullName, genericTypeArguments[0]);
207+
throw new InvalidOperationException(message);
208+
}
209+
else
210+
{
211+
actualType = genericTypeArguments[1];
212+
}
213+
}
214+
}
215+
216+
actualType = actualType ?? itemType;
217+
if (!TypeHelper.IsSimpleType(actualType))
218+
{
219+
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
220+
var message = Resources.FormatTempData_CannotSerializeToSession(
221+
typeof(SessionStateTempDataProvider).FullName, underlyingType);
222+
throw new InvalidOperationException(message);
223+
}
224+
}
225+
226+
private static IList<TVal> ConvertArray<TVal>(JArray array)
227+
{
228+
return array.Values<TVal>().ToArray();
229+
}
230+
231+
private static IDictionary<string, TVal> ConvertDictionary<TVal>(JObject jObject)
232+
{
233+
var convertedDictionary = new Dictionary<string, TVal>(StringComparer.Ordinal);
234+
foreach (var item in jObject)
235+
{
236+
convertedDictionary.Add(item.Key, jObject.Value<TVal>(item.Key));
237+
}
238+
return convertedDictionary;
239+
}
83240
}
84241
}

src/Microsoft.AspNet.Mvc/MvcServices.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,9 @@ public static IServiceCollection GetDefaultServices()
166166
services.AddTransient<IApiDescriptionProvider, DefaultApiDescriptionProvider>();
167167

168168
// Temp Data
169-
services.AddSingleton<ITempDataProvider, SessionStateTempDataProvider>();
170169
services.AddScoped<ITempDataDictionary, TempDataDictionary>();
170+
// This does caching so it should stay singleton
171+
services.AddSingleton<ITempDataProvider, SessionStateTempDataProvider>();
171172

172173
return services;
173174
}

0 commit comments

Comments
 (0)