|
31 | 31 | If you provide a type resolver to the <xref:System.Web.Script.Serialization.JavaScriptSerializer> instance, the serializer will use the <xref:System.Web.Script.Serialization.JavaScriptTypeResolver.ResolveTypeId%2A> and <xref:System.Web.Script.Serialization.JavaScriptTypeResolver.ResolveType%2A> methods to map between the managed type and the string value during the serialization and deserialization process, respectively.
|
32 | 32 |
|
33 | 33 | The <xref:System.Web.Script.Serialization.JavaScriptTypeResolver> class is the base class for the <xref:System.Web.Script.Serialization.SimpleTypeResolver> class, which provides an implementation of a type resolver that uses the managed type assembly-qualified name.
|
34 |
| - |
35 |
| - |
36 |
| - |
| 34 | +
|
| 35 | +> [!NOTE] |
| 36 | +> When using a `JavaScriptTypeResolver`, the resulting JSON payload contains a special `__type` property. This property includes the full type name, including namespace, of the target type. Before using a custom resolver, verify that the full name of the target type does not contain sensitive or privileged information. |
| 37 | +
|
37 | 38 | ## Examples
|
38 |
| - The following example demonstrates how to create a custom type resolver. |
39 |
| - |
40 |
| - [!code-csharp[System.Web.Script.Serialization.TypeResolver#2](~/samples/snippets/csharp/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/CS/App_Code/TypeResolver.cs#2)] |
41 |
| - [!code-vb[System.Web.Script.Serialization.TypeResolver#2](~/samples/snippets/visualbasic/VS_Snippets_Atlas/System.Web.Script.Serialization.TypeResolver/VB/App_Code/TypeResolver.vb#2)] |
| 39 | +
|
| 40 | +The following example shows how to create a custom `JavaScriptTypeResolver` and how to use it to serialize or deserialize an object. |
| 41 | +
|
| 42 | +```cs |
| 43 | +using System; |
| 44 | +using System.Linq; |
| 45 | +using System.Web.Script.Serialization; |
| 46 | +
|
| 47 | +namespace SampleApp |
| 48 | +{ |
| 49 | + class Program |
| 50 | + { |
| 51 | + static void Main(string[] args) |
| 52 | + { |
| 53 | + // The object array to serialize. |
| 54 | + Person[] people = new Person[] |
| 55 | + { |
| 56 | + new Person() |
| 57 | + { |
| 58 | + Name = "Kristen Solstad", |
| 59 | + Age = 15, |
| 60 | + HomeAddress = new Address() |
| 61 | + { |
| 62 | + Street1 = "123 Palm Ave", |
| 63 | + City = "Some City", |
| 64 | + StateOrProvince = "ST", |
| 65 | + Country = "United States", |
| 66 | + PostalCode = "00000" |
| 67 | + } |
| 68 | + }, |
| 69 | + new Adult() |
| 70 | + { |
| 71 | + Name = "Alex Johnson", |
| 72 | + Age = 39, |
| 73 | + Occupation = "Mechanic", |
| 74 | + HomeAddress = new Address() |
| 75 | + { |
| 76 | + Street1 = "445 Lorry Way", |
| 77 | + Street2 = "Unit 3A", |
| 78 | + City = "Some City", |
| 79 | + Country = "United Kingdom", |
| 80 | + PostalCode = "AA0 A00" |
| 81 | + } |
| 82 | + } |
| 83 | + }; |
| 84 | +
|
| 85 | + // Serialize the object array, then write it to the console. |
| 86 | + string serializedData = SerializePeopleArray(people); |
| 87 | + Console.WriteLine("Serialized:"); |
| 88 | + Console.WriteLine(serializedData); |
| 89 | + Console.WriteLine(); |
| 90 | +
|
| 91 | + // Now deserialize the object array. |
| 92 | + Person[] deserializedArray = DeserializePeopleArray(serializedData); |
| 93 | + Console.WriteLine("Deserialized " + deserializedArray.Length + " people."); |
| 94 | + foreach (Person person in deserializedArray) |
| 95 | + { |
| 96 | + Console.WriteLine(person.Name + " (Age " + person.Age + ") [" + person.GetType() + "]"); |
| 97 | + } |
| 98 | + } |
| 99 | +
|
| 100 | + static string SerializePeopleArray(Person[] people) |
| 101 | + { |
| 102 | + // The custom type resolver to use. |
| 103 | + // Note: Except for primitives like int and string, *every* type that |
| 104 | + // we might see in the object graph must be listed here. |
| 105 | + CustomTypeResolver resolver = new CustomTypeResolver( |
| 106 | + typeof(Person), |
| 107 | + typeof(Adult), |
| 108 | + typeof(Address)); |
| 109 | +
|
| 110 | + // Instantiate the serializer. |
| 111 | + JavaScriptSerializer serializer = new JavaScriptSerializer(resolver); |
| 112 | +
|
| 113 | + // Serialize the object array, then return it. |
| 114 | + string serialized = serializer.Serialize(people); |
| 115 | + return serialized; |
| 116 | + } |
| 117 | +
|
| 118 | + static Person[] DeserializePeopleArray(string serializedData) |
| 119 | + { |
| 120 | + // The custom type resolver to use. |
| 121 | + // Note: This is the same list that was provided to the Serialize routine. |
| 122 | + CustomTypeResolver resolver = new CustomTypeResolver( |
| 123 | + typeof(Person), |
| 124 | + typeof(Adult), |
| 125 | + typeof(Address)); |
| 126 | +
|
| 127 | + // Instantiate the serializer. |
| 128 | + JavaScriptSerializer serializer = new JavaScriptSerializer(resolver); |
| 129 | +
|
| 130 | + // Deserialize the object array, then return it. |
| 131 | + Person[] deserialized = serializer.Deserialize<Person[]>(serializedData); |
| 132 | + return deserialized; |
| 133 | + } |
| 134 | + } |
| 135 | +
|
| 136 | + public class Person |
| 137 | + { |
| 138 | + public string Name { get; set; } |
| 139 | + public int Age { get; set; } |
| 140 | + public Address HomeAddress { get; set; } |
| 141 | + } |
| 142 | +
|
| 143 | + public class Adult : Person |
| 144 | + { |
| 145 | + public string Occupation { get; set; } |
| 146 | + } |
| 147 | +
|
| 148 | + public class Address |
| 149 | + { |
| 150 | + public string Street1 { get; set; } |
| 151 | + public string Street2 { get; set; } |
| 152 | + public string City { get; set; } |
| 153 | + public string StateOrProvince { get; set; } |
| 154 | + public string Country { get; set; } |
| 155 | + public string PostalCode { get; set; } |
| 156 | + } |
| 157 | +
|
| 158 | + // A custom JavaScriptTypeResolver that restricts the payload |
| 159 | + // to a set of known good types. |
| 160 | + class CustomTypeResolver : JavaScriptTypeResolver |
| 161 | + { |
| 162 | + private readonly Type[] _allowedTypes; |
| 163 | +
|
| 164 | + public CustomTypeResolver(params Type[] allowedTypes) |
| 165 | + { |
| 166 | + if (allowedTypes == null) |
| 167 | + { |
| 168 | + throw new ArgumentNullException("allowedTypes"); |
| 169 | + } |
| 170 | +
|
| 171 | + // Make a copy of the array the caller gave us. |
| 172 | + _allowedTypes = (Type[])allowedTypes.Clone(); |
| 173 | + } |
| 174 | +
|
| 175 | + public override Type ResolveType(string id) |
| 176 | + { |
| 177 | + // Iterate over all of the allowed types, looking for a match |
| 178 | + // for the 'id' parameter. Calling Type.GetType(id) is dangerous, |
| 179 | + // so we instead perform a match on the Type.FullName property. |
| 180 | + foreach (Type allowedType in _allowedTypes) |
| 181 | + { |
| 182 | + if (allowedType.FullName == id) |
| 183 | + { |
| 184 | + return allowedType; |
| 185 | + } |
| 186 | + } |
| 187 | +
|
| 188 | + // The caller provided a type we don't recognize. This could be |
| 189 | + // dangerous, so we'll fail the operation immediately. |
| 190 | + throw new ArgumentException("Unknown type: " + id, "id"); |
| 191 | + } |
| 192 | +
|
| 193 | + public override string ResolveTypeId(Type type) |
| 194 | + { |
| 195 | + // Before we serialize data, quickly double-check to make |
| 196 | + // sure we're allowed to deserialize the data. Otherwise it's |
| 197 | + // no good serializing something if we can't deserialize it. |
| 198 | + if (_allowedTypes.Contains(type)) |
| 199 | + { |
| 200 | + return type.FullName; |
| 201 | + } |
| 202 | +
|
| 203 | + throw new InvalidOperationException("Cannot serialize an object of type " + type + ". Did you forget to add it to the allow list?"); |
| 204 | + } |
| 205 | + } |
| 206 | +} |
| 207 | +``` |
| 208 | +
|
| 209 | +The preceding app outputs the following to the console, formatted for readability. |
| 210 | +
|
| 211 | +```txt |
| 212 | +Serialized: |
| 213 | +[ |
| 214 | + { |
| 215 | + "__type": "SampleApp.Person", |
| 216 | + "Name": "Kristen Solstad", |
| 217 | + "Age": 15, |
| 218 | + "HomeAddress": { |
| 219 | + "__type": "SampleApp.Address", |
| 220 | + "Street1": "123 Palm Ave", |
| 221 | + "Street2": null, |
| 222 | + "City": "Some City", |
| 223 | + "StateOrProvince": "ST", |
| 224 | + "Country": "United States", |
| 225 | + "PostalCode": "00000" |
| 226 | + } |
| 227 | + }, |
| 228 | + { |
| 229 | + "__type": "SampleApp.Adult", |
| 230 | + "Occupation": "Mechanic", |
| 231 | + "Name": "Alex Johnson", |
| 232 | + "Age": 39, |
| 233 | + "HomeAddress": { |
| 234 | + "__type": "SampleApp.Address", |
| 235 | + "Street1": "445 Lorry Way", |
| 236 | + "Street2": "Unit 3A", |
| 237 | + "City": "Some City", |
| 238 | + "StateOrProvince": null, |
| 239 | + "Country": "United Kingdom", |
| 240 | + "PostalCode": "AA0 A00" |
| 241 | + } |
| 242 | + } |
| 243 | +] |
| 244 | +
|
| 245 | +Deserialized 2 people. |
| 246 | +Kristen Solstad (Age 15) [SampleApp.Person] |
| 247 | +Alex Johnson (Age 39) [SampleApp.Adult] |
| 248 | +``` |
| 249 | +
|
| 250 | +In the preceding sample, the `Adult` type subclasses the `Person` type. A custom `JavaScriptTypeResolver` is used to include the type information as part of the generated JSON payload. This allows limited polymorphism when deserializing the JSON payload back into a .NET object graph. The payload can control whether to return a base `Person` instance or a derived `Adult` instance back to the caller. |
| 251 | +
|
| 252 | +This sample is safe because it uses an `allow-list` mechanism to control deserialization. The code: |
| 253 | +
|
| 254 | +* Initializes the `CustomTypeResolver` with an explicit list of allowed types. |
| 255 | +* Restricts the deserialization process to only approved list of types. The restriction prevents [deserialization attacks](https://owasp.org/www-community/vulnerabilities/Deserialization_of_untrusted_data), where the remote client specifies a malicious `__type` in the JSON payload and tricks the server into deserializing a dangerous type. |
| 256 | +
|
| 257 | +Even though the app only expects `Person` and `Adult` instances to be deserialized as part of the top-level array, it's still necessary to add `Address` to the allow-list because: |
| 258 | +
|
| 259 | +* Serializing a `Person` or `Adult` also serializes an `Address` as part of the object graph. |
| 260 | +* All types that might be present in the object graph need to be accounted for in the allow list. Primitives like `int` and `string` do not need to be specified. |
| 261 | +
|
| 262 | +> [!WARNING] |
| 263 | +> Do not call `Type.GetType(id)` within the `ResolveType` method. This could introduce a security vunerability into the app. Instead, iterate through the list of allowed types and compare their `Type.FullName` property against the incoming `id`, as shown in the preceding sample. |
42 | 264 |
|
43 | 265 | ]]></format>
|
44 | 266 | </remarks>
|
|
99 | 321 |
|
100 | 322 | ## Remarks
|
101 | 323 | When a type resolver is associated with a <xref:System.Web.Script.Serialization.JavaScriptSerializer> instance, the serializer uses the <xref:System.Web.Script.Serialization.JavaScriptTypeResolver.ResolveType%2A> method when it iterates through a JSON string to determine the specific managed type to which the JSON type should be converted.
|
102 |
| - |
| 324 | +
|
103 | 325 | ]]></format>
|
104 | 326 | </remarks>
|
105 | 327 | <block subset="none" type="overrides">
|
|
0 commit comments