Введення залежності з класами, відмінними від класу Controller


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
    }
}

Проблема, яку я маю відстежувати, - це практично все, що говорить про DI, говорить про це на рівні контролера. Я намагався шукати, де це відбувається, у 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.
H. Tugkan Kibar

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 ядра 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
        }
    }
}

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 (), щоб визначити назву збірки, в якій розміщений ваш клас. Це ім'я використовується у 3-му параметрі функції обгортки.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.