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 配置中注册以上所有组件。

它很干净,可以测试,而且非常可靠。