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.
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.
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.
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;
}
}
}
Imports System.Resources
<Assembly: NeutralResourcesLanguage("en-US")>
Public Class UILibrary
Private Const nFields As Integer = 4
Public Shared Function GetTitle() As String
Dim retval As String = My.Resources.LibResources.Title
If String.IsNullOrEmpty(retval) Then retval = "<No value>"
Return retval
End Function
Public Shared Function GetFieldNames() As String()
Dim fieldnames(nFields - 1) As String
fieldnames(0) = My.Resources.LibResources.Name
fieldnames(1) = My.Resources.LibResources.ID
fieldnames(2) = My.Resources.LibResources.Born
fieldnames(3) = My.Resources.LibResources.Hired
Return fieldnames
End Function
Public Shared Function GetFieldLengths() As Integer()
Dim fieldLengths(nFields - 1) As Integer
fieldLengths(0) = Int32.Parse(My.Resources.LibResources.NameLength)
fieldLengths(1) = Int32.Parse(My.Resources.LibResources.IDLength)
fieldLengths(2) = Int32.Parse(My.Resources.LibResources.BornLength)
fieldLengths(3) = Int32.Parse(My.Resources.LibResources.HiredLength)
Return fieldLengths
End Function
End Class
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;
}
}
Imports MyCompany.Employees
Imports System.Collections.Generic
Module Module1
Sub Main()
' Get the data from some data source.
Dim employees = InitializeData()
' Display application title.
Dim title As String = UILibrary.GetTitle()
Dim start As Integer = (Console.WindowWidth + title.Length) \ 2
Dim titlefmt As String = String.Format("{{0,{0}{1}", start, "}")
Console.WriteLine(titlefmt, title)
Console.WriteLine()
' Retrieve resources.
Dim fields() As String = UILibrary.GetFieldNames()
Dim lengths() As Integer = UILibrary.GetFieldLengths()
Dim fmtString As String = String.Empty
' Create format string for field headers and data.
For ctr = 0 To fields.Length - 1
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths(ctr), IIf(ctr >= 2, ":d", ""), "}")
Next
' Display the headers.
Console.WriteLine(fmtString, fields)
Console.WriteLine()
' Display the data.
For Each e In employees
Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4)
Next
Console.ReadLine()
End Sub
Private Function InitializeData() As List(Of Tuple(Of String, String, Date, Date))
Dim employees As New List(Of Tuple(Of String, String, Date, Date))
Dim t1 = Tuple.Create("John", "16302", #8/18/1954#, #9/8/2006#)
employees.Add(t1)
t1 = Tuple.Create("Alice", "19745", #5/10/1995#, #10/17/2012#)
employees.Add(t1)
Return employees
End Function
End Module
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;
}
}
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;
}
}
Imports MyCompany.Employees
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Threading
Module Module1
Sub Main()
Dim culture As CultureInfo = CultureInfo.CreateSpecificCulture("fr-FR")
Thread.CurrentThread.CurrentCulture = culture
Thread.CurrentThread.CurrentUICulture = culture
Console.WriteLine("Current culture is {0}", CultureInfo.CurrentCulture.Name)
' Get the data from some data source.
Dim employees = InitializeData()
' Display application title.
Dim title As String = UILibrary.GetTitle()
Dim start As Integer = (Console.WindowWidth + title.Length) \ 2
Dim titlefmt As String = String.Format("{{0,{0}{1}", start, "}")
Console.WriteLine(titlefmt, title)
Console.WriteLine()
' Retrieve resources.
Dim fields() As String = UILibrary.GetFieldNames()
Dim lengths() As Integer = UILibrary.GetFieldLengths()
Dim fmtString As String = String.Empty
' Create format string for field headers and data.
For ctr = 0 To fields.Length - 1
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths(ctr), IIf(ctr >= 2, ":d", ""), "}")
Next
' Display the headers.
Console.WriteLine(fmtString, fields)
Console.WriteLine()
' Display the data.
For Each e In employees
Console.WriteLine(fmtString, e.Item1, e.Item2, e.Item3, e.Item4)
Next
Console.ReadLine()
End Sub
Private Function InitializeData() As List(Of Tuple(Of String, String, Date, Date))
Dim employees As New List(Of Tuple(Of String, String, Date, Date))
Dim t1 = Tuple.Create("John", "16302", #8/18/1954#, #9/8/2006#)
employees.Add(t1)
t1 = Tuple.Create("Alice", "19745", #5/10/1995#, #10/17/2012#)
employees.Add(t1)
Return employees
End Function
End Module
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;
}
}
}
Imports Windows.Globalization
Imports MyCompany.Employees
Public NotInheritable Class BlankPage
Inherits Page
Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
Example.Demo(outputBlock)
End Sub
End Class
Public Class Example
Public Shared Sub Demo(outputBlock As Windows.UI.Xaml.Controls.TextBlock)
' Set the application preferences.
ApplicationLanguages.PrimaryLanguageOverride = "fr-FR"
' Get the data from some data source.
Dim employees = InitializeData()
outputBlock.FontFamily = New FontFamily("Courier New")
' Display application title.
Dim title As String = UILibrary.GetTitle()
outputBlock.Text += title + vbCrLf + vbCrLf
' Retrieve resources.
Dim fields() As String = UILibrary.GetFieldNames()
Dim lengths() As Integer = UILibrary.GetFieldLengths()
Dim fmtString As String = String.Empty
' Create format string for field headers and data.
For ctr = 0 To fields.Length - 1
fmtString += String.Format("{{{0},-{1}{2}{3} ", ctr, lengths(ctr), If(ctr >= 2, ":d", ""), "}")
Next
' Display the headers.
outputBlock.Text += String.Format(fmtString, fields) + vbCrLf + vbCrLf
' Display the data.
For Each e In employees
outputBlock.Text += String.Format(fmtString, e.Item1, e.Item2, e.Item3, e.Item4) + vbCrLf
Next
End Sub
Private Shared Function InitializeData() As List(Of Tuple(Of String, String, Date, Date))
Dim employees As New List(Of Tuple(Of String, String, Date, Date))
Dim t1 = Tuple.Create("John", "16302", #8/18/1954#, #9/8/2006#)
employees.Add(t1)
t1 = Tuple.Create("Alice", "19745", #5/10/1995#, #10/17/2012#)
employees.Add(t1)
Return employees
End Function
End Class