Внедрение компонентов простого инжектора в IHostedService с ASP.NET Core 2.0 - PullRequest
0 голосов
/ 17 мая 2018

В 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 в прошлом году.

Ответы [ 2 ]

0 голосов
/ 02 мая 2019

Я бы подключился к методу 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:

Практика использования Simple Injector - это использование Simple Injector для построения графов объектов компонентов вашего приложения и предоставление встроенной инфраструктуры построения контейнеров и сторонних компонентов. Практика использования Simple Injector заключается в использовании Simple Injector для созданиясоздайте графы объектов для компонентов вашего приложения и позвольте встроенной структуре сборки контейнера и сторонним компонентам

То же самое должно применяться только к ядру .net и универсальному HostBuilder.

0 голосов
/ 17 мая 2018

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

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);
...