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.
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.
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.
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
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
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.
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.
I use them in views and it looks ok (using blog.eworldui.net/...)
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?
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
<asp:Button ID="Button1" runat="server"
Text="<%$ Resources:WebResources, Button1Caption %>" />
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
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
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...??
I noticed that for each resource file I create, VS creates a new file under /obj/ directory.
ResourceManagers.GetResourceManager(resourceFullFileName).
GetString(optionResourceKey, new CultureInfo("en"))
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/).
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
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.