Unity 与 ASP.NET 核心和 MVC6(核心)

Unity with ASP.NET Core and MVC6 (Core)

更新 09.08.2018
Unity 正在开发 here,但我还没有时间测试它如何与 ASP.NET 核心框架一起使用。

更新 15.03.2018
此解决方案针对在使用 .NET Framework 4.5.2 NOT .NET Core Framework 时将 ASP.NET Core v1 与 Unity 结合使用的特定问题。我不得不使用这个设置,因为我需要一些 .Net 4.5.2 DLL,但对于任何重新开始的人,我不推荐这种方法。此外,Unity 没有进一步开发(据我所知),因此我建议对新项目使用 Autofac 框架。有关如何执行此操作的更多信息,请参阅此 Post

简介
我正在构建一个使用 ASP.NET 和 MVC 的 Web 应用程序。此应用程序依赖于某些服务(WCF 服务、数据存储服务等)。现在为了保持良好和解耦,我想使用 DI(依赖注入)框架,特别是 Unity。

初步研究
我发现了这个 blog post 但遗憾的是它不起作用。这个想法虽然很好。
它基本上是说你不应该将 ServiceCollection 中注册的所有服务注册到你自己的容器中,而是引用默认的 ServiceProvider。
所以。如果需要解决某些问题,则会调用默认的 ServiceProvider,如果没有解决方案,将使用您的自定义 UnityContainer 解决类型。

问题
MVC 始终尝试使用默认的 ServiceProvider 来解析 Controller。
此外,我注意到即使控制器能够正确解析,我也永远无法“混合”依赖项。现在,如果我想使用我的服务之一,但也想使用来自 ASP 的 IOptions 接口,则 class 永远无法解析,因为这两个容器中没有一个具有两种类型的分辨率。

我需要的
所以回顾一下,我需要以下东西:

编辑:
所以问题是我怎样才能达到这些要点?

环境
project.json:

经过一些研究,我想出了以下解决问题的方法:

将 Unity 与 ASP 结合使用
为了能够将 Unity 与 ASP 一起使用,我需要一个自定义的 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
}

我还必须在我的 Startup class 中更改 ConfigureServices 方法的签名:

public void ConfigureServices(IServiceCollection services)

对此:

public IServiceProvider ConfigureServices(IServiceCollection services)

现在我可以 return 我的自定义 IServiceProvider,它将被用来代替默认的 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
}

现在,如果控制器 class 被激活,它将通过我的 UnityContainer 实例化。因此,我的 UnityContainer 必须知道如何解析任何控制器!

下一个问题:使用默认的IServiceProvider
现在,如果我在 ASP.NET 中注册诸如 Mvc 之类的服务,我通常会这样做:

services.AddMvc();

现在,如果我使用 UnityContainer,则所有 MVC 依赖项都无法解析,因为它们未注册。所以我可以注册它们(比如 AutoFac)或者我可以创建一个 UnityContainerExtension。我选择了扩展并提出了以下两个类:
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 没有注册某些东西,它只需要向默认提供者询问。
我从几篇不同的文章中了解到所有这些

这种方法的好处是我现在还可以 "mix" 依赖项。如果我需要我的任何服务和来自 ASP 的 IOptions 接口,我的 UnityContainer 将解决所有这些依赖关系并将它们注入我的控制器!!!
唯一要记住的是,如果我使用我自己的任何依赖项,我必须向 Unity 注册我的控制器 class,因为默认的 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;
    }

由于我在过去一周了解了我对 DI 的大部分了解,我希望我没有破坏任何大的东西Pricipal/Pattern如果是的话请告诉我!

对于 ASP.Net Core 2.0、2.1、2.2、3.1 和 Unity,Unity 作者提供了作为 NuGet 包的官方解决方案:NuGetPackage

这里是 Git 示例存储库:Git repo

用法很简单(来自Git repo主页):

public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
       .UseUnityServiceProvider()   <---- Add this line
       .UseStartup<Startup>()
       .Build();

并且 here 是 ASP.Net Core 的 Unity DI 示例。

我在我的 ASP.Net 核心应用程序中使用此解决方案并且运行良好。