- 
                Notifications
    You must be signed in to change notification settings 
- Fork 4.9k
Better handle resx scenarios #38012
Better handle resx scenarios #38012
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -25,13 +25,14 @@ public void AddResource(string name, byte[] value) { } | |
| public void AddResource(string name, System.IO.Stream value, bool closeAfterWrite = false) { } | ||
| public void AddResource(string name, object value) { } | ||
| public void AddResource(string name, string value) { } | ||
| public void AddResource(string name, string typeName, string value) { } | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel strongly about  | ||
| public void Close() { } | ||
| public void Dispose() { } | ||
| public void Generate() { } | ||
|  | ||
| public void AddBinaryFormattedResource(string name, byte[] value) { } | ||
| public void AddBinaryFormattedResource(string name, string typeName, byte[] value) { } | ||
| public void AddActivatorResource(string name, string typeName, System.IO.Stream value, bool closeAfterWrite = false) { } | ||
| public void AddTypeConverterResource(string name, string typeName, byte[] value) { } | ||
| public void AddTypeConverterResource(string name, string typeName, string value) { } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
|  | @@ -2,11 +2,16 @@ | |
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|  | ||
| #nullable enable | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel; | ||
| using System.Diagnostics; | ||
| using System.IO; | ||
|  | ||
| namespace System.Resources.Extensions | ||
| { | ||
| internal class UnknownType { } | ||
|  | ||
| partial class PreserializedResourceWriter | ||
| { | ||
| // indicates if the types of resources saved will require the DeserializingResourceReader | ||
|  | @@ -17,6 +22,11 @@ partial class PreserializedResourceWriter | |
| internal const string DeserializingResourceReaderFullyQualifiedName = "System.Resources.Extensions.DeserializingResourceReader, System.Resources.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"; | ||
| internal const string RuntimeResourceSetFullyQualifiedName = "System.Resources.Extensions.RuntimeResourceSet, System.Resources.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51"; | ||
|  | ||
| // an internal type name used to represent an unknown resource type, explicitly omit version to save | ||
| // on size and avoid changes in user resources. This works since we only ever load this type name | ||
| // from calls to GetType from this assembly. | ||
| private static readonly string UnknownObjectTypeName = typeof(UnknownType).FullName; | ||
|  | ||
| private string ResourceReaderTypeName => _requiresDeserializingResourceReader ? | ||
| DeserializingResourceReaderFullyQualifiedName : | ||
| ResourceReaderFullyQualifiedName; | ||
|  | @@ -25,14 +35,42 @@ partial class PreserializedResourceWriter | |
| RuntimeResourceSetFullyQualifiedName : | ||
| ResSetTypeName; | ||
|  | ||
| // a collection of primitive types in a dictionary, indexed by type name | ||
| // using a comparer which handles type name comparisons similar to what | ||
| // is done by reflection | ||
| private static readonly IReadOnlyDictionary<string, Type> s_primitiveTypes = new Dictionary<string, Type>(16, TypeNameComparer.Instance) | ||
| { | ||
| { typeof(string).FullName, typeof(string) }, | ||
| { typeof(int).FullName, typeof(int) }, | ||
| { typeof(bool).FullName, typeof(bool) }, | ||
| { typeof(char).FullName, typeof(char) }, | ||
| { typeof(byte).FullName, typeof(byte) }, | ||
| { typeof(sbyte).FullName, typeof(sbyte) }, | ||
| { typeof(short).FullName, typeof(short) }, | ||
| { typeof(long).FullName, typeof(long) }, | ||
| { typeof(ushort).FullName, typeof(ushort) }, | ||
| { typeof(uint).FullName, typeof(uint) }, | ||
| { typeof(ulong).FullName, typeof(ulong) }, | ||
| { typeof(float).FullName, typeof(float) }, | ||
| { typeof(double).FullName, typeof(double) }, | ||
| { typeof(decimal).FullName, typeof(decimal) }, | ||
| { typeof(DateTime).FullName, typeof(DateTime) }, | ||
| { typeof(TimeSpan).FullName, typeof(TimeSpan) } | ||
| // byte[] and Stream are primitive types but do not define a conversion from string | ||
| }; | ||
|  | ||
| /// <summary> | ||
| /// Adds a resource of specified type represented by a string value which will be | ||
| /// passed to the type's TypeConverter when reading the resource. | ||
| /// Adds a resource of specified type represented by a string value. | ||
| /// If the type is a primitive type, the value will be converted using TypeConverter by the writer | ||
| /// to that primitive type and stored in the resources in binary format. | ||
| /// If the type is not a primitive type, the string value will be stored in the resources as a | ||
| /// string and converted with a TypeConverter for the type when reading the resource. | ||
| /// This is done to avoid activating arbitrary types during resource writing. | ||
| /// </summary> | ||
| /// <param name="name">Resource name</param> | ||
| /// <param name="typeName">Assembly qualified type name of the resource</param> | ||
| /// <param name="value">Value of the resource in string form understood by the type's TypeConverter</param> | ||
| public void AddTypeConverterResource(string name, string typeName, string value) | ||
| public void AddResource(string name, string typeName, string value) | ||
| { | ||
| if (name == null) | ||
| throw new ArgumentNullException(nameof(name)); | ||
|  | @@ -41,9 +79,39 @@ public void AddTypeConverterResource(string name, string typeName, string value) | |
| if (value == null) | ||
| throw new ArgumentNullException(nameof(value)); | ||
|  | ||
| AddResourceData(name, typeName, new ResourceDataRecord(SerializationFormat.TypeConverterString, value)); | ||
| // determine if the type is a primitive type | ||
| if (s_primitiveTypes.TryGetValue(typeName, out Type primitiveType)) | ||
| { | ||
| // directly add strings | ||
| if (primitiveType == typeof(string)) | ||
| { | ||
| AddResource(name, value); | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is interesting, since I have similar code in the resgen task. But that also has to handle the  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thought was that you can pretty much plumb the no-mime-type/non-ResXFileRef case straight through to this API. For mime-types and ResXFileRef you'll need to do some work in translating to different calls. Hopefully those can largely just pass the string value of the type through. Let me know if you have cases where those methods require "primitive type" knowledge. | ||
| } | ||
| else | ||
| { | ||
| // for primitive types that are not strings, convert the string value to the | ||
| // primitive type value. | ||
| // we intentionally avoid calling GetType on the user provided type name | ||
| // and instead will only ever convert to one of the known types. | ||
| TypeConverter converter = TypeDescriptor.GetConverter(primitiveType); | ||
|  | ||
| if (converter == null) | ||
| { | ||
| throw new TypeLoadException(SR.Format(SR.TypeLoadException_CannotLoadConverter, primitiveType)); | ||
| } | ||
|  | ||
| _requiresDeserializingResourceReader = true; | ||
| object primitiveValue = converter.ConvertFromInvariantString(value); | ||
|  | ||
| Debug.Assert(primitiveValue.GetType() == primitiveType); | ||
|  | ||
| AddResource(name, primitiveValue); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| AddResourceData(name, typeName, new ResourceDataRecord(SerializationFormat.TypeConverterString, value)); | ||
| _requiresDeserializingResourceReader = true; | ||
| } | ||
| } | ||
|  | ||
| /// <summary> | ||
|  | @@ -67,6 +135,29 @@ public void AddTypeConverterResource(string name, string typeName, byte[] value) | |
| _requiresDeserializingResourceReader = true; | ||
| } | ||
|  | ||
| /// <summary> | ||
| /// Adds a resource of unknown type represented by a byte[] value which will be | ||
| /// passed to BinaryFormatter when reading the resource. | ||
| /// </summary> | ||
| /// <param name="name">Resource name</param> | ||
| /// <param name="value">Value of the resource in byte[] form understood by BinaryFormatter</param> | ||
| public void AddBinaryFormattedResource(string name, byte[] value) | ||
| { | ||
| if (name == null) | ||
| throw new ArgumentNullException(nameof(name)); | ||
| if (value == null) | ||
| throw new ArgumentNullException(nameof(value)); | ||
|  | ||
| // Some resx-files are missing type information for binary-formatted resources. | ||
| // These would have previously been handled by deserializing once, capturing the type | ||
| // and reserializing when writing the resources. We don't want to do that so instead | ||
| // we just omit the type. | ||
| AddResourceData(name, UnknownObjectTypeName, new ResourceDataRecord(SerializationFormat.BinaryFormatter, value)); | ||
|  | ||
| // ResourceReader will validate the type so we must use the new reader. | ||
| _requiresDeserializingResourceReader = true; | ||
| } | ||
|  | ||
| /// <summary> | ||
| /// Adds a resource of specified type represented by a byte[] value which will be | ||
| /// passed to BinaryFormatter when reading the resource. | ||
|  | @@ -127,7 +218,7 @@ internal ResourceDataRecord(SerializationFormat format, object data, bool closeA | |
|  | ||
| private void WriteData(BinaryWriter writer, object dataContext) | ||
| { | ||
| ResourceDataRecord record = dataContext as ResourceDataRecord; | ||
| ResourceDataRecord? record = dataContext as ResourceDataRecord; | ||
|  | ||
| Debug.Assert(record != null); | ||
|  | ||
|  | ||
Uh oh!
There was an error while loading. Please reload this page.