Using ASP.NET Web API, my ExecutionContext isn't flowing in async actions

纵然是瞬间 提交于 2019-11-28 06:24:43

I don't have all the answers, but I can help fill in some blanks and guess at the problem.

By default, the ASP.NET SynchronizationContext will flow, but the way it flows identity is a bit weird. It actually flows HttpContext.Current.User and then sets Thread.CurrentPrincipal to that. So if you just set Thread.CurrentPrincipal, you won't see it flow correctly.

In fact, you'll see the following behavior:

  • From the time Thread.CurrentPrincipal is set on a thread, that thread will have that same principal until it re-enters an ASP.NET context.
  • When any thread enters the ASP.NET context, Thread.CurrentPrincipal is cleared (because it's set to HttpContext.Current.User).
  • When a thread is used outside the ASP.NET context, it just retains whatever Thread.CurrentPrincipal happened to be set on it.

Applying this to your original code and output:

  • The first 3 are all reported synchronously from thread 63 after its CurrentPrincipal was explicitly set, so they all have the expected value.
  • Thread 77 is used to resume the async method, thus entering the ASP.NET context and clearing any CurrentPrincipal it may have had.
  • Thread 63 is used for ProcessResponse. It re-enters the ASP.NET context, clearing its Thread.CurrentPrincipal.
  • Thread 65 is the interesting one. It is running outside the ASP.NET context (in a ContinueWith without a scheduler), so it just retains whatever CurrentPrincipal it happened to have before. I assume that its CurrentPrincipal is just left over from an earlier test run.

The updated code changes PostFile to run its second portion outside the ASP.NET context. So it picks up thread 65, which just happens to have CurrentPrincipal set. Since it's outside the ASP.NET context, CurrentPrincipal isn't cleared.

So, it looks to me like ExecutionContext is flowing fine. I'm sure Microsoft has tested ExecutionContext flow out the wazoo; otherwise every ASP.NET app in the world would have a serious security flaw. It's important to note that in this code Thread.CurrentPrincipal just refers to the current user's claims and does not represent actual impersonation.

If my guesses are correct, then the fix is quite simple: in SendAsync, change this line:

Thread.CurrentPrincipal = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));

to this:

HttpContext.Current.User = new ClaimsPrincipal(new ClaimsPrincipal(new ClaimsIdentity(new[]{ new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "dgdev") }, "myauthisthebest")));
Thread.CurrentPrincipal = HttpContext.Current.User;

I understand that reentering the ASP.NET synchronization context will cause Thread.CurrentPrincipal to be set to HttpContext.Current.User. But I am still not seeing the behavior I expected. I didn't expect that every awaited call up the chain would set Thread.CurrentPrincipal = HttpContext.Current.User. I see this even going beyond the async void event handler that I started the async/await chain in. Is this the behavior others are seeing? I expected the calls up the chain to use their captured context to continue but they are showing the reentrant behavior.

I am not using .ContinueAwait(false) on any of my awaited calls. We have targetFramework="4.6.1" in the web.config which under the covers sets UseTaskFriendlySynchronizationContext = true, among other things. A 3rd party API client is causing the reentrant behavior at the bottom of the async/await chain.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!