Menyelesaikan instance dengan ASP.NET Core DI

302

Bagaimana cara saya menyelesaikan suatu jenis menggunakan kerangka kerja injeksi dependensi built-in ASP.NET Core MVC?

Menyiapkan wadah cukup mudah:

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

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

Tetapi bagaimana saya bisa menyelesaikannya ISomeServicetanpa melakukan injeksi? Sebagai contoh, saya ingin melakukan ini:

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

Tidak ada metode seperti itu di IServiceCollection.

Dave New
sumber
1
duplikat yang mungkin dari Bagaimana Mengatasi Instance Di Dalam ConfigureServices di ASP.NET 5
Muhammad Rehan Saeed
3
Apakah Anda ingin menyelesaikannya dalam ConfigureServices()metode (dengan IServiceCollection) atau di mana saja dalam aplikasi?
Henk Mollema
2
@HenkMollema: Di mana saja di dalam Startup sebenarnya.
Dave New

Jawaban:

486

The IServiceCollectionantarmuka digunakan untuk membangun wadah injeksi ketergantungan. Setelah sepenuhnya dibangun, itu akan dikomposisikan ke sebuah IServiceProviderinstance yang dapat Anda gunakan untuk menyelesaikan layanan. Anda dapat menyuntikkan IServiceProviderke dalam kelas apa pun. The IApplicationBuilderdan HttpContextkelas dapat memberikan penyedia layanan juga, melalui mereka ApplicationServicesatauRequestServices sifat masing-masing.

IServiceProvidermendefinisikan GetService(Type type)metode untuk menyelesaikan layanan:

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

Ada juga beberapa metode ekstensi kenyamanan yang tersedia, seperti serviceProvider.GetService<IFooService>()(tambahkan usinguntuk Microsoft.Extensions.DependencyInjection).

Menyelesaikan layanan di dalam kelas startup

Dependensi menyuntikkan

Penyedia layanan hosting runtime dapat menyuntikkan layanan tertentu ke konstruktor Startupkelas, seperti IConfiguration, IWebHostEnvironment( IHostingEnvironmentdalam versi pra-3.0), ILoggerFactorydan IServiceProvider. Perhatikan bahwa yang terakhir adalah contoh yang dibangun oleh lapisan hosting dan hanya berisi layanan penting untuk memulai aplikasi .

The ConfigureServices()Metode tidak memungkinkan layanan suntik, hanya menerima sebuah IServiceCollectionargumen. Ini masuk akal karena di ConfigureServices()situlah Anda mendaftarkan layanan yang diperlukan oleh aplikasi Anda. Namun Anda dapat menggunakan layanan yang disuntikkan di konstruktor startup di sini, misalnya:

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

public IConfiguration Configuration { get; }

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

Setiap layanan yang terdaftar di ConfigureServices()kemudian dapat disuntikkan ke dalam Configure()metode; Anda dapat menambahkan sejumlah layanan sewenang-wenang setelah IApplicationBuilderparameter:

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

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

Mengatasi dependensi secara manual

Jika Anda perlu menyelesaikan layanan secara manual, Anda sebaiknya menggunakan yang ApplicationServicesdisediakan oleh IApplicationBuilderdalam Configure()metode:

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

Dimungkinkan untuk lulus dan secara langsung menggunakan nilai IServiceProviderdalam konstruktor Startupkelas Anda , tetapi seperti di atas ini akan berisi subset layanan yang terbatas , dan dengan demikian memiliki utilitas terbatas:

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

Jika Anda harus menyelesaikan layanan dalam ConfigureServices()metode ini, diperlukan pendekatan yang berbeda. Anda dapat membangun perantara IServiceProviderdari IServiceCollectioninstance yang berisi layanan yang telah terdaftar hingga saat itu :

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>();
}

Harap dicatat: Secara umum Anda harus menghindari menyelesaikan layanan di dalam ConfigureServices()metode, karena ini sebenarnya adalah tempat di mana Anda mengkonfigurasi layanan aplikasi. Terkadang Anda hanya perlu akses ke sebuah IOptions<MyOptions>instance. Anda bisa mencapai ini dengan mengikat nilai-nilai dari IConfigurationinstance ke instance MyOptions(yang pada dasarnya adalah apa yang dilakukan kerangka kerja opsi):

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

Layanan penyelesaian secara manual (alias Service Locator) umumnya dianggap sebagai anti-pola . Meskipun memiliki kasus penggunaannya (untuk kerangka kerja dan / atau lapisan infrastruktur), Anda harus menghindarinya sebanyak mungkin.

Henk Mollema
sumber
14
@HenkMollema tetapi bagaimana jika saya tidak dapat menyuntikkan apa pun, maksud saya saya tidak bisa IServiceCollectionmenyuntikkan, beberapa kelas yang sedang dibuat secara manual (di luar ruang tengah ware ), scheduler dalam kasus saya, yang secara berkala membutuhkan beberapa layanan untuk menghasilkan dan kirim email.
Merdan Gochmuradov
52
peringatan jika Anda perlu menyelesaikan layanan ConfigureServicesdan layanan itu adalah singleton itu akan menjadi singleton yang berbeda dengan yang Anda Controllergunakan! Saya berasumsi ini karena menggunakan yang berbeda IServiceProvider- untuk menghindari hal ini, JANGAN menyelesaikan melalui BuildServiceProviderdan sebaliknya memindahkan pencarian Anda dari singleton ConfigureServiceske Configure(..other params, IServiceProvider serviceProvider)inStartup.cs
wal
3
@wal poin bagus. Karena ini adalah IServiceProvidercontoh yang berbeda, itu akan membuat contoh singleton baru. Anda bisa menghindari ini dengan mengembalikan instance penyedia layanan dari ConfigureServicesmetode sehingga akan menjadi wadah yang digunakan aplikasi Anda juga.
Henk Mollema
1
Memohon collection.BuildServiceProvider();adalah yang saya butuhkan, terima kasih!
Chris Marisic
2
@HenkMollema bagaimana Anda membuatnya berfungsi hanya dengan satu contoh penyedia layanan? Biasanya, Anda akan 1) Mendaftarkan beberapa dependensi Anda 2) membangun instance penyedia layanan sementara 3) Gunakan penyedia layanan itu untuk menyelesaikan sesuatu yang Anda butuhkan untuk mendaftarkan beberapa dependensi lainnya. Setelah itu, Anda tidak dapat mengembalikan instance sementara, karena itu kehilangan beberapa dependensi Anda (terdaftar di 3). Apakah saya melewatkan sesuatu?
Filipi
109

Penyelesaian instans secara manual melibatkan penggunaan IServiceProvider antarmuka:

Mengatasi Ketergantungan pada Startup.ConfigureServices

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

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

Menyelesaikan Dependensi dalam 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>();

    // ...
}

Menyelesaikan Dependensi dalam Startup. Konfigurasi di ASP.NET Core 3

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

Menggunakan Layanan Injeksi Runtime

Beberapa jenis dapat disuntikkan sebagai parameter metode:

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)
    {
    }
}

Menyelesaikan Dependensi dalam Aksi Controller

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";
Muhammad Rehan Saeed
sumber
1
@AfsharMohebbi GetServiceyang generik adalah metode ekstensi di Microsoft.Extensions.DependencyInjectionnamespace.
ahmadali shafiee
Tentang Metode Ekstensi: Metode ekstensi adalah metode statis yang menambahkan fungsi ke kelas, Anda bisa mendeklarasikan public static TheReturnType TheMethodName (TheTypeYouExtend theTypeYouExtend {// BODY} dan kemudian Anda dapat menggunakannya seperti: TheTypeYouExtend.TheMethodName (); menjadi pendekatan yang sangat umum dengan .NET Core, sehingga pengembang dapat memperluas fungsionalitas basis ... contoh yang baik di sini: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
Juan
17

Jika Anda membuat aplikasi dengan templat, Anda akan memiliki sesuatu seperti ini di Startupkelas:

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

    services.AddMvc();
}

Anda kemudian dapat menambahkan dependensi di sana, misalnya:

services.AddTransient<ITestService, TestService>();

Jika Anda ingin mengakses ITestServicepada controller Anda, Anda dapat menambahkan IServiceProviderkonstruktor dan itu akan disuntikkan:

public HomeController(IServiceProvider serviceProvider)

Kemudian Anda dapat menyelesaikan layanan yang Anda tambahkan:

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

Perhatikan bahwa untuk menggunakan versi generik Anda harus menyertakan namespace dengan ekstensi:

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();
        }
BrunoLM
sumber
10

Jika Anda hanya perlu menyelesaikan satu dependensi untuk tujuan meneruskannya ke konstruktor dependensi lain yang Anda daftarkan, Anda dapat melakukan ini.

Katakanlah Anda memiliki layanan yang mengambil string dan layanan ISOMS.

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

Saat Anda mendaftar ini di dalam Startup.cs, Anda harus melakukan ini:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);
raterus
sumber
OP tidak menyatakan alasan untuk perlu menyelesaikan layanan dalam metode ConfigureService, tetapi ini kemungkinan besar alasan seseorang akan berpikir untuk melakukan itu
kilkfoe
1
Sebenarnya ini harus menjadi jawaban yang diterima ... Meskipun jawaban Henk Mollema sangat ilustratif, saat ini jawaban Anda lebih bersih dan tidak menimbulkan masalah terkait membangun IServiceProvider perantara (contoh yang berbeda dari lajang ...). Mungkin, solusi ini tidak tersedia pada tahun 2015 ketika Henk menjawab, tetapi sekarang saatnya untuk pergi.
Vi100
Sudah mencoba ini tetapi ISomeServicemasih nol untuk saya.
ajbeaven
2 pertanyaan: 1) Jika konstruktor parameter kelas layanan AnotherService berubah (dihapus atau ditambahkan layanan), maka saya perlu memodifikasi segmen register layanan IAnotherService dan terus berubah? 2) Sebagai gantinya, saya hanya dapat menambahkan satu konstruktor untuk AnotherService dengan 1 parameter seperti layanan AnotherService publik (IServiceProvider serviceProvider) dan mendapatkan layanan yang saya butuhkan dari konstruktor. Dan saya hanya perlu mendaftarkan kelas layanan AnotherService di kelas Startup seperti services.AddTransient <IAnotherService, AnotherService> (sp => {var service = new AnotherService (sp); return service;});
Thomas.Benz
2

Anda bisa menyuntikkan dependensi dalam atribut seperti AuthorizeAttribute dengan cara ini

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));
Bora Aydın
sumber
Inilah yang saya cari .. Terima kasih
Reyan Chougle
0

Saya tahu ini adalah pertanyaan lama tetapi saya heran bahwa peretasan yang agak jelas dan menjijikkan tidak ada di sini.

Anda dapat mengeksploitasi kemampuan untuk mendefinisikan fungsi ctor Anda sendiri untuk mengambil nilai yang diperlukan dari layanan Anda saat Anda mendefinisikannya ... jelas ini akan dijalankan setiap kali layanan diminta kecuali Anda secara eksplisit menghapus / menghapus dan menambahkan kembali definisi dari layanan ini dalam konstruksi pertama dari aktor yang mengeksploitasi .

Metode ini memiliki keuntungan karena tidak mengharuskan Anda untuk membangun pohon layanan, atau menggunakannya, selama konfigurasi layanan. Anda masih mendefinisikan bagaimana layanan akan dikonfigurasi.

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

Cara untuk memperbaiki pola ini adalah dengan memberikan OtherServicedependensi eksplisit pada IServiceINeedToUse, daripada bergantung secara implisit padanya atau nilai kembalinya metodanya ... atau menyelesaikan dependensi itu secara eksplisit dalam beberapa cara lain.

Izzy
sumber
-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>();
}
Nathan Alard
sumber
5
Jawaban Anda kemungkinan besar akan diterima dan ditingkatkan jika Anda memberikan penjelasan singkat mengapa itu adalah jawaban yang baik, bukan hanya cuplikan kode. Ini juga membantu penanya untuk memastikan ini benar-benar menjawab pertanyaan yang mereka tanyakan.
Jim L
Seseorang salah menandai jawaban Anda sebagai berkualitas rendah. Anda harus menambahkan beberapa teks yang menyertai untuk menjelaskan bagaimana jawaban Anda bekerja untuk mencegah penandaan lebih lanjut dan / atau downvotes. Jawaban hanya kode tidak berkualitas rendah . Apakah ia mencoba menjawab pertanyaan itu? Jika tidak, tandai sebagai 'bukan jawaban' atau rekomendasikan penghapusan (jika ada dalam antrean ulasan). b) Apakah secara teknis salah? Downvote atau komentar. Dari ulasan .
Wai Ha Lee