Saya memiliki layanan yang berasal dari antarmuka yang sama.
public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { }
public class ServiceC : IService { }
Biasanya, wadah IOC lainnya seperti Unity
memungkinkan Anda untuk mendaftar implementasi konkret oleh beberapa Key
yang membedakannya.
Di ASP.NET Core, bagaimana cara mendaftarkan layanan ini dan menyelesaikannya saat runtime berdasarkan beberapa kunci?
Saya tidak melihat Add
metode Layanan yang mengambil key
atau name
parameter, yang biasanya akan digunakan untuk membedakan implementasi konkret.
public void ConfigureServices(IServiceCollection services)
{
// How do I register services of the same interface?
}
public MyController:Controller
{
public void DoSomething(string key)
{
// How do I resolve the service by key?
}
}
Apakah pola Pabrik satu-satunya pilihan di sini?
Update1
Saya telah membaca artikel di sini yang menunjukkan bagaimana menggunakan pola pabrik untuk mendapatkan contoh layanan ketika kita memiliki beberapa implementasi konkret. Namun, itu masih bukan solusi lengkap. Ketika saya memanggil _serviceProvider.GetService()
metode, saya tidak bisa menyuntikkan data ke konstruktor.
Sebagai contoh pertimbangkan ini:
public class ServiceA : IService
{
private string _efConnectionString;
ServiceA(string efconnectionString)
{
_efConnecttionString = efConnectionString;
}
}
public class ServiceB : IService
{
private string _mongoConnectionString;
public ServiceB(string mongoConnectionString)
{
_mongoConnectionString = mongoConnectionString;
}
}
public class ServiceC : IService
{
private string _someOtherConnectionString
public ServiceC(string someOtherConnectionString)
{
_someOtherConnectionString = someOtherConnectionString;
}
}
Bagaimana cara _serviceProvider.GetService()
menyuntikkan string koneksi yang sesuai? Di Unity, atau pustaka IoC lainnya, kita bisa melakukannya di tipe registrasi. Saya dapat menggunakan IOpsi , yang mengharuskan saya menyuntikkan semua pengaturan. Saya tidak dapat menyuntikkan string koneksi tertentu ke dalam layanan.
Perhatikan juga bahwa saya mencoba untuk menghindari menggunakan wadah lain (termasuk Unity) karena saya harus mendaftarkan yang lain (misalnya, Pengendali) dengan wadah baru juga.
Juga, menggunakan pola pabrik untuk membuat mesin virtual layanan bertentangan dengan DIP, karena meningkatkan jumlah dependensi klien memiliki rincian di sini .
Jadi, saya pikir DI default di ASP.NET Core hilang dua hal:
- Kemampuan untuk mendaftarkan instance menggunakan kunci
- Kemampuan untuk menyuntikkan data statis ke konstruktor selama pendaftaran
Update1
dipindahkan ke pertanyaan yang berbeda karena menyuntikkan sesuatu dalam konstruktor sangat berbeda dari mengerjakan objek mana yang akan dibangunJawaban:
Saya melakukan solusi sederhana menggunakan
Func
ketika saya menemukan diri saya dalam situasi ini.Pertama mendeklarasikan delegasi bersama:
Kemudian di Anda
Startup.cs
, siapkan beberapa pendaftaran konkret dan pemetaan manual jenis-jenis itu:Dan gunakan dari kelas mana saja yang terdaftar dengan DI:
Perlu diingat bahwa dalam contoh ini kunci untuk resolusi adalah string, demi kesederhanaan dan karena OP meminta kasus ini pada khususnya.
Tetapi Anda bisa menggunakan jenis resolusi khusus apa pun sebagai kunci, karena Anda biasanya tidak ingin saklar n-case besar membusuk kode Anda. Tergantung bagaimana skala aplikasi Anda.
sumber
Opsi lain adalah menggunakan metode ekstensi
GetServices
dariMicrosoft.Extensions.DependencyInjection
.Daftarkan layanan Anda sebagai:
Kemudian atasi dengan sedikit Linq:
atau
(dengan asumsi bahwa
IService
memiliki properti string yang disebut "Nama")Pastikan untuk memilikinya
using Microsoft.Extensions.DependencyInjection;
Memperbarui
Sumber AspNet 2.1:
GetServices
sumber
IEnumerable<IService>
Itu tidak didukung oleh
Microsoft.Extensions.DependencyInjection
.Tetapi Anda dapat memasukkan mekanisme injeksi ketergantungan lain, seperti
StructureMap
Lihat halaman Beranda dan itu adalah Proyek GitHub .Tidak sulit sama sekali:
Tambahkan dependensi ke StructureMap di
project.json
:Suntikkan ke dalam pipa ASP.NET di dalam
ConfigureServices
dan daftarkan kelas Anda (lihat dokumen)Kemudian, untuk mendapatkan instance bernama, Anda harus meminta
IContainer
Itu dia.
Sebagai contoh untuk membangun, Anda perlu
sumber
Anda benar, wadah Core ASP.NET bawaan tidak memiliki konsep mendaftarkan beberapa layanan dan kemudian mengambil yang spesifik, seperti yang Anda sarankan, pabrik adalah satu-satunya solusi nyata dalam kasus itu.
Atau, Anda bisa beralih ke wadah pihak ketiga seperti Unity atau StructureMap yang memang memberikan solusi yang Anda butuhkan (didokumentasikan di sini: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- the-default-services-container ).
sumber
Saya telah menghadapi masalah yang sama dan ingin berbagi bagaimana saya menyelesaikannya dan mengapa.
Seperti yang Anda sebutkan ada dua masalah. Pertama:
Jadi opsi apa yang kita miliki? Orang menyarankan dua:
Gunakan pabrik khusus (seperti
_myFactory.GetServiceByKey(key)
)Gunakan mesin DI lainnya (seperti
_unityContainer.Resolve<IService>(key)
)Sebenarnya kedua opsi adalah pabrik karena setiap Kontainer IoC juga merupakan pabrik (sangat dapat dikonfigurasi dan rumit). Dan menurut saya, opsi lain juga merupakan variasi dari pola Pabrik.
Jadi, opsi apa yang lebih baik? Di sini saya setuju dengan @Sock yang menyarankan menggunakan pabrik kustom, dan itulah sebabnya.
Pertama, saya selalu berusaha menghindari menambahkan dependensi baru ketika mereka tidak benar-benar dibutuhkan. Jadi saya setuju dengan Anda dalam hal ini. Selain itu, menggunakan dua kerangka kerja DI lebih buruk daripada membuat abstraksi pabrik kustom. Dalam kasus kedua Anda harus menambahkan dependensi paket baru (seperti Unity) tetapi tergantung pada antarmuka pabrik baru kurang jahat di sini. Gagasan utama ASP.NET Core DI, saya percaya, adalah kesederhanaan. Ini memelihara satu set fitur minimal mengikuti prinsip KISS . Jika Anda memerlukan beberapa fitur tambahan maka DIY atau gunakan Plungin terkait yang mengimplementasikan fitur yang diinginkan (Prinsip Terbuka Tertutup).
Kedua, seringkali kita perlu menyuntikkan banyak dependensi bernama untuk layanan tunggal. Dalam kasus Unity Anda mungkin harus menentukan nama untuk parameter konstruktor (menggunakan
InjectionConstructor
). Registrasi ini menggunakan refleksi dan beberapa logika pintar untuk menebak argumen konstruktor. Ini juga dapat menyebabkan kesalahan runtime jika pendaftaran tidak cocok dengan argumen konstruktor. Dari sisi lain, ketika menggunakan pabrik Anda sendiri, Anda memiliki kontrol penuh tentang cara memberikan parameter konstruktor. Ini lebih mudah dibaca dan diselesaikan pada saat kompilasi. Prinsip CIUMAN lagi.Masalah kedua:
Pertama, saya setuju dengan Anda bahwa tergantung pada hal-hal baru seperti
IOptions
(dan karena itu pada paketMicrosoft.Extensions.Options.ConfigurationExtensions
) bukan ide yang baik. Saya telah melihat beberapa diskusi tentang diIOptions
mana ada pendapat yang berbeda tentang manfaatnya. Sekali lagi, saya mencoba untuk menghindari menambahkan dependensi baru ketika mereka tidak benar-benar dibutuhkan. Apakah ini benar-benar dibutuhkan? Saya pikir tidak. Kalau tidak, setiap implementasi harus bergantung padanya tanpa ada kebutuhan yang jelas datang dari implementasi itu (bagi saya itu tampak seperti pelanggaran ISP, di mana saya setuju dengan Anda juga). Ini juga berlaku tentang bergantung pada pabrik tetapi dalam kasus ini dapat dihindari.ASP.NET Core DI memberikan kelebihan yang sangat bagus untuk tujuan itu:
sumber
IServiceA
dalam kasus Anda. Karena Anda menggunakan metodeIService
hanya dari , Anda seharusnya hanya bergantung padaIService
.services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));
untuk kelas pertama danservices.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));
untuk yang kedua.Saya hanya cukup menyuntikkan IEnumerable
ConfigureServices di Startup.cs
Folder Layanan
MyController.cs
Extensions.cs
sumber
Sebagian besar jawaban di sini melanggar prinsip tanggung jawab tunggal (kelas layanan tidak boleh menyelesaikan dependensi itu sendiri) dan / atau menggunakan anti-pola locator layanan.
Pilihan lain untuk menghindari masalah ini adalah:
Saya telah menulis sebuah artikel dengan rincian lebih lanjut: Injeksi Ketergantungan di .NET: Cara untuk mengatasi pendaftaran yang hilang
sumber
Agak terlambat ke pesta ini, tapi ini solusinya: ...
Startup.cs atau Program.cs jika Generic Handler ...
IMyInterface dari T Interface Setup
Implementasi konkret dari IMyInterface of T
Semoga jika ada masalah dengan melakukannya dengan cara ini, seseorang akan dengan ramah menunjukkan mengapa ini adalah cara yang salah untuk melakukan ini.
sumber
IMyInterface<CustomerSavedConsumer>
danIMyInterface<ManagerSavedConsumer>
yang berbeda jenis layanan - ini tidak menjawab pertanyaan Ops sama sekali.Tampaknya, Anda bisa menyuntikkan IEnumerable dari antarmuka layanan Anda! Dan kemudian temukan contoh yang Anda inginkan menggunakan LINQ.
Contoh saya adalah untuk layanan AWS SNS tetapi Anda dapat melakukan hal yang sama untuk setiap layanan yang disuntikkan.
Memulai
SNSConfig
appsettings.json
Pabrik SNS
Sekarang Anda bisa mendapatkan layanan SNS untuk wilayah yang Anda inginkan di layanan kustom Anda atau controller
sumber
Pendekatan pabrik tentu bisa berjalan. Pendekatan lain adalah dengan menggunakan pewarisan untuk membuat antarmuka individual yang mewarisi dari IService, mengimplementasikan antarmuka yang diwariskan dalam implementasi IService Anda, dan mendaftarkan antarmuka yang diwarisi daripada basis. Apakah menambahkan hierarki warisan atau pabrik adalah pola "benar" semua tergantung pada siapa Anda berbicara. Saya sering harus menggunakan pola ini ketika berhadapan dengan beberapa penyedia database dalam aplikasi yang sama yang menggunakan generik, seperti
IRepository<T>
, sebagai dasar untuk akses data.Contoh antarmuka dan implementasi:
Wadah:
sumber
Necromancing.
Saya pikir orang-orang di sini menciptakan kembali roda - dan buruknya, jika boleh saya katakan demikian ...
Jika Anda ingin mendaftarkan komponen dengan kunci, cukup gunakan kamus:
Dan kemudian daftarkan kamus dengan koleksi layanan:
jika kemudian Anda tidak ingin mendapatkan kamus dan mengaksesnya dengan kunci, Anda dapat menyembunyikan kamus dengan menambahkan metode pencarian kunci tambahan ke koleksi layanan:
(penggunaan delegasi / penutupan harus memberi peluang kepada calon pengelola tempat di memahami apa yang terjadi - notasi panah agak samar)
Sekarang Anda dapat mengakses tipe Anda dengan baik
atau
Seperti yang dapat kita lihat, yang pertama benar-benar berlebihan, karena Anda juga dapat melakukan hal itu dengan kamus, tanpa memerlukan penutupan dan AddTransient (dan jika Anda menggunakan VB, bahkan kurung kurawal tidak akan berbeda):
(Lebih sederhana lebih baik - Anda mungkin ingin menggunakannya sebagai metode ekstensi)
Tentu saja, jika Anda tidak suka kamus, Anda juga bisa melengkapi antarmuka Anda dengan properti
Name
(atau apa pun), dan mencari itu dengan kunci:Tapi itu membutuhkan perubahan antarmuka Anda untuk mengakomodasi properti, dan perulangan melalui banyak elemen harus jauh lebih lambat daripada pencarian asosiatif-array (kamus).
Sangat menyenangkan mengetahui bahwa hal itu dapat dilakukan tanpa kamus.
Ini hanya $ 0,05 saya
sumber
IDispose
diterapkan, siapa yang bertanggung jawab untuk membuang layanan? Anda telah mendaftarkan kamus sebagaiSingleton
sejak posting saya di atas, saya telah pindah ke Generic Factory Class
Pemakaian
Penerapan
Perpanjangan
sumber
Walaupun sepertinya @Miguel A. Arilla telah menunjukkannya dengan jelas dan saya memilihnya, saya menciptakan di atas solusi yang bermanfaat solusi lain yang terlihat rapi tetapi membutuhkan lebih banyak pekerjaan.
Itu pasti tergantung pada solusi di atas. Jadi pada dasarnya saya membuat sesuatu yang mirip
Func<string, IService>>
dan saya menyebutnyaIServiceAccessor
sebagai antarmuka dan kemudian saya harus menambahkan beberapa ekstensi keIServiceCollection
:Accessor layanan terlihat seperti:
Hasil akhirnya, Anda akan dapat mendaftarkan layanan dengan nama atau contoh bernama seperti yang biasa kami lakukan dengan wadah lain .. misalnya:
Itu sudah cukup untuk saat ini, tetapi untuk menyelesaikan pekerjaan Anda, lebih baik menambahkan lebih banyak metode ekstensi yang Anda bisa untuk mencakup semua jenis pendaftaran mengikuti pendekatan yang sama.
Ada posting lain di stackoverflow, tapi saya tidak dapat menemukannya, di mana poster telah menjelaskan secara rinci mengapa fitur ini tidak didukung dan bagaimana cara mengatasinya, pada dasarnya mirip dengan apa yang dikatakan @Miguel. Itu posting yang bagus meskipun saya tidak setuju dengan setiap poin karena saya pikir ada situasi di mana Anda benar-benar perlu contoh bernama. Saya akan memposting tautan itu di sini setelah saya menemukannya lagi.
Faktanya, Anda tidak perlu melewati Pemilih atau Accessor itu:
Saya menggunakan kode berikut dalam proyek saya dan itu berhasil dengan baik sejauh ini.
sumber
Saya telah membuat perpustakaan untuk ini yang mengimplementasikan beberapa fitur bagus. Kode dapat ditemukan di GitHub: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/
Penggunaannya mudah:
Dalam contoh di atas, perhatikan bahwa untuk setiap pendaftaran yang disebutkan, Anda juga menentukan masa pakai atau Singleton, Scoped, atau Transient.
Anda dapat menyelesaikan layanan dalam salah satu dari dua cara, tergantung pada apakah Anda merasa nyaman dengan layanan Anda yang bergantung pada paket tidak ini:
atau
Saya telah secara khusus mendesain perpustakaan ini untuk bekerja dengan baik dengan Microsoft.Extensions.DependencyInjection - misalnya:
Ketika Anda mendaftar layanan bernama, semua jenis yang Anda daftarkan dapat memiliki konstruktor dengan parameter - mereka akan puas melalui DI, dengan cara yang sama
AddTransient<>
,AddScoped<>
danAddSingleton<>
metode bekerja secara normal.Untuk layanan bernama sementara dan ruang lingkup, registri membangun
ObjectFactory
sehingga dapat mengaktifkan contoh baru dari jenis sangat cepat bila diperlukan. Ini jauh lebih cepat daripada pendekatan lain dan sejalan dengan cara Microsoft.Extensions.DependencyInjection melakukan sesuatu.sumber
Solusi saya untuk apa nilainya ... dianggap beralih ke Castle Windsor karena tidak bisa mengatakan saya menyukai salah satu solusi di atas. Maaf!!
Buat berbagai implementasi Anda
Registrasi
Konstruktor dan penggunaan instance ...
sumber
Memperluas solusi @rnrneverdies. Alih-alih ToString (), opsi berikut juga dapat digunakan- 1) Dengan implementasi properti bersama, 2) Layanan layanan yang disarankan oleh @Craig Brunetti.
sumber
Setelah membaca jawaban di sini dan artikel-artikel di tempat lain saya bisa membuatnya bekerja tanpa ikatan. Ketika Anda memiliki beberapa implementasi dari antarmuka yang sama, DI akan menambahkan ini ke koleksi, jadi mungkin untuk mengambil versi yang Anda inginkan dari koleksi menggunakan
typeof
.sumber
var serviceA = new ServiceA();
Meskipun penerapan out of the box tidak menawarkannya, berikut adalah contoh proyek yang memungkinkan Anda untuk mendaftar instance bernama, dan kemudian menyuntikkan INamedServiceFactory ke dalam kode Anda dan mengeluarkan instance dengan nama. Tidak seperti solusi facory lain di sini, ini akan memungkinkan Anda untuk mendaftar beberapa contoh implementasi yang sama tetapi dikonfigurasi secara berbeda
https://github.com/macsux/DotNetDINamedInances
sumber
Bagaimana dengan layanan untuk layanan?
Jika kami memiliki antarmuka INamedService (dengan properti .Name), kami dapat menulis ekstensi IServiceCollection untuk .GetService (nama string), di mana ekstensi akan mengambil parameter string itu, dan melakukan .GetServices () pada dirinya sendiri, dan di masing-masing kembali contoh, cari contoh yang INamedService.Name cocok dengan nama yang diberikan.
Seperti ini:
Karena itu, IMyService Anda harus mengimplementasikan INamedService, tetapi Anda akan mendapatkan resolusi berbasis kunci yang Anda inginkan, bukan?
Agar adil, bahkan harus memiliki antarmuka INamedService ini tampak jelek, tetapi jika Anda ingin melangkah lebih jauh dan membuat hal-hal lebih elegan, maka [NamedServiceAttribute ("A")] pada implementasi / kelas dapat ditemukan oleh kode dalam ini ekstensi, dan itu akan berhasil juga. Agar lebih adil, Refleksi lambat, jadi pengoptimalan mungkin dilakukan, tapi jujur itu sesuatu yang seharusnya dibantu oleh mesin DI. Kecepatan dan kesederhanaan adalah masing-masing kontributor utama TCO.
Semua dalam semua, tidak perlu untuk pabrik yang eksplisit, karena "menemukan layanan bernama" adalah konsep yang dapat digunakan kembali, dan kelas pabrik tidak skala sebagai solusi. Dan Func <<tampaknya baik-baik saja, tetapi blok switch begitu bleh , dan lagi, Anda akan menulis Funcs sesering Anda akan menulis Pabrik. Mulai yang sederhana, dapat digunakan kembali, dengan kode lebih sedikit, dan jika itu ternyata tidak berhasil untuk Anda, maka lanjutkan menjadi rumit.
sumber
Saya telah mengalami masalah yang sama dan saya bekerja dengan ekstensi sederhana untuk mengizinkan layanan Bernama. Anda dapat menemukannya di sini:
Ini memungkinkan Anda untuk menambahkan layanan (bernama) sebanyak yang Anda inginkan seperti ini:
Perpustakaan juga memungkinkan Anda untuk menerapkan "pola pabrik" dengan mudah seperti ini:
Semoga ini bisa membantu
sumber
Saya membuat ekstensi sendiri di atas ekstensi yang
IServiceCollection
digunakanWithName
:ServiceCollectionTypeMapper
adalah contoh tunggal yang petaIService
>NameOfService
>Implementation
di mana sebuah antarmuka bisa memiliki banyak implementasi dengan nama yang berbeda, ini memungkinkan untuk mendaftarkan jenis dari yang kita dapat mengatasi saat wee kebutuhan dan pendekatan yang berbeda dari tekad beberapa layanan untuk memilih apa yang kita inginkan.Untuk mendaftarkan layanan baru:
Untuk menyelesaikan layanan, kami membutuhkan ekstensi
IServiceProvider
seperti ini.Ketika menyelesaikan:
Ingatlah bahwa serviceProvider dapat disuntikkan ke dalam konstruktor di aplikasi kita sebagai
IServiceProvider
.Saya harap ini membantu.
sumber