You already have a good answer on "how should I fix this?" Here's more of a description of why it's behaving this way.
AsyncLocal<T>
has the same semantics as logging scopes. Because it has those same semantics, I always prefer to use it with an IDisposable
, so that the scope is clear and explicit, and there's no weird rules around whether a method is marked async
or not.
For specifics on the weird rules, see this. In summary:
- Writing a new value to an
AsyncLocal<T>
sets that value in the current scope.
- Methods marked
async
will copy their scope to a new scope the first time it's written to (and it's the new scope that is modified).
I've made it similar to how IHttpContextAccessor works, but still nowhere near.
I don't recommend copying the design of IHttpContextAccessor
. It works... for that very specific use case. If you want to use AsyncLocal<T>
, then use a design like this:
static class MyImplicitValue
{
private static readonly AsyncLocal<T> Value = new();
public static T Get() => Value.Value;
public static IDisposable Set(T newValue)
{
var oldValue = Value.Value;
Value.Value = newValue;
return new Disposable(() => Value.Value = oldValue);
}
}
usage:
using (MyImplicitValue.Set(myValue))
{
// Code in here can get myValue from MyImplicitValue.Get().
}
You can wrap that into an IMyImplicitValueAccessor
if desired, but note that any "setter" logic should be using the IDisposable
pattern as shown.
AsyncLocal instance is set at a certain point in the AuthenticationHandler, but does not reach the controller
That's because your AuthenticationHandler sets the value but doesn't call the controller after setting that value (and it shouldn't).
However, if I set the AsyncLocal from a Middleware, it reaches the controller.
That's because middleware is calls the next middleware (eventually getting to the controller). I.e., middleware is structured like this:
public async Task InvokeAsync(HttpContext context)
{
using (implicitValue.Set(myValue))
{
await _next(context);
}
}
So the controllers are in the scope of when that AsyncLocal<T>
value was set.
How is HttpContext able to retain Items property contents all the way
Items
is just a property bag. It doesn't have anything to do with AsyncLocal<T>
. It exists because it's a property on HttpContext
, and it persists because the same HttpContext
instance is used throughout the request.
is ASP.NET runtime disposing the captured ExecutionContext of my DomainContextAccessor for some security reason because of where it is being set at?
Not exactly. The AsyncLocal<T>
is being set just fine; it's just that the controllers are not called within the scope of that AsyncLocal<T>
being set.