【问题标题】:Setting HttpContext.Current using HttpContextBase使用 HttpContextBase 设置 HttpContext.Current
【发布时间】:2018-06-12 14:48:38
【问题描述】:

我有一个在线程中运行的长时间运行的任务。我想在这个线程中设置HttpContext.Current,这样我的HttpContextFactory就可以得到当前的HttpContext。

这是我的 TaskRunner 类:

public class TaskRunner
{
    public TaskRunner(
        IQueueProcessorFactory queueProcessorFactory,
        IHttpContextFactory httpContextFactory)
    {
        _queueProcessorFactory = queueProcessorFactory;
        _httpContextFactory = httpContextFactory;
    }

    public void StartQueueProcessorThread()
    {
        var currentContext = _httpContextFactory.Create(); // Simply Gets new HttpContextWrapper(HttpContext.Current);
        queueProcessor = new Thread(
        () =>
        {
            HttpContext.Current = currentContext; // Cannot implicitly convert type 'System.Web.HttpContextBase' to 'System.Web.HttpContext'
            _queueProcessorFactory.Create().ProcessQueue(); // Log running task
        })
        { Name = "QueueProcessor" };
        queueProcessor.Start();
    }
}

有没有使用注入的 _httpContextFactory 设置 HttpContext.Current 的简单方法?

【问题讨论】:

    标签: multithreading dependency-injection httpcontext


    【解决方案1】:

    你想要的东西是不可能的,你也不应该尝试实现这样的事情。您的问题是由严重依赖HttpContext.Current 的代码引起的。这违反了依赖倒置原则。由于您的代码在后台线程上运行,因此它不是在 HTTP 请求的上下文中运行,因此不应使用请求信息。

    解决方案是定义抽象HttpContext 的特定于应用程序的抽象。例如,当该代码需要有关当前用户的信息时,定义一个 IUserContext 抽象:

    public interface IUserContext
    {
        Guid UserId { get; }
    }
    

    这允许您在后台线程上运行时注入不同的IUserContext 实现。

    您的网络请求的 IUserContext 实现可能如下所示:

    public sealed class AspNetUserContext : IUserContext
    {
        public Guid UserId => (Guid)HttpContext.Current.Session["UserId"];
    }
    

    另一方面,您在后台线程上使用的用户上下文可能实现如下:

    public sealed class ThreadStaticUserContext : IUserContext
    {
        [ThreadStatic]
        public static Guid UserIdField;
    
        public Guid UserId => this.UserIdField;
    }
    

    这个ThreadStaticUserContext 允许设置UserId。如果您想分离一个在与发起请求相同的用户 ID 中运行的后台线程,您必须将用户 ID 传递给后台线程,并在运行完整操作之前设置 FixedUserContext.UserId 值。这可能看起来像这样:

    public AsynchronousWelcomeMessageSenderDecorator : IWelcomeMessageSender
    {
        private readonly IUserContext userContext;
        private readonly Func<IWelcomeMessageSender> senderFactory;
    
        public AsynchronousWelcomeMessageSenderDecorator(
            IUserContext userContext, 
            Func<IWelcomeMessageSender> senderFactory) { ... }
    
        public void SendWelcomeMessage(WelcomeMessage message)
        {
            // Read the user ID while running on the request thread
            Guid userId = this.userContext.UserId;
    
            ThreadPool.QueueUserWorkItem(_ =>
            {
                // Set the user ID as the first thing to do on the background thread.
                ThreadStaticUserContext.UserIdField = userId;
    
                // Resolve the 'real' sender within the thread.
                IWelcomeMessageSender realSender = this.senderFactory();
    
                // Forward the call to the real sender.
                realSender.SendWelcomeMessage(message);
            });
        }
    }
    

    给你一个想法,在没有 DI 容器的情况下,对象图的这一部分可以构造如下:

    IWelcomeMessageSender sender =
        new AsynchronousWelcomeMessageSenderDecorator(
            new AspNetUserContext(),
            () => new EmailMessageSender(new ThreadSpecificUserContext()));
    

    换句话说,任何依赖于IWelcomeMessageSender 的组件都会注入AsynchronousWelcomeMessageSenderDecorator。当它的SendWelcomeMessage 被调用时,它会启动一个后台线程,该线程将通过senderFactory 请求一个IWelcomeMessageSendersenderFactory 将创建一个新的EmailMessageSender 来发送实际邮件。这个EmailMessageSender 再次依赖于IUserContext,但在这种情况下,它被注入了ThreadSpecificUserContext

    【讨论】:

    • 谢谢@Steven,但我还是有点困惑。假设我定义了AspNetUserCotnext,这是IUserContext. This means that AspNetUserContext`的具体形式,最终将取决于HttpContext.Current,对吧?请记住,我无法访问 IHttpContextAccessor,因为我正在处理一个 asp.net 4.6 项目。
    • 这对我帮助很大!!非常感激。最后一个问题:有两个接口IUserContextIThreadStaticUserContext 有意义吗?否则,如何指示我的 DI 容器将线程静态实现注入启动工作线程的代码模块?
    • 您使用的是哪个 DI 容器?
    • 我正在使用 Unity。
    • 我不确定如何使用 Unity 实现这一点,但从设计的角度来看,创建多个接口没有意义,因为您希望业务逻辑忽略它的位置跑步。另一种选择是将两个实现合并为 2。换句话说,有一个从 HttpContext 加载的实现,除非它被显式设置。
    猜你喜欢
    • 1970-01-01
    • 2015-08-06
    • 2010-11-16
    • 1970-01-01
    • 2012-08-16
    • 2014-02-11
    • 2019-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多