Creating Resources
Resource management, a feature of the .NET Framework class library, can be used to extract
localizable elements from source code and to store them with a string key as
resources. At runtime an instance of the ResourceManager class can be used to resolve
the key to the original resource or a localized version. Resources can be stored as
independent ("loose") files or as a part of an assembly.
ASP.NET pages can utilize resource files; compiled code-behind controls can, in
addition, utilize resources embedded or linked into their assembly.
Resources can be created using the ResourceWriter class programmatically or by the tool
Resgen.exe. Resgen.exe can use a simple key=value format as input or an XML file in .resx
format.
;
; Lines beginning with a semicolon can be used for comments.
;
[strings]
greeting=Welcome !
more=Read more ...
...
ResourceWriter and Resgen.exe create a .resources file, which can be used as is or as part
of an assembly. To include a .resources file in an assembly, use the related compiler
switch or the Al.exe tool. Assemblies containing only localized resources and no code
are called satellite assemblies.
Using Resources on a Page
The following sample implements only one .aspx page, which is localized
for each request. The supported languages are English, German, and Japanese.
The language is determined by examining the Content-Language field
of the HTTP header in the Global.asax file. The contents of the
field are accessible through the UserLanguages collection:
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(Request.UserLanguages(0))
VB
To change the initial language setting, you can use differently localized clients or
change the language setting on your browser. For Internet Explorer 5.x, for
example, select Tools -> Internet Options from the menu and click the Languages
button at the bottom. In the following dialog you can add additional languages and
define their priority. For simplicity the sample always chooses the first entry.
After the page is loaded the first time, the user can select another culture in the
drop-down list control MyUICulture. If a valid culture is selected, this value
overrides the setting acquired from UserLanguages:
Dim SelectedCulture As String = MyUICulture.SelectedItem.Text
If Not(SelectedCulture.StartsWith("Choose")) Then
' If another culture was selected, use that instead.
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(Request.UserLanguages(0))
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture
End If
VB
In the previous code, the use of the CreateSpecificCulture method is required because you cannot set the current CultureInfo of your Thread to a neutral culture. However, the string available from the UserLanguages setting may be a neutral culture. Therefore, the CreateSpecificCulture method takes this string, and makes an appropriate CultureInfo from it.
Also, in the Global.asax file, a ResourceManager instance with application scope
is initialized. This way, resources are only loaded once per application. Because
resources are read-only, no lock contention should occur.
Public Sub Application_Start()
Application("RM") = New ResourceManager("articles", _
Server.MapPath("resources") + Path.DirectorySeparatorChar, _
Nothing)
End Sub
VB
The resource manager then can easily be used on the page. The greeting string is
simply localized by:
<%=rm.GetString("greeting")%>
Using Satellite Assemblies
If you look at the structure of the directories in the sample in the previous section,
you see that the resources for the sample are loaded not from DLLs, but
from .resource files. Although this is certainly one solution,
you can also compile your code into satellite assemblies. A satellite assembly
is defined as an assembly with resources only, no executable code. For more
information on satellite assemblies, see the section
How Do I... Create Resources?
.
The benefit of using satellite assemblies becomes apparent when you realize that
.resources files are not shadow-copied because they are not DLLs, and therefore
Web sites can encounter locking problems when using them. The alternative is to use a
parallel main assembly for application resources. The main assembly contains fallback
resources; the satellites (one per culture) contain localized resources.
The main assembly is installed into the \bin directory, and the satellites are
stored in the usual xx-XX subdirectories (see
How Do I... Create Resources?
). Being assemblies, they are shadow-copied and are not locked. To create an assembly-aware .asp application:
- Create the resource DLL and copy it into the \bin directory. For example:
resgen qq.txt qq.resources
al /embed:qq.resources,qq.resources /out:qq.dll
The "y" refers to whether the blob should be visible to other assemblies. Since the ResourceManager lives in Mscorlib and
is a different assembly from "qq", the .resources file must be publically
visible. The "y" says whether this should be public.
- On your page, include the following statement. Note that the name of the
assembly here is in the System.Reflection namespace defined in Mscorlib
(which is always referenced for you when compiling):
<%
Dim a As Assembly = Assembly.Load("qq")
Dim rm As ResourceManager = New ResourceManager("qq", a)
Response.Write(rm.GetString("key"))
%>
VB
- Compile each satellite resource into its own
assembly, placing it into the correct required directory
structure within the /bin directory:
al /embed:qq.en-US.resources,qq.en-US.resources /out:qq.resources.dll /c:en-US
Substitute the code for the culture into which you are localizing for en-US.
Remember that the /c: tag is the culture specifier.
After the DLLs are in the right locations (/bin and /bin/en-US in the
above samples), the resources can be retrieved appropriately. Note that
everything gets shadow-copied by assembly cache and thus is replaceable, avoiding
potential locking scenarios.
Using Satellite Assemblies for Controls
Compiled code-behind controls can also use satellite assemblies to supply
localized content. From a deployment perspective, this is an especially good
thing, because satellite assemblies can be version-independent from the code.
As a result, support for additional languages can be provided just by copying
the module of the satellite to the server, and no code change is required.
The following sample contains the LocalizedButton control in the
assembly LocalizedControls (module LocalizedControls.dll). On the page
Showcontrols.aspx, the compiled control is registered and used later on:
<%@Register TagPrefix="Loc" namespace="LocalizedControls" %>
...
<Loc:LocalizedButton runat="server" Text="ok" />
The LocalizedButton control stores a ResourceManager instance, which is
shared by all instances of LocalizedButton. Whenever a control is
rendered, the value of the Text property is replaced with the localized
version:
_rm = New ResourceManager("LocalizedStrings", _
Assembly.GetExecutingAssembly(), _
Nothing, _
True )
...
Overrides Protected Sub Render (writer As HtmlTextWriter)
Text = ResourceFactory.RManager.GetString(Text)
base.Render(writer)
End Sub
VB
The ResourceManager instance is responsible for resolving the key to a
localized resource. If a satellite assembly with the correct culture
is not available and no related culture is found, the neutral resource
of the main assembly is used ("en-us" -> "en" ->
neutral). Support for another language is simply granted by copying the
module file for the new satellite assembly in place.