如何在多层应用程序中定位Ninject模块

14
我的应用程序包括多个后端组件(包括Entity Framework数据存储库层),这些组件与多个前端组件(包括Windows服务和MVC3 Web应用程序)共享。
我对Ninject绑定过程的理解是,每个包含可注入类型的程序集都应该包含一个Ninject模块,定义这些类型的默认绑定。然后,定义的模块集将被加载到使用它们的程序集的Ninject内核中。
但是,我遇到了问题,因为所需的绑定范围并不总是一致的。例如,我的MVC项目需要绑定到数据上下文"InRequestScope",而Windows服务绑定到同一个类"InThreadScope"。
显然,我可以通过将所有模块移到前端项目中来解决此问题,并为每个使用场景维护单独的模块副本,但这似乎有些笨拙,因为它在多个项目之间重复了大量模块内容。
在多层应用程序中,是否存在关于模块应在何处放置以及如何协调我的绑定差异需求的最佳实践?
感谢您的建议,
Tim

请参考https://dev59.com/1nI-5IYBdhLWcg3wu7Lv(如果我没记错,这个问题是一个重复的问题,但这是我现在能找到的最好的答案)。 - Ruben Bartelink
谢谢Ruben。你说得对,这两个问题有很多共同之处。我特别喜欢你的建议,将运行时参数传递到驻留在支持程序集中的模块中-非常灵活。 - Tim Coulter
嗯;那是一段时间以前的事了(并不是在任何方式上推销我的答案)。我当时可能只是字面意义上的传递参数 - 通常尽可能通过接口来实现。此外,那是在http://manning.com/seemann 出版之前的,这本书极大地减少了您在 DI 架构中遇到的困惑问题的数量 - 肯定要购买,毫无疑问。 - Ruben Bartelink
2个回答

14

针对单个应用程序的解决方案,通常建议在应用程序项目(Web 应用程序或 Web 服务项目)中注册您的容器。对于 Web 应用程序,这通常是 Global.asax 中的 Application_Start。负责将所有组件连接起来的位置称为 DI 术语中的组合根

对于多个应用程序解决方案,每个应用程序项目仍然需要一个单独的组合根,因为每个应用程序都有其独特的配置。但是,复制的代码总是不好的。您不想在引入新的抽象时更改三个位置。

技巧在于将所有注册向下移动到项目层次结构中。例如,您可以定义一个单独的“引导程序程序集”,该程序集依赖于业务层程序集(及以下),并让其具有那些不变的程序集的所有注册表。然后,应用程序的组合根可以使用该程序集获取默认注册表,并使用应用程序特定的依赖项进行扩展。

这样的东西可能看起来像这样:

// MVC Composition root
public static void Bootstrap()
{
    var container = new Container();

    // Default registrations
    BusinessLayerBootstrapper.Bootstrap(container);

    // Application specific registrations
    container.Bind<IUserContext>().To<AspNetUserContext>();

    DependencyResolver.Current = 
        new ContainerDependencyResolver(container);
}

// Windows Service Composition root
public static void Bootstrap()
{
    var container = new Container();

    // Default registrations
    BusinessLayerBootstrapper.Bootstrap(container);

    // Application specific registrations
    container.Bind<IUserContext>().To<SystemUserContext>()
        .SingleScoped();

    // Store somewhere.
    Bootstrapper.Container = container;
}

// In the BL bootstrap assembly
public static class BusinessLayerBootstrapper
{
    public static void Bootstrap(Container container)
    {
        container.Bind<IDepenency>().To<RealThing>();
        // etc
    }
}

虽然您不必拥有单独的启动程序集(可以将此代码放在BL本身中),但这可以使您的业务层程序集不受容器的任何依赖关系的影响。

还要注意,我只是调用了一个静态的Bootstrap()方法,而不是使用(Ninject)模块。我试图让我的答案独立于框架,因为您的问题很普遍,对所有DI框架都适用的建议也是相同的。但是,当然如果您想要,您也可以使用Ninject模块功能。


在这种情况下,“功能”或“打包”方法更受推荐? - Ewerton

6
有关MVC应用的范围限定,需要与Windows服务具有不同的CompositionRoot。我建议您尝试通过功能模块进行组织(对于那些与应用程序无关的部分),并将所有其他绑定直接放在MVC或WindowsService项目的CompositionRoot中。
另一种非常好的方法是定义一组通用约定,帮助您在几行代码中表达最重要的绑定问题。因此,您的应用程序可以具有以下两个绑定:MVC应用程序。
Bind(c => c.FromAssemblyContaining<IRepository>()
           .SelectAllClasses()
           .InheritedFrom<IRepository>()
           .Configure(b => b.InRequestScope()));

您的Windows服务应用程序

Bind(c => c.FromAssemblyContaining<IRepository>()
           .SelectAllClasses()
           .InheritedFrom<IRepository>()
           .Configure(b => b.InThreadScope()));

在我的观点中,与面向特性的结构相结合,惯例方法是最清晰的方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接