Click here to Skip to main content
15,899,313 members
Articles / Programming Languages / C#

ASP.NET Core 3.0 MVC Request Localization or How to Set the Culture of a User Session

Rate me:
Please Sign up or sign in to vote.
4.33/5 (2 votes)
22 Feb 2020CPOL4 min read 28.3K   1   1
Handle the language and culture selection in an ASP.NET Core 3.0 MVC application and adjust the request handling thread accordingly
Setting the culture of a request in an ASP.NET Core 3.0 is a matter of setting the culture of the thread that is going to handle the request. There are two ways to achieve that: either use a Middleware, a classic solution, or if you prefer the ...improved way, use a Request Culture Provider.
  • Source code can be found at GitHub

Introduction

A web site may provide a way for the visitor to select a preferred language for the displayed content.

After such a selection is made, the web site has a number of options as to how that preferred language should be attached to visitor's session and its upcoming requests.

  • It may use the route URL to convey the information, e.g., https://company.com/en/home/index.
  • It may use the query string, e.g., https://company.com/home/index?lang=en.
  • It may store the information in a Session Variable
  • It may use cookies.
  • Or it may even trust on the current Accept-Language HTTP header.

Mapping a Language to a Culture

Using the language information, as described above, the web site presents localized content to the visitor, by mapping a Culture to that language.

The first step in localizing an ASP.NET MVC application is to set the Culture of the HTTP Request.

A culture code is something like en-US, which means english-United States, or el-GR, which means greek-Greece. The first part, as the en above, identifies the language, while the second part, as the US above, denotes a certain configuration used in handling dates, numbers and text issues.

Thus the culture used, while serving a request, has impact not only in choosing localized content or localized string resources but even how dates and numbers are formatted. Therefore it's a crucial issue.

Background

A classic ASP.NET Framework MVC application uses the MvcApplication class, found in the Global.asax.cs file, which is an HttpApplication derived class. In the Application_BeginRequest() method of that MvcApplication class, the developer writes something like the following:

C#
public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        // code here
    }

    CultureInfo GetRequestCultureFromSomeWhere()
    {
        return null;  // return a CultureInfo instance here
    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        CultureInfo Culture = GetRequestCultureFromSomeWhere();

        Thread.CurrentThread.CurrentCulture = Culture;
        Thread.CurrentThread.CurrentUICulture = Culture;
    }
}

The above sets the culture of the current thread, which is the thread that serves the request. That's all that needs to be done.

In the ASP.NET Core 3.0, things are ...improved. Let's explore the new options.

ASP.NET Core 3.0 MVC Request Localization

There are two options:

  • either use a classic approach, that is a Middleware that handles the culture changing. You may find an example of that approach in Microsoft's documentation. Another one example using Middleware is provided by the sample application of this post.
  • or use a Request Culture Provider.

The Middleware Option

This is easy. Just go to ConfigureServices() method of the Startup class, and just before the app.UseEndpoints() call or the app.UseMvc() call, code and "inline" middleware, something like the following:

C#
app.Use(async (context, next) =>
{
    CultureInfo.CurrentCulture = GetRequestCultureFromSomeWhere();
    CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;

    await next.Invoke();
})

Or if you prefer a stand-alone middleware class, code something like the following:

C#
public class RequestLocalizationCustomMiddleware
{
   RequestDelegate _next;

    public RequestLocalizationCustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        CultureInfo.CurrentCulture = GetRequestCultureFromSomeWhere();
        CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture;

        await _next(context);
    }
}

and then adjust the ConfigureServices() to use it as:

C#
app.UseMiddleware<RequestLocalizationCustomMiddleware>();

The Request Culture Provider Option

The first step is to configure the services properly.

In case your application needs just a single culture and uses that to handle all requests:

C#
public void ConfigureServices(IServiceCollection services)
{
    // code here

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

    services.AddMvc();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // code here

    app.UseRequestLocalization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

The app.UseRequestLocalization() call adds the RequestLocalizationMiddleware which adds the three built-in Request Culture Providers:

You can remove those providers from the configuration, if you think you don't need them.

C#
public void ConfigureServices(IServiceCollection services)
{
    // code here

    services.Configure<RequestLocalizationOptions>(options =>
    {
        options.DefaultRequestCulture = new RequestCulture("en-US");
        options.RequestCultureProviders.Clear(); // <<<<
    });

    services.AddMvc();
}

Or you may choose to just add a custom Request Culture Provider on top of the list, say a CustomRequestCultureProvider. Well, surprise, there is already a CustomRequestCultureProvider in ASP.NET Core 3.0 source code.

And here is how to use it:

C#
public void ConfigureServices(IServiceCollection services)
{
    // code here

    CustomRequestCultureProvider Provider =
        new CustomRequestCultureProvider(async (HttpContext) => {
        await Task.Yield();
        CultureInfo CI = GetRequestCultureFromSomeWhere();
        return new ProviderCultureResult(CI.Name);
    });

    services.Configure<RequestLocalizationOptions>(options =>
    {
        options.DefaultRequestCulture = new RequestCulture('en-US');
        options.SupportedCultures = new List<CultureInfo>
                { new CultureInfo("en-US"), new CultureInfo("el-GR") };
        options.SupportedUICultures = options.SupportedCultures;

        //options.RequestCultureProviders.Clear();
        options.RequestCultureProviders.Insert(0, Provider);
    });

    services.AddMvc();
}

NOTE: SupportedCultures property defines a list of cultures supported by the application. For a culture to be used by the application as the current culture, it must be contained in the SupportedCultures, as seen above. Your GetRequestCultureFromSomeWhere() should return one of the SupportedCultures.

Of course, you may use your own custom Request Culture Provider class. Something like the following:

C#
public class MyCustomRequestCultureProvider : RequestCultureProvider
 {
     public override async Task<ProviderCultureResult>
            DetermineProviderCultureResult(HttpContext httpContext)
     {
         await Task.Yield();
         CultureInfo CI = GetRequestCultureFromSomeWhere();
         return new ProviderCultureResult(CI.Name);
     }
 }

and register it as:

C#
public void ConfigureServices(IServiceCollection services)
{
    // code here

    services.Configure<RequestLocalizationOptions>(options =>
    {
        options.DefaultRequestCulture = new RequestCulture('en-US');
        options.SupportedCultures = new List<CultureInfo>
                 { new CultureInfo("en-US"), new CultureInfo("el-GR") };
        options.SupportedUICultures = options.SupportedCultures;

        //options.RequestCultureProviders.Clear();
        options.RequestCultureProviders.Insert(0, new MyCustomRequestCultureProvider());
    });

    services.AddMvc();
}

A Sample Application

There is an ASP.NET Core 3.0 MVC application that accompanies this post.

The Startup Class

The sample application provides a Startup class that uses both variations, Middleware or Request Culture Provider, based on a flag.

C#
bool UseRequestLocalizationProvider = false;

Set it to true to use the new toys.

There is a predefined list of languages.

C#
void LoadLanguages()
 {
     var En = new LanguageItem()
              { Id = "", Name = "English", Code = "en", CultureCode = "en-US" };
     var Gr = new LanguageItem()
              { Id = "", Name = "Greek", Code = "el", CultureCode = "el-GR" };
     Languages.Add(En);
     Languages.Add(Gr);
 }

Both variations use the same custom static Session class where the sample application stores visitor's preferred language and any session information. Thus, services.AddSession() and app.UseSession() are called by the Startup class.

That Session class uses the HttpContext.Session property, in keeping that session information, so it needs an IHttpContextAccessor instance. Therefore the Startup class calls services.AddHttpContextAccessor().

Also, a Session.HttpContextAccessor property is assigned properly in the Configure() method.

C#
Session.HttpContextAccessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();

The LanguageSelector ViewComponent

The LanguageSelector class is a very basic ViewComponent.

C#
public class LanguageSelector : ViewComponent
{
    public IViewComponentResult Invoke()
    {
        var List = Languages.Items;
        return View(List);
    }
}

It renders HTML markup for setting the language. Here is the Default.cshtml of the LanguageSelector ViewComponent.

C#
@model LanguageItem[]

@if (Model != null & Model.Length > 0)
{
    <div style="display: flex; flex-direction:row-reverse; padding-right: 4px">
        <ul style="list-style: none; display: flex">
            @foreach (var Item in Model)
            {
                <li style="padding:0 8px">
                    <a asp-controller="Home" asp-action="SetLanguage" 
                     asp-route-LanguageCode="@Item.Code">
                        @Item.Name
                    </a>
                </li>
            }
        </ul>
    </div>
}

It routes back to HomeController's SetLanguage() action which handles the request.

C#
public IActionResult SetLanguage(string LanguageCode)
{
    LanguageItem Lang = Languages.Find(LanguageCode);
    if (Lang != null && Lang.CultureCode != Session.Language.CultureCode)
    {
        Session.Language = Lang;
    }

    return RedirectToAction("Index");
}

The LanguageSelector ViewComponent renders its markup at the far right of the navigation bar. For that, the _Layout.cshtml contains the following:

HTML
@await Component.InvokeAsync("LanguageSelector")

The lang Attribute

The sample application writes the Culture code of the selected language in the lang attribute of the html element.

HTML
<html lang="@Session.Language.CultureCode">

Thus, it becomes easy for a JavaScript code to get the selected Culture and language.

JavaScript
let CultureCode = document.querySelector('html').getAttribute('lang');

After that, the script may use that culture in formatting numbers and dates.

JavaScript
let n = 123.456;
let S = n.toLocaleString(CultureCode);

let DT = new Date();
S = DT.toLocaleDateString(CultureCode);
S = DT.toLocaleTimeString(CultureCode);

Conclusion

Setting the culture of a request in an ASP.NET Core 3.0 is a matter of setting the culture of the thread that is going to handle the request. There are two ways to achieve that: either use a Middleware, a classic solution, or if you prefer the ...improved way, use a Request Culture Provider.

History

  • 23rd February, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer AntyxSoft
Greece Greece
I’m a (former) musician, programmer, wanna-be system administrator and grandpa, living in Thessaloniki, Greece.

Comments and Discussions

 
QuestionQuestion about default.cshtml Pin
neobugs12-Mar-20 10:53
neobugs12-Mar-20 10:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.