Dirk Boer
Dirk Boer

Reputation: 9123

How to manually set the Culture during a ASP.NET Core MVC request?

I'm looking for something similar like the old behaviour of setting the Culture and everything in the request after that has that Culture.

Similar like ASP.NET 4.5:

Thread.CurrentThread.CurrentCulture     = culture_info; 
Thread.CurrentThread.CurrentUICulture   = culture_info;

When I do this right now, some code seems to be constantly resetting the Culture to English at various points in the MVC pipeline.

Why do I want that? Because I want to set the culture through my extended routing logic in a very large (and customizable) platform-type of web project.

The default RouteDataRequestCultureProvider doesn't seem to work at all - see explained here:

https://irensaltali.com/en/asp-net-core-mvc-localization-by-url-routedatarequestcultureprovider/

and

Localization works when culture is set via QueryString but not when culture is in route

I don't want to use the "hardcoded" url parsing that is being presented as a solution, because my routing logic is complicated - not all routes will have a culture defined in the same spot in the URL.

I tried setting this, but that also didn't work:

httpContext.Features.Set<IRequestCultureFeature>(new RequestCultureFeature(request_culture, new DummyProvider()));

Is there any workaround for just simply setting the Culture for the rest of the request?

Upvotes: 2

Views: 4276

Answers (3)

Dave B
Dave B

Reputation: 1810

I was looking for a solution similar to the OP's question for setting a culture after the request. I needed to retrieve a culture string in a query string or a request header and @Stilgar's answer provided the solution I needed. Here's a working solution of his answer. It extracts a culture key/value pair in the query string. (The code to check the request header was left out.)

For a URL request https://localhost:7181/?culture=fr, the culture fr-FR is set. In the OnPost method, Thread.CurrentThread.CurrentCulture returns fr-FR.

public void OnPost()
{
    // Display the current culture
    System.Diagnostics.Debug.WriteLine("Current culture is " + Thread.CurrentThread.CurrentCulture.EnglishName);
}

program.cs

app.UseRequestLocalization(options =>
    {
        string defaultCulture = "en-US";
        string[] supportedCultures = new string[] { "fr-FR", defaultCulture };

        options.AddSupportedUICultures(supportedCultures)
            .AddSupportedCultures(supportedCultures)
            .AddInitialRequestCultureProvider(
                new CustomRequestCultureProvider(async context =>
                {
                    string? cultureCode =
                        await GetCultureFromRequest(context.Request, supportedCultures) ?? defaultCulture;
                    return new ProviderCultureResult(cultureCode);
                }));
    });

static Task<string?> GetCultureFromRequest(
    HttpRequest request, string[] supportedCultures)
{
    if (request.QueryString.Value is null ||
        !request.QueryString.Value.Contains("culture="))
    {
        return Task.FromResult<string?>(default);
    }

    string? cultureVal =
        request.Query.FirstOrDefault(q => q.Key == "culture").Value;

    if (cultureVal is null || string.IsNullOrEmpty(cultureVal))
    {
        return Task.FromResult<string?>(default);
    }

    string? cultureEntry = supportedCultures
        .Where(c => c.Contains(cultureVal)).FirstOrDefault();

    return Task.FromResult(cultureEntry ?? default);
}

The above solution is the same as setting up a localization middleware found here:

program.cs

builder.Services.Configure<RequestLocalizationOptions>(options =>
{
    CultureInfo[] supportedCultures = new[]
     {
        new CultureInfo("fr"),
    };
    options.DefaultRequestCulture = new RequestCulture("en-US");
    options.SupportedCultures = supportedCultures;
    options.SupportedUICultures = supportedCultures;

    options.RequestCultureProviders.Insert(0, new QueryStringRequestCultureProvider());
});

IOptions<RequestLocalizationOptions> locOptions = builder.Services.BuildServiceProvider().GetService<IOptions<RequestLocalizationOptions>>();
app.UseRequestLocalization(locOptions.Value);

Upvotes: 0

Stilgar
Stilgar

Reputation: 23571

While I too can't find a way to set the request culture for the rest of the request you can use the CustomRequestCultureProvider and use your custom routing logic to get set the culture for the request

app.UseRequestLocalization(options =>
{
    options.AddSupportedUICultures(... set some cultures here...)
           .AddSupportedCultures(... and here...)
           .AddInitialRequestCultureProvider(new CustomRequestCultureProvider(async context =>
           {
               string cultureCode = await RunCustomRoutingAndGetCulture(context.Request);
               return new ProviderCultureResult(cultureCode);
           }));
});

If your custom logic does not require awaiting remove async and return with Task.FromResult

Upvotes: 1

Rena
Rena

Reputation: 36715

A workaround for simply setting the Culture for the rest of the request is to use default QueryStringRequestCultureProvider.

Here is the whole demo:

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddLocalization(options => options.ResourcesPath = "Resources");

    services.AddControllersWithViews()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix)
            .AddDataAnnotationsLocalization();
    //...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    var supportedCultures = new[]
    {
        new CultureInfo("en-US"),
        new CultureInfo("de"),
        new CultureInfo("fr"),
    };

    app.UseRequestLocalization(new RequestLocalizationOptions
    {
        DefaultRequestCulture = new RequestCulture("en-US"),
        // Formatting numbers, dates, etc.
        SupportedCultures = supportedCultures,
        // UI strings that we have localized.
        SupportedUICultures = supportedCultures
    });

    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();

    app.UseAuthorization();

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

Create Resource folder and add resource file as below:

enter image description here

Configure resource file like below:

Controllers.HomeController.de.resx

enter image description here

Controllers.HomeController.fr.resx

enter image description here

Reference:Resource file naming

Controller:

public class HomeController : Controller
{
    private readonly IStringLocalizer<HomeController> _localizer;

    public HomeController(IStringLocalizer<HomeController> localizer)
    {
        _localizer = localizer;
    }

    public IActionResult Index()
    {
        ViewData["Title"] = _localizer["Your Title"];
        return View();
    }
}

Index.cshtml:

@ViewData["Title"]

Result: enter image description here

Upvotes: 2

Related Questions