Внедрение зависимостей с классами, отличными от класса контроллера

84

На этом этапе я с легкостью встраиваю элементы в свои контроллеры, в некоторых случаях создавая собственный класс ResolverServices. Жизнь хороша .

Что я не могу понять, так это заставить фреймворк автоматически внедряться в неконтроллерные классы. Что действительно работает, так это то, что фреймворк автоматически вводится в мой контроллер IOptions, что фактически является конфигурацией для моего проекта:

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;

    public MessageCenterController(IOptions<MyOptions> options)
    {
        _options = options.Value;
    }
}

Я думаю, смогу ли я сделать то же самое для моих собственных классов. Я предполагаю, что я близок, когда имитирую контроллер, например:

public class MyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    }

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

Я думаю, что ошибаюсь, когда называю это так:

public void DoSomething()
{
    var helper = new MyHelper(??????);

    if (helper.CheckIt())
    {
        // Do Something
    }
}

Проблема, которую я отслеживаю, - это практически все, что говорит о D, я говорю об этом на уровне контроллера. Я пытался выследить, где это происходит в Controllerисходном коде объекта, но там это становится немного сумасшедшим.

Я знаю, что могу вручную создать экземпляр IOptions и передать его MyHelperконструктору, но похоже, что я смогу заставить фреймворк сделать это, поскольку он работает для Controllers.

Роберт Полсен
источник
8
Когда вы используете инъекцию зависимостей, вы не вызываете new. Никогда для объектов, которые необходимо разрешить
Ценг
1
Когда я пытаюсь создать экземпляр MyHelper, я не вызываю новый? (1) Звучит слишком просто. (2) Это синтаксическая ошибка. :-)
Роберт Полсен
2
Да, в этом весь смысл внедрения зависимостей (особенно при использовании инверсии управляющих контейнеров, которые управляют этим экземпляром и выполняют его). Чтобы подтолкнуть моментальную копию за пределы ваших служб / классов до точки, в которой контейнер ioc делает это внутри. В тех случаях, когда вы не можете внедрить его через конструктор, вы создаете фабрику и передаете интерфейс фабрики своей службе. его реализация использует контейнер для его решения, в случае ASP.NET Core, внедряя IServiceProviderв вашу фабрику и вызываяIMyHelper helper = services.RequestService<IMyHelper>()
Ценг

Ответы:

42

Ниже приведен рабочий пример использования DI без чего-либо, что связано с контроллерами MVC. Это то, что мне нужно было сделать, чтобы понять процесс, так что, возможно, это поможет кому-то другому.

Объект ShoppingCart получает через DI экземпляр INotifier (который уведомляет покупателя об их заказе).

using Microsoft.Extensions.DependencyInjection;
using System;

namespace DiSample
{
    // STEP 1: Define an interface.
    /// <summary>
    /// Defines how a user is notified. 
    /// </summary>
    public interface INotifier
    {
        void Send(string from, string to, string subject, string body);
    }

    // STEP 2: Implement the interface
    /// <summary>
    /// Implementation of INotifier that notifies users by email.
    /// </summary>
    public class EmailNotifier : INotifier
    {
        public void Send(string from, string to, string subject, string body)
        {
            // TODO: Connect to something that will send an email.
        }
    }

    // STEP 3: Create a class that requires an implementation of the interface.
    public class ShoppingCart
    {
        INotifier _notifier;

        public ShoppingCart(INotifier notifier)
        {
            _notifier = notifier;
        }

        public void PlaceOrder(string customerEmail, string orderInfo)
        {
            _notifier.Send("admin@store.com", customerEmail, $"Order Placed", $"Thank you for your order of {orderInfo}");
        }

    }

    public class Program
    {
        // STEP 4: Create console app to setup DI
        static void Main(string[] args)
        {
            // create service collection
            var serviceCollection = new ServiceCollection();

            // ConfigureServices(serviceCollection)
            serviceCollection.AddTransient<INotifier, EmailNotifier>();

            // create service provider
            var serviceProvider = serviceCollection.BuildServiceProvider();

            // This is where DI magic happens:
            var myCart = ActivatorUtilities.CreateInstance<ShoppingCart>(serviceProvider);

            myCart.PlaceOrder("customer@home.com", "2 Widgets");

            System.Console.Write("Press any key to end.");
            System.Console.ReadLine();
        }
    }
}
Роберт Полсен
источник
17
Что, если я хочу создать экземпляр ShoppingCartв другом классе или методе, к которому у нас нет доступа к serviceProviderобъекту?
Багерани
1
с такой же проблемой здесь
Кейси
4
Спасибо, мне пришлось искать слишком широко, чтобы добраться до ActivatorUtilities.CreateInstance.
Х. Тугкан Кибар
1
что делать, если у меня нет serviceProvider ?? !!
HelloWorld
1
Спасибо! Я использую его с Microsoft.AspNetCore.TestHost, создаю TestServer и вызываю ActivatorUtilities.CreateInstance <MyCustomerController> (_server.Host.Services);
Толга
35

Скажем, MyHelperиспользуется, MyServiceкоторый, в свою очередь, используется вашим контроллером.

Способ разрешения этой ситуации:

  • Зарегистрируйте оба MyServiceи MyHelperв Startup.ConfigureServices.

    services.AddTransient<MyService>();
    services.AddTransient<MyHelper>();
    
  • Контроллер получает экземпляр MyServiceв своем конструкторе.

    public HomeController(MyService service) { ... }
    
  • MyServiceконструктор, в свою очередь, получит экземпляр MyHelper.

    public MyService(MyHelper helper) { ... }
    

Фреймворк DI сможет без проблем разрешить весь граф объектов. Если вас беспокоит создание новых экземпляров каждый раз при разрешении объекта, вы можете прочитать о различных вариантах времени жизни и регистрации, таких как время жизни синглтона или запроса.

Вы должны быть действительно подозрительными, если думаете, что вам нужно вручную создать экземпляр какой-либо службы, поскольку вы можете оказаться в анти-шаблоне локатора служб . Лучше оставить создание объектов в контейнере DI. Если вы действительно оказались в этой ситуации (скажем, вы создаете абстрактную фабрику), вы можете использовать IServiceProviderнапрямую (либо запросите IServiceProviderв своем конструкторе, либо используйте тот, который представлен в httpContext ).

var foo = serviceProvider.GetRequiredService<MyHelper>();

Я бы порекомендовал прочитать конкретную документацию о платформе ASP.Net 5 DI и о внедрении зависимостей в целом.

Дэниел Дж.
источник
Проблема, с которой я столкнулся, заключается в том, что я использую фоновые службы, которые взаимодействуют с базой данных, поэтому время жизни с заданной областью действия с dbcontext не работает, верно? Как правильно использовать DI с основными и фоновыми службами EF?
6

К сожалению, прямого пути нет. Единственный способ заставить его работать - это создать статический класс и использовать его везде, как показано ниже:

public static class SiteUtils
{

 public static string AppName { get; set; }

    public static string strConnection { get; set; }

}

Затем в своем классе запуска заполните его, как показано ниже:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    //normal as detauls , removed for space 
    // set my variables all over the site

    SiteUtils.strConnection = Configuration.GetConnectionString("DefaultConnection");
    SiteUtils.AppName = Configuration.GetValue<string>("AppName");
}

Хотя это плохой шаблон, так как он будет оставаться на протяжении всего жизненного цикла приложения, и я не мог найти лучшего способа использовать его вне контроллера.

Али
источник
4

Вот более полный пример прямого ответа на вопрос OP, основанный на текущей документации .NET Core 2.2 DI здесь . Добавляем этот ответ, поскольку он может помочь кому-то, кто плохо знаком с .NET Core DI, и потому, что этот вопрос является лучшим результатом поиска Google.

Сначала добавьте интерфейс для MyHelper:

public interface IMyHelper
{
    bool CheckIt();
}

Во-вторых, обновите класс MyHelper, чтобы реализовать интерфейс (в Visual Studio нажмите ctrl-., Чтобы реализовать интерфейс):

public class MyHelper : IMyHelper
{
    private readonly ProfileOptions _options;

    public MyHelper(IOptions<ProfileOptions> options)
    {
        _options = options.Value;
    {

    public bool CheckIt()
    {
        return _options.SomeBoolValue;
    }
}

В-третьих, зарегистрируйте интерфейс как сервис, предоставляемый платформой, в сервисном контейнере DI. Сделайте это, зарегистрировав службу IMyHelper с конкретным типом MyHelper в методе ConfigureServices в Startup.cs.

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddScoped<IMyHelper, MyHelper>();
    ...
}

В-четвертых, создайте частную переменную для ссылки на экземпляр службы. Передайте службу в качестве аргумента в конструкторе (через внедрение конструктора), затем инициализируйте переменную экземпляром службы. Ссылка на любые свойства или методы вызова этого экземпляра настраиваемого класса через частную переменную.

public class MessageCenterController : Controller
{
    private readonly MyOptions _options;
    private readonly IMyHelper _myHelper;

    public MessageCenterController(
        IOptions<MyOptions> options,
        IMyHelper myHelper
    )
    {
        _options = options.value;
        _myHelper = myHelper;
    }

    public void DoSomething()
    {
        if (_myHelper.CheckIt())
        {
            // Do Something
        }
    }
}
sfors говорит восстановить Монику
источник
0

Вы можете использовать Activator.CreateInstance (). Вот функция-оболочка для него. Вы используете это следующим образом.

var determinedProgrammatically = "My.NameSpace.DemoClass1"; // implements IDemo interface
var obj = CreateInstance<My.NameSpace.IDemo, string>(determinedProgrammatically, "This goes into the parameter of the constructor.", "Omit this parameter if your class lives in the current assembly");

Теперь у вас есть экземпляр obj, который создается программно определенного типа. Этот объект может быть введен в классы неконтроллера.

public TInterface CreateInstance<TInterface, TParameter>(string typeName, TParameter constructorParam, string dllName = null)
{
    var type = dllName == null ? System.Type.GetType(typeName) :
            System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName.StartsWith(dllName, System.StringComparison.OrdinalIgnoreCase)).GetType(typeName);
    return (TInterface)System.Activator.CreateInstance(type, constructorParam);

}

PS: Вы можете выполнить итерацию через System.AppDomain.CurrentDomain.GetAssemblies (), чтобы определить имя сборки, в которой находится ваш класс. Это имя используется в третьем параметре функции-оболочки.

Денни Джейкоб
источник