Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET CurrentUICulture behaves differently in Task and WebAPI deferred result

I'm working on an ASP.NET Web API project in which I had an issue where the child Tasks which run asynchronously were not inheriting the the current culture of the parent thread; i.e., even after setting the Thread.CurrentThread.CurrentUICulture in the controller constructor, any Task created within the action was having the invariant culture by default unless I set it separately. This seems to have fixed in framework > 4.0 and was working fine after upgrading the target framework from 3.5.2 to 4.6.2 as mentioned here.

Starting with apps that target the .NET Framework 4.6, the calling thread's culture is inherited by each task, even if the task runs asynchronously on a thread pool thread.

Now that the culture inheritance in the Tasks are working as expected, I'm facing another weird issue which was not present in 4.5.2. If the WebApi action result is of type IEnumerable, then the result will have the default culture in it. Maybe the issue could be better explained using a sample code snippet:

public SampleController()
{
    System.Threading.Thread.CurrentThread.CurrentUICulture =
        new System.Globalization.CultureInfo("ar-AE");
}

[Route]
public IHttpActionResult Get()
{
    IEnumerable<Employee> employeesDeferred = BuildEmployees(true);
    return Ok(employeesDeferred);
}

private IEnumerable<Employee> BuildEmployees(bool isDeferred)
{
    var employees = Enumerable.Range(0, 2)
        .Select(count => new Employee
        {
            Id = count++,
            Culture = Thread.CurrentThread.CurrentUICulture.Name
        });

    if (isDeferred) { return employees; }

    return employees.ToList();
}

Please note that I'm setting the UI culture as "ar-AE" in the constructor. PFB the results:

  • framework 4.5.2 - The Culture of all employees will be ar-AE as expected
  • framework 4.6.2 - The Culture of all employees will be en-US (default)
  • framework 4.6.2 by doing a .ToList() in the result (calling BuildEmployees(false) instead of BuildEmployees(true) in the above code) - The Culture of all employees will be ar-AE as expected

I did had some research and saw answers which suggests to set the NoAsyncCurrentCulture switch, for eg, by adding the switch in the app settings <add key="appContext.SetSwitch:Switch.System.Globalization.NoAsyncCurrentCulture" value="true" /> - but this will kinda rollback to the behavior we had in <4.0 and will cause the issue with the culture inheritance in the Tasks as mentioned in the first para.

What am I missing?

Update: Verified that the context culture is not getting inherited in the thread where the (JSON/XML) serialization is happening in Web API.

like image 289
Developer Avatar asked Jun 27 '18 10:06

Developer


1 Answers

As mentioned in the question, the calling thread's UICulture was not flowing to the thread which was performing the serialization. To verify this, I created a custom JsonConverter (NewtonSoft) and added as the default converter to my WebAPI and checked the UICulture in the WriteJson method - as expected the UICulture which I set was overridden/lost and this thread was having the invariant culture.

I dint want to play around too much rewriting the entire JsonConverter and hence didn't proceed with it; instead what I needed was to force serialize the response of the API in the context of the action itself (where the UI Culture is set properly). And the same was achieved by the below custom filter where I called LoadIntoBufferAsync() of the response content:

    /// <summary>
    /// BUG Description and History:
    /// We had an issue where the child Tasks/async operations were not inheriting the the current culture of the parent thread;
    /// i.e., even after setting the Thread.CurrentThread.CurrentUICulture in the controller constructor, any Task created within the action was having the invariant culture by default 
    /// unless it is set it separately. This seems to have fixed in framework > 4.0 and was working fine after upgrading the target framework from 3.5.2 to 4.6.2
    /// ref - https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming
    /// But with this fix in framework, there introduced an unnoticed? bug where in the UI culture do not flow from the calling thread's context to the NewtonSoft Json Serializer write
    /// And hence, any deferred serialization (like IEnumerable) will run under the Invariant Culture (en-US) thus ending up picking the english resource file instead of the culture set for the thread.
    /// This works fine in framework 3.5.2 or if we enable the "Switch.System.Globalization.NoAsyncCurrentCulture" switch in 4.6.2. But using the switch will diable the flow of culture in async operations and will end up having the issue
    /// we had in 3.5.2.
    /// Inorder to fix this issue, the workaround is to force the serialization happen in the action context itself where the thread UI culture is the one we have set.
    /// </summary>
    public class ForceSerializeHttpContentFilter : ActionFilterAttribute
    {
        public override void OnActionExecuted(HttpActionExecutedContext filterContext)
        {
            base.OnActionExecuted(filterContext);
            filterContext.Response.Content.LoadIntoBufferAsync().Wait();
        }
    }

Not sure whether this is the right fix; waiting for more responses from the community. Thank you.

like image 57
Developer Avatar answered Oct 26 '22 11:10

Developer