web-dev-qa-db-ja.com

ASP.NET Core2.0を使用してIHostedServiceに単純なインジェクターコンポーネントを挿入する

ASP.NET Core 2.0には、IHostedServiceインターフェイスを実装することでバックグラウンドタスクを追加する方法があります( https://docs.Microsoft.com/en-us/aspnet/core/fundamentalsを参照) /hosted-services?view=aspnetcore-2. )。このチュートリアルに従うことで、ASP.NETCoreコンテナーに登録することで機能させることができました。私の目標は、キューからメッセージを読み取り、バックグラウンドでジョブを処理することです。メッセージは(コントローラーアクションを介して)キューに投稿され、一定の間隔でバックグラウンドで処理されます。

// Not registered in SimpleInjector
services.AddSingleton<IHostedService, MyTimedService>(); 

この登録をASP.NETCoreコンテナーに入れると、アプリケーションの起動時にプロセスが自動的に開始されます。ただし、これを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はSimpleInjectorではなくASP.NETCoreに登録されているため、時限バックグラウンドサービスを実行すると、次のエラーが発生します。

未処理の例外:System.InvalidOperationException:「Optimization.API.BackgroundServices.TimedService」をアクティブ化しようとしているときに、タイプ「Optimization.Core.Interfaces.IJobRepository」のサービスを解決できません。

だから私の質問は、Simple Injectorでバックグラウンドタスクを実装するための最良の方法は何ですか?これには、標準のMVC統合とは別の統合パッケージが必要ですか? SimpleInjectorの登録をIHostedServiceに注入するにはどうすればよいですか? Simple Injectorに登録した後、自動的にサービスを開始できれば、この問題は解決すると思います。

ここでのポインタとこのトピックに関するアドバイスをありがとう!私は何か間違ったことをしている可能性があります。過去1年間、SimpleInjectorの使用を本当に楽しんでいます。

10

これにアプローチする方法は複数あります。最も簡単な方法は、組み込みの構成システムが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>());

ホストされたサービスは一度だけ解決され、永久にキャッシュされるため、事実上シングルトンになることに注意してください。これが、SimpleInjectorにシングルトンとして登録する必要がある理由です。

ただし、その結果、ホストされているサービスにScopedまたはTransientの依存関係を挿入することはできなくなります。さらに、アプリケーションコンポーネント(TimedService)がASP.NET Core抽象化(IHostedService)に依存するようにする必要があります。これは理想的ではありません。

したがって、私の推奨するアプローチは、代わりに、アプリケーション固有の抽象化を使用してサービスを実装しながら、呼び出しをSimpleInjectorに転送するASP.NETCore構成システムに登録するアダプター実装を作成することです。したがって、多くの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() => ...
}

また、すべてのジョブは次のようにSimpleInjectorに登録できます。

 // NOTE: Simple Injector v4.3 API
container.Collection.Register<IMyJob>(typeof(CoolJob).Assembly);
14
Steven

HostBuilderのConfigureContainerメソッドにフックして、次のように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の実装を使用することはできますが、実際に何が起こっているのかを隠すことができると思います。インフラストラクチャのブートストラップは、1つの場所で実行するか、少なくとも1つの場所で調整する必要があると思います。コンテナーはインフラストラクチャーであると考えており、HostBuilderメソッドを介してアプリの残りの部分ですべてをセットアップします。

追加の利点は、他のフレームワーク関連の処理でうまく機能するため、ServiceCollectionを完全に置き換えないことでもあります。私がまだServiceCollectionで行ういくつかの例:

                   IHostBuilder()
                   .ConfigureServices((hostContext, services) =>
                   {
                       services.AddLogging();
                       services.AddOptions();
                   })

これは、ASP.NETCoreを使用したコンテナーのセットアップに関するsimpleinjectorドキュメントに記載されている内容と一致しています。

Simple Injectorの実践は、Simple Injectorを使用してアプリケーションコンポーネントのオブジェクトグラフを構築し、組み込みコンテナーにフレームワークとサードパーティコンポーネントを構築させることです。SimpleInjectorの実践は、SimpleInjectorを使用してのオブジェクトグラフを構築することです。アプリケーションコンポーネントを作成し、組み込みコンテナにフレームワークとサードパーティコンポーネントを構築させます

同じことが.netコアと汎用HostBuilderだけにも当てはまります。

0