【问题标题】:AsyncLocal<T> doesn't reach controllersAsyncLocal<T> 没有到达控制器
【发布时间】:2021-03-05 23:09:41
【问题描述】:

我不太了解这种情况,其中 AsyncLocal instance 设置在 AuthenticationHandler 中的某个点,但在注入 constructor 时没有到达控制器。

我已经使它类似于IHttpContextAccessor 的工作方式,但仍然相去甚远。但是,如果我从 Middleware 设置 AsyncLocal,它会到达控制器。此外,从 AuthenticationHandler 设置 HttpContext.Items 属性也可以正常工作。

问题:HttpContext 如何能够一直保留 Items 属性内容,以及 ASP.NET 运行时是否因为某些安全原因而将捕获的 DomainContextAccessor 的 ExecutionContext 设置在哪里?

我制作了一个sample app 来演示这个用例。我真的很感谢有人能阐明这个问题。

【问题讨论】:

  • 一切都是 HTTP404。我看不到你的任何链接。
  • 奇怪...对我来说不是 404。
  • 是的,它是私人的.. 很抱歉。立即尝试。
  • 实际上你不应该链接到外部站点并将相关部分发布在 StackOverflow 上,因为这不是你的私人问题解决平台,而是对未来某个时候来到这里的每个人都有帮助.但是可能成为无效或死链接的外部链接/示例对于未来寻找问题答案的访问者来说毫无用处

标签: asp.net-core asp.net-web-api


【解决方案1】:

所以必须发生的事情是执行上下文更改会清除该值。它可以在中间件中使用,因为您的控制器与中间件处于相同的执行上下文中。

将您的代码更改为:

private static void DomainContextChangeHandler(AsyncLocalValueChangedArgs<DomainContextHolder> args)
{
    Trace.WriteLine($"ThreadContextChanged: {args.ThreadContextChanged}");
    Trace.WriteLine($"Current: {args.CurrentValue?.GetHashCode()}");
    Trace.WriteLine($"Previous: {args.PreviousValue?.GetHashCode()}");
    Trace.WriteLine($"Thread Id: {Thread.CurrentThread.ManagedThreadId}");
}

现在您可以看到上下文何时发生变化。

你可以这样做:

private static void DomainContextChangeHandler(AsyncLocalValueChangedArgs<DomainContextHolder> args)
{
    if (args.ThreadContextChanged && (args.PreviousValue != null) && (args.CurrentValue == null))
    {
        Trace.WriteLine(
            "***** Detected context change with a previous value but setting current " +
            "value to null. Resetting value to previous.");
        _domainContextCurrent.Value = args.PreviousValue;
        return;
    }
    Trace.WriteLine($"ThreadContextChanged: {args.ThreadContextChanged}");
    Trace.WriteLine($"Current: {args.CurrentValue?.GetHashCode()}");
    Trace.WriteLine($"Previous: {args.PreviousValue?.GetHashCode()}");
    Trace.WriteLine($"Thread Id: {Thread.CurrentThread.ManagedThreadId}");
}

但是,这有点违背了使用 AsyncLocal 作为后备存储的目的。

我的建议是你放弃AsyncLocal 并使用普通的类范围存储:

namespace WebApp.Models
{
    public interface IDomainContextAccessor
    {
        DomainContext DomainContext { get; set; }
    }

    public sealed class DomainContextAccessor : IDomainContextAccessor
    {
        public DomainContext DomainContext { get; set; }
    }
}

并将其作为作用域而不是单例注入:

services.AddScoped<IDomainContextAccessor, DomainContextAccessor>();

它会完全按照你的意愿去做,没有任何障碍——而且,未来你(或开发者)将绝对了解正在发生的事情以及为什么会这样。

没有中间件,没有AsyncLocal funny-business。它只是工作。

【讨论】:

    【解决方案2】:

    关于“我应该如何解决这个问题”,您已经有了一个很好的答案。这里有更多关于它为什么会这样表现的描述。

    AsyncLocal&lt;T&gt;logging scopes 具有相同的语义。因为它具有相同的语义,所以我总是更喜欢将它与IDisposable 一起使用,以便范围清晰明确,并且没有关于方法是否标记为async 的奇怪规则。

    有关奇怪规则的详细信息,请参阅this。总结:

    • 将新值写入AsyncLocal&lt;T&gt; 会将该值设置为在当前范围内
    • 标记为 async 的方法将在第一次写入时将其范围复制到新范围(并且修改的是新范围)。

    我已经使它类似于 IHttpContextAccessor 的工作方式,但还差得远。

    我不建议抄袭IHttpContextAccessor的设计。它适用于那个非常具体的用例。如果你想使用AsyncLocal&lt;T&gt;,那么使用这样的设计:

    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);
      }
    }
    

    用法:

    using (MyImplicitValue.Set(myValue))
    {
      // Code in here can get myValue from MyImplicitValue.Get().
    }
    

    如果需要,您可以将其包装到 IMyImplicitValueAccessor 中,但请注意,任何“设置器”逻辑都应使用 IDisposable 模式,如图所示。

    AsyncLocal 实例设置在 AuthenticationHandler 中的某个点,但没有到达控制器

    这是因为您的 AuthenticationHandler 设置了该值,但在设置该值后不调用控制器(它不应该)。

    但是,如果我从中间件设置 AsyncLocal,它会到达控制器。

    这是因为中间件会调用下一个中间件(最终到达控制器)。即,中间件的结构如下:

    public async Task InvokeAsync(HttpContext context)
    {
      using (implicitValue.Set(myValue))
      {
        await _next(context);
      }
    }
    

    因此,控制器在设置 AsyncLocal&lt;T&gt; 值时的范围内。

    HttpContext如何能够一直保留Items属性内容

    Items 只是一个属性包。它与AsyncLocal&lt;T&gt; 没有任何关系。它之所以存在是因为它是 HttpContext 上的一个属性,它之所以存在是因为在整个请求中使用了同一个 HttpContext 实例。

    ASP.NET 运行时是否因为某些安全原因而将捕获的 DomainContextAccessor 的 ExecutionContext 设置在何处?

    不完全是。 AsyncLocal&lt;T&gt; 设置得很好;只是在设置的AsyncLocal&lt;T&gt; 范围内没有调用控制器。

    【讨论】:

      猜你喜欢
      • 2018-11-30
      • 2010-11-19
      • 1970-01-01
      • 2015-02-28
      • 2021-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多