使用 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
请求一个 IWelcomeMessageSender
。 senderFactory
将创建一个新的 EmailMessageSender
来发送实际邮件。此 EmailMessageSender
再次依赖于 IUserContext
,但在本例中它注入了 ThreadSpecificUserContext
.
我在一个线程中 运行 有一个很长的 运行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
请求一个 IWelcomeMessageSender
。 senderFactory
将创建一个新的 EmailMessageSender
来发送实际邮件。此 EmailMessageSender
再次依赖于 IUserContext
,但在本例中它注入了 ThreadSpecificUserContext
.