我有一个长期运行的任务,我在一个线程中运行。我想在这个线程中设置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?
答案 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
请求senderFactory
。 senderFactory
将创建一个新的EmailMessageSender
,用于发送实际邮件。此EmailMessageSender
又取决于IUserContext
,但在这种情况下,它会注入ThreadSpecificUserContext
。