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.


Comments
Stan Thursday, July 16, 2009
Thank you for saving hours of my time!
Kevin Radcliffe Thursday, July 16, 2009
Thanks! Very useful info.
jin Friday, July 17, 2009
hi,scott
i have read your article "ASP.NET and Windows Workflows Foundation",and i got some
question about your sample code.There is a code snippet:
public class OrderService : IOrderService
{
public bool CreateOrder(Order order)
{
Check.ArgumentIsNotNull(order, "order");

// create order must first create the workflow, then raise event, then run again
WorkflowResults workflowResults = WorkflowMediator.Instance.RunWorkflow(typeof(OrderStateMachine));
Check.IsNotNull(workflowResults, "Could not harvest workflow results");
VerifyResults(workflowResults, WorkflowStatus.Running);
order.WorkflowId = workflowResults.InstanceId;

bool eventResult = RaiseEvent(OrderCreated, order, order.WorkflowId);
if (eventResult == true)
{
workflowResults = WorkflowMediator.Instance.RunWorkflow(order.WorkflowId);
Check.IsNotNull(workflowResults, "Could not harvest workflow results");
VerifyResults(workflowResults, WorkflowStatus.Running);

}
return eventResult;
}
}

First you create the workflow and then raise evnet, but the workflowItem dosen't be invoked by the event,and then you run workflow again.This time the handleExternalEventActivity1 in Intial
state is invoked by the event,so i want to know why the event invoke the
handleExternalEventActivity1 in Intial state again.And i think that the event is invoked in the first time and never be invoked again except for call explicity,but it dosent' true.
Please help me and forgive my poor english
jin
scott Friday, July 17, 2009
Hi Jin:

The first time we run the workflow is to just get it initialized and into the initial state. The "run workflow" step wouldn't be needed, except we are using the manual scheduler that requires us to give it the thread to run the workflow.
jin Monday, July 20, 2009
Hi Scott:
Thank you for your explanation of the manual scheduler service.I have read your another article "Hosting Windows Workflow",so
i get an result that the first runworkflow is just do some preparation,and raise the event, the workflow queues just take the event
into queue,then runworkflow with an instance ID,the workflow queues find the handleExternalEventActivity1 in Intial
state.That's all of the content of the CreateOrder funtion.Thank you again.
Microsoft .NET Training Tuesday, July 21, 2009
Pluralsight offers comprehensive .NET training
Mathias Fritsch Friday, July 24, 2009
What problem you see in local resources?

I use them in views and it looks ok (using blog.eworldui.net/...)
scott Monday, July 27, 2009
@mathias:

Great article, Mathias. I believe I'd still have a problem using the local resources in a controller that is unit tested even using your approach, right?
Mathias Fritsch Monday, August 3, 2009
I believe I'd still have a problem ...
Yes.
The article in the link is only about views. I thought you argued against using local resources at all and was afraid I would see the limits to late in my project. Its clear to me now, that you talk about controller/model.

thnx
Mathias
gravatar Jeff Wednesday, October 14, 2009
It appears that moving the resources out of the App_GlobalResources disables the Explicit Localization expressions in ASP.NET Web Application Project such as
<asp:Button ID="Button1" runat="server"
Text="<%$ Resources:WebResources, Button1Caption %>" />

gravatar Nady Fayek Tuesday, November 24, 2009
HI,
I am using local resources in asp.net website.
i use two language English as a default and Danish. the problem that sometimes when adding a key in Danish ,it can not bee seen when running website, it just get the value from english local resource file although that the current culture is Danish.
Thx
gravatar scott Tuesday, November 24, 2009
@Nady: I'm afraid I don't know the answer. It sounds like something that should just work.
gravatar David Thursday, January 14, 2010
Hi,

I have a class where i want to use a resources file in.
How can i retrieve a value from a specific resource file?

Thanks
gravatar Anders Monday, February 8, 2010
Why would you ever use resources from within controllers? Resources exist on the view-level, and your unit tests should not ever rely on resources being available.
gravatar Scott Allen Monday, February 8, 2010
@Anders: I'm not sure what you mean by "resources exist on the view-level". You can use resource to localize exception messages and the text of validation messages, for example, and neither of those activities are tied to a view. I can put resource files into any assembly.
gravatar Shanu Tuesday, February 9, 2010
Hi,
I have a small doubt..
Is resource file have any specific size limit for the string or images..

ie, If i need to insert bulk content, then How it performs...? Can anyone help me...??
gravatar sikat ang pinoy Tuesday, February 9, 2010
asp.net comments using server side comments that can disabled code controls in the page. Because AJAX enabled the comment form.
gravatar Andy Saturday, February 20, 2010
Do you recommend having a specific resource file for each view and also having a global resource file or having one resource for the entire application?

I noticed that for each resource file I create, VS creates a new file under /obj/ directory.
gravatar Adam Kahtava Thursday, May 13, 2010
Man this is brutal oversight for testing controllers / views. Thanks for the article.
gravatar Michael Friday, May 14, 2010
Almost there... but I still cannot get a call like the following to work..

ResourceManagers.GetResourceManager(resourceFullFileName).
GetString(optionResourceKey, new CultureInfo("en"))


gravatar Adam Kahtava Friday, May 14, 2010
@Michael,

Sounds like I'm in the same boat. I can't get culture specific resources from satellite assemblies to load. Please send me your contact details (http://adam.kahtava.com/contact-me/).
gravatar Odom24Blanca Tuesday, July 6, 2010
I took 1 st loans when I was not very old and that helped me a lot. But, I need the small business loan also.
jyang Friday, August 27, 2010
Thanks a lot for this posting.
gravatar RredCat Friday, September 17, 2010
I've found approach for testing code that uses resources inside App_* directories. I described my solution in my blog rredcat.blogspot.com/...
gravatar Bharat Chabra Wednesday, January 5, 2011
Hi Scott,
Thanks for the post.
When I made the changes you explained, my view fails.
Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "Resources.Resources.resources" was correctly embedded or linked into assembly "App_GlobalResources.hncn1jil" at compile time, or that all the satellite assemblies required are loadable and fully signed.

the last part of the class ".resources" is weird as my Namespace is "Resources" and Custom Namespace is also "Resources" there is no reference to "resources" in lower case.

You mentioned that there is a way to make resx files in App_GlobalResources work. Can you explain that as well.

Thanks in advance
-Bharat
gravatar scott Wednesday, January 5, 2011
@Bharat:

IT does seem like there is a namespace problem. Can you see the resources anywhere in your project if you use a tool like Reflector to see what is embedded into the .dll?

It's been a while since I've had to work with resources. I believe App_GlobalResources works as long as you don't try to use the resource from a controller or unit test.
gravatar Jesse Monday, January 17, 2011
Question: is it possible to update the resx files without recompiling using this technique (embedded resource)? my guess is no it is not?
gravatar scott Monday, January 17, 2011
@Jesse - that's right. Recompiling would be required.
Comments are now closed.
by K. Scott Allen K.Scott Allen
My Pluralsight Courses
The Podcast!