ASP.NET endpont not respecting current thread UI culture set with a disposable culture switcher

Viewed 31

I have a strange translation issue with an ASP.net application running on netcore 3.1, where around 20 minutes after deployment, a resource string only comes in the language set as the primary culture.

The project uses .resx files to store strings in three languages (general/generic/en, sv, en), which are stored in a separate project so that they can be referenced as, for example, Lang.General.EmailSubject. This is working everywhere else, other than this endpoint, but it is the only one with this logic.

There are two deployments, one with primary culture as sv-SE and one as en-GB. The caller of the endpoint can specify the 'requestedCulture' as something else, either sv-GB or en-SE.

99% of the time, this requested culture is the same as the primary culture. However, sometimes it's specified differently, and then the translation is wrong. While I can replicate this after each deployment, I don't understand the cause?

The code looks like this;

public sealed class TemporaryLanguageSwitcher : IDisposable
{
    private readonly CultureInfo initialCulture;
    private readonly CultureInfo initialUiCulture;

    public TemporaryLanguageSwitcher(CultureInfo culture)
    {
        initialCulture = CultureInfo.CurrentCulture;
        initialUiCulture = CultureInfo.CurrentUICulture;

        CultureInfo.CurrentCulture = culture;
        CultureInfo.CurrentUICulture = culture;
    }

    void IDisposable.Dispose()
    {
        CultureInfo.CurrentCulture = initialCulture;
        CultureInfo.CurrentUICulture = initialUiCulture;
    }
}

public static class CultureInfoExtensions
{
    public static TemporaryLanguageSwitcher AsThreadCulture(this CultureInfo culture)
    {
        return new TemporaryLanguageSwitcher(culture);
    }
}

public class MailController : ControllerBase
{
    public static IDisposable CultureSetter(CultureInfo newCulture) => newCulture.AsThreadCulture();

    public static IDisposable CreateEmailRequestScope(EmailRequest request)
    {
        string culture = string.IsNullOrEmpty(request.requestedCulture) ? "sv-SE" : request.requestedCulture; //en-SE
        return CultureSetter(CultureInfo.GetCultureInfo(culture));
    }

    [HttpPost]
    public async Task<MailResponse> SendEmail([FromBody] EmailRequest request)
    {
        var requestCulture = request.requestedCulture; //en-SE
        var emailSubject = Lang.General.EmailSubject; //This is always the default
        using (CreateEmailRequestScope(request))
        {
            var emailSubject = Lang.General.EmailSubject; //This is wrong 10-20 mins after deployment - Should be Hello, But is Hej
            request.emailSubject = emailSubject; 
            return await mailer.Send(request);
        }
    }
}

Thanks for any suggestions!

1 Answers

In the end I found the cause of the issue with help from other sources.

We were using the ResX resource manager and it put an override in our static typed translations, which overrides Thread.CurrentUICulture and always return the same translation. In this case it's a field called Culture

Somewhere else in the endpoint Culture was set and after that translation stopped working on this endpoint.

Hope this helps someone else!

Related