使用HttpContextBase设置HttpContext.Current

时间:2018-06-12 14:48:38

标签: multithreading dependency-injection httpcontext

我有一个长期运行的任务,我在一个线程中运行。我想在这个线程中设置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?

1 个答案:

答案 0 :(得分: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时,它会剥离后台线程,后台线程将通过IWelcomeMessageSender请求senderFactorysenderFactory将创建一个新的EmailMessageSender,用于发送实际邮件。此EmailMessageSender又取决于IUserContext,但在这种情况下,它会注入ThreadSpecificUserContext