Spark Globalization with ASP.NET MVC - c#

I am using the spark viewengine, asp.net mvc, and .resx files.
I want to set a language through my custom SessionModel (Session) which is registered through Castle.Windsor and has a string property of Culture which can be set by the user...
I need the current language to persist on every view, without having to constantly set the current UICulture.
Not having to do this everytime in each Controller Action:
public SessionModel SessionModel { get; set; }
public ActionResult Index()
{
Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(SessionModel.Culture);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}
The problem with doing it this way, is if I go onto another page the current culture will flip back to the default language.
On the spark view I simply call, to obtain the current Culture:
${SR.Home}
SR.resx contains a public entry for Home.
Does anyone have a good idea of how to do this, should I do this with an ActionFilter?

Action filter seems like a good idea:
public class SetCultureActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CultureInfo culture = FetchCultureFromContext(filterContext);
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
base.OnActionExecuting(filterContext);
}
private CultureInfo FetchCultureFromContext(ActionExecutingContext filterContext)
{
throw new NotImplementedException();
}
}
and then decorate your base controller with this attribute.

I guess I am missing something. This is what I am trying to avoid (Application.cs) inside of the spark samples, I already have a custom session, it don't need to manage another, just for "culture":
the sample code:
public static string GetSessionCulture(ControllerContext controllerContext)
{
return Convert.ToString(controllerContext.HttpContext.Session["culture"]);
}
public static void SetSessionCulture(ControllerContext controllerContext, string culture)
{
controllerContext.HttpContext.Session["culture"] = culture;
}
Also after its done, how do I load the current culture for every page I have, I would have to make the call inside of HomeController Index to pull the current culture out of the session:
public object Index()
{
var user = new UserInfo
{
Name = "Frankie",
Culture = Application.GetSessionCulture(ControllerContext)
};
try
{
Thread.CurrentThread.CurrentCulture =
CultureInfo.CreateSpecificCulture(user.Culture);
}
catch
{
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-GB");
}
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
ViewData["user"] = user;
return View();
}
Which the only way I can come up with right now is by either creating a custom ActionFilter or creating a base Controller.

There is a much better and scalable (from a flexibility point of view) solution to this problem which has been solved if you're using the Spark View Engine.
Have a look at the sample solution here in the code base for an excellent example of how to do Internationalization or Globalization with Spark. There is absolutely no need to start doing fancy things with your ActionFilters - and this method works in MVC2 and MVC3 to allow for an easy migration if that's your future plan.
Update
In response to your other question regarding the sample in the Spark code base I think there are a few ways of skinning that cat. One way would be to keep the culture as part of the session, but in past projects where we didn't want that, we did what many website already do - we included the culture as a parameter in the route data by making sure it is included in the URL:
routes.Add(new Route("{culture}/{controller}/{action}/{id}", new MvcRouteHandler())
{
Defaults = new RouteValueDictionary(
new { culture="en-us" action="Index", id="" }),
});
This kind of thing in combination with the Spark module I mentioned above gives you the freedom to just spend time focussing on your .resx files instead of all your time figuring out the culture and routing manually.
The fact that you have an Index.spark and an Index.fr.spark in the same views folder means the rest gets taken care of for you.
Hope that helps!
All the best,
Rob

Related

C# Localization and Resource Files

I'm trying to get localization working in my application. I have my ui resource files, named "ui.resx" and "ui.de.resx" in my solution. However, something in my implementation is incorrect, and I'm stumped.
ResourceManager res_man;
CultureInfo culture;
string exception;
private void myForm_Load(object sender, EventArgs e)
{
culture = CultureInfo.CreateSpecificCulture("de");
res_man = new ResourceManager("MyApp.ui.resx", typeof(myForm).Assembly);
doTheThing();
}
private void doTheThing()
{
try
{
BTN_save.text = ui.SAVE;
//Do code here
}
catch(Exception e)
{
exception = e.toString();
}
}
When I run the program, it errors out and exception reads:
"Exception: System.Resources.MissingManifestResourceException: Could not find any resources appropriate for the specified culture or the neutral culture. Make sure "ui.resx.resources" was correctly embedded or linked into assembly "myProgram" at compile time, or that all the satellite assemblies required are loadable and fully signed."
You should use the full name (with namespace) of your class as first parameter lik:
var resman= new ResourceManager("Sample.Resources", typeof(Resources).Assembly);
To know what name you should use, open ui.cs under the ui.resx node and look at the namespace of the class and the class name and use them as shown above.
Pay attention that you should not pass "MyApp.ui.resx", instead pass "MyApp.ui"
Then you can simply use the manager to get a resource for specific culture like this:
var str = resman.GetString("YourResourceKey", culture);
Note:
Another thing you should notice, there is more simple way to read culture specific resources. You can simply set :
var culture= ...
System.Threading.Thread.CurrentThread.CurrentUICulture = culture;
System.Threading.Thread.CurrentThread.CurrentCulture = culture;
After setting culture, wherever you use for example MyApp.Ui.Key the value of the Key specific to that culture will be used.

Double ToString("C") culture specific

I know that this works (source):
double x = 9.7;
x.ToString("C", CultureInfo.CreateSpecificCulture("nl-NL"));
But I have this code in my code behind:
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("en-GB");
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo("en-GB");
and in my aspx webform, I'd like to use double.tostring("C") so no cultureinfo there. Because it is set on the current thread. But the local machine is nl-NL. The CurrentThread is set to en-GB in the code behind, but the valutasign is still a euro sign instead of pound.
Am I missing something? Or is using the overload of tostring with the cultureinfo required? In other words, do I have to rewrite all double tostrings to use cultureinfo?
Make sure you initialize the culture soon enough in the page life cycle. In fact, there's even a method for you to override specially for this purpose: Page.InitializeCulture.
Example based on a cookie:
protected override void InitializeCulture()
{
var cookie = Request.Cookies[WebConfigurationManager.AppSettings["LocaleCookieName"]];
if (cookie != null)
Culture = UICulture = cookie.Value;
}
No need to call the base method, see the documentation for more information.

Culture in Web API not used by FluentValidation

I have a Web API, and in global.asax I set culture as follows:
protected void Application_PostAuthenticateRequest()
{
var culture = CultureInfo.CreateSpecificCulture("nl-BE");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
}
I have added the Fluent Validation for .NET nuget, and so in the bin folder I have /nl/FluentValidation.resources.dll.
Next, I have a validator like:
public class AddWeightCommandValidator : AbstractValidator<AddWeightCommand>
{
public AddWeightCommandValidator()
{
RuleFor(command => command.PatientId).GreaterThan(0);
RuleFor(command => command.WeightValue).InclusiveBetween(20, 200);
}
}
And this is called from my command like:
new AddWeightCommandValidator().ValidateAndThrow(request);
The problem is that validation messages are still in English instead of Dutch.
If I debug, right before the validator is called the culture is correctly set on CurrentUICulture and CurrentCulture.
Anyone has an idea what I'm doing wrong?
Thanks to the tip of Stijn I started to look on how I could use my own resources for Fluent Validation, and this is how I did it.
In global.asax, culture is set and the resource provider type for Fluent Validation is set depending on that culture:
protected void Application_PostAuthenticateRequest()
{
// Set culture
var culture = CultureInfo.CreateSpecificCulture("nl-BE");
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = culture;
// Set Fluent Validation resource based on culture
switch (Thread.CurrentThread.CurrentUICulture.ToString())
{
case "nl-BE":
ValidatorOptions.ResourceProviderType = typeof(Prim.Mgp.Infrastructure.Resources.nl_BE);
break;
}
}
After this, Fluent Validation will look for translations in the appropriate resource file.
The resource files are in a separate project. Here, all Fluent Validation keys are defined, like inclusivebetween_error etc. Also, the various properties like WeightValue are defined there.
Finally, in the validator, WithLocalizedName is used to localize the property names:
RuleFor(command => command.WeightValue).InclusiveBetween(20, 200).WithLocalizedName(() => Prim.Mgp.Infrastructure.Resources.nl_BE.WeightValue);
I used this code
RuleFor(rule => rule.CultureName).Must(BeValidateCultureInfo).WithMessage(errorMessage => string.Format(CultureNameInvalidMessage, errorMessage.Locale));
private bool BeValidateCultureInfo(string locale)
{
if (string.IsNullOrWhiteSpace(locale))
return true;
try
{
var cultureInfo = CultureInfo.GetCultureInfo(locale);
return true;
}
catch
{
return false;
}
}

Caching ASP.NET Web API with CacheCow

I am trying to implement caching using CacheCow. I have two problems:
In some cases I need to invalidate manually the cache of some resources.
For example, I have a resource that it is called purchase, and other that is called pointMovements. They are not totally connected, but doing a post in purchase, implies some changes in pointMovement. Cachecow is not detecting these changes because I am not calling the API of pointmovements. So when I call the endpoint of pointmovements, the values are cached and I cannot get the new values.
To solve this, I need to invalidate that manually, how is that possible?
There are some controllers that I don't want to cache. I am trying to use attributes for doing that but it is not working. I am following this article but the attributes are ignored.
How can I specify which controllers to cache?
I came across the same set of problems and found a solution for problem 2 (disable caching regardless of the default settings).
// This forces the server to not provide any caching by refreshing its cache table immediately (0 sec)
[HttpCacheRefreshPolicy(0)]
// This forces the client (browser) to not cache any data returned from the server (even if ETag is present) by setting the time-out to 0 and no-cache to true.
[HttpCacheControlPolicy(true, 0, true)]
public void MyController : ApiControler {... }
The attributes must be applied together for this to work. You can also control the caching at the action level by providing the same rules to each action.
I've still to figure out the solution for problem 1. but watch this space for updates.
Update
I have found a solution to problem 1.
Register the CachingHandler with your IoC container (in my case it's IUnityContainer)
Inject the ICachingHandler into your Web API controller.
To invalidate the resource, use ICachingHandler.InvalidateResource(HttpRequestMessage)
Please see a code example below. The solution has been tested.
public class Bootstrapper
{
//...
// Create a new caching handler and register it with the container.
public void RegisterCache(HttpConfiguration config, IUnityContainer container)
{
var cachingHandler = new CachingHandler(config);
// ...
container.RegisterInstance<ICachingHandler>(cachingHandler);
}
}
public class ResourceContoller : ApiController
{
private ICachingHandler _cachingHandler;
public ResourceContoller(ICachingHandler cachingHandler)
{
_cachingHandler = cachingHandler;
}
[HttpPost]
public void DeleteResource(int resourceId)
{
// Do the delete
// ...
// Now invalidate the related resource cache entry
// Construct a http request message to the related resource
// HINT: The "DefaultApi" may not be your api route name, so change this to match your route.
// GOTCHA: The route matching mechanism is case sensitive, so be aware!
var relatedResource = new HttpRequestMessage(HttpMethod.Get, Url.Link("DefaultApi", new {controller = "linkedresource", action = "getlinkedresource", id: resourceId}));
// Invalidate the resource with the caching handler.
_cachingHandler.InvalidateResource(relatedResource);
}
}
Sorry for the late response.
As #Tri Q said, the way to do this is to use attributes which I have explained in this blog:
http://byterot.blogspot.co.uk/2013/03/rest-asp-net-wep-api-0.4-new-features-breaking-change-cachecow-server.html
I solved your #1 question using below code. Here I extend the IRoutePatternProvider interface. Remember, what you return in GetRoutePattern should match what you return in GetLinkedRoutePatterns. Only then the adding and removing will work. Try it out.
Inside Application_Start
CachingHandler cacheHandler = new CachingHandler(GlobalConfiguration.Configuration);
cacheHandler.RoutePatternProvider = new CacheRoutePatternProvider();
GlobalConfiguration.Configuration.MessageHandlers.Add(cacheHandler);
Custom Class
public class CacheRoutePatternProvider : IRoutePatternProvider
{
public string GetRoutePattern(HttpRequestMessage request)
{
string path = request.RequestUri.AbsolutePath;
if (!path.EndsWith("/"))
path += "/";
return path;
}
public IEnumerable<string> GetLinkedRoutePatterns(HttpRequestMessage request)
{
string path = request.RequestUri.AbsolutePath;
if(!path.EndsWith("/"))
path += "/";
int segmentIndex;
// return each segment of the resource heirarchy
while ((segmentIndex = path.LastIndexOf("/")) > 0)
{
path = path.Substring(0, segmentIndex);
if(path.Contains("/api/"))
yield return path + "/";
}
yield break;
}
}

Is there a way of setting culture for a whole application? All current threads and new threads?

Is there a way of setting culture for a whole application? All current threads and new threads?
We have the name of the culture stored in a database, and when our application starts, we do
CultureInfo ci = new CultureInfo(theCultureString);
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
But, of course, this gets "lost" when we want to do something in a new thread. Is there a way of setting that CurrentCulture and CurrentUICulture for the whole application? So that new threads also gets that culture? Or is it some event fired whenever a new thread is created that I can hook up to?
In .NET 4.5, you can use the CultureInfo.DefaultThreadCurrentCulture property to change the culture of an AppDomain.
For versions prior to 4.5 you have to use reflection to manipulate the culture of an AppDomain. There is a private static field on CultureInfo (m_userDefaultCulture in .NET 2.0 mscorlib, s_userDefaultCulture in .NET 4.0 mscorlib) that controls what CurrentCulture returns if a thread has not set that property on itself.
This does not change the native thread locale and it is probably not a good idea to ship code that changes the culture this way. It may be useful for testing though.
This gets asked a lot. Basically, no there isn't, not for .NET 4.0. You have to do it manually at the start of each new thread (or ThreadPool function). You could perhaps store the culture name (or just the culture object) in a static field to save having to hit the DB, but that's about it.
If you are using resources, you can manually force it by:
Resource1.Culture = new System.Globalization.CultureInfo("fr");
In the resource manager, there is an auto generated code that is as follows:
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
Now every time you refer to your individual string within this resource, it overrides the culture (thread or process) with the specified resourceCulture.
You can either specify language as in "fr", "de" etc. or put the language code as in 0x0409 for en-US or 0x0410 for it-IT. For a full list of language codes please refer to: Language Identifiers and Locales
For .NET 4.5 and higher, you should use:
var culture = new CultureInfo("en-US");
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
Actually you can set the default thread culture and UI culture, but only with Framework 4.5+
I put in this static constructor
static MainWindow()
{
CultureInfo culture = CultureInfo
.CreateSpecificCulture(CultureInfo.CurrentCulture.Name);
var dtf = culture.DateTimeFormat;
dtf.ShortTimePattern = (string)Microsoft.Win32.Registry.GetValue(
"HKEY_CURRENT_USER\\Control Panel\\International", "sShortTime", "hh:mm tt");
CultureInfo.DefaultThreadCurrentUICulture = culture;
}
and put a breakpoint in the Convert method of a ValueConverter to see what arrived at the other end. CultureInfo.CurrentUICulture ceased to be en-US and became instead en-AU complete with my little hack to make it respect regional settings for ShortTimePattern.
Hurrah, all is well in the world! Or not. The culture parameter passed to the Convert method is still en-US. Erm, WTF?! But it's a start. At least this way
you can fix the UI culture once when your app loads
it's always accessible from CultureInfo.CurrentUICulture
string.Format("{0}", DateTime.Now) will use your customised regional settings
If you can't use version 4.5 of the framework then give up on setting CurrentUICulture as a static property of CultureInfo and set it as a static property of one of your own classes. This won't fix default behaviour of string.Format or make StringFormat work properly in bindings then walk your app's logical tree to recreate all the bindings in your app and set their converter culture.
This answer is a bit of expansion for #rastating's great answer. You can use the following code for all versions of .NET without any worries:
public static void SetDefaultCulture(CultureInfo culture)
{
Type type = typeof (CultureInfo);
try
{
// Class "ReflectionContext" exists from .NET 4.5 onwards.
if (Type.GetType("System.Reflection.ReflectionContext", false) != null)
{
type.GetProperty("DefaultThreadCurrentCulture")
.SetValue(System.Threading.Thread.CurrentThread.CurrentCulture,
culture, null);
type.GetProperty("DefaultThreadCurrentUICulture")
.SetValue(System.Threading.Thread.CurrentThread.CurrentCulture,
culture, null);
}
else //.NET 4 and lower
{
type.InvokeMember("s_userDefaultCulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null,
culture,
new object[] {culture});
type.InvokeMember("s_userDefaultUICulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null,
culture,
new object[] {culture});
type.InvokeMember("m_userDefaultCulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null,
culture,
new object[] {culture});
type.InvokeMember("m_userDefaultUICulture",
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null,
culture,
new object[] {culture});
}
}
catch
{
// ignored
}
}
}
DefaultThreadCurrentCulture and DefaultThreadCurrentUICulture are present in Framework 4.0 too, but they are Private. Using Reflection you can easily set them. This will affect all threads where CurrentCulture is not explicitly set (running threads too).
Public Sub SetDefaultThreadCurrentCulture(paCulture As CultureInfo)
Thread.CurrentThread.CurrentCulture.GetType().GetProperty("DefaultThreadCurrentCulture").SetValue(Thread.CurrentThread.CurrentCulture, paCulture, Nothing)
Thread.CurrentThread.CurrentCulture.GetType().GetProperty("DefaultThreadCurrentUICulture").SetValue(Thread.CurrentThread.CurrentCulture, paCulture, Nothing)
End Sub
Working solution to set CultureInfo for all threads and windows.
Open App.xaml file and add a new "Startup" attribute to assign startup event handler for the app:
<Application ........
Startup="Application_Startup"
>
Open App.xaml.cs file and add this code to created startup handler (Application_Startup in this case). The class App will look like this:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
CultureInfo cultureInfo = CultureInfo.GetCultureInfo("en-US");
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;
}
}
For ASP.NET5, i.e. ASPNETCORE, you can do the following in configure:
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(new CultureInfo("en-gb")),
SupportedCultures = new List<CultureInfo>
{
new CultureInfo("en-gb")
},
SupportedUICultures = new List<CultureInfo>
{
new CultureInfo("en-gb")
}
});
Here's a series of blog posts that gives more information:
How ASP.NET 5 determines the culture settings for localization
Allowing user to set culture settings in ASP.NET 5:
Part 1
Part 2
Here is the solution for c# MVC:
First : Create a custom attribute and override method like this:
public class CultureAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// Retreive culture from GET
string currentCulture = filterContext.HttpContext.Request.QueryString["culture"];
// Also, you can retreive culture from Cookie like this :
//string currentCulture = filterContext.HttpContext.Request.Cookies["cookie"].Value;
// Set culture
Thread.CurrentThread.CurrentCulture = new CultureInfo(currentCulture);
Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(currentCulture);
}
}
Second : In App_Start, find FilterConfig.cs, add this attribute. (this works for WHOLE application)
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// Add custom attribute here
filters.Add(new CultureAttribute());
}
}
That's it !
If you want to define culture for each controller/action in stead of whole application, you can use this attribute like this:
[Culture]
public class StudentsController : Controller
{
}
Or:
[Culture]
public ActionResult Index()
{
return View();
}

Categories

Resources