ResXResourceSet Class

Represents the complete content of an XML resource (.resx) file including resources, metadata and aliases.

Definition

Namespace: KGySoft.Resources
Assembly: KGySoft.CoreLibraries (in KGySoft.CoreLibraries.dll) Version: 8.1.0
C#
[SerializableAttribute]
public sealed class ResXResourceSet : ResourceSet, 
	IExpandoResourceSet, IDisposable, IEnumerable
Inheritance
Object    ResourceSet    ResXResourceSet
Implements
IExpandoResourceSet, IEnumerable, IDisposable

Remarks

The ResXResourceSet class represents a single XML resource file (.resx file) in memory. It uses ResXResourceReader internally to read the .resx content and ResXResourceWriter to save it.

A ResXResourceSet instance can contain resources, metadata and aliases (unlike the System.Resources.ResXResourceSet class, which contains only the resources). These contents are available either by enumerators (GetEnumerator, GetMetadataEnumerator and GetAliasEnumerator methods) or directly by key (GetString and GetObject methods for resources, GetMetaString and GetMetaObject for metadata, and GetAliasValue for aliases).

Example: Enumerating resources, metadata and aliases

The following example demonstrates how to access the content of a .resx file by the ResXResourceSet class using the enumerators. This is very similar to the first example of ResXResourceReader.

C#
 using System;
 using System.Collections;
 using System.IO;
 using KGySoft.Resources;

 public class Example
 {
     private const string resx = @"<?xml version='1.0' encoding='utf-8'?>
 <root>
   <data name='string'>
     <value>Test string</value>
     <comment>Default data type is string.</comment>
   </data>

   <metadata name='meta string'>
     <value>Meta String</value>
   </metadata>

   <data name='int' type='System.Int32'>
     <value>42</value>
   </data>

   <assembly alias='CustomAlias' name='System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' />

   <data name='color' type='System.Drawing.Color, CustomAlias'>
     <value>Red</value>
     <comment>When this entry is deserialized, System.Drawing assembly will be loaded.</comment>
   </data>

   <data name='bytes' type='System.Byte[]'>
     <value>VGVzdCBieXRlcw==</value>
   </data>

   <data name='dangerous' mimetype='application/x-microsoft.net.object.binary.base64'>
     <value>YmluYXJ5</value>
     <comment>BinaryFormatter will throw an exception for this invalid content.</comment>
   </data>

 </root>";

    public static void Main()
    {
        var set = new ResXResourceSet(new StringReader(resx));
        Console.WriteLine("____Resources in .resx:____");
        Dump(set, set.GetEnumerator);
        Console.WriteLine("____Metadata in .resx:____");
        Dump(set, set.GetMetadataEnumerator);
        Console.WriteLine("____Aliases in .resx:____");
        Dump(set, set.GetAliasEnumerator);
    }

    private static void Dump(ResXResourceSet set, Func<IDictionaryEnumerator> getEnumeratorFunction)
    {
        var enumerator = getEnumeratorFunction();
        while (enumerator.MoveNext())
        {
            Console.WriteLine($"Name: {enumerator.Key}");
            set.SafeMode = true;
            Console.WriteLine($"  Value in SafeMode:     {enumerator.Value} ({enumerator.Value.GetType()})");
            try
            {
                set.SafeMode = false;
                Console.WriteLine($"  Value in non-SafeMode: {enumerator.Value} ({enumerator.Value.GetType()})");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Getting the deserialized value thrown an exception: {e.Message}");
            }
            Console.WriteLine();
        }
    }
}

 // The example displays the following output:
 // ____Resources in .resx:____
 // Name: string
 // Value in SafeMode:     Test string (KGySoft.Resources.ResXDataNode)
 // Value in non-SafeMode: Test string (System.String)

 // Name: int
 // Value in SafeMode:     42 (KGySoft.Resources.ResXDataNode)
 // Value in non-SafeMode: 42 (System.Int32)

 // Name: color
 // Value in SafeMode:     Red (KGySoft.Resources.ResXDataNode)
 // Value in non-SafeMode: Color[Red] (System.Drawing.Color)

 // Name: bytes
 // Value in SafeMode:     VGVzdCBieXRlcw== (KGySoft.Resources.ResXDataNode)
 // Value in non-SafeMode: System.Byte[] (System.Byte[])

 // Name: dangerous
 // Value in SafeMode:     YmluYXJ5 (KGySoft.Resources.ResXDataNode)
 // Getting the deserialized value thrown an exception: End of Stream encountered before parsing was completed.

 // ____Metadata in .resx:____
 // Name: meta string
 // Value in SafeMode:     Meta String (KGySoft.Resources.ResXDataNode)
 // Value in non-SafeMode: Meta String (System.String)

 // ____Aliases in .resx:____
 // Name: CustomAlias
 // Value in SafeMode:     System.Drawing, Version= 4.0.0.0, Culture= neutral, PublicKeyToken= b03f5f7f11d50a3a (System.String)
 // Value in non-SafeMode: System.Drawing, Version= 4.0.0.0, Culture= neutral, PublicKeyToken= b03f5f7f11d50a3a (System.String)

The ResXResourceSet class supports adding new resources (SetObject), metadata (SetMetaObject) and aliases (SetAliasValue). Existing entries can be removed by RemoveObject, RemoveMetaObject and RemoveAliasValue methods. The changed set can be saved by the Save overloads.

Example: Populating and saving a new resource set

The following example shows how to create a new resource set, add a new resource and save the content. It demonstrates the usage of the key-based resource access, too.

C#
using System;
using System.IO;
using System.Text;
using KGySoft.Resources;

public class Example
{
    public static void Main()
    {
        const string key = "myKey";
        var set = new ResXResourceSet();

        // GetString/GetObject: reads a resource by key (GetMetaString/GetMetaObject for metadata, GetAliasValue for alias)
        Console.WriteLine($"Getting a non-existing key: {set.GetString(key) ?? "<null>"}");

        // SetObject: adds a new resource or replaces an existing one (SetMetaObject for metadata, SetAliasValue for assembly alias)
        // you can even remove entries by RemoveObject/RemoveMetaObject/RemoveAliasValue)
        set.SetObject(key, "a string value");
        Console.WriteLine($"Getting an existing key: {set.GetString(key) ?? "<null>"}");

        var savedContent = new StringBuilder();
        set.Save(new StringWriter(savedContent), compatibleFormat: false); // try compatibleFormat: true as well
        Console.WriteLine("Saved .resx content:");
        Console.WriteLine(savedContent);
    }
}

// The example displays the following output:
// Getting a non-existing key: <null>
// Getting an existing key: a string value
// Saved .resx content:
// <?xml version="1.0" encoding="utf-8"?>
// <root>
//   <data name="myKey">
//     <value>a string value</value>
//   </data>
// </root>

If a .resx content contains the same resource name multiple times, ResXResourceSet will contain the lastly defined key. To obtain redefined values use ResXResourceReader explicitly and set AllowDuplicatedKeys to .

If the SafeMode property is the value of the IDictionaryEnumerator.Value property returned by the enumerator methods is a ResXDataNode instance rather than the resource value. The same applies for the return value of GetObject and GetMetaObject methods. This makes possible to check the raw .resx content before deserialization if the .resx file is from an untrusted source. See also the example at ResXDataNode.

  Security Note

Even if SafeMode is , loading a .resx content with corrupt or malicious entry will have no effect until we try to obtain the corresponding value. See the example below for the demonstration.

If SafeMode property is the GetString and GetMetaString methods will not throw an InvalidOperationException even for non-string values; they return the raw XML value instead.

Example: The SafeMode property

The following example demonstrates the behavior of SafeMode property (see the first example as well, where the entries are accessed by the enumerators).

C#
using System;
using KGySoft.Resources;

public class Example
{
    private const string resx = @"<?xml version='1.0' encoding='utf-8'?>
<root>
  <data name='string'>
    <value>Test string</value>
    <comment>Default data type is string (when there is neither 'type' nor 'mimetype' attribute).</comment>
  </data>

  <data name='binary' type='System.Byte[]'>
    <value>VGVzdCBieXRlcw==</value>
  </data>

  <data name='dangerous' mimetype='application/x-microsoft.net.object.binary.base64'>
    <value>boo!</value>
    <comment>BinaryFormatter will throw an exception for this invalid content.</comment>
  </data>
</root>";

    public static void Main()
    {
        // please note that default value of SafeMode is false. Nevertheless, reading the .resx containing an invalid node (dangerous)
        // will not cause any problem because nothing is deserialized yet.
        var set = ResXResourceSet.FromFileContents(resx); // same as "new ResXResourceSet(new StringReader(resx));"

        // enabling SafeMode changes the GetObject/GetString behavior
        set.SafeMode = true;

        Console.WriteLine($"Return type of GetObject in safe mode: {set.GetObject("string").GetType()}");

        Console.WriteLine();
        Console.WriteLine("*** Demonstrating SafeMode=true ***");
        TreatSafely(set, "unknown");
        TreatSafely(set, "string");
        TreatSafely(set, "binary");
        TreatSafely(set, "dangerous");

        set.SafeMode = false;
        Console.WriteLine();
        Console.WriteLine("*** Demonstrating SafeMode=false ***");
        TreatUnsafely(set, "unknown");
        TreatUnsafely(set, "string");
        TreatUnsafely(set, "binary");
        TreatUnsafely(set, "dangerous");
    }

    private static void TreatSafely(ResXResourceSet set, string resourceName)
    {
        // in SafeMode GetObject returns a ResXDataNode
        var resource = set.GetObject(resourceName) as ResXDataNode;
        if (resource == null)
        {
            Console.WriteLine($"Resource name '{resourceName}' does not exist in resource set or SafeMode is off.");
            return;
        }

        if (resource.TypeName == null && resource.MimeType == null)
        {
            // to deserialize a node considered safe call GetValue
            Console.WriteLine($"Resource with name '{resourceName}' is a string so it is safe. Its value is '{resource.GetValue()}'");
            return;
        }

        if (resource.TypeName != null)
        {
            Console.WriteLine($"Resource with name '{resourceName}' is a '{resource.TypeName}'. If we trust this type we can call GetValue to deserialize it.");
        }
        else
        {
            Console.WriteLine($"Resource with name '{resourceName}' has only mime type: '{resource.MimeType}'.");
            Console.WriteLine("  We cannot tell its type before we deserialize it. We can consider this entry potentially dangerous.");
        }

        // In SafeMode GetString(resourceName) never fails.
        // resource.ValueData is similar but ValueData can be null if we allow cleanup .resx content after deserialization.
        Console.WriteLine($"  Raw string value: {set.GetString(resourceName)}");
    }

    private static void TreatUnsafely(ResXResourceSet set, string resourceName)
    {
        // If SafeMode is false, GetObject returns null for existing resources containing null value, too.
        // Use ContainsResource to distinct non-existing and null values.
        if (!set.ContainsResource(resourceName))
        {
            Console.WriteLine($"The resource set does not contain a resource named '{resourceName}'.");
            return;
        }
        try
        {
            // If SafeMode is false, GetObject tries to deserialize the resource.
            // GetString would throw an InvalidOperationException on non-string values.
            var value = set.GetObject(resourceName);
            Console.WriteLine($"Type of resource with name '{resourceName}' is {value?.GetType().ToString() ?? "<none>"}. String representation: {value ?? "<null>"}");
        }
        catch (Exception e)
        {
            Console.WriteLine($"Obtaining '{resourceName}' failed with an error: {e.Message}");
        }
    }
}

// The example displays the following output:
// Return type of GetObject in safe mode: KGySoft.Resources.ResXDataNode
// 
// *** Demonstrating SafeMode=true ***
// Resource name 'unknown' does not exist in resource set or SafeMode is off.
// Resource with name 'string' is a string so it is safe. Its value is 'Test string'
// Resource with name 'binary' is a 'System.Byte[]'. If we trust this type we can call GetValue to deserialize it.
//   Raw string value: VGVzdCBieXRlcw==
// Resource with name 'dangerous' has only mime type: 'application/x-microsoft.net.object.binary.base64'.
//   We cannot tell its type before we deserialize it. We can consider this entry potentially dangerous.
//   Raw string value: boo!
// 
// *** Demonstrating SafeMode=false ***
// The resource set does not contain a resource named 'unknown'.
// Type of resource with name 'string' is System.String. String representation: Test string
// Type of resource with name 'binary' is System.Byte[]. String representation: System.Byte[]
// Obtaining 'dangerous' failed with an error: The input is not a valid Base-64 string as it contains a non-base 64 character,
// more than two padding characters, or an illegal character among the padding characters.

Comparison with System.Resources.ResXResourceSet 

ResXResourceSet can load .resx files produced both by ResXResourceWriter and System.Resources.ResXResourceWriter.

Incompatibility with System.Resources.ResXResourceSet:

New features and improvements compared to System.Resources.ResXResourceSet:

  • Supporting file references – If the .resx file contains file references with relative paths, then a base path can be defined in the constructors so the file references can be resolved successfully. See also the ResXFileRef class.
  • Full .resx support – A .resx file can contain also metadata and assembly alias entries in addition to resources and this ResXResourceSet implementation handles them.
  • Performance – Load time is much faster because the constructors just simply parse the raw XML content. The actual deserialization occurs on demand only for the really accessed resources and metadata. Memory footprint is tried to be kept minimal as well. If AutoFreeXmlData is , then raw XML data is freed after deserializing and caching an entry.
  • Security – This ResXResourceSet is much more safe, even if SafeMode is , because no object is deserialized at load time. If SafeMode is , then security is even more increased as GetObject and GetMetaObject methods, and the IDictionaryEnumerator.Value property of the enumerators returned by GetEnumerator and GetMetadataEnumerator methods return a ResXDataNode instance instead of a deserialized object so you can check whether the resource or metadata can be treat as a safe object before actually deserializing it. See the example above for more details.
  • Write support – The .resx file content can be expanded, existing entries can be replaced or removed and the new content can be saved by the Save methods. You can start even with a completely empty set, add content dynamically and save the new resource set.

Constructors

ResXResourceSet(Stream, String) Initializes a new instance of the ResXResourceSet class using the ResXResourceReader to read resources from the specified stream.
ResXResourceSet(String, String) Initializes a new instance of a ResXResourceSet class using the ResXResourceReader that opens and reads resources from the specified file.
ResXResourceSet(TextReader, String) Initializes a new instance of the ResXResourceSet class using the ResXResourceReader to read resources from the specified textReader.

Properties

AutoFreeXmlData Gets or sets whether the raw XML data of the stored elements should be freed once their value has been deserialized.
Default value: .
BasePath Gets the base path for the relative file paths specified in a ResXFileRef object.
CloneValues Gets or sets whether GetObject/GetMetaObject and GetEnumerator/GetMetadataEnumerator methods return always a new copy of the stored values.
Default value: .
FileName If this ResXResourceSet has been created from a file, returns the name of the original file. This property will not change if the ResXResourceSet is saved into another file.
IsModified Gets whether this ResXResourceSet instance is modified (contains unsaved data).
SafeMode Gets or sets whether the ResXResourceSet works in safe mode. In safe mode the retrieved objects are not deserialized automatically.
Default value: .

Methods

ContainsMeta Gets whether the current ResXResourceSet contains a metadata with the given name.
ContainsResource Gets whether the current ResXResourceSet contains a resource with the given name.
FromFileContents Creates a new ResXResourceSet object and initializes it to read a string whose contents are in the form of an XML resource file.
GetAliasEnumerator Returns an IDictionaryEnumerator that can iterate through the aliases of the ResXResourceSet.
GetAliasValue Gets the assembly name for the specified alias.
GetDefaultReader Returns the type of ResXResourceReader, which is the preferred resource reader class for ResXResourceSet.
(Overrides ResourceSetGetDefaultReader)
GetDefaultWriter Returns the type of ResXResourceWriter, which is the preferred resource writer class for ResXResourceSet.
(Overrides ResourceSetGetDefaultWriter)
GetEnumerator Returns an IDictionaryEnumerator that can iterate through the resources of the ResXResourceSet.
(Overrides ResourceSetGetEnumerator)
GetMetadataEnumerator Returns an IDictionaryEnumerator that can iterate through the metadata of the ResXResourceSet.
GetMetaObject Searches for a metadata object with the specified name.
GetMetaString Searches for a String metadata with the specified name.
GetObject(String) Searches for a resource object with the specified name.
(Overrides ResourceSetGetObject(String))
GetObject(String, Boolean) Searches for a resource object with the specified name.
(Overrides ResourceSetGetObject(String, Boolean))
GetString(String) Searches for a String resource with the specified name.
(Overrides ResourceSetGetString(String))
GetString(String, Boolean) Searches for a String resource with the specified name.
(Overrides ResourceSetGetString(String, Boolean))
RemoveAliasValue Removes an assembly alias value from the current ResXResourceSet.
RemoveMetaObject Removes a metadata object from the current ResXResourceSet with the specified name.
RemoveObject Removes a resource object from the current ResXResourceSet with the specified name.
Save(Stream, Boolean, Boolean, String) Saves the ResXResourceSet to the specified stream.
Save(String, Boolean, Boolean, String) Saves the ResXResourceSet to the specified file.
Save(TextWriter, Boolean, Boolean, String) Saves the ResXResourceSet by the specified textWriter.
SetAliasValue Adds or replaces an assembly alias value in the current ResXResourceSet.
SetMetaObject Adds or replaces a metadata object in the current ResXResourceSet with the specified name.
SetObject Adds or replaces a resource object in the current ResXResourceSet with the specified name.

Extension Methods

Convert Converts an Object specified in the obj parameter to the desired targetType.
See the Examples section of the generic ConvertTTarget(Object, CultureInfo) overload for an example.
(Defined by ObjectExtensions)
ConvertTTarget Converts an Object specified in the obj parameter to the desired TTarget.
(Defined by ObjectExtensions)
In Gets whether item is among the elements of set.
See the Examples section of the generic InT(T, T) overload for an example.
(Defined by ObjectExtensions)
IndexOf Searches for an element in the source enumeration where the specified predicate returns .
(Defined by EnumerableExtensions)
IndexOf Searches for an element in the source enumeration.
(Defined by EnumerableExtensions)
IsNullOrEmpty Determines whether the specified source is or empty (has no elements).
(Defined by EnumerableExtensions)
TryAdd Tries to add the specified item to the collection.
(Defined by EnumerableExtensions)
TryAddRange Tries to add the specified collection to the target collection.
(Defined by EnumerableExtensions)
TryClear Tries to remove all elements from the collection.
(Defined by EnumerableExtensions)
TryConvert Tries to convert an Object specified in the obj parameter to the desired targetType.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions)
TryConvert Tries to convert an Object specified in the obj parameter to the desired targetType.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions)
TryConvertTTarget Tries to convert an Object specified in the obj parameter to the desired TTarget.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions)
TryConvertTTarget Tries to convert an Object specified in the obj parameter to the desired TTarget.
See the Examples section of the ConvertTTarget(Object, CultureInfo) method for a related example.
(Defined by ObjectExtensions)
TryGetCount Tries to get the number of elements in the source enumeration without enumerating it.
(Defined by EnumerableExtensions)
TryGetElementAt Tries to get an item at the specified index in the collection.
(Defined by EnumerableExtensions)
TryInsert Tries to insert the specified item at the specified index to the collection.
(Defined by EnumerableExtensions)
TryInsertRange Tries to insert the specified collection into the target collection.
(Defined by EnumerableExtensions)
TryRemove Tries to remove the specified item from to the collection.
(Defined by EnumerableExtensions)
TryRemoveAt Tries to remove an item at the specified index from the collection.
(Defined by EnumerableExtensions)
TryRemoveRange Tries to remove count amount of items from the specified collection at the specified index.
(Defined by EnumerableExtensions)
TryReplaceRange Tries to remove count amount of items from the target at the specified index, and to insert the specified collection at the same position. The number of elements in collection can be different from the amount of removed items.
(Defined by EnumerableExtensions)
TrySetElementAt Tries to set the specified item at the specified index in the collection.
(Defined by EnumerableExtensions)

See Also