2016-08-26 262 views
11

介紹
我正在使用ASP.NET與MVC構建一個Web應用程序。此應用程序依賴於某些服務(WCF服務和數據存儲服務等)。現在爲了讓事情保持良好和解耦,我想使用DI(依賴注入)框架,特別是Unity。

初步研究
我發現這個blog post但可惜它不工作。這個想法雖然很好。
它基本上說,你不應該註冊在ServiceCollection中註冊的所有服務到你自己的容器中,而是引用默認的ServiceProvider。
所以。如果需要解決某些問題,則調用默認的ServiceProvider,如果沒有解析,則使用您的自定義UnityContainer解析該類型。

的問題
MVC總是試圖解決與默認的ServiceProvider控制器。
另外,我注意到,即使控制器能夠正確解析,我也不能「混合」依賴關係。現在,如果我想使用我的服務之一,而且還要使用ASP中的IOptions接口,則該類永遠不會解析,因爲這兩個容器中沒有一個容器具有兩種類型的解決方案。

我需要什麼
所以開始我需要以下幾件事:與ASP.NET核心和MVC6(核心)的統一

  • 在那裏我不需要ASP.NET依賴複製到我的UnityContainer
  • 的容器可以解決安裝我的MVC控制器
  • 可以解決「混合」依賴的容器

編輯:
所以問題是我該如何實現這些要點?

環境
project.json:
enter image description here

+0

不是真的問題 – RollRoll

+0

現在好了嗎?如果不是,我應該如何重述它? –

回答

29

所以經過一番研究,我想出了以下解決方案,以我的問題:

使用統一與ASP
爲了能夠與ASP一起使用Unity我需要一個定製的IServiceProvider(ASP Documentation),所以我爲IUnityContainer寫了一個包裝,看起來像這樣

public class UnityServiceProvider : IServiceProvider 
{ 
    private IUnityContainer _container; 

    public IUnityContainer UnityContainer => _container; 

    public UnityServiceProvider() 
    { 
     _container = new UnityContainer(); 
    } 

    #region Implementation of IServiceProvider 

    /// <summary>Gets the service object of the specified type.</summary> 
    /// <returns>A service object of type <paramref name="serviceType" />.-or- null if there is no service object of type <paramref name="serviceType" />.</returns> 
    /// <param name="serviceType">An object that specifies the type of service object to get. </param> 
    public object GetService(Type serviceType) 
    { 
     //Delegates the GetService to the Containers Resolve method 
     return _container.Resolve(serviceType); 
    } 

    #endregion 
} 

此外,我不得不從這個在我的啓動類改變ConfigureServices方法的簽名:

public void ConfigureServices(IServiceCollection services) 

這樣:

public IServiceProvider ConfigureServices(IServiceCollection services) 

現在我可以回到我的自定義的IServiceProvider和它將被使用,而不是默認的。
完整的ConfigureServices方法顯示在底部的接線部分。

解析控制器
我找到了this blog post。從中我瞭解到,MVC使用IControllerActivator接口來處理Controller實例化。所以我寫了我自己的,看起來像這樣:

public class UnityControllerActivator : IControllerActivator 
{ 
    private IUnityContainer _unityContainer; 

    public UnityControllerActivator(IUnityContainer container) 
    { 
     _unityContainer = container; 
    } 

    #region Implementation of IControllerActivator 

    public object Create(ControllerContext context) 
    { 
     return _unityContainer.Resolve(context.ActionDescriptor.ControllerTypeInfo.AsType()); 
    } 


    public void Release(ControllerContext context, object controller) 
    { 
     //ignored 
    } 

    #endregion 
} 

現在,如果一個控制器類被激活,它會與我UnityContainer被instatiated。因此,我的UnityContainer必須知道如何解決任何控制器!

下一個問題:使用默認的IServiceProvider
現在,如果我註冊服務,例如在的mvc ASP.NET我通常會做這樣的:如果我使用一個UnityContainer所有MVC

services.AddMvc(); 

現在無法解決依賴關係,因爲它們沒有註冊。所以我可以註冊它們(比如AutoFac)或者我可以創建一個UnityContainerExtension。我選擇了擴展,並與以下兩個clases上來:
UnityFallbackProviderExtension

public class UnityFallbackProviderExtension : UnityContainerExtension 
{ 
    #region Const 

    ///Used for Resolving the Default Container inside the UnityFallbackProviderStrategy class 
    public const string FALLBACK_PROVIDER_NAME = "UnityFallbackProvider"; 

    #endregion 

    #region Vars 

    // The default Service Provider so I can Register it to the IUnityContainer 
    private IServiceProvider _defaultServiceProvider; 

    #endregion 

    #region Constructors 

    /// <summary> 
    /// Creates a new instance of the UnityFallbackProviderExtension class 
    /// </summary> 
    /// <param name="defaultServiceProvider">The default Provider used to fall back to</param> 
    public UnityFallbackProviderExtension(IServiceProvider defaultServiceProvider) 
    { 
     _defaultServiceProvider = defaultServiceProvider; 
    } 

    #endregion 

    #region Overrides of UnityContainerExtension 

    /// <summary> 
    /// Initializes the container with this extension's functionality. 
    /// </summary> 
    /// <remarks> 
    /// When overridden in a derived class, this method will modify the given 
    /// <see cref="T:Microsoft.Practices.Unity.ExtensionContext" /> by adding strategies, policies, etc. to 
    /// install it's functions into the container.</remarks> 
    protected override void Initialize() 
    { 
     // Register the default IServiceProvider with a name. 
     // Now the UnityFallbackProviderStrategy can Resolve the default Provider if needed 
     Context.Container.RegisterInstance(FALLBACK_PROVIDER_NAME, _defaultServiceProvider); 

     // Create the UnityFallbackProviderStrategy with our UnityContainer 
     var strategy = new UnityFallbackProviderStrategy(Context.Container); 

     // Adding the UnityFallbackProviderStrategy to be executed with the PreCreation LifeCycleHook 
     // PreCreation because if it isnt registerd with the IUnityContainer there will be an Exception 
     // Now if the IUnityContainer "magically" gets a Instance of a Type it will accept it and move on 
     Context.Strategies.Add(strategy, UnityBuildStage.PreCreation); 
    } 

    #endregion 
} 


UnityFallbackProviderStrategy

public class UnityFallbackProviderStrategy : BuilderStrategy 
{ 
    private IUnityContainer _container; 

    public UnityFallbackProviderStrategy(IUnityContainer container) 
    { 
     _container = container; 
    } 

    #region Overrides of BuilderStrategy 

    /// <summary> 
    /// Called during the chain of responsibility for a build operation. The 
    /// PreBuildUp method is called when the chain is being executed in the 
    /// forward direction. 
    /// </summary> 
    /// <param name="context">Context of the build operation.</param> 
    public override void PreBuildUp(IBuilderContext context) 
    { 
     NamedTypeBuildKey key = context.OriginalBuildKey; 

     // Checking if the Type we are resolving is registered with the Container 
     if (!_container.IsRegistered(key.Type)) 
     { 
      // If not we first get our default IServiceProvider and then try to resolve the type with it 
      // Then we save the Type in the Existing Property of IBuilderContext to tell Unity 
      // that it doesnt need to resolve the Type 
      context.Existing = _container.Resolve<IServiceProvider>(UnityFallbackProviderExtension.FALLBACK_PROVIDER_NAME).GetService(key.Type); 
     } 

     // Otherwise we do the default stuff 
     base.PreBuildUp(context); 
    } 

    #endregion 
} 

現在,如果我UnityContainer沒有註冊的東西,它只是要求默認的提供者。
我瞭解到這一切從幾個不同的文章

這種方法的好處是,我也可以 「混合」 的依賴了。如果我需要我的任何服務和一個來自ASP的IOptions接口,我的UnityContainer將解決所有這些依賴關係,並將它們注入到我的控制器中!
唯一要記住的是,如果我使用我自己的任何依賴項,我必須在Unity中註冊我的Controller類,因爲默認的IServiceProvider不能再解析我的控制器依賴項。

終於:連線
現在我在我的項目中使用不同的服務(ASP選項,帶選項的MVC)。爲了使這一切工作,我的ConfigureServices方法看起來像現在這樣:

public IServiceProvider ConfigureServices(IServiceCollection services) 
    { 
     // Add all the ASP services here 
     // #region ASP 
     services.AddOptions(); 
     services.Configure<WcfOptions>(Configuration.GetSection("wcfOptions")); 

     var globalAuthFilter = new AuthorizationPolicyBuilder() 
      .RequireAuthenticatedUser() 
      .Build(); 

     services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(globalAuthFilter)); }) 
       .AddJsonOptions 
      (
       options => options.SerializerSettings.ContractResolver = new DefaultContractResolver() 
      ); 
     // #endregion ASP 

     // Creating the UnityServiceProvider 
     var unityServiceProvider = new UnityServiceProvider(); 

     IUnityContainer container = unityServiceProvider.UnityContainer; 

     // Adding the Controller Activator 
     // Caution!!! Do this before you Build the ServiceProvider !!! 
     services.AddSingleton<IControllerActivator>(new UnityControllerActivator(container)); 

     //Now build the Service Provider 
     var defaultProvider = services.BuildServiceProvider(); 

     // Configure UnityContainer 
     // #region Unity 

     //Add the Fallback extension with the default provider 
     container.AddExtension(new UnityFallbackProviderExtension(defaultProvider)); 

     // Register custom Types here 

     container.RegisterType<ITest, Test>(); 

     container.RegisterType<HomeController>(); 
     container.RegisterType<AuthController>(); 

     // #endregion Unity 

     return unityServiceProvider; 
    } 

因爲我學到最多的就是我在過去的一週,我希望我沒有違反任何大Pricipal /模式如果是的話,請告訴我知道DI!

+0

不錯,想把它轉換成博客文章。如果你允許。 – Neel

+0

當然。獲得的人越多,可以做的事情就越多:D –

+2

非常感謝這個解決方案。節省了我的時間! –