2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections . Concurrent ;
5
6
using System . Collections . Generic ;
7
+ using System . Diagnostics ;
6
8
using System . IO ;
9
+ using System . Linq ;
10
+ using System . Reflection ;
7
11
using Microsoft . AspNet . Http ;
12
+ using Microsoft . AspNet . Mvc . Core ;
8
13
using Microsoft . Framework . Internal ;
9
14
using Newtonsoft . Json ;
10
15
using Newtonsoft . Json . Bson ;
16
+ using Newtonsoft . Json . Linq ;
11
17
12
18
namespace Microsoft . AspNet . Mvc
13
19
{
@@ -16,8 +22,34 @@ namespace Microsoft.AspNet.Mvc
16
22
/// </summary>
17
23
public class SessionStateTempDataProvider : ITempDataProvider
18
24
{
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
+ } ;
21
53
22
54
/// <inheritdoc />
23
55
public virtual IDictionary < string , object > LoadTempData ( [ NotNull ] HttpContext context )
@@ -37,9 +69,68 @@ public virtual IDictionary<string, object> LoadTempData([NotNull] HttpContext co
37
69
using ( var memoryStream = new MemoryStream ( value ) )
38
70
using ( var writer = new BsonReader ( memoryStream ) )
39
71
{
40
- tempDataDictionary = jsonSerializer . Deserialize < Dictionary < string , object > > ( writer ) ;
72
+ tempDataDictionary = _jsonSerializer . Deserialize < Dictionary < string , object > > ( writer ) ;
41
73
}
42
74
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
+
43
134
// If we got it from Session, remove it so that no other request gets it
44
135
session . Remove ( TempDataSessionStateKey ) ;
45
136
}
@@ -59,13 +150,19 @@ public virtual void SaveTempData([NotNull] HttpContext context, IDictionary<stri
59
150
var hasValues = ( values != null && values . Count > 0 ) ;
60
151
if ( hasValues )
61
152
{
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
+
62
159
// Accessing Session property will throw if the session middleware is not enabled.
63
160
var session = context . Session ;
64
161
65
162
using ( var memoryStream = new MemoryStream ( ) )
66
163
using ( var writer = new BsonWriter ( memoryStream ) )
67
164
{
68
- jsonSerializer . Serialize ( writer , values ) ;
165
+ _jsonSerializer . Serialize ( writer , values ) ;
69
166
session [ TempDataSessionStateKey ] = memoryStream . ToArray ( ) ;
70
167
}
71
168
}
@@ -80,5 +177,65 @@ private static bool IsSessionEnabled(HttpContext context)
80
177
{
81
178
return context . GetFeature < ISessionFeature > ( ) != null ;
82
179
}
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
+ }
83
240
}
84
241
}
0 commit comments