App Resources for Libraries That Target Multiple Platforms

You can use the .NET Framework Portable Class Library project type to ensure that resources in your class libraries can be accessed from multiple platforms. This project type is available in Visual Studio 2012 and targets the portable subset of the .NET Framework class library. Using a Portable Class Library ensures that your library can be accessed from desktop apps, Silverlight apps, Windows Phone apps, and Windows 8.x Store apps.

Importante

We strongly discourage the use of portable class libraries in new application development, because they target only a very specific subset of .NET implementations. If you're building a reusable library to share code between .NET Framework and other .NET implementations, the recommended replacement is a .NET Standard 2.0 library. Otherwise, use .NET 5 or later. For more information, see .NET Standard.

The Portable Class Library project makes only a very limited subset of the types in the System.Resources namespace available to your application, but it does allow you to use the ResourceManager class to retrieve resources. However, if you are creating an app by using Visual Studio, you should use the strongly typed wrapper created by Visual Studio instead of using the ResourceManager class directly.

To create a strongly typed wrapper in Visual Studio, set the main resource file's Access Modifier in the Visual Studio Resource Designer to Public. This creates a [resourceFileName].designer.cs or [resourceFileName].designer.vb file that contains the strongly typed ResourceManager wrapper. For more information about using a strongly typed resource wrapper, see the "Generating a Strongly Typed Resource Class" section in the Resgen.exe (Resource File Generator) topic.

Resource Manager in the Portable Class Library

In a Portable Class Library project, all access to resources is handled by the ResourceManager class. Because types in the System.Resources namespace, such as ResourceReader and ResourceSet, are not accessible from a Portable Class Library project, they cannot be used to access resources.

The Portable Class Library project includes the four ResourceManager members listed in the following table. These constructors and methods enable you to instantiate a ResourceManager object and retrieve string resources.

ResourceManager member Description
ResourceManager(String, Assembly) Creates a ResourceManager instance to access the named resource file found in the specified assembly.
ResourceManager(Type) Creates a ResourceManager instance that corresponds to the specified type.
GetString(String) Retrieves a named resource for the current culture.
GetString(String, CultureInfo) Retrieves a named resource belonging to the specified culture.

The exclusion of other ResourceManager members from the Portable Class Library means that serialized objects, non-string data, and images cannot be retrieved from a resource file. To use resources from a Portable Class Library, you should store all object data in string form. For example, you can store numeric values in a resource file by converting them to strings, and you can retrieve them and then convert them back to numbers by using the numeric data type's Parse or TryParse method. You can convert images or other binary data to a string representation by calling the Convert.ToBase64String method, and restore them to a byte array by calling the Convert.FromBase64String method.

The Portable Class Library and Windows Store Apps

Portable Class Library projects store resources in .resx files, which are then compiled into .resources files and embedded in the main assembly or in satellite assemblies at compile time. Windows 8.x Store apps, on the other hand, require resources to be stored in .resw files, which are then compiled into a single package resource index (PRI) file. However, despite the incompatible file formats, your Portable Class Library will work in a Windows 8.x Store app.

To consume your class library from a Windows 8.x Store app, add a reference to it in your Windows Store app project. Visual Studio will transparently extract the resources from your assembly into a .resw file and use it to generate a PRI file from which the Windows Runtime can extract resources. At run time, the Windows Runtime executes the code in your Portable Class Library, but it retrieves your Portable Class Library's resources from the PRI file.

If your Portable Class Library project includes localized resources, you use the hub-and-spoke model to deploy them just as you would for a library in a desktop app. To consume your main resource file and any localized resource files in your Windows 8.x Store app, you add a reference to the main assembly. At compile time, Visual Studio extracts the resources from your main resource file and any localized resource files into separate .resw files. It then compiles the .resw files into a single PRI file that the Windows Runtime accesses at run time.

Example: Non-Localized Portable Class Library

The following simple, non-localized Portable Class Library example uses resources to store the names of columns and to determine the number of characters to reserve for tabular data. The example uses a file named LibResources.resx to store the string resources listed in the following table.

Resource name Resource value
Born Birthdate
BornLength 12
Hired Hire Date
HiredLength 12
ID ID
ID.Length 12
Name Name
NameLength 25
Title Employee Database

The following code defines a UILibrary class that uses the Resource Manager wrapper named resources generated by Visual Studio when the Access Modifier for the file is changed to Public. The UILibrary class parses the string data as necessary. . Note that the class is in the MyCompany.Employees namespace.

using System;
using System.Resources;
using MyCompany.Employees;

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

namespace MyCompany.Employees
{
   public class UILibrary
   {
      private const int nFields = 4;

      public static string GetTitle()
      {
         string retval = LibResources.Born;
         if (String.IsNullOrEmpty(retval))
            retval = "";

         return retval;
      }

      public static string[] GetFieldNames()
      {
         string[] fieldnames = new string[nFields];
         fieldnames[0] = LibResources.Name;
         fieldnames[1] = LibResources.ID;
         fieldnames[2] = LibResources.Born;
         fieldnames[3] = LibResources.Hired;
         return fieldnames;
      }

      public static int[] GetFieldLengths()
      {
         int[] fieldLengths = new int[nFields];
         fieldLengths[0] = Int32.Parse(LibResources.NameLength);
         fieldLengths[1] = Int32.Parse(LibResources.IDLength);
         fieldLengths[2] = Int32.Parse(LibResources.BornLength);
         fieldLengths[3] = Int32.Parse(LibResources.HiredLength);
         return fieldLengths;
      }
   }
}

The following code illustrates how the UILibrary class and its resources can be accessed from a console-mode app. It requires a reference to UILibrary.dll to be added to the console app project.

using System;
using System.Collections.Generic;
using MyCompany.Employees;

class Program
{
   static void Main()
   {
     // Get the data from some data source.
      var employees = InitializeData();

      // Display application title.
      string title = UILibrary.GetTitle();
      int start = (Console.WindowWidth + title.Length) / 2;
      string titlefmt = String.Format("{{0,{0}{1}", start, "}");
      Console.WriteLine(titlefmt, title);
      Console.WriteLine();

      // Retrieve resources.
      string[] fields = UILibrary.GetFieldNames();
      int[] lengths = UILibrary.GetFieldLengths();
      string fmtString = String.Empty;
      // Create format string for field headers and data.
      for (int ctr = 0; ctr < fields.Length; ctr++)
         fmtString += String.Format("{{{0},-{1}{2}{3}   ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

      // Display the headers.
      Console.WriteLine(fmtString, fields);
      Console.WriteLine();
      // Display the data.
      foreach (var e in employees)
         Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4);

      Console.ReadLine();
   }

   private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()
   {
      List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime, DateTime>>();
      var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
      employees.Add(t1);
      t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
      employees.Add(t1);
      return employees;
   }
}

The following code illustrates how the UILibrary class and its resources can be accessed from a Windows 8.x Store app. It requires a reference to UILibrary.dll to be added to the Windows Store app project.

using System;
using System.Collections.Generic;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using MyCompany.Employees;

namespace ConsumerCS
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class BlankPage : Page
    {
        public BlankPage()
        {
            this.InitializeComponent();
         }

        /// <summary>
        /// Invoked when this page is about to be displayed in a Frame.
        /// </summary>
        /// <param name="e">Event data that describes how this page was reached.  The Parameter
        /// property is typically used to configure the page.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
           Example.DisplayData(outputBlock);
       }
    }
}

public class Example
{
    static public void DisplayData(Windows.UI.Xaml.Controls.TextBlock outputBlock)
    {
        // Get the data from some data source.
        var employees = InitializeData();
        outputBlock.FontFamily = new FontFamily("Courier New");
        // Display application title.
        string title = UILibrary.GetTitle();
        outputBlock.Text += title + Environment.NewLine + Environment.NewLine;

        // Retrieve resources.
        string[] fields = UILibrary.GetFieldNames();
        int[] lengths = UILibrary.GetFieldLengths();
        string fmtString = String.Empty;
        // Create format string for field headers and data.
        for (int ctr = 0; ctr < fields.Length; ctr++)
            fmtString += String.Format("{{{0},-{1}{2}{3}   ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

        // Display the headers.
        outputBlock.Text += String.Format(fmtString, fields) + Environment.NewLine + Environment.NewLine;
        // Display the data.
        foreach (var e in employees)
            outputBlock.Text += String.Format(fmtString, e.Item1, e.Item2, e.Item3, e.Item4) + Environment.NewLine;
    }

    private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()
    {
        List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime, DateTime>>();
        var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
        employees.Add(t1);
        t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
        employees.Add(t1);
        return employees;
    }
}

Example: Localized Portable Class Library

The following localized Portable Class Library example includes resources for the French (France) and English (United States) cultures. The English (United States) culture is the app's default culture; its resources are shown in the table in the previous section. The resources file for the French (France) culture is named LibResources.fr-FR.resx and consists of the string resources listed in the following table. The source code for the UILibrary class is the same as that shown in the previous section.

Resource name Resource value
Born Date de naissance
BornLength 20
Hired Date embauché
HiredLength 16
ID ID
Name Nom
Title Base de données des employés

The following code illustrates how the UILibrary class and its resources can be accessed from a console-mode app. It requires a reference to UILibrary.dll to be added to the console app project.

using System;
using System.Collections.Generic;
using System.Globalization;

using MyCompany.Employees;

class Program
{
   static void Main(string[] args)
   {

      // Get the data from some data source.
      var employees = InitializeData();

      // Display application title.
      string title = UILibrary.GetTitle();
      int start = (Console.WindowWidth + title.Length) / 2;
      string titlefmt = String.Format("{{0,{0}{1}", start, "}");
      Console.WriteLine(titlefmt, title);
      Console.WriteLine();

      // Retrieve resources.
      string[] fields = UILibrary.GetFieldNames();
      int[] lengths = UILibrary.GetFieldLengths();
      string fmtString = String.Empty;
      // Create format string for field headers and data.
      for (int ctr = 0; ctr < fields.Length; ctr++)
         fmtString += String.Format("{{{0},-{1}{2}{3}   ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

      // Display the headers.
      Console.WriteLine(fmtString, fields);
      Console.WriteLine();
      // Display the data.
      foreach (var e in employees)
         Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4);

      Console.ReadLine();
   }

   private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()
   {
      List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime, DateTime>>();
      var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
      employees.Add(t1);
      t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
      employees.Add(t1);
      return employees;
   }
}

The following code illustrates how the UILibrary class and its resources can be accessed from a Windows 8.x Store app. It requires a reference to UILibrary.dll to be added to the Windows Store app project. It uses the static ApplicationLanguages.PrimaryLanguageOverride property to set the app's preferred language to French.

using System;
using System.Collections.Generic;
using Windows.Globalization;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using MyCompany.Employees;

namespace LocConsumerCS
{
    public sealed partial class BlankPage : Page
    {
        public BlankPage()
        {
            this.InitializeComponent();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            Example.Demo(outputBlock);
        }
    }

    public class Example
    {
        public static void Demo(TextBlock outputBlock)
        {
            // Set the application preferences.
            ApplicationLanguages.PrimaryLanguageOverride = "fr-FR";

            // Get the data from some data source.
            var employees = InitializeData();
            outputBlock.FontFamily = new FontFamily("Courier New");
            // Display application title.
            string title = UILibrary.GetTitle();
            outputBlock.Text += title + Environment.NewLine + Environment.NewLine;

            // Retrieve resources.
            string[] fields = UILibrary.GetFieldNames();
            int[] lengths = UILibrary.GetFieldLengths();
            string fmtString = String.Empty;
            // Create format string for field headers and data.
            for (int ctr = 0; ctr < fields.Length; ctr++)
                fmtString += String.Format("{{{0},-{1}{2}{3}   ", ctr, lengths[ctr], ctr >= 2 ? ":d" : "", "}");

            // Display the headers.
            outputBlock.Text += String.Format(fmtString, fields) + Environment.NewLine + Environment.NewLine;

            // Display the data.
            foreach (var e in employees)
                outputBlock.Text += String.Format(fmtString, e.Item1, e.Item2, e.Item3, e.Item4) + Environment.NewLine;
        }

        private static List<Tuple<String, String, DateTime, DateTime>> InitializeData()
        {
            List<Tuple<String, String, DateTime, DateTime>> employees = new List<Tuple<String, String, DateTime, DateTime>>();
            var t1 = Tuple.Create("John", "16302", new DateTime(1954, 8, 18), new DateTime(2006, 9, 8));
            employees.Add(t1);
            t1 = Tuple.Create("Alice", "19745", new DateTime(1995, 5, 10), new DateTime(2012, 10, 17));
            employees.Add(t1);
            return employees;
        }
    }
}

See also