使用 HttpContextBase 设置 HttpContext.Current

Setting HttpContext.Current using HttpContextBase

我在一个线程中 运行 有一个很长的 运行ning 任务。我想在此线程中设置 HttpContext.Current,以便我的 HttpContextFactory 可以获得当前的 HttpContext。

这是我的 TaskRunner class:

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 的简单方法?

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

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

public interface IUserContext
{
    Guid UserId { get; }
}

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

您的 Web 请求的 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 传递给后台线程,并在 [=49= 之前设置 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.