Request Culture In ASP.NET Core

Culture in ASP.net has always been a bit finicky to get right. In ASP.NET Core there is no exception but with the addition of middleware, things are now configured a little differently. Lifting and shifting your standard ASP.NET code may not always work in ASP.NET Core, and at the very least, it won’t be done in the new “ASP.NET Core way”.

It should be noted that there are two “cultures” in the .NET eco system. The first is labelled just “Culture”. This handles things like date formatting, money, how decimals are displayed etc. There is a second culture called “UICulture” in .NET, this usually handles full language translations using resource files. For the purpose of this article, we will just be handling the former. All examples will be given using datetime strings that will change if you are in the USA (MM-dd-yyyy) or anywhere else in the world (dd-MM-yyyy).

Setting A Default Request Culture

If you are just looking for a way to hardcode a particular culture for all requests, this is pretty easy to do.

In the ConfigureServices method of your startup.cs, you can set the DefaultRequestCulture property of the RequestLocalizationOptions object.

public void ConfigureServices(IServiceCollection services)
{
	services.Configure<RequestLocalizationOptions>(options =>
	{
		options.DefaultRequestCulture = new RequestCulture("en-US");
	});
	services.AddMvc();
}

In the Configure method of your app, you also need to set up the middleware that actually sets the culture. This should be placed atleast before your UseMvc call, and likely as one of the very first middlewares in your pipeline if you intend to use Culture anywhere else in your app.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
	app.UseRequestLocalization();

	app.UseMvc();
}

I can write a simple API action like so :

[HttpGet]
public string GetCurrentCultureDate()
{
	return DateTime.Now.ToString();
}

When calling this action I can see the date is output as MM-dd-yyyy (I am from NZ so this is not the usual output!)

Default Request Culture Providers

When you use the request localization out of the box it comes with a few “providers” that you (Or a user) can use to override the default culture you set. This can be handy if your app expects these, otherwise it can actually be a hindrance if you expect your application to be displayed in a certain locale only.

The default providers are :

QueryStringRequestCultureProvider
This provider allows you to pass a query string to override the culture like so http://localhost:5000/api/home?culture=en-nz

CookieRequestCultureProvider
If the user has a cookie named “.AspNetCore.Culture” and the contents is in the format of “c=en-nz|uic=en-nz”, then it will override the culture.

AcceptLanguageHeaderRequestCultureProvider
Your browser by default actually sends through a culture that it wishes to use. A browser typically sends through your native language, but it will throw in a couple of “options”. So for example my browser currently ends through “en-GB and “en-US” (Even though I am in NZ).

This header in particular can become very problematic. If the default culture of your *server* matches the culture that a browser sends. Then by default it doesn’t matter what you set the “DefaultRequestCulture” to, this will override it.

There are two ways to keep this from happening. The first is to tell ASP.NET Core that the only supported cultures are the one that you want the default to be. You can do this in the ConfigureServices method of your startup.cs.

public void ConfigureServices(IServiceCollection services)
{
	services.Configure<RequestLocalizationOptions>(options =>
	{
		options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en-US");
		//By default the below will be set to whatever the server culture is. 
		options.SupportedCultures = new List<CultureInfo> { new CultureInfo("en-US") }; 
	});
	services.AddMvc();
}

The second way is to remove the CultureProviders from the pipeline all together.

public void ConfigureServices(IServiceCollection services)
{
	services.Configure<RequestLocalizationOptions>(options =>
	{
		options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en-US");
		//By default the below will be set to whatever the server culture is. 
		options.SupportedCultures = new List<CultureInfo> { new CultureInfo("en-US") };

		options.RequestCultureProviders = new List<IRequestCultureProvider>();
	});

	services.AddMvc();
}

The second method should be done in anycase if you don’t intend to use them as this makes your pipeline much cleaner. It will no longer be running a bunch of code that you actually have no use for.

Supported Cultures List

If you do intend to allow the user to set their own culture in their browser, you intend to use a query string to define the culture, or you intend to do a custom request culture provider (outlined in the next section) to allow code to set a custom culture based on other parameters, then you need to provide a list of supported cultures.

Supported cultures is a list of cultures that your web app will be able to run under. By default this is set to a the culture of the machine. Even if you use a custom culture provider as outlined below to set a culture, it actually won’t be set unless the culture is in the supported list.

You can set the list as follows :

public void ConfigureServices(IServiceCollection services)
{
	services.Configure<RequestLocalizationOptions>(options =>
	{
		options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en-US");
		options.SupportedCultures = new List<CultureInfo> { new CultureInfo("en-US"), new CultureInfo("en-NZ") };
	});

	services.AddMvc();
}

So in this example, I have set the default localization to “en-US”. But if a user comes through with a cultureprovider that wants to set them to en-NZ, the code will allow this. If for example a user came through with a culture of “en-GB”, this would not be allowed and they would be forced to use “en-US”.

Custom Request Culture Providers

Going beyond hard coding a particular culture, often you will want to set culture based on a particular user’s settings. In a global application for example, you may have saved into a database what datetime format a user wants. In this scenario, you can use a custom RequestCultureProvider class.

The code looks something like the following :

public class CustomerCultureProvider : RequestCultureProvider
{
	public override async Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext)
	{
		//Go away and do a bunch of work to find out what culture we should do. 
		await Task.Yield();

		//Return a provider culture result. 
		return new ProviderCultureResult("en-US");

		//In the event I can't work out what culture I should use. Return null. 
		//Code will fall to other providers in the list OR use the default. 
		//return null;
	}
}

Inside your ConfigureServices method in startup.cs, you then need to add your new CultureProvider to the RequestCultureProviders list. In the below example I have cleared out all other providers as I don’t care about query strings, cookies or the accept language header.

public void ConfigureServices(IServiceCollection services)
{
	services.Configure<RequestLocalizationOptions>(options =>
	{
		options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en-NZ");
		options.SupportedCultures = new List<CultureInfo> { new CultureInfo("en-US"), new CultureInfo("en-NZ") };
		options.RequestCultureProviders.Clear();
		options.RequestCultureProviders.Add(new CustomerCultureProvider());
	});

	services.AddMvc();
}

However let’s say that you do care, and if your custom provider can’t sort out a culture you want it to try query strings, cookies and the accept header. Then the best way is to insert it at the start of the list. Remember that these are run in order, the first provider that returns a culture instead of null wins.

services.Configure<RequestLocalizationOptions>(options =>
{
	options.DefaultRequestCulture = new Microsoft.AspNetCore.Localization.RequestCulture("en-NZ");
	options.SupportedCultures = new List<CultureInfo> { new CultureInfo("en-US"), new CultureInfo("en-NZ") };
	options.RequestCultureProviders.Insert(0, new CustomerCultureProvider());
});

Remember! It doesn’t matter what your custom culture provider returns if the culture it tries to set is not in the supported culture list. This is extremely important and the cause of many headaches!

7 thoughts on “Request Culture In ASP.NET Core”

  1. Great article – got me up and running in Azure, which seems to set the server locale to en-US irrespective of which datacenter you’re hosting in.

    Reply
  2. Thank you very much for this. I had possibly the same problem as Tom – deploying to Azure broke my date formatting. I removed the CultureProviders from the pipeline and then my dates were correct again.

    Reply
  3. nice article, but it would have been nice if you also had included how to implement this culture service either using resx or database

    Reply
  4. Very helpful! Thank you. The lasts sentence is very important. What can we do if we want all system cultures in supported list?

    Reply
  5. Thanks from a fellow Kiwi, helped me out big time with an Asp.Net Core 3.1 and Blazor Server Side project I’m working on. Blazor has a few differences with how localization is implemented versus standard MVC/Razor Pages and I found the official documentation didn’t go into enough detail for me. Your exmaples and descritptions helped me understand the inner workings better so I could resolve my issue.

    Reply
  6. I’m trying to implement a custom provider, but DetermineProviderCultureResult is only called for the first request from a given browser window (using Swagger) and not for every HTTP request. Any idea of how to have the provider triggered for every HTTP request?
    Thanks!

    Reply

Leave a Comment