使用NInject建立工厂的最佳方法是什么?


27

我对在MVC3中使用NInject进行依赖项注入非常满意。在MVC3应用程序中工作时,我使用NInject开发了一个自定义的Controller Creation Factory,因此所创建的任何控制器都将通过此Controller Factory注入依赖项。

现在,我开始开发Windows应用程序,我想使用应用程序范围依赖注入。即,必须通过NInject创建每个对象,以简化单元测试。请指导我确保创建的每个对象都只能通过NInject Factory。

例如,如果在Button_Click事件的任何Windows窗体上我都写:

TestClass testClass = new TestClass()

并且TestClass有任何依赖,例如,ITest必须自动解决。我知道我可以使用:

Ikernel kernel = new StandardKenel()
//AddBinding()
TestClass testClass = kenel.get<TestClass>();

但是我发现每次要创建对象时都要执行此操作很麻烦。它还迫使开发人员以特定方式创建对象。可以做得更好吗?

我可以有一个用于对象创建的中央存储库,然后每个对象创建都会自动使用该存储库吗?


1
嗨Pravin Patil:很好的问题。我对您的标题进行了小幅更改,以使您的要求更加清楚。如果我错过了标记,请随时进行修改。

@MarkTrapp:感谢您提供合适的标题。我错过了那个标语...
Pravin Patil '02

作为次要说明,该项目的拼写为“ Ninject”,而不是“ NInject”。尽管可能是En-Inject,但如今他们还是以nin-ja为主题播放。:) ninject.org
Cornelius

Answers:


12

对于客户端应用程序,通常最好是采用MVP(或MVVM)之类的模式,并使用从表单到底层ViewModel或Presenter的数据绑定。

对于ViewModel,您可以使用标准的Constructor Injection注入所需的依赖项。

在应用程序的“ 合成根”中,可以为应用程序连接整个对象图。您不需要为此使用DI容器(例如Ninject),但是可以。


7

Windows Forms应用程序通常具有一个如下所示的入口点:

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }

如果您在代码的构成根目录中指出了这一点,则可以极大地减少代码显式调用Ninject的位置,就好像它是服务定位符一样。

    // Program.cs
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var kernel = InitializeNinjectKernel();
        Application.Run(kernel.Get<MainForm>());
    }

从这一点开始,您将通过构造函数注入来注入所有依赖项。

public MainForm(TestClass testClass) {
    _testClass = testClass;
}

如果您需要能够多次生产“依赖性”,那么您真正需要的是工厂:

public MainForm(IFactory<TestClass> testClassFactory) {
    _testClassFactory = testClassFactory;
}

...
var testClass = _testClassFactory.Get();

您可以通过以下方式实现IFactory接口,从而避免创建大量一次性的实现:

public class InjectionFactory<T> : IFactory<T>, IObjectFactory<T>, IDependencyInjector<T>
{
    private readonly IKernel _kernel;
    private readonly IParameter[] _contextParameters;

    public InjectionFactory(IContext injectionContext)
    {
        _contextParameters = injectionContext.Parameters
            .Where(p => p.ShouldInherit).ToArray();
        _kernel = injectionContext.Kernel;
    }

    public T Get()
    {
        try
        {
            return _kernel.Get<T>(_contextParameters.ToArray());
        }
        catch (Exception e)
        {
            throw new Exception(
                string.Format("An error occurred while attempting to instantiate an object of type <{0}>",
                typeof(T)));
        }
    }

...
Bind(typeof (IFactory<>)).To(typeof (InjectionFactory<>));
Bind(typeof (IContext)).ToMethod(c => c.Request.ParentContext);

请,您对此工厂有完整的实施吗?
2015年

@ColourBlend:否,但是如果您摆脱了我已经InjectionFactory<T>实现的其他接口,那么提供的代码应该可以正常工作。您有什么特别的问题吗?
StriplingWarrior

我已经实现了它,我只想知道课程中是否还有其他有趣的东西。
2016年

@Tebo:我只是让它实现了几个其他与DI相关的接口,例如您可以将其传递给的工厂Type,但这将保证它为该对象水合的对象Type实现或扩展给定的泛型类型。没什么特别的。
StriplingWarrior

4

我总是为任何IoC容器编写一个Adapter包装器,如下所示:

public static class Ioc
{
    public static IIocContainer Container { get; set; }
}

public interface IIocContainer 
{
    object Get(Type type);
    T Get<T>();
    T Get<T>(string name, string value);
    void Inject(object item);
    T TryGet<T>();
}

特别是对于Ninject,具体的Adapter类如下所示:

public class NinjectIocContainer : IIocContainer
{
    public readonly IKernel Kernel;
    public NinjectIocContainer(params INinjectModule[] modules) 
    {
        Kernel = new StandardKernel(modules);
        new AutoWirePropertyHeuristic(Kernel);
    }

    private NinjectIocContainer()
    {
        Kernel = new StandardKernel();
        Kernel.Load(AppDomain.CurrentDomain.GetAssemblies());

        new AutoWirePropertyHeuristic(Kernel);
    }

    public object Get(Type type)
    {
        try
        {
            return Kernel.Get(type);
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }              
    }

    public T TryGet<T>()
    {
        return Kernel.TryGet<T>();
    }

    public T Get<T>()
    {
        try
        {
            return Kernel.Get<T>();
        }
        catch (ActivationException exception)
        {
            throw new TypeNotResolvedException(exception);
        }           
    }

    public T Get<T>(string name, string value)
    {
        var result = Kernel.TryGet<T>(metadata => metadata.Has(name) &&
                     (string.Equals(metadata.Get<string>(name), value,
                                    StringComparison.InvariantCultureIgnoreCase)));

        if (Equals(result, default(T))) throw new TypeNotResolvedException(null);
            return result;
    }

    public void Inject(object item)
    {
        Kernel.Inject(item);
    }
}

这样做的主要原因是要抽象出IoC框架,因此我可以随时替换它-鉴于框架之间的差异通常在于配置而不是用法。

但是,作为奖励,使用IoC框架在其他本来就不支持它的框架中使用也变得容易得多。例如,对于WinForms,这是两个步骤:

在您的Main方法中,只需在执行其他任何操作之前实例化一个容器即可。

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        try
        {
            Ioc.Container = new NinjectIocContainer( /* include modules here */ );
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MyStartupForm());
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString());
        }
    }
}

然后有一个基础表单,其他表单也从中衍生而来,它本身调用Inject。

public IocForm : Form
{
    public IocForm() : base()
    {
        Ioc.Container.Inject(this);
    }
}

这告诉自动装配试探法尝试以适合您模块中设置规则的形式递归注入所有属性。


非常好的解决方案.....我会尝试的。
Pravin Patil'2

10
那是一个服务定位器,这是一个非常糟糕的主意:blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx
Mark Seemann

2
@MarkSeemann:服务定位器是个坏主意,如果您可以从任何地方访问它,而不是让它尽可能多地连接顶层对象。阅读Mark自己的评论,在页面的下方:“在这种情况下,您实际上无能为力,只能将Composition Root移到每个对象(例如,Page)中,然后让DI Container从那里连接您的依赖项。这看起来像服务定位器反模式,但这不是因为您仍将容器使用量保持在绝对最低水平。” (编辑:等等,你是马克!那有什么区别?)
pdr

1
区别在于您仍然可以将其余代码库与Composer隔离开,而不是使Singleton Service Locator对任何类都可用。
马克·西曼

2
@pdr:根据我的经验,如果您试图将服务注入到诸如属性类之类的东西中,那么您就不能正确地分离关注点。在某些情况下,您正在使用的框架实际上使无法使用适当的依赖注入,有时我们被迫使用服务定位器,但是我肯定会尝试尽可能采用真实的DI,然后再还原到此图案。
StriplingWarrior '02

1

依赖注入的良好使用通常依赖于分离创建对象的代码和实际的业务逻辑。换句话说,我不希望我的团队以new这种方式频繁使用和创建类的实例。一旦完成,就无法轻松地将创建的类型换成另一个类型,因为您已经指定了具体类型。

因此,有两种方法可以解决此问题:

  1. 注入类将需要的实例。在您的示例中,将a TestClass注入Windows窗体,以便在需要时已经有一个实例。当Ninject实例化表单时,它将自动创建依赖关系。
  2. 在你真的情况下,希望创建一个实例,直到你需要它,你可以注入一个工厂到业务逻辑。例如,您可以将注入IKernelWindows窗体,然后使用实例化TestClass。根据您的风格,还有其他方法可以完成此操作(注入工厂类,工厂委托等)。

这样做可以轻松交换出TestClass的具体类型,以及修改测试类的实际构造,而无需实际修改使用测试类的代码。


1

我没有使用过Ninject,但是使用IoC时创建东西的标准方法是通过Func<T>where T来创建对象的类型。因此,如果对象T1需要创建类型的对象,T2则的构造函数T1必须具有类型的参数,Func<T1>然后将其存储为的字段/属性T2。现在,当您要创建类型的对象时T2T1请调用Func

这完全使您脱离了IoC框架,并且是在IoC思维方式中进行编码的正确方法。

这样做的不利之处在于,当您需要手动连接Funcs或创建者需要某些参数的示例时,它会很烦人,因此IoC无法Func为您自动连接。

http://code.google.com/p/autofac/wiki/RelationshipTypes

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.