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

Commit 2972e6a

Browse files
committed
Addressed feedback and added support for dictionaries
1 parent 75ae46b commit 2972e6a

File tree

5 files changed

+233
-19
lines changed

5 files changed

+233
-19
lines changed

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

Lines changed: 32 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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,10 @@
454454
<data name="TempData_CannotSerializeToSession" xml:space="preserve">
455455
<value>The type {0} cannot be serialized to Session by '{1}'.</value>
456456
</data>
457+
<data name="TempData_CannotDeserializeToken" xml:space="preserve">
458+
<value>Cannot deserialize JToken of type {0}.</value>
459+
</data>
460+
<data name="TempData_CannotSerializeDictionary" xml:space="preserve">
461+
<value>The dictionary with TKey {0} cannot be serialized to Session by '{1}'.</value>
462+
</data>
457463
</root>

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

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@ public class SessionStateTempDataProvider : ITempDataProvider
2929
});
3030

3131
private static readonly MethodInfo _convertArrayMethodInfo = typeof(SessionStateTempDataProvider).GetMethod(
32-
nameof(ConvertArray), BindingFlags.NonPublic | BindingFlags.Static, null, new[] { typeof(JArray) }, null);
32+
nameof(ConvertArray), BindingFlags.Static | BindingFlags.NonPublic);
33+
private static readonly MethodInfo _convertDictMethodInfo = typeof(SessionStateTempDataProvider).GetMethod(
34+
nameof(ConvertDict), BindingFlags.Static | BindingFlags.NonPublic);
3335

34-
private readonly ConcurrentDictionary<Type, Func<JArray, object>> _arrayConverters =
36+
private static readonly ConcurrentDictionary<Type, Func<JArray, object>> _arrayConverters =
3537
new ConcurrentDictionary<Type, Func<JArray, object>>();
38+
private static readonly ConcurrentDictionary<Type, Func<JObject, object>> _dictConverters =
39+
new ConcurrentDictionary<Type, Func<JObject, object>>();
3640

37-
private static Dictionary<JTokenType, Type> _arrayTypeLookup = new Dictionary<JTokenType, Type>
41+
private static readonly Dictionary<JTokenType, Type> _tokenTypeLookup = new Dictionary<JTokenType, Type>
3842
{
3943
{ JTokenType.String, typeof(string) },
4044
{ JTokenType.Integer, typeof(int) },
4145
{ JTokenType.Boolean, typeof(bool) },
4246
{ JTokenType.Float, typeof(float) },
4347
{ JTokenType.Guid, typeof(Guid) },
44-
{ JTokenType.Object, typeof(object) },
4548
{ JTokenType.Date, typeof(DateTime) },
4649
{ JTokenType.TimeSpan, typeof(TimeSpan) },
4750
{ JTokenType.Uri, typeof(Uri) },
@@ -67,27 +70,60 @@ public virtual IDictionary<string, object> LoadTempData([NotNull] HttpContext co
6770
{
6871
tempDataDictionary = _jsonSerializer.Deserialize<Dictionary<string, object>>(writer);
6972
}
70-
foreach (var item in tempDataDictionary.ToList())
73+
74+
var convertedDictionary = new Dictionary<string, object>(tempDataDictionary, StringComparer.OrdinalIgnoreCase);
75+
foreach (var item in tempDataDictionary)
7176
{
7277
var jArrayValue = item.Value as JArray;
7378
if (jArrayValue != null && jArrayValue.Count > 0)
7479
{
75-
Type returnType = null;
76-
_arrayTypeLookup.TryGetValue(jArrayValue[0].Type, out returnType);
77-
if (returnType != null)
80+
Type returnType;
81+
if (_tokenTypeLookup.TryGetValue(jArrayValue[0].Type, out returnType))
7882
{
7983
var arrayConverter = _arrayConverters.GetOrAdd(returnType, type =>
8084
{
81-
return (Func<JArray, object>)Delegate.CreateDelegate(typeof(Func<JArray, object>),
82-
_convertArrayMethodInfo.MakeGenericMethod(type));
85+
return (Func<JArray, object>)_convertArrayMethodInfo.MakeGenericMethod(type).CreateDelegate(typeof(Func<JArray, object>));
8386
});
8487
var result = arrayConverter(jArrayValue);
8588

86-
tempDataDictionary[item.Key] = result;
89+
convertedDictionary[item.Key] = result;
90+
}
91+
else
92+
{
93+
var message = Resources.FormatTempData_CannotDeserializeToken(jArrayValue[0].Type);
94+
throw new InvalidOperationException(message);
95+
}
96+
}
97+
else
98+
{
99+
var jObjectValue = item.Value as JObject;
100+
if (jObjectValue == null || !jObjectValue.HasValues)
101+
{
102+
continue;
103+
}
104+
105+
var jTokenType = jObjectValue.Properties().First().Value.Type;
106+
Type valueType;
107+
if (_tokenTypeLookup.TryGetValue(jTokenType, out valueType))
108+
{
109+
var dictConverter = _dictConverters.GetOrAdd(valueType, type =>
110+
{
111+
return (Func<JObject, object>)_convertDictMethodInfo.MakeGenericMethod(type).CreateDelegate(typeof(Func<JObject, object>));
112+
});
113+
var result = dictConverter(jObjectValue);
114+
115+
convertedDictionary[item.Key] = result;
116+
}
117+
else
118+
{
119+
var message = Resources.FormatTempData_CannotDeserializeToken(jTokenType);
120+
throw new InvalidOperationException(message);
87121
}
88122
}
89123
}
90124

125+
tempDataDictionary = convertedDictionary;
126+
91127
// If we got it from Session, remove it so that no other request gets it
92128
session.Remove(TempDataSessionStateKey);
93129
}
@@ -146,14 +182,23 @@ internal void EnsureObjectCanBeSerialized(object item)
146182
}
147183
else if (itemType.GetTypeInfo().IsGenericType)
148184
{
149-
if (itemType.ExtractGenericInterface(typeof(IList<>)) != null)
185+
if (itemType.ExtractGenericInterface(typeof(IList<>)) != null ||
186+
itemType.ExtractGenericInterface(typeof(IDictionary<,>)) != null)
150187
{
151188
actualTypes = itemType.GetGenericArguments();
152189
}
153190
}
154191

155192
actualTypes = actualTypes ?? new Type[] { itemType };
156193

194+
// Throw if the key type of the dictionary is not string.
195+
if (actualTypes.Length > 1 && actualTypes[0] != typeof(string))
196+
{
197+
var message = Resources.FormatTempData_CannotSerializeDictionary(actualTypes[0],
198+
typeof(SessionStateTempDataProvider).FullName);
199+
throw new InvalidOperationException(message);
200+
}
201+
157202
foreach (var actualType in actualTypes)
158203
{
159204
var underlyingType = Nullable.GetUnderlyingType(actualType) ?? actualType;
@@ -170,5 +215,15 @@ private static IList<TVal> ConvertArray<TVal>(JArray array)
170215
{
171216
return array.Values<TVal>().ToArray();
172217
}
218+
219+
private static IDictionary<string, T> ConvertDict<T>(JObject jObject)
220+
{
221+
var convertedDict = new Dictionary<string, T>();
222+
foreach (var item in jObject)
223+
{
224+
convertedDict.Add(item.Key, jObject.Value<T>(item.Key));
225+
}
226+
return convertedDict;
227+
}
173228
}
174229
}

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
}

test/Microsoft.AspNet.Mvc.Core.Test/SessionStateTempDataProviderTest.cs

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
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;
56
using System.Collections.Generic;
67
using Microsoft.AspNet.Http;
78
using Moq;
@@ -86,11 +87,7 @@ public static TheoryData<object, Type> InvalidTypes
8687
{ new object[3], typeof(object) },
8788
{ new TestItem(), typeof(TestItem) },
8889
{ new List<TestItem>(), typeof(TestItem) },
89-
{ new Dictionary<string, int>(), typeof(Dictionary<string, int>) },
90-
{ new Dictionary<Uri, Guid>(), typeof(Dictionary<Uri, Guid>) },
91-
{ new Dictionary<string, TestItem>(), typeof(Dictionary<string, TestItem>) },
92-
{ new Dictionary<object, string>(), typeof(Dictionary<object, string>) },
93-
{ new Dictionary<TestItem, TestItem>(), typeof(Dictionary<TestItem, TestItem>) }
90+
{ new Dictionary<string, TestItem>(), typeof(TestItem) },
9491
};
9592
}
9693
}
@@ -111,6 +108,36 @@ public void EnsureObjectCanBeSerialized_InvalidType_Throws(object value, Type ty
111108
exception.Message);
112109
}
113110

111+
public static TheoryData<object, Type> InvalidDictionaryTypes
112+
{
113+
get
114+
{
115+
return new TheoryData<object, Type>
116+
{
117+
{ new Dictionary<int, string>(), typeof(int) },
118+
{ new Dictionary<Uri, Guid>(), typeof(Uri) },
119+
{ new Dictionary<object, string>(), typeof(object) },
120+
{ new Dictionary<TestItem, TestItem>(), typeof(TestItem) }
121+
};
122+
}
123+
}
124+
125+
[Theory]
126+
[MemberData(nameof(InvalidDictionaryTypes))]
127+
public void EnsureObjectCanBeSerialized_InvalidDictionaryType_Throws(object value, Type type)
128+
{
129+
// Arrange
130+
var testProvider = new SessionStateTempDataProvider();
131+
132+
// Act & Assert
133+
var exception = Assert.Throws<InvalidOperationException>(() =>
134+
{
135+
testProvider.EnsureObjectCanBeSerialized(value);
136+
});
137+
Assert.Equal($"The dictionary with TKey {type} cannot be serialized to Session by '{typeof(SessionStateTempDataProvider).FullName}'.",
138+
exception.Message);
139+
}
140+
114141
public static TheoryData<object> ValidTypes
115142
{
116143
get
@@ -125,6 +152,7 @@ public static TheoryData<object> ValidTypes
125152
{ new List<string> { "foo", "bar" } },
126153
{ new DateTimeOffset() },
127154
{ 100.1m },
155+
{ new Dictionary<string, int>() },
128156
{ new Uri[] { new Uri("http://Foo"), new Uri("http://Bar") } }
129157
};
130158
}
@@ -141,6 +169,47 @@ public void EnsureObjectCanBeSerialized_ValidType_DoesNotThrow(object value)
141169
testProvider.EnsureObjectCanBeSerialized(value);
142170
}
143171

172+
[Fact]
173+
public void SaveAndLoad_WorksAsExpected()
174+
{
175+
// Arrange
176+
var testProvider = new SessionStateTempDataProvider();
177+
var inputGuid = Guid.NewGuid();
178+
var inputDict = new Dictionary<string, string>
179+
{
180+
{ "Hello", "World" },
181+
};
182+
var input = new Dictionary<string, object>
183+
{
184+
{ "string", "value" },
185+
{ "int", 10 },
186+
{ "bool", false },
187+
{ "DateTime", new DateTime() },
188+
{ "Guid", inputGuid },
189+
{ "List`string", new List<string> { "one", "two" } },
190+
{ "Dictionary", inputDict },
191+
};
192+
var context = GetHttpContext(new TestSessionCollection(), true);
193+
194+
// Act
195+
//System.Diagnostics.Debugger.Launch();
196+
testProvider.SaveTempData(context, input);
197+
var TempData = testProvider.LoadTempData(context);
198+
199+
// Assert
200+
Assert.Equal("value", TempData["string"]);
201+
Assert.Equal(10, Convert.ToInt32(TempData["int"]));
202+
Assert.Equal(false, (bool)TempData["bool"]);
203+
Assert.Equal(new DateTime().ToString(), ((DateTime)TempData["DateTime"]).ToString());
204+
Assert.Equal(inputGuid.ToString(), ((Guid)TempData["Guid"]).ToString());
205+
var list = (IList<string>)TempData["List`string"];
206+
Assert.Equal(2, list.Count);
207+
Assert.Equal("one", list[0]);
208+
Assert.Equal("two", list[1]);
209+
var dict = (IDictionary<string, string>)TempData["Dictionary"];
210+
Assert.Equal("World", dict["Hello"]);
211+
}
212+
144213
private class TestItem
145214
{
146215
public int DummyInt { get; set; }
@@ -157,12 +226,63 @@ private HttpContext GetHttpContext(ISessionCollection session, bool sessionEnabl
157226
{
158227
httpContext.Setup(h => h.Session).Throws<InvalidOperationException>();
159228
}
229+
else
230+
{
231+
httpContext.Setup(h => h.Session[It.IsAny<string>()]);
232+
}
160233
if (sessionEnabled)
161234
{
162235
httpContext.Setup(h => h.GetFeature<ISessionFeature>()).Returns(Mock.Of<ISessionFeature>());
163-
httpContext.Setup(h => h.Session[It.IsAny<string>()]);
164236
}
165237
return httpContext.Object;
166238
}
239+
240+
private class TestSessionCollection : ISessionCollection
241+
{
242+
private Dictionary<string, byte[]> _innerDict = new Dictionary<string, byte[]>();
243+
244+
public byte[] this[string key]
245+
{
246+
get
247+
{
248+
return _innerDict[key];
249+
}
250+
251+
set
252+
{
253+
_innerDict[key] = value;
254+
}
255+
}
256+
257+
public void Clear()
258+
{
259+
_innerDict.Clear();
260+
}
261+
262+
public IEnumerator<KeyValuePair<string, byte[]>> GetEnumerator()
263+
{
264+
return _innerDict.GetEnumerator();
265+
}
266+
267+
public void Remove(string key)
268+
{
269+
_innerDict.Remove(key);
270+
}
271+
272+
public void Set(string key, ArraySegment<byte> value)
273+
{
274+
_innerDict[key] = value.AsArray();
275+
}
276+
277+
public bool TryGetValue(string key, out byte[] value)
278+
{
279+
return _innerDict.TryGetValue(key, out value);
280+
}
281+
282+
IEnumerator IEnumerable.GetEnumerator()
283+
{
284+
return _innerDict.GetEnumerator();
285+
}
286+
}
167287
}
168288
}

0 commit comments

Comments
 (0)