Enabling ASP.NET 2.0 Localization in a DotNetNuke Application

Editor’s note: This approach has been obsoleted in favor of the more straightforward, core-friendly ASP.NET 2.0 Localization approach outlined here.


DotNetNuke provided rich localization support (in the DotNetNuke.Services.Localization namespace) long before ASP.NET caught up in version 2.0. Indeed, the core DotNetNuke libraries and UI continue to heavily rely upon this custom implementation to this day.

However, after ASP.NET 2.0 was released, there were apparently a number of issues revolving around the new localization, backwards compatibility, and the existing DotNetNuke localization. As a result, the core team chose to disable all ASP.NET 2.0 localization at the application level and to rely exclusively upon its internal implementation. This is unfortunate, but understandable — the core team has a lot of audiences, and must balance the act of pleasing them all.

DotNetNuke localization is excellent, and for almost every scenario, I strongly recommend using this API.  However, there do exist some niche circumstances that lend toward using ASP.NET 2.0 localization.
Fortunately, with a few small changes, it is possible to restore limited ASP.NET 2.0 functionality to your DotNetNuke installation. Herein I discuss these changes and their system-wide impact.

Before I begin, all the usual warnings apply: back up your data, attempt this only on a development machine, thoroughly test before entering production, et cetera, et cetera. Caveat lector!

With that, let’s get started.

Restore .resx compilation

DotNetNuke disables ASP.NET 2.0 localization by explicitly removing .resx and .resources from the set of custom buildProviders. This is accomplished in the web.config as follows:

<buildProviders> <remove extension=”.resx”/> <remove extension=”.resources”/> </buildProviders>

Perhaps obviously, we’ll first need to re-enable .resx compilation by deleting “<remove extension=”.resx”/>”.

Warning: Because DotNetNuke uses the .resources extension for a number of other purposes (including to prevent IIS from serving sensitive files), I neither recommend enabling this extension, nor have performed ANY testing with regards to files having this extension. I strongly suggest leaving it alone.

Dealing with Portal-Specific Resources in App_GlobalResources

Once the .resx extension has been re-enabled, ASP.NET will begin compiling existing .resx resource files. However, this will cause a problem for those resources that have been localized for a specific portal. DotNetNuke creates these files with a [FileName].Portal-[PortalId].resx format (e.g. “SharedResources.Portal-0.resx”). When the ASP.NET resource compiler attempts to compile these files, it will generate an error. This is because ASP.NET expects the “Portal-[PortalId]” token to be a valid language (e.g. en-US). Obviously, “Portal-[PortalId]” is not a valid language, and the resource compiler chokes with: “CS0101: The namespace ‘Resources’ already contains a definition for ‘SharedResources’”

If you never expect to localize any of your resources (never!), then you will not encounter this situation, and may skip this step.

Otherwise, we are thus forced to prevent the ASP.NET resource compiler from compiling these files. Although I am not fully happy with this solution, to date the best method that I have found to accomplish this is to rename the App_GlobalResources directory.

This requires two changes:

1) Rename your App_GlobalResources directory to GlobalResources.

2) Update the DotNetNuke core to look in the new GlobalResources directory. This value is set on or about line 73 of Library/Components/Localization/Localization.vb:

Public Const ApplicationResourceDirectory As String = “~/App_GlobalResources”

Changed to:

Public Const ApplicationResourceDirectory As String = “~/GlobalResources”

I have requested that this value be settable via external configuration so that a core compilation is not required, but this is yet unimplemented.

Restore resheaders and schema to invalid DotNetNuke resource files.

 ASP.NET 2.0 localization requires a valid set of resheaders and schema for its resource files. Since DotNetNuke treats .resx files as simple xml files, it does not need this additional information. As a result, some language pack designers have intentionally omitted these data. DotNetNuke itself provides ASP.NET 2.0 localization-friendly .resx files for the most part, but there are random and frustrating omissions.

You’ll know you’ve encountered one of these problematic .resx files when a page fails with the following exception: DotNetNuke.Services.Exceptions.ModuleLoadException: ResX input is not valid. Cannot find valid “resheader” tags for the ResX reader and writer type names. Note that that since ASP.NET 2.0 compiles all resources in App_LocalResources at once, the problem could be in ANY of the files, not just the one being accessed.

Unfortunately, these values must be manually added to those ASP.NET 2.0-invalid .resx files. Admin/Host/App_LocalResources/Solutions.ascx.resx is a continual culprit. Open with any text editor and paste the following schema and resheaders under the element:

<resheader name=”resmimetype”> <value>text/microsoft-resx</value> </resheader> <resheader name=”version”> <value>2.0</value> </resheader> <resheader name=”reader”> <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089<value> </resheader> <resheader name=”writer”> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader>

Note: After publishing, I discovered that only the resheaders are required; the schema remains optional.  For those that wish to include schema, I will include it as a comment below.

Once you save the resource file with the expected data, the exception should go away. I have requested that these data be added to all standard DotNetNuke resources files, but the core team has disagreed with me. Charles Nurse indicated that:

…We have for historical reasons used the resx language files as standard xml files. This is how they are processed – in fact the resx file extension is eplicitly disabled from the BuildManager in web.config.

While the schema is added automatically to the resx files by the resource editor in Visual Studio, it is our intention to remove the headers as they affect performance in our implementation.

The fact that you have decided to compile those resources in your modules means that your requirements conflict with the core requirements. We cannot address this.

He has an excellent point, and though my requirements do differ, I completely agree with his conclusion. Read the full details here.

Compile and Enjoy

These changes will allow you to use ASP.NET 2.0 localization at the App_LocalResources level – including .ascx syntax (via the <%$ %>or meta:resourcekey methods) and by code (via the strongly-typed Resources object, or by a GetLocalResourceObject call.

GlobalResources should also be accessible, provided a new App_GlobalResources directory is created and resources are placed within. However, the DotNetNuke GlobalResources and SharedResources values will need to be accessed via the DotNetNuke localization API (as they probably should be).

If anyone actually chooses to go this route and implement the changes I’ve outlined above, I’d appreciate feedback and information about any problems encountered. Eventually I’m sure that DotNetNuke will move to support ASP.NET 2.0 localization, and this is a good step in that direction!

B

Be Sociable, Share!

2 Comments

  1. brandonhaynes

    September 18, 2008 @ 4:25 pm

    1

    Below is the resource schema that I originally believed to be necessary for proper operation, but subsequently realized was optional:

      <xsd:schema id=”root” xmlns=”” xmlns:xsd=”http://www.w3.org/2001/XMLSchema” xmlns:msdata=”urn:schemas-microsoft-com:xml-msdata”>
        <xsd:import namespace=”http://www.w3.org/XML/1998/namespace” />
        <xsd:element name=”root” msdata:IsDataSet=”true”>
          <xsd:complexType>
            <xsd:choice maxOccurs=”unbounded”>
              <xsd:element name=”metadata”>
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name=”value” type=”xsd:string” minOccurs=”0″ />
                  </xsd:sequence>
                  <xsd:attribute name=”name” use=”required” type=”xsd:string” />
                  <xsd:attribute name=”type” type=”xsd:string” />
                  <xsd:attribute name=”mimetype” type=”xsd:string” />
                  <xsd:attribute ref=”xml:space” />
                </xsd:complexType>
              </xsd:element>
              <xsd:element name=”assembly”>
                <xsd:complexType>
                  <xsd:attribute name=”alias” type=”xsd:string” />
                  <xsd:attribute name=”name” type=”xsd:string” />
                </xsd:complexType>
              </xsd:element>
              <xsd:element name=”data”>
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name=”value” type=”xsd:string” minOccurs=”0″ msdata:Ordinal=”1″ />
                    <xsd:element name=”comment” type=”xsd:string” minOccurs=”0″ msdata:Ordinal=”2″ />
                  </xsd:sequence>
                  <xsd:attribute name=”name” type=”xsd:string” use=”required” msdata:Ordinal=”1″ />
                  <xsd:attribute name=”type” type=”xsd:string” msdata:Ordinal=”3″ />
                  <xsd:attribute name=”mimetype” type=”xsd:string” msdata:Ordinal=”4″ />
                  <xsd:attribute ref=”xml:space” />
                </xsd:complexType>
              </xsd:element>
              <xsd:element name=”resheader”>
                <xsd:complexType>
                  <xsd:sequence>
                    <xsd:element name=”value” type=”xsd:string” minOccurs=”0″ msdata:Ordinal=”1″ />
                  </xsd:sequence>
                  <xsd:attribute name=”name” type=”xsd:string” use=”required” />
                </xsd:complexType>
              </xsd:element>
            </xsd:choice>
          </xsd:complexType>
        </xsd:element>
      </xsd:schema>

  2. From the Desk of Brandon Haynes » A Second Look: Enabling ASP.NET 2.0 Localization in a DotNetNuke Application

    October 29, 2008 @ 10:02 am

    2

    […] time ago, I wrote about an approach for enabling ASP.NET 2.0 localization within a DotNetNuke application.  […]

Log in