OdeToCode IC Logo

Resource Files and ASP.NET MVC Projects

Thursday, July 16, 2009

If you try some of the traditional ASP.NET approaches to localization and internationalization in an MVC application you’re likely to run into a couple interesting* obstacles.

Resx Files In App_GlobalResources

Using resource files in App_GlobalResources from your controller code will break your unit tests.

When you drop a .resx file in the special App_GlobalResources folder, the IDE uses the GlobalResourceProxyGenerator to generate a strongly typed internal class to wrap the resources inside. The internal class gives any code in the MVC project access to the resources:

var greeting = Resources.Strings.Greeting;

You can also use the resources from a view:

<%= Html.Encode(Resources.Strings.Greeting) %>

The problem is that global resources are not actually embedded into the project’s .dll. Instead it is the ASP.NET runtime that creates an App_GlobalResources assembly with the resources inside. This assembly is referenced by all the view assemblies ASP.NET creates, and is explicitly loaded by the strongly typed wrapper generated by the GlobalResourceProxyGenerator. Since the App_GlobalResources assembly doesn’t exist without an ASP.NET compilation phase, it’s not available when unit tests are running. Controller code under test that tries to access the resources will bomb with an exception.

Note that you’ll also have some Intellisense problems when using the view syntax shown above. I'm guessing this is because the IDE is confused by seeing the resource wrapper in two places (the project assembly, and a wrapper also goes into the App_GlobalResource created by ASP.NET in the Temporary ASP.NET Files folder. ).

There is a way to make resx files in App_GlobalResources work, but the folder isn’t truly necessary in an MVC project (or a web application project, for that matter). I think it’s just as easy to add resx files in a different location, even a separate class library, to avoid any confusion on how App_GlobalResources will behave.

In short: avoid App_GlobalResources and App_LocalResources (which has its own set of problems) in MVC.

Resx Files Outside Of Special Resource Directoriesresx properties in MVC

If you add a resx file to any other folder in an MVC project or class library, the resx is automatically set to be embedded into the project’s output assembly - this is good. The IDE also assigns the resx a custom tool of ResxCodeFileGenerator to generate a strongly typed wrapper - this is good. The generated class is internal by default – this is bad. The assembly created for a view (by ASP.NET) won’t be able to use the internal class because it is in a different assembly – the project assembly compiled in Visual Studio.

Solution

The easy fix is to make sure the custom tool is set to  PublicResXFileCodeGenerator instead of ResXCodeFileGenerator. You can do this in the property window for the file, or in the resource editor that gives you a drop down for “Access Modifer” (the options are Internal, Public, and No Code Generation – choose Public).

You can also set the “Custom Tool Namespace” for the generated wrapper in the properties window. My suggestion is to use a convention like “Resources” for global resources, and “Resources.Controller.View” for resources dedicated to a specific view.

This approach means you can use the resources in unit-testable controller code, and in views, too. The syntax remains the same as above. The ResouceManager used in the wrapper classes can automatically resolve the proper resource to use depending on the current UI culture setting of the thread.

Setting The UI Culture

The easiest approach to having the correct UI culture in effect during web request processing is to use the globalization section of web.config.

<globalization uiCulture="auto" culture="auto"/>

The above will set both the current UI culture and current culture settings for the request. See Dennis Dietrich’s post for a good explanation of the two settings: YACVCP (Yet another CurrentCulture vs. CurrentUICulture post).

If you need to set the culture up according to a user’s preference, or a URL parameter, then the best bet is to write a custom HTTP module or action filter.

* Interesting only if you consider localization and resource files interesting, in which case you might need to take some medication.