Unity с ASP.NET Core и MVC6 (Core)

Обновление 09.08.2018
Unity разрабатывается здесь, но у меня не было времени, чтобы проверить, как он работает с платформой ASP.NET Core.

Обновление 15.03.2018
Это решение предназначено для конкретной проблемы использования ASP.NET Core v1 с Unity при использовании .NET Framework 4.5.2, а не.NET Core Framework. Мне пришлось использовать эту настройку, так как мне нужно было несколько DLL-библиотек .Net 4.5.2, но для всех, кто начинал заново, я бы не рекомендовал такой подход. Также Unity больше не разрабатывается (насколько мне известно), поэтому я бы рекомендовал использовать Autofac Framework для новых проектов. Смотрите этот пост для получения дополнительной информации о том, как это сделать.

вступление
Я строю веб-приложение с использованием ASP.NET с MVC. Это приложение зависит от определенных служб (служба WCF, служба хранилища данных и т.д.). Теперь, чтобы все было хорошо и развязано, я хочу использовать DI (Dependecy Injection Framework), в частности Unity.

Начальное исследование
Я нашел этот пост, но, к сожалению, он не работает. Хотя идея хороша.
В основном это говорит о том, что вы не должны регистрировать все сервисы, зарегистрированные в ServiceCollection, в свой собственный контейнер, а должны ссылаться на ServiceProvider по умолчанию.
Так. если что-то должно быть разрешено, вызывается ServiceProvider по умолчанию, и если у него нет разрешения, тип будет разрешен с использованием вашего пользовательского UnityContainer.

Проблемы
MVC всегда пытается разрешить контроллер с помощью ServiceProvider по умолчанию.
Кроме того, я заметил, что даже если контроллер будет разрешен правильно, я никогда не смогу "смешать" зависимости. Теперь, если я хочу использовать один из моих сервисов, а также интерфейс IOptions из ASP, класс никогда не может быть разрешен, потому что ни один из этих двух контейнеров не имеет разрешения для обоих типов.

Что мне нужно
Итак, подведем итог: мне нужны следующие вещи:

  • Настройка, в которой мне не нужно копировать зависимости ASP.NET в мой UnityContainer.
  • Контейнер, который может разрешить мои контроллеры MVC
  • Контейнер, который может разрешать "смешанные" зависимости

РЕДАКТИРОВАТЬ:
Итак, вопрос в том, как я могу достичь этих очков?

Среда
project.json:
enter image description here

Ответ 1

Итак, после некоторых исследований я придумал следующие решения моих проблем:

Использовать Unity с ASP
Чтобы использовать Unity с ASP, мне нужен пользовательский IServiceProvider (Документация по ASP), поэтому я написал оболочку для 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 в моем классе Startup:

public void ConfigureServices(IServiceCollection services)

:

public IServiceProvider ConfigureServices(IServiceCollection services)

Теперь я могу вернуть свой пользовательский IServiceProvider, и он будет использоваться вместо стандартного.
Полный метод ConfigureServices показан в разделе "Провод" внизу.

Решающие контроллеры
Я нашел этот пост в блоге. Из него я узнал, что 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
}

Теперь, если класс Controller активирован, он будет установлен с моим UnityContainer. Поэтому мой UnityContainer должен знать, как разрешить любой контроллер!

Следующая проблема: используйте стандартный IServiceProvider
Теперь, если я регистрирую службы, такие как Mvc в ASP.NET, я бы сделал это следующим образом:

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 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 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 не имеет регистрации для чего-то, он просто запрашивает у него поставщика по умолчанию.
Я узнал об этом из нескольких разных статей.

Приятная вещь в этом подходе заключается в том, что теперь я могу "смешать" зависимости. Если мне нужен какой-либо из моих сервисов и интерфейс IOptions из ASP, мой UnityContainer разрешит все эти зависимости и внедрит их в мой контроллер!!! Единственное, что нужно помнить, это то, что, если я использую какие-либо из моих собственных зависимостей, мне нужно зарегистрировать мой класс Controller с Unity, потому что по умолчанию IServiceProvider больше не может разрешать зависимости моих контроллеров.

Наконец: Подключите
В моем проекте я использую разные службы (ASP Options, 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, если так, пожалуйста, скажите мне!

Ответ 2

Для ASP.Net Core 2.0, 2.1, 2.2 и Unity есть официальное решение, доступное от авторов Unity в виде пакета NuGet здесь: NuGetPackage

Вот Git-репозиторий с образцами: Git-репо

Использование очень просто (с домашней страницы репозитория Git):

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

А вот пример с Unity DI для ASP.Net Core.

Я использую это решение в моем приложении ASP.Net Core и работает хорошо.