背景:
我在我的Web应用程序中使用DI。 (我正在使用NInject,但希望这不重要)
某些地方的构造函数注入是不可能的,例如在我的自定义log4net
数据库记录器中(不是我,实例化我的自定义记录器而不是log4net
框架)。所以我在服务定位器DP模式下使用我的DI容器,并在记录器代码中明确要求实例解析。
请注意,这只是一个示例,在许多其他情况下,我不得不使用NInject
作为服务定位器DP而不是构造函数注入。
现在问题: 我有一个IAuditContextProvider,它提供当前请求的审计数据,如IP等。问题出现了我如何配置我的DI容器来实例化一个具体的提供者。到目前为止,我已经使用了NINject支持的请求范围(按请求单身)。
但是最近我面临的事实是我必须开始一个请求启动的后台处理。这是由
完成的// This is 'outside' it's actually a request serving method running in the request context, btw it is an MVC action method,
// Pseudo code:
var auditProvider = Locator.Resolve<IAuditProvider>()
Task.Run(() =>
{
// I would like to get the very same resolved auditProvider instance here as outside.
// Please note: outer local variables are not solution, because of implicit calls here inside, for example if there is a logging statement here, then the service locator in the custom logger must resolve the very same instance as outside
// Some how I must 'stamp' this thread to be the 'same' as the outside
// request thread in point of view of my custom scope resolver (see below)
}
注意:配置DI容器的宽范围单例不是解决方案,因为多个请求是服务器并行的,并且它们不能使用公共auditProvider。
好的,我认为这是自定义(解析)范围的用途。这是伪代码我如何配置我的DI容器:
kernel
.Bind(typeof(IAuditContextProvider))
.To(typeof(WebAuditContextProvider)).InScope(dummy =>
{
// Here I have to return a very same object/number/id when in
// 'outside' the request thread, and inside the worker thread.
// This way I hopefully get the very same instance when resolving.
// To be short: I have no idea how?
});
答案 0 :(得分:0)
我不认为在目前的范围内对你的问题有一个很好的答案。
我确实有另一种建议 - 只需在另一个过程中同步执行工作。这需要一种形式的进程间通信(IPC),但不应该太困难。
一种简单但有效的IPC形式只是将记录写入数据库表(像队列一样),并使用Windows服务/守护程序轮询新记录到&#34;进程&#34;。在此示例中,您将在表中放置具有上下文信息(用户标识等)的记录,并且服务将利用此上下文同步执行工作,但工作流将与Web UI异步。
这也有一个很好的附带好处:您可以开始在服务中构建监控,重试逻辑等。在ASP.NET模型中,这些事情很难可靠地完成。
您可以通过使用消息队列/总线/事件等方式完全放弃数据库队列,但基本概念是相同的。
答案 1 :(得分:0)
<击>更新击>
<击>您是否尝试在C#中使用闭包?像这样:
var auditProvider = Locator.Resolve<IAuditProvider>()
Task.Run(() =>
{
// with closure you'll get that very variable you need:
auditProvider.SomeMethod();
}
您应该阅读whole article about closures by John Skeet以及他们如何与TPL一起帮助您。
其他有用信息:
这样的DI在着名的书Dependency Injection
by M. Seeman中被称为Ambient Context
:
如果您必须将实例传递给每个协作者,真正通用的CROSS-CUTTING CONCERN可能会污染应用程序的大部分API。另一种方法是定义一个可供任何需要它的人使用的上下文,并且可以被其他人忽略。
AMBIENT CONTEXT可通过静态属性提供给任何消费者 或方法。消费类可能会像这样使用它:
public string GetMessage() { return SomeContext.Current.SomeValue; }
在这种情况下,上下文具有消费者可以访问的静态
Current
属性。此属性可以是真正的静态,也可以与当前正在执行的线程相关联。要在DI场景中有用,上下文本身必须是ABSTRACTION,并且必须可以从外部修改上下文 - 在前面的示例中,这意味着Current
属性必须是可写的。可以实现上下文本身,如下面的清单所示。上下文是一个
abstract
类,它允许我们在运行时用另一个实现替换一个上下文。
public abstract class SomeContext
{
public static SomeContext Current
{
get
{
// Get current context from TLS
var ctx = Thread.GetData(Thread.GetNamedDataSlot("SomeContext")) as SomeContext;
if (ctx == null)
{
ctx = SomeContext.Default;
Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), ctx);
}
return ctx;
}
set
{
Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), value);
}
}
public static SomeContext Default = new DefaultContext();
public abstract string SomeValue { get; }
}
TLS
此处代表Thread Local Storage
,这对您来说很有用。
另外,我建议您阅读有关OperationContext
课程的内容,如果您想为Task
传递一些上下文,可能会对您有所帮助,如下所示:
// save current context before task start
var operationContext = OperationContext.Current;
Task.Run(() =>
{
// set current operation context inside your Task with closure
OperationContext.Current = operationContext;
// Your work here
}