Инъекция зависимостей с классами, отличными от класса контроллера
На этом этапе я легко вставляю вещи в свои контроллеры, в некоторых случаях создаю свой собственный класс 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
.
Ваша помощь приветствуется.
Ответы
Ответ 1
Ниже приведен рабочий пример использования 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("[email protected]", 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("[email protected]", "2 Widgets");
System.Console.Write("Press any key to end.");
System.Console.ReadLine();
}
}
}
Ответ 2
Допустим, 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>();
Я бы рекомендовал прочитать специальную документацию о платформе DI ASP.Net 5 и о внедрении зависимостей в целом.
Ответ 3
К сожалению, прямого пути нет. Единственный способ, которым мне удалось заставить его работать, - создать статический класс и использовать его везде, как показано ниже:
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, основанный на текущей документации DI.NET Core 2.2 здесь. Добавление этого ответа может помочь новичку в .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
}
}
}