Внедрение простых компонентов инжектора в IHostedService с помощью ASP.NET Core 2.0
В ASP.NET Core 2.0 есть способ добавить фоновые задачи путем реализации интерфейса IHostedService
(см. Https://docs.microsoft.com/en-us/aspnet/core/fundamentals/hosted-services?view=aspnetcore- 2.0). Следуя этому руководству, я смог заставить его работать, зарегистрировав его в контейнере ASP.NET Core. Моя цель - читать сообщения из очереди и обрабатывать задания в фоновом режиме; сообщение отправляется в очередь (через действие контроллера), а затем обрабатывается в фоновом режиме через определенный интервал времени.
// Not registered in SimpleInjector
services.AddSingleton<IHostedService, MyTimedService>();
Когда я помещаю эту регистрацию в контейнер ASP.NET Core, она автоматически запускает процесс при запуске приложения. Однако, когда я регистрирую это в SimpleInjector, сервис не запускается автоматически. Я полагаю, что это так, потому что мы регистрируем контейнер SimpleInjector только с MvcControllers и MvcViewComponents:
// Wire up simple injector to the MVC components
container.RegisterMvcControllers(app);
container.RegisterMvcViewComponents(app);
Проблема, с которой я сталкиваюсь, заключается в том, что я хочу начать внедрение компонентов реестра из SimpleInjector (например, репозитории, универсальные обработчики с декораторами...) в реализацию IHostedService, как показано ниже:
public class TimedService : IHostedService, IDisposable
{
private IJobRepository _repo;
private Timer _timer;
public TimedService(IJobRepository repo)
{
this._repo = repo;
}
...
...
...
}
Так как IHostedService
зарегистрирован в ASP.NET Core, а не в Simple Injector, я получаю следующую ошибку при запуске синхронизированной фоновой службы:
Необработанное исключение: System.InvalidOperationException: невозможно разрешить службу для типа "Optimization.Core.Interfaces.IJobRepository" при попытке активировать "Optimization.API.BackgroundServices.TimedService".
Итак, мой вопрос: как лучше всего реализовать фоновые задачи в Simple Injector? Требует ли это отдельного пакета интеграции, чем стандартная интеграция MVC? Как я могу ввести свои регистрации Simple Injector в IHostedService
? Если бы мы могли автоматически запустить службу после регистрации в Simple Injector, я думаю, что это решило бы эту проблему.
Спасибо за любые ссылки здесь и за любые советы на эту тему! Я мог бы сделать что-то не так. Мне очень понравилось использовать Simple Injector в прошлом году.
Ответы
Ответ 1
Есть несколько способов приблизиться к этому. Самый простой способ, вероятно, состоит в том, чтобы перекрестным образом подключить размещенный сервис таким образом, чтобы встроенная система конфигурации разрешала размещенный сервис из Simple Injector:
// Register in Simple Injector as Singleton
container.RegisterSingleton<THostedService>();
// Cross-wire TimedService in the built-in configuration system
services.AddSingleton<IHostedService>(
c => container.GetInstance<TimedService>());
Обратите внимание, что размещенные сервисы разрешаются только один раз и кешируются навсегда, что делает их одиночными. Вот почему вы должны зарегистрировать его в Simple Injector как Singleton.
Следствием этого, однако, является то, что вы не сможете внедрить какие-либо зависимости Scoped
или Transient
в вашу размещенную службу. Кроме того, он заставляет ваш компонент приложения (TimedService
) зависеть от абстракции ядра ASP.NET(IHostedService
). Это не идеально.
Поэтому мой предпочтительный подход состоит в том, чтобы вместо этого создать реализацию адаптера, которую вы регистрируете в системе конфигурации ASP.NET Core, которая перенаправляет вызовы Simple Injector, используя абстракцию для конкретного приложения для реализации вашего сервиса. Таким образом, вместо создания многих реализаций IHostedService
, вы определяете абстракцию, которая является специфической и идеальной для вашего приложения. Пусть эта абстракция IMyJob
.
IHostedService
адаптера IHostedService
может выглядеть следующим образом:
public class SimpleInjectorJobProcessorHostedService : IHostedService, IDisposable
{
private readonly Container container;
private Timer timer;
public SimpleInjectorJobProcessorHostedService(Container c) => this.container = c;
public Task StartAsync(CancellationToken cancellationToken)
{
this.timer = new Timer(this.DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
// Run operation in a scope
using (AsyncScopedLifestyle.BeginScope(this.container))
{
// Resolve the collection of IMyJob implementations
foreach (var service in this.container.GetAllInstances<IMyJob>())
{
service.DoWork();
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose() => this.timer?.Dispose();
}
Вы можете зарегистрировать его в ядре ASP.NET следующим образом:
services.AddSingleton<IHostedService>(
new SimpleInjectorJobProcessorHostedService(container));
Таким образом, фактические задания, которые вы запускаете, могут остаться незамеченными для ASP.NET Core и могут быть определены следующим образом:
public class CoolJob : IMyJob
{
private readonly IJobRepository repo;
public CoolJob(IJobRepository repo) => this.repo = repo;
public void DoWork() => ...
}
И все задания могут быть зарегистрированы в Simple Injector следующим образом:
// NOTE: Simple Injector v4.3 API
container.Collection.Register<IMyJob>(typeof(CoolJob).Assembly);
Ответ 2
Я бы подключился к методу ConfigureContainer HostBuilder и установил туда simpleinjectore следующим образом:
IHostBuilder()
.ConfigureContainer<ServiceCollection>((builder, services) =>
{
var container = new Container();
container.RegisterSingleton<IJobRepository, JobRepository>();
services.AddTransient<IHostedService, TimedService>();
})
.ConfigureServices((hostContext, services) =>
{
// Originally we would have done this
//services.AddHostedService<Service>();
})
.Build();
using (host)
{
await host.StartAsync();
await host.WaitForShutdownAsync();
}
Хотя вы действительно можете использовать свою реализацию IHostedService, я думаю, что она может скрыть то, что происходит. Я считаю, что начальную загрузку инфраструктуры следует проводить в одном месте или организовывать хотя бы в одном месте. Я считаю, что контейнер является инфраструктурой и настроил бы все это вместе с остальной частью приложения с помощью методов HostBuilder.
Дополнительным преимуществом также может быть то, что вы не полностью заменяете ServiceCollection, поскольку она хорошо работает с другими вещами, связанными с платформой. Пример некоторых вещей, которые я все еще делал бы с ServiceCollection:
IHostBuilder()
.ConfigureServices((hostContext, services) =>
{
services.AddLogging();
services.AddOptions();
})
Это соответствует тому, что указано в документах simpleinjector о настройке контейнера с помощью ASP.NET Core:
Практика с Простым Инжектором состоит в том, чтобы использовать Простой Инжектор для создания графов объектов ваших компонентов приложения и позволить встроенной структуре построения контейнера и сторонним компонентам. Практика с Простым Инжектором состоит в том, чтобы использовать Простой Инжектор для построения графов объектов. компоненты вашего приложения и пусть встроенный контейнер строит каркас и сторонние компоненты
То же самое должно применяться только к ядру .net и универсальному HostBuilder.