Share This Using Popular Bookmarking Services


Google ads

Recommended

network monitoring software




Programs & websites tailormade for you.

Using precompiled resources (resx files) in ASP.NET 

Saturday, March 17, 2012 8:00:00 AM

Or: What Microsoft forgot to tell about localization!

I was recently reading the book "MCTS Self-Paced Training Kit (Exam 70-515): Web Applications Development with Microsoft .NET Framework 4", just to catch up on the development since I last formally studied ASP.NET.

In their chapter about localization they write about how to use the App_GlobalResources and the App_LocalResources folders - but they forgot to mention a third option.

The thing is: Those two folders give problems if you want to compile your stuff into a library, and they can give problems in some configurations of Web Applications, because - just like the App_Code folder - they are meant to be dynamically compiled by the webserver, and that means they might appear in a different namespace than your library when you deploy them, or they will somehow appear twice with a namespace conflict as the result...

When I did the AjaxSafeUpload control, I also wanted those resources to be compiled into a DLL because one of the CMS systems, where I needed it, used a totally fourth way of doing localization. (Hint: the behaviour of the App_GlobalResources folder can also be controlled by your web.config file - which inherit settings from the web.config file of a parent web application under IIS, no matter if they use a different worker process or not, so getting your own web application to work in a subfolder under a CMS system can be a matter of restoring the asp.net default web.config values.)

I have also experienced that the just-in-time compillation of App_GlobalResources can reduce application start times a great deal - for example for mojoPortal the website starts a good deal quicker after deleting all the language resources that I am not using.

So how do you get ASP.NET to compile your resx files into DLL libraries?

The third option: Use another folder for global resx files!

Simply rename the App_GlobalResources to something else, like "MyTexts", "Translation" or even "Resources".

Then right click on the resx files in the Solution Explorer of Visual Studio and choose properties. You have to manually adjust the properties to look something like this:

Setting properties for resx files to be compiled

The key is setting the "Build action" to "Embedded Resource" (which means put the text inside DLLs) and set "Custom Tool" to "ResXFileCodeGenerator" (which is the tool that can do this compilation).

You might also set "Custom Tool Namespace" to something - here it is empty, so since the folder is called "Translation" and the file is called "Texts.resx", I can now reference those texts using the notation: Translation.Texts.xxxx (where "xxxx" is any of the names defined in the resource). If you set the namespace to "Resources" then most of the code that was written to use resources from App_GlobalResources will still work after renaming the folder!

If you need access to the resources from other projects, then you need to do an additional change - open the resx file, and (with the above property changes) the top of the window will look like this:

Changeing resx to be publicly visible

In the AjaxSafeUpload project I did not change the Access Modifier, but you can change it to public - but only if you do the previous changes to "Custom Tool"!

Now what if you need to access one of those resources based on a code lookup of a Name?

First I added this helper function to the aspx page (or to an utility class):

    private ResourceManager rm=null;

    protected string GetResource(string name)
    {
      if (rm == null)
      {
        Assembly a = Assembly.Load("Kindbergs.Controls");
        rm = new ResourceManager("Kindbergs.Controls.Translation.Texts", a);
      }
      return rm.GetString(name);
    }

As you can see, although the resources was accessible using "Translation.Texts.xxxx" from code, when I here load the resources I specify the complete namespace including the namespace of the Visual Studio project, and the "Kindbergs.Controls" that I load is the name and namespace of the compiled DLL file.

But with this helper function you can access resource with a code piece like this:

this.GetResource("Button"+"_AttachFile")

Of course, if your code tries to get a resource that does not exists in the current language or the fallback language, then you probably get an exception. Using the strongly typed way of accessing resources is always preferred!

Note that the bin-folder will, in addition to your main DLL with the default/fallback language, also contain subfolders for each language covered by a compiled resx file. As long as you have set the correct culture all the correct places, the right DLL will automatically be used for your texts.

 

Avoid huge ViewStates due to databinding global resources

(Another thing Microsoft forgot to write)

You should read this excellent article about ViewState. I wish I read it before doing a lot of the projects that I am currently working on!

The point is that if you use global resources in controls by binding them like:

<asp:Label ... Text='<%# MyResources.main.IntroText %>' />

And then call DataBind() somewhere in the OnLoad event, the result is that all your localized text is added to the ViewState - making it bigger and bigger for no reason at all.

The two alternatives that do not need DataBind() and does not influence ViewState is:

1) either insert text outside asp.net controls using:

<%= Translation.Texts.xxxx %>

2) or do it like he suggest in the article:

<asp:Label ...etc... Text='<%$ Code: Translation.Texts.xxxx %>' >

Read the article for full understanding, but just for my own sake I repeat the code to make the above work. First make a file containing something like this:

using System;
using System.CodeDom;
using System.Web.UI;
using System.Web.Compilation; 
namespace Kindbergs.Controls2 
{
   public class CodeExpressionBuilder : ExpressionBuilder
   {
     public override CodeExpression GetCodeExpression(BoundPropertyEntry entry, object parsedData, ExpressionBuilderContext context)
     {
       return new CodeSnippetExpression(entry.Expression);
     }
   }
 }

In my case it is in its own class library project which I with great imagination called "Kindbergs.Controls2", and the compiled dll is then referenced from the web application where you want to use it.

In that web application you then add these lines to your web.config file under system.web:

  <compilation ...etc... >
    <expressionBuilders>
      <add expressionPrefix="Code" type="Kindbergs.Controls2.CodeExpressionBuilder"/>
    </expressionBuilders>
  </compilation>

And with that you can use viewstate friendly translation, and you would only need to call DataBind() on those controls that actually need to be data bound to some data source (like a ListView or Repeater control).

 

Doing and maintaining the translation

I maintain websites that needs translations to English, Danish, Swedish, Norwegian, Polish, German, Hungarian and earlier also Russian.

Now taking a resx file and copying its content to Word or Excel is simple enough, and pasting it all back into the file when you get it back from a translator is also simple enough...

But what when you have added a new label to your resource file, while it was being translated? I am always working on the next thing to release, so there is almost always some beta functionality text changes happening for me.

Or what when you change the English text and need your translation of just the changed pieces updated - what do you do?

Or what if you think that some of the resources are no longer used, but the program is too big to identify which ones are not used, so you don't dare delete any of them, but you also don't want to send more pages to new language translations that what is needed? (If for no other reason: It is embarrasing to tell a translator when she asks in which context a piece of text is used: "sorry, it seams like it is no longer being used by the program...")

Well, I wrote a program to compare word documents with resx files, and thereby ease the headache of maintaining translations of so many languages!

It is now released as shareware, and you can get it here: www.kindbergs.dk/resx-to-docx-verifier.aspx

 

Allan K. Nielsen, Kindbergs Program Udvikling
Tweet This