n 层应用程序中库依赖的 DI 方法
DI approach for library dependency in n-tier application
我有一个 4 层的 .NET MVC 应用程序。我正在尝试使用依赖注入(通过 Ninject),但不断意识到我真正考虑的是服务位置。我目前的问题是:
我有依赖注入设置并处理在我的应用程序层(MVC 5 网络应用程序)中实例化的许多对象。现在,我正在对审计跟踪插入进行编码,希望它是:
public ActionResult Edit(int id)
{
// ...
AuditTrail.LogVisit("Edit Screen", id);
return View();
}
AuditTrail
是 Entity Framework class 表示审计跟踪 table(在单独的 assembly/layer 中),LogVisit
是静态方法,因为我不需要现有审计跟踪记录的上下文(这是一个插入)。
AuditTrail.LogVisit
创建一个新的DbContext 短时间插入记录,还需要登录的用户id。登录的用户 ID 可通过会话获得,我已将其公开为 class 的强类型成员,即 bound/injected 使用 InRequestScope
- 希望这能让我保持该值在会话中,但在不知道 System.Web
或类似依赖项的更高层中访问它。
我无法在 Audit Trail
class 中获取注入的用户 ID property/class,因为 DbContext 的创建与 AuditTrail.LogVisit
方法隔离。我想避免传递上下文根,因为这似乎不是最佳做法并且会产生其他问题。我已经查看了工厂扩展,但从示例中您仍然没有 static 工厂 - 您有一个实例 class 提供了一个工厂 class 实例。
我可以避免使用静态方法,但是 a) 这只会将问题推得更深(非静态助手 classes 如何在业务层内实例化?)和 b) 似乎太局限了 - 可以'当您只需要找到最相关的实现时,不使用静态?
我可以让 MVC 应用程序处理实例化所有依赖项并将它们传递给业务层方法,但这不是依赖项注入试图解决的问题吗?
依赖注入是关于注入实例的,因此静态 classes and/or 方法不能在此上下文中使用。整个想法是创建松散耦合的代码,我们这样做是 编程到抽象 ,这意味着接口。接口不适用于静态方法。
会话中的值与 DI 无关。这个想法是将定义(接口)与其实现(classes)分开。接口可以定义在与相应实现 class 不同的层中。这就是它如此强大的原因。您可以在相当低级别的层中定义接口,并将实现放在 UI 层中。这方面的一个例子是用户上下文 class,其中接口位于业务层,而实现 class 位于 Web 层,因为那是您拥有所需会话的地方在您的实施中使用。 DI 使得在您的业务层中使用此接口的实现成为可能,而您对 Web 或会话一无所知。
回到你的案例。从我从你的问题中得到的信息,我可以看到以下内容。
在较低层(例如业务层),我们定义这些:
public interface IUserContext {
int UserId { get; set; }
}
public interface IAuditTrail {
void LogVisit(string controller, int id);
}
此外,在业务(或数据)层,我们定义审计跟踪的实现。
public class AuditTrail : IAuditTrail {
public AuditTrail(
Func<DbContext> dbContextFactory,
IUserContext userContext
) {
// omitted: null guards
m_DbContextFactory = dbContextFactory;
m_UserContext = userContext;
}
private readonly DbContextFactory m_DbContextFactory;
private readonly IUserContext m_UserContext;
public void LogVisit(string controller, int id) {
using (var ctx = m_DbContextFactory()) {
var userId = m_UserContext.UserId;
// TODO: Log...
ctx.SaveChanges();
}
}
}
在 Web 应用程序中,我们定义了用户上下文的实现。
public AspNetMvcUserContext : IUserContext {
private const UserIdSessionKey = "UserId";
public int UserId {
get { return (int)Session[UserIdSessionKey]; }
set { Session[UserIdSessionKey] = value; }
}
}
最后,我们在您的 Web 应用程序的控制器中使用上述内容。
public class SomeController {
public SomeController(
IAuditTrail auditTrail
) {
// omitted: null guards
m_AuditTrail = auditTrail;
}
private readonly IAuditTrail m_AuditTrail;
public ActionResult Edit(int id) {
m_AuditTrail.LogVisit("Edit Screen", id);
return View();
}
}
当然,您需要在 Ninject 配置中注册以上所有组件。
它很干净,可以测试,而且非常可靠。
我有一个 4 层的 .NET MVC 应用程序。我正在尝试使用依赖注入(通过 Ninject),但不断意识到我真正考虑的是服务位置。我目前的问题是:
我有依赖注入设置并处理在我的应用程序层(MVC 5 网络应用程序)中实例化的许多对象。现在,我正在对审计跟踪插入进行编码,希望它是:
public ActionResult Edit(int id)
{
// ...
AuditTrail.LogVisit("Edit Screen", id);
return View();
}
AuditTrail
是 Entity Framework class 表示审计跟踪 table(在单独的 assembly/layer 中),LogVisit
是静态方法,因为我不需要现有审计跟踪记录的上下文(这是一个插入)。
AuditTrail.LogVisit
创建一个新的DbContext 短时间插入记录,还需要登录的用户id。登录的用户 ID 可通过会话获得,我已将其公开为 class 的强类型成员,即 bound/injected 使用 InRequestScope
- 希望这能让我保持该值在会话中,但在不知道 System.Web
或类似依赖项的更高层中访问它。
我无法在 Audit Trail
class 中获取注入的用户 ID property/class,因为 DbContext 的创建与 AuditTrail.LogVisit
方法隔离。我想避免传递上下文根,因为这似乎不是最佳做法并且会产生其他问题。我已经查看了工厂扩展,但从示例中您仍然没有 static 工厂 - 您有一个实例 class 提供了一个工厂 class 实例。
我可以避免使用静态方法,但是 a) 这只会将问题推得更深(非静态助手 classes 如何在业务层内实例化?)和 b) 似乎太局限了 - 可以'当您只需要找到最相关的实现时,不使用静态?
我可以让 MVC 应用程序处理实例化所有依赖项并将它们传递给业务层方法,但这不是依赖项注入试图解决的问题吗?
依赖注入是关于注入实例的,因此静态 classes and/or 方法不能在此上下文中使用。整个想法是创建松散耦合的代码,我们这样做是 编程到抽象 ,这意味着接口。接口不适用于静态方法。
会话中的值与 DI 无关。这个想法是将定义(接口)与其实现(classes)分开。接口可以定义在与相应实现 class 不同的层中。这就是它如此强大的原因。您可以在相当低级别的层中定义接口,并将实现放在 UI 层中。这方面的一个例子是用户上下文 class,其中接口位于业务层,而实现 class 位于 Web 层,因为那是您拥有所需会话的地方在您的实施中使用。 DI 使得在您的业务层中使用此接口的实现成为可能,而您对 Web 或会话一无所知。
回到你的案例。从我从你的问题中得到的信息,我可以看到以下内容。
在较低层(例如业务层),我们定义这些:
public interface IUserContext {
int UserId { get; set; }
}
public interface IAuditTrail {
void LogVisit(string controller, int id);
}
此外,在业务(或数据)层,我们定义审计跟踪的实现。
public class AuditTrail : IAuditTrail {
public AuditTrail(
Func<DbContext> dbContextFactory,
IUserContext userContext
) {
// omitted: null guards
m_DbContextFactory = dbContextFactory;
m_UserContext = userContext;
}
private readonly DbContextFactory m_DbContextFactory;
private readonly IUserContext m_UserContext;
public void LogVisit(string controller, int id) {
using (var ctx = m_DbContextFactory()) {
var userId = m_UserContext.UserId;
// TODO: Log...
ctx.SaveChanges();
}
}
}
在 Web 应用程序中,我们定义了用户上下文的实现。
public AspNetMvcUserContext : IUserContext {
private const UserIdSessionKey = "UserId";
public int UserId {
get { return (int)Session[UserIdSessionKey]; }
set { Session[UserIdSessionKey] = value; }
}
}
最后,我们在您的 Web 应用程序的控制器中使用上述内容。
public class SomeController {
public SomeController(
IAuditTrail auditTrail
) {
// omitted: null guards
m_AuditTrail = auditTrail;
}
private readonly IAuditTrail m_AuditTrail;
public ActionResult Edit(int id) {
m_AuditTrail.LogVisit("Edit Screen", id);
return View();
}
}
当然,您需要在 Ninject 配置中注册以上所有组件。
它很干净,可以测试,而且非常可靠。