2013-07-28 29 views
13

我在ASP.NET MVC項目中使用簡單注入器。我添加了SimpleInjector.Integration.Web.Mvc nuget包。這會在App_Start文件夾中添加SimpleInjectorInitializer類並初始化DI。代碼看起來像獲取簡單注射器的容器實例

public static void Initialize() 
{ 
    // Did you know the container can diagnose your configuration? 
    // Go to: https://simpleinjector.org/diagnostics 
    var container = new Container(); 

    //Container configuration code 
    DependencyResolver.SetResolver(
     new SimpleInjectorDependencyResolver(container)); 
} 

這將正確配置MVC控制器的DI。

我的問題是,如果我想要在任何控制器\類中獲取容器的實例來手動解決某些依賴項,我該怎麼做。

我早先在AutoFac上工作過,它有一個依賴接口IComponentContext,它可以被注入任何需要手動執行任何分辨率的類。

更新

這裏是一個場景。我的控制器使用一個服務,它的初始化取決於在控制器方法中傳遞的輸入參數,因此在構建時不能實例化依賴項。

據我所知,這是DI的一種反模式,但它是在少數地方的要求,因此注入DI容器是次佳選擇。簡單的注射器樣本應該使用靜態變量來共享我想避免的容器,也不可能按照SimpleInjectorInitializer的方式工作。

+0

您可以注入容器,但不應該。在這種情況下,您正在濫用容器作爲[服務定位器](http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)。你爲什麼需要該控制器中的容器? – Steven

+0

添加了一個需要支持的場景 – Chandermani

回答

28

除了作爲應用程序啓動路徑一部分的任何代碼之外,代碼不應直接依賴容器(或容器抽象,容器外觀等)。這種模式被稱爲Service LocatorMark Seemann有一個good explanation爲什麼這是一個壞主意。

因此,組件(如控制器)不應該直接依賴容器,因爲這隱藏了使用的依賴關係,使得類難以測試。此外,您的代碼開始依賴於外部框架(難以改變),或者取決於它不需要了解的抽象。

我的控制器使用誰的初始化取決於控制器方法傳遞的輸入 參數和服務,因此依賴 不能在施工時間

有這個問題的一般模式進行實例化:在abstract factory design pattern。工廠模式允許您延遲創建類型,並允許您傳入額外的運行時參數以構建特定類型。當你這樣做時,你的控制器不需要依賴Container,它可以防止你在你的單元測試中傳入一個構建好的容器(DI框架通常不應該用在你的單元測試項目中)。

但請注意,讓您的組件需要runtime data during creation is a code smell。防止這樣做。

您可能認爲通過這樣做,我們只是將問題轉移到工廠實施。雖然我們將容器的依賴關係轉移到工廠實現中,但實際上我們正在解決這個問題,因爲工廠實現將成爲應用程序的一部分Composition Root,它允許應用程序代碼本身無視任何DI框架。

所以這就是我建議您構造代碼:

// Definition of the factory in the UI or BL layer 
public interface ISomeServiceFactory 
{ 
    ISomeService Create(int inputParameter); 
} 

// Controller depending on that factory: 
public class MyController : Controller 
{ 
    private readonly ISomeServiceFactory factory; 

    public MyController(ISomeServiceFactory factory) 
    { 
     this.factory = factory; 
    } 

    public ActionResult Index(int value) 
    { 
     // here we use that factory 
     var service = this.factory.Create(value); 
    } 
} 

在你的作文根(的啓動路徑),我們爲它定義的工廠實現和註冊:

private class SomeServiceFactory : ISomeServiceFactory 
{ 
    private readonly Container container; 

    // Here we depend on Container, which is fine, since 
    // we're inside the composition root. The rest of the 
    // application knows nothing about a DI framework. 
    public SomeServiceFactory(Container container) 
    { 
     this.container = container; 
    } 

    public ISomeService Create(int inputParameter) 
    { 
     // Do what ever we need to do here. For instance: 
     if (inputParameter == 0) 
      return this.container.GetInstance<Service1>(); 
     else 
      return this.container.GetInstance<Service2>(); 
    } 
} 

public static void Initialize() 
{ 
    var container = new Container(); 

    container.RegisterSingle<ISomeServiceFactory, SomeServiceFactory>(); 
} 

創建後,Container會自行註冊(使用呼叫RegisterSingle<Container>(this)),以便始終將容器注入任何組件。這與使用Autofac時注入IComponentContext類似。但是對於Autofac,Simple Injector和其他任何容器也是如此:您不希望將容器注入位於組合根之外的組件(並且幾乎沒有任何理由)。

+1

謝謝史蒂夫,是的,我明白這個部分,並且採用了與你在這裏解釋的相同的模式。但是,由於Factory本身需要容器,我使用AutoFac'IComponentContext'。我很困惑如何在SimpleInjector中做同樣的事情。 – Chandermani

+0

除了Steven的優秀答案,我們可以很容易地使用泛型來通過使用以下方法返回'ISomeService'(而不是使用if/else進行硬編碼)的具體實現:'public T Create ()where T:ISomeService {return T)this.container.GetInstance(typeof運算(T)); }'。然後使用:'var service = this.factory.Create ();'。您還需要使用以下命令更新'ISomeServiceFactory'接口:'T Create ()where T:ISomeService;'。 – GFoley83

相關問題