Retrieving Resources in Desktop Apps

When you work with localized resources in .NET Framework desktop apps, you should ideally package the resources for the default or neutral culture with the main assembly and create a separate satellite assembly for each language or culture that your app supports. You can then use the ResourceManager class as described in the next section to access named resources. If you choose not to embed your resources in the main assembly and satellite assemblies, you can also access binary .resources files directly, as discussed in the section Retrieving Resources from .resources files later in this article. To retrieve resources in Windows Store apps, see Creating and retrieving resources in Windows Store apps in the Windows Dev Center.

The ResourceManager class provides access to resources at run time. You use the ResourceManager.GetString method to retrieve string resources and the ResourceManager.GetObject or ResourceManager.GetStream method to retrieve non-string resources. Each method has two overloads:

The resource manager uses the resource fallback process to control how the app retrieves culture-specific resources. For more information, see the "Resource Fallback Process" section in Packaging and Deploying Resources in Desktop Apps. For information about instantiating a ResourceManager object, see the "Instantiating a ResourceManager Object" section in the ResourceManager class topic.

The following example calls the GetString(String) method to retrieve the string resources of the current UI culture. It includes a neutral string resource for the English (United States) culture and localized resources for the French (France) and Russian (Russia) cultures. The following English (United States) resource is in a file named Strings.txt:

TimeHeader=The current time is

The French (France) resource is in a file named Strings.fr-FR.txt:

TimeHeader=L'heure actuelle est

The Russian (Russia) resource is in a file named Strings.ru-RU-txt:

TimeHeader=Текущее время —

The source code for this example, which is in a file named GetString.cs for the C# version of the code and GetString.vb for the Visual Basic version, defines a string array that contains the name of four cultures: the three cultures for which resources are available and the Spanish (Spain) culture. A loop that executes five times randomly selects one of these cultures and assigns it to the Thread.CurrentCulture and CultureInfo.CurrentUICulture properties. It then calls the GetString(String) method to retrieve the localized string, which it displays along with the time of day.

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

[assembly: NeutralResourcesLanguageAttribute("en-US")]

public class Example
{
   public static void Main()
   {
      string[] cultureNames = { "en-US", "fr-FR", "ru-RU", "es-ES" };
      Random rnd = new Random();
      ResourceManager rm = new ResourceManager("Strings", 
                               typeof(Example).Assembly);

      for (int ctr = 0; ctr <= cultureNames.Length; ctr++) {
         string cultureName = cultureNames[rnd.Next(0, cultureNames.Length)];
         CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureName);
         Thread.CurrentThread.CurrentCulture = culture;
         Thread.CurrentThread.CurrentUICulture = culture;

         Console.WriteLine("Current culture: {0}", culture.NativeName);
         string timeString = rm.GetString("TimeHeader");
         Console.WriteLine("{0} {1:T}\n", timeString, DateTime.Now);   
      }   
   }
}
// The example displays output like the following: 
//    Current culture: English (United States) 
//    The current time is 9:34:18 AM 
//     
//    Current culture: Español (España, alfabetización internacional) 
//    The current time is 9:34:18 
//     
//    Current culture: русский (Россия) 
//    Текущее время — 9:34:18 
//     
//    Current culture: français (France) 
//    L'heure actuelle est 09:34:18 
//     
//    Current culture: русский (Россия) 
//    Текущее время — 9:34:18

The following batch (.bat) file compiles the example and generates satellite assemblies in the appropriate directories. The commands are provided for the C# language and compiler. For Visual Basic, change csc to vbc, and change GetString.cs to GetString.vb.

resgen strings.txt
csc GetString.cs /resource:strings.resources

resgen strings.fr-FR.txt
md fr-FR
al /embed:strings.fr-FR.resources /culture:fr-FR /out:fr-FR\GetString.resources.dll

resgen strings.ru-RU.txt
md ru-RU
al /embed:strings.ru-RU.resources /culture:ru-RU /out:ru-RU\GetString.resources.dll

When the current UI culture is Spanish (Spain), note that the example displays English language resources, because Spanish language resources are unavailable, and English is the example's default culture.

You can use the GetObject and GetStream methods to retrieve object data. This includes primitive data types, serializable objects, and objects that are stored in binary format (such as images).

The following example uses the GetStream(String) method to retrieve a bitmap that is used in an app's opening splash window. The following source code in a file named CreateResources.cs (for C#) or CreateResources.vb (for Visual Basic) generates a .resx file that contains the serialized image. In this case, the image is loaded from a file named SplashScreen.jpg; you can modify the file name to substitute your own image.

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Resources;

public class Example
{
   public static void Main()
   {
      Bitmap bmp = new Bitmap(@".\SplashScreen.jpg");
      MemoryStream imageStream = new MemoryStream();
      bmp.Save(imageStream, ImageFormat.Jpeg);

      ResXResourceWriter writer = new ResXResourceWriter("AppResources.resx");
      writer.AddResource("SplashScreen", imageStream);
      writer.Generate();
      writer.Close();      
   }
}

The following code retrieves the resource and displays the image in a PictureBox control.

using System;
using System.Drawing;
using System.IO;
using System.Resources;
using System.Windows.Forms;

public class Example
{
   public static void Main()
   {
      ResourceManager rm = new ResourceManager("AppResources", typeof(Example).Assembly);
      Bitmap screen = (Bitmap) Image.FromStream(rm.GetStream("SplashScreen"));

      Form frm = new Form();
      frm.Size = new Size(300, 300);

      PictureBox pic = new PictureBox();
      pic.Bounds = frm.RestoreBounds;
      pic.BorderStyle = BorderStyle.Fixed3D; 
      pic.Image = screen;
      pic.SizeMode = PictureBoxSizeMode.StretchImage;

      frm.Controls.Add(pic);
      pic.Anchor = AnchorStyles.Top | AnchorStyles.Bottom |
                   AnchorStyles.Left | AnchorStyles.Right;

      frm.ShowDialog();
   }
}

You can use the following batch file to build the C# example. For Visual Basic, change csc to vbc, and change the extension of the source code file from .cs to .vb.

csc CreateResources.cs
CreateResources

resgen AppResources.resx

csc GetStream.cs /resource:AppResources.resources

The following example uses the ResourceManager.GetObject(String) method to deserialize a custom object. The example includes a source code file named UIElements.cs (UIElements.vb for Visual Basic) that defines the following structure named PersonTable. This structure is intended to be used by a general table display routine that displays the localized names of table columns. Note that the PersonTable structure is marked with the SerializableAttribute attribute.

using System;

[Serializable] public struct PersonTable
{
   public readonly int nColumns;
   public readonly string column1; 
   public readonly string column2;
   public readonly string column3; 
   public readonly int width1;
   public readonly int width2;
   public readonly int width3;

   public PersonTable(string column1, string column2, string column3,
                  int width1, int width2, int width3)
   {
      this.column1 = column1;
      this.column2 = column2;
      this.column3 = column3;
      this.width1 = width1;
      this.width2 = width2;
      this.width3 = width3;
      this.nColumns = typeof(PersonTable).GetFields().Length / 2; 
   }
}

The following code from a file named CreateResources.cs (CreateResources.vb for Visual Basic) creates an XML resource file named UIResources.resx that stores a table title and a PersonTable object that contains information for an app that is localized for the English language.

using System;
using System.Resources;

public class CreateResource
{
   public static void Main()
   {
      PersonTable table = new PersonTable("Name", "Employee Number", 
                                          "Age", 30, 18, 5);
      ResXResourceWriter rr = new ResXResourceWriter(@".\UIResources.resx");
      rr.AddResource("TableName", "Employees of Acme Corporation");
      rr.AddResource("Employees", table);
      rr.Generate();
      rr.Close();
   }
}

The following code in a source code file named GetObject.cs (GetObject.vb) then retrieves the resources and displays them to the console.

using System;
using System.Resources;

[assembly: NeutralResourcesLanguageAttribute("en")]

public class Example
{
   public static void Main()
   {
      string fmtString = String.Empty;
      ResourceManager rm = new ResourceManager("UIResources", typeof(Example).Assembly);       
      string title = rm.GetString("TableName");
      PersonTable tableInfo = (PersonTable) rm.GetObject("Employees");

      if (! String.IsNullOrEmpty(title)) {
         fmtString = "{0," + ((Console.WindowWidth + title.Length) / 2).ToString() + "}"; 
         Console.WriteLine(fmtString, title);      
         Console.WriteLine();
      }

      for (int ctr = 1; ctr <= tableInfo.nColumns; ctr++) {
         string columnName = "column"  + ctr.ToString();
         string widthName = "width" + ctr.ToString();
         string value = tableInfo.GetType().GetField(columnName).GetValue(tableInfo).ToString();
         int width = (int) tableInfo.GetType().GetField(widthName).GetValue(tableInfo);
         fmtString = "{0,-" + width.ToString() + "}";
         Console.Write(fmtString, value);
      }      
      Console.WriteLine();
   }
}

You can build the necessary resource file and assemblies and run the app by executing the following batch file. You must use the /r option to supply Resgen.exe with a reference to UIElements.dll so that it can access information about the PersonTable structure. If you're using C#, replace the vbc compiler name with csc, and replace the .vb extension with .cs.

vbc /t:library UIElements.vb
vbc CreateResources.vb /r:UIElements.dll
CreateResources

resgen UIResources.resx  /r:UIElements.dll
vbc GetObject.vb /r:UIElements.dll /resource:UIResources.resources

GetObject.exe

By default, when the ResourceManager object retrieves requested resources, it looks for satellite assemblies that have version numbers that match the version number of the main assembly. After you have deployed an app, you might want to update the main assembly or specific resource satellite assemblies. The .NET Framework provides support for versioning the main assembly and satellite assemblies.

The SatelliteContractVersionAttribute attribute provides versioning support for a main assembly. Specifying this attribute on an app's main assembly enables you to update and redeploy a main assembly without updating its satellite assemblies. After you update the main assembly, increment the main assembly's version number but leave the satellite contract version number unchanged. When the resource manager retrieves requested resources, it loads the satellite assembly version specified by this attribute.

Publisher policy assemblies provide support for versioning satellite assemblies. You can update and redeploy a satellite assembly without updating the main assembly. After you update a satellite assembly, increment its version number and ship it with a publisher policy assembly. In the publisher policy assembly, specify that your new satellite assembly is backward-compatible with its previous version. The resource manager will use the SatelliteContractVersionAttribute attribute to determine the version of the satellite assembly, but the assembly loader will bind to the satellite assembly version specified by the publisher policy. For more information about publisher policy assemblies, see Creating a Publisher Policy File.

To enable full assembly versioning support, we recommend that you deploy strong-named assemblies in the global assembly cache and deploy assemblies that don't have strong names in the application directory. If you want to deploy strong-named assemblies in the application directory, you will not be able to increment a satellite assembly's version number when you update the assembly. Instead, you must perform an in-place update where you replace the existing code with the updated code and maintain the same version number. For example, if you want to update version 1.0.0.0 of a satellite assembly with the fully specified assembly name "myApp.resources, Version=1.0.0.0, Culture=de, PublicKeyToken=b03f5f11d50a3a", overwrite it with the updated myApp.resources.dll that has been compiled with the same, fully specified assembly name "myApp.resources, Version=1.0.0.0, Culture=de, PublicKeyToken=b03f5f11d50a3a". Note that using in-place updates on satellite assembly files makes it difficult for an app to accurately determine the version of a satellite assembly.

For more information about assembly versioning, see Assembly Versioning and How the Runtime Locates Assemblies.

If you choose not to deploy resources in satellite assemblies, you can still use a ResourceManager object to access resources from .resources files directly. To do this, you must deploy the .resources files correctly. Then you use the ResourceManager.CreateFileBasedResourceManager method to instantiate a ResourceManager object and specify the directory that contains the standalone .resources files.

When you embed .resources files in an application assembly and satellite assemblies, each satellite assembly has the same file name, but is placed in a subdirectory that reflects the satellite assembly's culture. In contrast, when you access resources from .resources files directly, you can place all the .resources files in a single directory, usually a subdirectory of the application directory. The name of the app's default .resources file consists of a root name only, with no indication of its culture (for example, strings.resources). The resources for each localized culture are stored in a file whose name consists of the root name followed by the culture (for example, strings.ja.resources or strings.de-DE.resources). The following illustration shows where resource files should be located in the directory structure.

Directory structure and naming conventions for .resources files

Main directory for your application

After you have created your resources and placed them in the appropriate directory, you create a ResourceManager object to use the resources by calling the CreateFileBasedResourceManager(String, String, Type) method. The first parameter specifies the root name of the app's default .resources file (this would be "strings" for the example in the previous section). The second parameter specifies the location of the resources ("Resources" for the previous example). The third parameter specifies the ResourceSet implementation to use. If the third parameter is null, the default runtime ResourceSet is used.

Note Note

Do not deploy ASP.NET apps using standalone .resources files. This can cause locking issues and breaks XCOPY deployment. We recommend that you deploy ASP.NET resources in satellite assemblies. For more information, see ASP.NET Web Page Resources Overview.

After you instantiate the ResourceManager object, you use the GetString, GetObject, and GetStream methods as discussed earlier to retrieve the resources. However, the retrieval of resources directly from .resources files differs from the retrieval of embedded resources from assemblies. When you retrieve resources from .resources files, the GetString(String), GetObject(String), and GetStream(String) methods always retrieve the default culture's resources regardless of the current culture. To retrieve the resources of the either the app's current culture or a specific culture, you must call the GetString(String, CultureInfo), GetObject(String, CultureInfo), or GetStream(String, CultureInfo) method and specify the culture whose resources are to be retrieved. To retrieve the resources of the current culture, specify the value of the CultureInfo.CurrentCulture property as the culture argument. If the resource manager cannot retrieve the resources of culture, it uses the standard resource fallback rules to retrieve the appropriate resources.

The following example illustrates how the resource manager retrieves resources directly from .resources files. The example consists of three text-based resource files for the English (United States), French (France), and Russian (Russia) cultures. English (United States) is the example's default culture. Its resources are stored in the following file named Strings.txt:

Greeting=Hello
Prompt=What is your name?

Resources for the French (France) culture are stored in the following file, which is named Strings.fr-FR.txt:

Greeting=Bon jour
Prompt=Comment vous appelez-vous?

Resources for the Russian (Russia) culture are stored in the following file, which is named Strings.ru-RU.txt:

Greeting=Здравствуйте
Prompt=Как вас зовут?

The following is the source code for the example. The example instantiates CultureInfo objects for the English (United States), English (Canada), French (France), and Russian (Russia) cultures, and makes each the current culture. The ResourceManager.GetString(String, CultureInfo) method then supplies the value of the CultureInfo.CurrentCulture property as the culture argument to retrieve the appropriate culture-specific resources.

using System;
using System.Globalization;
using System.Resources;
using System.Threading;

[assembly: NeutralResourcesLanguage("en-US")]

public class Example
{
   public static void Main()
   {
      string[] cultureNames = { "en-US", "en-CA", "ru-RU", "fr-FR" };
      ResourceManager rm = ResourceManager.CreateFileBasedResourceManager("Strings", "Resources", null);

      foreach (var cultureName in cultureNames) {
         Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName);
         string greeting = rm.GetString("Greeting", CultureInfo.CurrentCulture);
         Console.WriteLine("\n{0}!", greeting);
         Console.Write(rm.GetString("Prompt", CultureInfo.CurrentCulture));
         string name = Console.ReadLine();
         if (! String.IsNullOrEmpty(name))
            Console.WriteLine("{0}, {1}!", greeting, name);
      }
      Console.WriteLine();
   }
}
// The example displays output like the following: 
//       Hello! 
//       What is your name? Dakota 
//       Hello, Dakota! 
//        
//       Hello! 
//       What is your name? Koani 
//       Hello, Koani! 
//        
//       Здравствуйте! 
//       Как вас зовут?Samuel 
//       Здравствуйте, Samuel! 
//        
//       Bon jour! 
//       Comment vous appelez-vous?Yiska 
//       Bon jour, Yiska!

You can compile the C# version of the example by running the following batch file. If you're using Visual Basic, replace csc with vbc, and replace the .cs extension with .vb.

Md Resources
Resgen Strings.txt Resources\Strings.resources
Resgen Strings.fr-FR.txt Resources\Strings.fr-FR.resources
Resgen Strings.ru-RU.txt Resources\Strings.ru-RU.resources

csc Example.cs
Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft