ASP.NET Core DIを使用したインスタンスの解決


302

ASP.NET Core MVC組み込みの依存関係注入フレームワークを使用して、型を手動で解決するにはどうすればよいですか?

コンテナの設定は簡単です。

public void ConfigureServices(IServiceCollection services)
{
    // ...

    services.AddTransient<ISomeService, SomeConcreteService>();
}

しかしISomeService、注入を実行せずに解決するにはどうすればよいですか?たとえば、私はこれをしたいです:

ISomeService service = services.Resolve<ISomeService>();

にはそのようなメソッドはありませんIServiceCollection



3
ConfigureServices()メソッドで(IServiceCollection)を使用して解決しますか、それともアプリケーションのどこかで解決しますか?
Henk Mollema

2
@HenkMollema:実際にはスタートアップ内のどこでも。
デイブニュー

回答:


486

このIServiceCollectionインターフェースは、依存関係注入コンテナーを作成するために使用されます。完全にビルドされると、IServiceProviderサービスの解決に使用できるインスタンスに構成されます。IServiceProviderを任意のクラスに注入できます。IApplicationBuilderそしてHttpContextクラスはその経て、同様のサービスプロバイダを提供することができApplicationServicesたりRequestServices、それぞれの特性。

IServiceProviderGetService(Type type)サービスを解決するメソッドを定義します。

var service = (IFooService)serviceProvider.GetService(typeof(IFooService));

serviceProvider.GetService<IFooService>()(add a usingfor Microsoft.Extensions.DependencyInjection)などの便利な拡張メソッドもいくつかあります。

スタートアップクラス内のサービスの解決

依存関係の注入

ランタイムのホスティングサービスプロバイダは、のコンストラクタに特定のサービスを注入できるStartupよう、クラスIConfigurationIWebHostEnvironmentIHostingEnvironment前の3.0バージョンでは)、ILoggerFactoryおよびIServiceProvider。後者はホスティング層によって構築されたインスタンスであり、アプリケーションの起動に必要なサービスのみが含まれていることに注意してください。

このConfigureServices()メソッドはサービスの注入を許可していませんIServiceCollection。引数を受け入れるだけです。これはConfigureServices()、アプリケーションで必要なサービスを登録する場所だからです。ただし、ここではスタートアップのコンストラクターに注入されたサービスを使用できます。

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    // Use Configuration here
}

ConfigureServices()次に、登録されているサービスをConfigure()メソッドに注入できます。IApplicationBuilderパラメータの後に任意の数のサービスを追加できます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IFooService>();
}

public void Configure(IApplicationBuilder app, IFooService fooService)
{
    fooService.Bar();
}

依存関係を手動で解決する

サービスを手動で解決する必要がある場合は、メソッドでApplicationServices提供さIApplicationBuilderれているを使用することをお勧めしますConfigure()

public void Configure(IApplicationBuilder app)
{
    var serviceProvider = app.ApplicationServices;
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}

クラスIServiceProviderのコンストラクターでを渡して直接使用することは可能ですStartupが、上記のように、これにはサービスの限られたサブセットが含まれるため、ユーティリティが制限されます。

public Startup(IServiceProvider serviceProvider)
{
    var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}

ConfigureServices()メソッドでサービスを解決する必要がある場合は、別のアプローチが必要です。その時点までに登録さているサービスを含むインスタンスIServiceProviderから中間体を構築できIServiceCollectionます。

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFooService, FooService>();

    // Build the intermediate service provider
    var sp = services.BuildServiceProvider();

    // This will succeed.
    var fooService = sp.GetService<IFooService>();
    // This will fail (return null), as IBarService hasn't been registered yet.
    var barService = sp.GetService<IBarService>();
}

注:ConfigureServices()これは実際にはアプリケーションサービスを構成する場所である ため、通常、メソッド内のサービスの解決は避けてください。場合によっては、IOptions<MyOptions>インスタンスへのアクセスが必要なだけです。これを実現するには、IConfigurationインスタンスの値をインスタンスのインスタンスにバインドしますMyOptions(これは、基本的にオプションフレームワークが行うことです)。

public void ConfigureServices(IServiceCollection services)
{
    var myOptions = new MyOptions();
    Configuration.GetSection("SomeSection").Bind(myOptions);
}

手動でサービスを解決すること(別名Service Locator)は、一般的にアンチパターンと見なされます。(フレームワークやインフラストラクチャ層の)ユースケースはありますが、できる限り回避する必要があります。


14
@HenkMollemaしかし、何も注入できない場合IServiceCollection、つまり、手動で作成されているいくつかのクラス(ミドルウェアスコープ外)、私の場合はスケジューラ、生成するサービスが定期的に必要な場合メールを送信します。
Merdan Gochmuradov

52
サービスを解決する必要がありConfigureServices、そのサービスがシングルトンである場合は、Controller使用しているものとは異なるシングルトンになります。これは別のものを使用しているためだと思いますIServiceProviderを通じて解決しないこの問題を回避するために- BuildServiceProvider、代わりからシングルトンのあなたの検索を移動ConfigureServicesするConfigure(..other params, IServiceProvider serviceProvider)にはStartup.cs
WAL

3
@ウォルグッドポイント。これは別のIServiceProviderインスタンスなので、新しいシングルトンインスタンスを作成します。これは、ConfigureServicesメソッドからサービスプロバイダーインスタンスを返すことで回避できます。これにより、アプリケーションが使用するコンテナーにもなります。
Henk Mollema 2017年

1
呼び出しcollection.BuildServiceProvider();は私が必要としたものでした、ありがとう!
Chris Marisic

2
@HenkMollema 1つのサービスプロバイダーインスタンスでのみ機能させるにはどうすればよいですか。通常、1)依存関係の一部を登録します。2)暫定サービスプロバイダーインスタンスを構築します。3)そのサービスプロバイダーを使用して、他の依存関係を登録するために必要なものを解決します。その後、依存関係(3で登録済み)の一部が欠落しているため、中間インスタンスを返すことはできません。何か不足していますか?
フィリップ2018

109

インスタンスを手動で解決するには、IServiceProviderインターフェースを使用する必要があります。

Startup.ConfigureServicesの依存関係の解決

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();

    var serviceProvider = services.BuildServiceProvider();
    var service = serviceProvider.GetService<IMyService>();
}

Startup.Configureでの依存関係の解決

public void Configure(
    IApplicationBuilder application,
    IServiceProvider serviceProvider)
{
    // By type.
    var service1 = (MyService)serviceProvider.GetService(typeof(MyService));

    // Using extension method.
    var service2 = serviceProvider.GetService<MyService>();

    // ...
}

ASP.NET Core 3のStartup.Configureでの依存関係の解決

public void Configure(
    IApplicationBuilder application,
    IWebHostEnvironment webHostEnvironment)
{
    app.ApplicationServices.GetService<MyService>();
}

ランタイム注入サービスの使用

一部のタイプはメソッドパラメータとして挿入できます。

public class Startup
{
    public Startup(
        IHostingEnvironment hostingEnvironment,
        ILoggerFactory loggerFactory)
    {
    }

    public void ConfigureServices(
        IServiceCollection services)
    {
    }

    public void Configure(
        IApplicationBuilder application,
        IHostingEnvironment hostingEnvironment,
        IServiceProvider serviceProvider,
        ILoggerFactory loggerfactory,
        IApplicationLifetime applicationLifetime)
    {
    }
}

コントローラーアクションの依存関係の解決

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";

1
@AfsharMohebbi GetServiceジェネリックは、Microsoft.Extensions.DependencyInjection名前空間の拡張メソッドです。
ahmadali shafiee 2017年

拡張メソッドについて:拡張メソッドは、クラスに機能を追加する静的メソッドです。パブリック静的TheReturnType TheMethodName(this TheTypeYouExtend theTypeYouExtend {// BODY}を宣言して、次のように使用できます:TheTypeYouExtend.TheMethodName();開発者はここに...良い例を基本機能を拡張できるようにすることを、.NETのコアと非常に共通aproachになる:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...
フアン

17

テンプレートを使用してアプリケーションを生成すると、Startupクラスに次のようなものが作成されます。

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    services.AddMvc();
}

次に、依存関係を追加できます。次に例を示します。

services.AddTransient<ITestService, TestService>();

ITestServiceコントローラにアクセスしたい場合IServiceProviderは、コンストラクタを追加すると、注入されます。

public HomeController(IServiceProvider serviceProvider)

次に、追加したサービスを解決できます。

var service = serviceProvider.GetService<ITestService>();

ジェネリックバージョンを使用するには、拡張に名前空間を含める必要があることに注意してください。

using Microsoft.Extensions.DependencyInjection;

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs(ConfigureServices)

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(Configuration);
    services.AddMvc();

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(IServiceProvider serviceProvider)
        {
            var service = serviceProvider.GetService<ITestService>();
            int rnd = service.GenerateRandom();
        }

10

登録している別の依存関係のコンストラクターに渡すために、1つの依存関係を解決する必要がある場合は、これを行うことができます。

文字列を取り込むサービスとISomeServiceがあるとします。

public class AnotherService : IAnotherService
{
    public AnotherService(ISomeService someService, string serviceUrl)
    {
        ...
    }
}

Startup.cs内でこれを登録する場合は、次のようにする必要があります。

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);

OPは、ConfigureServiceメソッドでサービスを解決する必要がある理由を述べていませんが、これはおそらく誰かがそうしようと考える理由です
kilkfoe

1
実際、これは受け入れられる答えであるはずです... Henk Mollemaの答えは非常に例証的ですが、今日のあなたの答えはより明確で、中間のIServiceProvider(シングルトンの異なるインスタンス...)の構築に関連する問題を引き起こしません。おそらく、このソリューションは、ヘンクが回答した2015年には利用できませんでしたが、今ではそれが可能です。
Vi100

これを試しましたISomeServiceが、私にとってはまだ空でした。
ajbeaven

2つの質問:1)サービスクラスAnotherServiceのパラメーターコンストラクターが変更(サービスが削除または追加)された場合、サービスIAnotherServiceのレジスタセグメントを変更する必要がありますが、それは変化し続けますか?2)代わりに、パブリックAnotherService(IServiceProvider serviceProvider)のような1つのパラメーターを持つAnotherServiceのコンストラクターを1つだけ追加し、コンストラクターから必要なサービスを取得できます。そして、services.AddTransient <IAnotherService、AnotherService>(sp => {var service = new AnotherService(sp); return service;});のように、サービスクラスAnotherServiceをスタートアップクラスに登録するだけです。
Thomas.Benz

2

この方法でAuthorizeAttributeなどの属性に依存関係を注入できます

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));

これは私が探していたものです。ありがとう
Reyan Chougle

0

私はこれが古い質問であることを知っていますが、かなり明白で嫌なハッキングがここにないことに驚いています。

独自のctor関数を定義する機能を利用して、サービスを定義するときにサービスから必要な値を取得できます。明らかに、これを明示的に削除/クリアしてから再度追加しない限り、サービスが要求されるたびに実行されますこのサービスは、悪用する攻撃者の最初の構築内にあります。

この方法には、サービスの構成時にサービスツリーを作成したり、使用したりする必要がないという利点があります。サービスの構成方法をまだ定義しています。

public void ConfigureServices(IServiceCollection services)
{
    //Prey this doesn't get GC'd or promote to a static class var
    string? somevalue = null;

    services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
         //create service you need
         var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
         //get the values you need
         somevalue = somevalue ?? service.MyDirtyHack();
         //return the instance
         return service;
    });
    services.AddTransient<IOtherService, OtherService>(scope => {
         //Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
         scope.GetService<IServiceINeedToUse>();
         //TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
         //Wow!
         return new OtherService(somevalue);
    });
}

このパターンを修正する方法は、暗黙的に依存するか、そのメソッドの戻り値OtherServiceに依存するのIServiceINeedToUseではなく、に明示的な依存関係を与えるか、または他の方法でその依存関係を明示的に解決することです。


-4
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDbContext<ConfigurationRepository>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));

    services.AddScoped<IConfigurationBL, ConfigurationBL>();
    services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}

5
コードスニペットだけでなく、それがなぜ良い答えであるのかについての簡単な説明を提供すると、回答が受け入れられ、賛成される可能性が高くなります。また、質問者が尋ねた質問に実際に答えているかどうかを確認するのにも役立ちます。
ジムL

誰かがあなたの回答に低品質のフラグを付けました。追加のフラグや反対投票を防ぐために回答がどのように機能するかを説明するために、いくつかの付随するテキストを追加する必要があります。コードのみの回答は低品質ではありません。質問に答えようとしますか?そうでない場合は、「回答ではない」としてフラグを立てるか、削除を推奨します(レビューキューにある場合)。b)技術的に間違っていますか?反対票またはコメント。レビューから
Wai Ha Lee
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.