Bagaimana cara mendaftar beberapa implementasi dari antarmuka yang sama di Asp.Net Core?

240

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 Unitymemungkinkan Anda untuk mendaftar implementasi konkret oleh beberapa Keyyang membedakannya.

Di ASP.NET Core, bagaimana cara mendaftarkan layanan ini dan menyelesaikannya saat runtime berdasarkan beberapa kunci?

Saya tidak melihat Addmetode Layanan yang mengambil keyatau nameparameter, 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:

  1. Kemampuan untuk mendaftarkan instance menggunakan kunci
  2. Kemampuan untuk menyuntikkan data statis ke konstruktor selama pendaftaran
LP13
sumber
4
Kemungkinan duplikat penyelesaian Ketergantungan dengan nama
adem caglin
2
Akhirnya ada ekstensi di nuget untuk registrasi berbasis nama, semoga bisa membantu
neleus
Hai, maaf untuk pertanyaan bodoh saya, tapi saya baru tentang Microsoft.Extensions.DependencyInjection ... apakah Anda berpikir bahwa membuat 3 antarmuka kosong yang memperpanjang Iservice seperti "antarmuka publik IServiceA: IService" dan daripada "public class ServiceA: IServiceA "... bisa jadi pilihan latihan yang bagus?
Emiliano Magliocca
Apakah artikel ini bisa digunakan? stevejgordon.co.uk/...
Mike B
Dapat Update1dipindahkan ke pertanyaan yang berbeda karena menyuntikkan sesuatu dalam konstruktor sangat berbeda dari mengerjakan objek mana yang akan dibangun
Neil

Jawaban:

246

Saya melakukan solusi sederhana menggunakan Funcketika saya menemukan diri saya dalam situasi ini.

Pertama mendeklarasikan delegasi bersama:

public delegate IService ServiceResolver(string key);

Kemudian di Anda Startup.cs, siapkan beberapa pendaftaran konkret dan pemetaan manual jenis-jenis itu:

services.AddTransient<ServiceA>();
services.AddTransient<ServiceB>();
services.AddTransient<ServiceC>();

services.AddTransient<ServiceResolver>(serviceProvider => key =>
{
    switch (key)
    {
        case "A":
            return serviceProvider.GetService<ServiceA>();
        case "B":
            return serviceProvider.GetService<ServiceB>();
        case "C":
            return serviceProvider.GetService<ServiceC>();
        default:
            throw new KeyNotFoundException(); // or maybe return null, up to you
    }
});

Dan gunakan dari kelas mana saja yang terdaftar dengan DI:

public class Consumer
{
    private readonly IService _aService;

    public Consumer(ServiceResolver serviceAccessor)
    {
        _aService = serviceAccessor("A");
    }

    public void UseServiceA()
    {
        _aService.DoTheThing();
    }
}

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.

Miguel A. Arilla
sumber
1
@MatthewStevenMonkan memperbarui jawaban saya dengan contoh
Miguel A. Arilla
2
Menggunakan pola pabrik seperti ini adalah cara terbaik untuk pergi. Terima kasih telah berbagi!
Sergey Akopov
2
+1 Sangat rapi dan bersih, karena ketika kita menggunakan di-container lain kita harus memasukkan paket mereka kapan pun kita perlu menyelesaikan dependensi, mis. ILifetimeScope di AutoFac.
Anupam Singh
1
@AnupamSingh Menurut pendapat saya, sebagian besar jenis aplikasi kecil hingga sedang yang berjalan di .NET Core tidak memerlukan kerangka DI, hanya menambah kompleksitas dan ketergantungan yang tidak diinginkan, keindahan dan kesederhanaan DI built-in lebih dari cukup, dan itu bisa juga diperpanjang dengan mudah.
Miguel A. Arilla
7
Down voting description - Sangat menarik tetapi saya saat ini sedang melakukan refactoring basis kode besar untuk menghapus semua fungsi Func yang dilakukan seseorang beberapa tahun yang lalu (sebelum revolusi MS DI). Masalahnya adalah ini secara dramatis meningkatkan kerumitan kerumitan pada properti yang dapat menyebabkan resolusi DI berbelit-belit lebih jauh di telepon. Sebagai contoh saya bekerja pada handler layanan Windows memiliki lebih dari 1,6 k baris kode yang harus dilakukan dengan Func dan setelah melakukannya dengan cara yang disarankan DI saya menguranginya menjadi 0,2k baris. OK-Baris kode tidak berarti apa-apa .. kecuali lebih mudah dibaca dan digunakan kembali sekarang ...
Piotr Kula
79

Opsi lain adalah menggunakan metode ekstensi GetServicesdari Microsoft.Extensions.DependencyInjection.

Daftarkan layanan Anda sebagai:

services.AddSingleton<IService, ServiceA>();
services.AddSingleton<IService, ServiceB>();
services.AddSingleton<IService, ServiceC>();

Kemudian atasi dengan sedikit Linq:

var services = serviceProvider.GetServices<IService>();
var serviceB = services.First(o => o.GetType() == typeof(ServiceB));

atau

var serviceZ = services.First(o => o.Name.Equals("Z"));

(dengan asumsi bahwa IServicememiliki properti string yang disebut "Nama")

Pastikan untuk memilikinya using Microsoft.Extensions.DependencyInjection;

Memperbarui

Sumber AspNet 2.1: GetServices

rnrneverdies
sumber
6
Tidak yakin, tapi saya pikir itu tidak deterministik. Setiap hasil yang Anda dapatkan hari ini dapat berubah besok, sepertinya bukan praktik yang baik.
rnrneverdies
4
Suara positif ke tautan untuk GetServices, yang menunjukkan kepada saya bahwa Anda dapat meminta daftar layanan dengan layanan bergantung dengan memintaIEnumerable<IService>
johnny 5
20
serviceProvider.GetServices <IService> () akan membuat instantiate masing-masing ServiceA, ServiceB dan ServiceC. Anda ingin memanggil konstruktor hanya dari satu layanan - layanan yang benar-benar Anda butuhkan. Ini adalah masalah besar jika implementasi tidak ringan atau Anda memiliki banyak implementasi IService (misalnya, Anda memiliki implementasi IRepository yang dibuat secara otomatis untuk setiap model).
Uros
6
Saya setuju dengan @Uros. Ini bukan solusi yang baik. Bayangkan apa yang terjadi jika Anda mendaftar 10 implementasi-layanan-IS dan contoh yang Anda butuhkan adalah yang terakhir. Dalam hal ini, 9 instance sebenarnya dibuat oleh DI, yang tidak pernah digunakan.
thomai
4
Gagasan buruk: Beberapa instance yang tidak digunakan, pola anti lokasi locator dan penggabungan langsung ke implementasi aktual (typeof <ServiceA>).
Rico Suter
20

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:

  1. Tambahkan dependensi ke StructureMap di project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. Suntikkan ke dalam pipa ASP.NET di dalam ConfigureServicesdan daftarkan kelas Anda (lihat dokumen)

    public IServiceProvider ConfigureServices(IServiceCollection services) // returns IServiceProvider !
    {
        // Add framework services.
        services.AddMvc();
        services.AddWhatever();
    
        //using StructureMap;
        var container = new Container();
        container.Configure(config =>
        {
            // Register stuff in container, using the StructureMap APIs...
            config.For<IPet>().Add(new Cat("CatA")).Named("A");
            config.For<IPet>().Add(new Cat("CatB")).Named("B");
            config.For<IPet>().Use("A"); // Optionally set a default
            config.Populate(services);
        });
    
        return container.GetInstance<IServiceProvider>();
    }
    
  3. Kemudian, untuk mendapatkan instance bernama, Anda harus meminta IContainer

    public class HomeController : Controller
    {
        public HomeController(IContainer injectedContainer)
        {
            var myPet = injectedContainer.GetInstance<IPet>("B");
            string name = myPet.Name; // Returns "CatB"
    

Itu dia.

Sebagai contoh untuk membangun, Anda perlu

    public interface IPet
    {
        string Name { get; set; }
    }

    public class Cat : IPet
    {
        public Cat(string name)
        {
            Name = name;
        }

        public string Name {get; set; }
    }
Gerardo Grignoli
sumber
Saya sudah mencoba pendekatan ini, tetapi saya mendapatkan kesalahan runtime pada pengontrol saya karena IContainer tidak ditemukan dalam rencana pembangunan. Apakah saya harus meminta IContainer untuk diinjeksi otomatis?
mohrtan
BTW, saya menggunakan StructureMap.Micorosoft.DependencyInjection 1.3.0.
mohrtan
Apakah Anda mengembalikan wadah baru di ConfigureServices?
Gerardo Grignoli
Saya mengembalikan IServiceProviderInstance wadah baru seperti yang ditunjukkan pada langkah # 2 di atas. Saya menyalin bahwa persis hanya mengubahnya untuk tipe saya. Ini adalah solusi yang bagus dan bekerja dengan sempurna. Satu-satunya kelemahan adalah bahwa saya tidak dapat menggunakan wadah yang disuntikkan dan saya beralih ke wadah statis, yang tidak ingin saya lakukan.
mohrtan
1
Ini bekerja untuk saya, terima kasih GerardoGrignoli. @ mohrtan kode sampel ada di sini jika Anda masih melihat ini. github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza
13

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

Kaus kaki
sumber
13

Saya telah menghadapi masalah yang sama dan ingin berbagi bagaimana saya menyelesaikannya dan mengapa.

Seperti yang Anda sebutkan ada dua masalah. Pertama:

Di Asp.Net Core, bagaimana cara mendaftarkan layanan ini dan menyelesaikannya saat runtime berdasarkan beberapa kunci?

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

Apakah pola Pabrik satu-satunya pilihan di sini?

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:

Bagaimana _serviceProvider.GetService () dapat menyuntikkan string koneksi yang sesuai?

Pertama, saya setuju dengan Anda bahwa tergantung pada hal-hal baru seperti IOptions(dan karena itu pada paket Microsoft.Extensions.Options.ConfigurationExtensions) bukan ide yang baik. Saya telah melihat beberapa diskusi tentang di IOptionsmana 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:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));
neleus
sumber
Hai, maaf untuk pertanyaan bodoh saya, tapi saya baru dengan Microsoft.Extensions.DependencyInjection ... apakah Anda berpikir bahwa membuat 3 antarmuka yang memperpanjang Iservice seperti "antarmuka publik IServiceA: IService" dan daripada "public class ServiceA: IServiceA" ... bisa menjadi opsi latihan yang bagus?
Emiliano Magliocca
1
@ emiliano-magliocca Secara umum, Anda tidak harus bergantung pada antarmuka yang tidak Anda gunakan (ISP), IServiceAdalam kasus Anda. Karena Anda menggunakan metode IServicehanya dari , Anda seharusnya hanya bergantung pada IService.
neleus
1
@ cagatay-kalan Dalam kasus pertanyaan OP, ia dapat dengan mudah mencapai tujuannya dengan ASP.NET Core DI. Tidak perlu untuk kerangka kerja DI lainnya.
neleus
1
@EmilianoMagliocca Dapat dengan mudah dipecahkan dengan cara ini: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));untuk kelas pertama dan services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));untuk yang kedua.
neleus
1
@EmilianoMagliocca dalam contoh saya baik 'MyFirstClass' dan 'MySecondClass' memiliki parameter ctor yang sama dari tipe antarmuka yang diterapkan baik oleh Escpos dan Usbpos. Jadi kode di atas hanya menginstruksikan wadah IoC bagaimana cara menginisiasi 'MyFirstClass' dan 'MySecondClass'. Tidak ada lagi. Jadi selain itu Anda mungkin perlu memetakan beberapa antarmuka lainnya ke 'MyFirstClass' dan 'MySecondClass'. Itu tergantung pada kebutuhan Anda dan saya tidak membahasnya dalam contoh saya.
neleus
13

Saya hanya cukup menyuntikkan IEnumerable

ConfigureServices di Startup.cs

Assembly.GetEntryAssembly().GetTypesAssignableFrom<IService>().ForEach((t)=>
                {
                    services.AddScoped(typeof(IService), t);
                });

Folder Layanan

public interface IService
{
    string Name { get; set; }
}

public class ServiceA : IService
{
    public string Name { get { return "A"; } }
}

public class ServiceB : IService
{    
    public string Name { get { return "B"; } }
}

public class ServiceC : IService
{    
    public string Name { get { return "C"; } }
}

MyController.cs

public class MyController
{
    private readonly IEnumerable<IService> _services;
    public MyController(IEnumerable<IService> services)
    {
        _services = services;
    }
    public void DoSomething()
    {
        var service = _services.Where(s => s.Name == "A").Single();
    }
...
}

Extensions.cs

    public static List<Type> GetTypesAssignableFrom<T>(this Assembly assembly)
    {
        return assembly.GetTypesAssignableFrom(typeof(T));
    }
    public static List<Type> GetTypesAssignableFrom(this Assembly assembly, Type compareType)
    {
        List<Type> ret = new List<Type>();
        foreach (var type in assembly.DefinedTypes)
        {
            if (compareType.IsAssignableFrom(type) && compareType != type)
            {
                ret.Add(type);
            }
        }
        return ret;
    }
T Brown
sumber
Dalam metode DoSomething () pada Controller Anda dapat menggunakan typeof untuk menyelesaikan layanan yang Anda inginkan: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen
Saya benar-benar mencoba segalanya, dan ini adalah satu-satunya solusi yang bekerja untuk saya. Terima kasih!
Skatz1990
@ Skatz1990 Coba solusi yang saya buat di bawah ini di posting lain. Saya pikir ini lebih bersih dan mudah digunakan.
T Brown
12

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:

  • menggunakan parameter tipe generik tambahan pada antarmuka atau antarmuka baru yang mengimplementasikan antarmuka non generik,
  • mengimplementasikan kelas adaptor / pencegat untuk menambahkan jenis penanda dan kemudian
  • gunakan tipe generik sebagai "nama"

Saya telah menulis sebuah artikel dengan rincian lebih lanjut: Injeksi Ketergantungan di .NET: Cara untuk mengatasi pendaftaran yang hilang

Rico Suter
sumber
bagaimana jawaban yang diterima melanggar prinsip tanggung jawab tunggal?
LP13
Lihat komentar stackoverflow.com/a/52066039/876814 dan juga dalam jawaban yang diterima, layanan ini diselesaikan dengan malas, yaitu Anda hanya tahu jika gagal saat runtime dan tidak ada cara untuk memeriksa secara statis ini pada startup setelah pembuatan wadah (mirip dengan jawabannya di komentar). SRP karena layanan ini tidak hanya bertanggung jawab atas logika bisnisnya tetapi juga untuk resolusi ketergantungan
Rico Suter
@RicoSuter Saya sangat menyukai solusi di blog Anda, tetapi saya bingung dengan DI Anda di dalam kelas Startup. Secara khusus, saya tidak mengerti baris MessagePublisher ("MyOrderCreatedQueue") karena saya tidak melihat konstruktor dengan tanda tangan itu. services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (MessagePublisher baru <OrderCreatedMessage> (Pengirim Pesan baru ("MyOrderCreatedQueue")));
Lee Z
Terima kasih, perbarui artikel dan gunakan MyMessagePublisher sebagai contoh implementasi IMessagePublisher
Rico Suter
7

Agak terlambat ke pesta ini, tapi ini solusinya: ...

Startup.cs atau Program.cs jika Generic Handler ...

services.AddTransient<IMyInterface<CustomerSavedConsumer>, CustomerSavedConsumer>();
services.AddTransient<IMyInterface<ManagerSavedConsumer>, ManagerSavedConsumer>();

IMyInterface dari T Interface Setup

public interface IMyInterface<T> where T : class, IMyInterface<T>
{
    Task Consume();
}

Implementasi konkret dari IMyInterface of T

public class CustomerSavedConsumer: IMyInterface<CustomerSavedConsumer>
{
    public async Task Consume();
}

public class ManagerSavedConsumer: IMyInterface<ManagerSavedConsumer>
{
    public async Task Consume();
}

Semoga jika ada masalah dengan melakukannya dengan cara ini, seseorang akan dengan ramah menunjukkan mengapa ini adalah cara yang salah untuk melakukan ini.

Abu-abu
sumber
2
IMyInterface<CustomerSavedConsumer>dan IMyInterface<ManagerSavedConsumer>yang berbeda jenis layanan - ini tidak menjawab pertanyaan Ops sama sekali.
Richard Hauer
2
OP ingin cara mendaftarkan beberapa implementasi dari antarmuka yang sama di inti Asp.net. Jika saya tidak melakukan ini, tolong jelaskan bagaimana (tepatnya).
Abu
1
Meskipun Anda benar, pola ini memungkinkan efek yang diinginkan op. Setidaknya ketika saya mencoba melakukan ini sendiri, saya menemukan posting ini dan solusi saya bekerja paling baik untuk situasi saya.
Gray
1
Saya berharap masalahnya lebih dari mendaftarkan beberapa implementasi untuk satu antarmuka (menggunakan MS DI) tidak memungkinkan wadah untuk membedakan satu implementasi dari yang lain. Di DIs lain Anda bisa memasukkan mereka sehingga wadah tahu mana yang harus dipilih. Di MS Anda harus menggunakan delegasi dan memilih secara manual. Solusi Anda tidak membahas skenario ini karena antarmuka Anda berbeda, sehingga wadah tidak memiliki masalah dalam memilih implementasi yang tepat. Meskipun sampel Anda jelas berfungsi, itu bukan solusi untuk masalah seperti yang dinyatakan.
Richard Hauer
3
@ Abu-abu Meskipun posting Anda mendapat pers yang buruk, saya berterima kasih telah mengedepankan solusi ini. Ini memberi pembaca pilihan lain untuk mengatasi keterbatasan dalam .net core DI. Meskipun mungkin tidak menjawab pertanyaan OP secara langsung, ia memberikan solusi alternatif yang sempurna, yang merupakan inti dari SO, bukan?
Neil Watson
5

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

foreach (string snsRegion in Configuration["SNSRegions"].Split(',', StringSplitOptions.RemoveEmptyEntries))
{
    services.AddAWSService<IAmazonSimpleNotificationService>(
        string.IsNullOrEmpty(snsRegion) ? null :
        new AWSOptions()
        {
            Region = RegionEndpoint.GetBySystemName(snsRegion)
        }
    );
}

services.AddSingleton<ISNSFactory, SNSFactory>();

services.Configure<SNSConfig>(Configuration);

SNSConfig

public class SNSConfig
{
    public string SNSDefaultRegion { get; set; }
    public string SNSSMSRegion { get; set; }
}

appsettings.json

  "SNSRegions": "ap-south-1,us-west-2",
  "SNSDefaultRegion": "ap-south-1",
  "SNSSMSRegion": "us-west-2",

Pabrik SNS

public class SNSFactory : ISNSFactory
{
    private readonly SNSConfig _snsConfig;
    private readonly IEnumerable<IAmazonSimpleNotificationService> _snsServices;

    public SNSFactory(
        IOptions<SNSConfig> snsConfig,
        IEnumerable<IAmazonSimpleNotificationService> snsServices
        )
    {
        _snsConfig = snsConfig.Value;
        _snsServices = snsServices;
    }

    public IAmazonSimpleNotificationService ForDefault()
    {
        return GetSNS(_snsConfig.SNSDefaultRegion);
    }

    public IAmazonSimpleNotificationService ForSMS()
    {
        return GetSNS(_snsConfig.SNSSMSRegion);
    }

    private IAmazonSimpleNotificationService GetSNS(string region)
    {
        return GetSNS(RegionEndpoint.GetBySystemName(region));
    }

    private IAmazonSimpleNotificationService GetSNS(RegionEndpoint region)
    {
        IAmazonSimpleNotificationService service = _snsServices.FirstOrDefault(sns => sns.Config.RegionEndpoint == region);

        if (service == null)
        {
            throw new Exception($"No SNS service registered for region: {region}");
        }

        return service;
    }
}

public interface ISNSFactory
{
    IAmazonSimpleNotificationService ForDefault();

    IAmazonSimpleNotificationService ForSMS();
}

Sekarang Anda bisa mendapatkan layanan SNS untuk wilayah yang Anda inginkan di layanan kustom Anda atau controller

public class SmsSender : ISmsSender
{
    private readonly IAmazonSimpleNotificationService _sns;

    public SmsSender(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForSMS();
    }

    .......
 }

public class DeviceController : Controller
{
    private readonly IAmazonSimpleNotificationService _sns;

    public DeviceController(ISNSFactory snsFactory)
    {
        _sns = snsFactory.ForDefault();
    }

     .........
}
ArcadeRenegade
sumber
5

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:

public interface IService 
{
}

public interface IServiceA: IService
{}

public interface IServiceB: IService
{}

public IServiceC: IService
{}

public class ServiceA: IServiceA 
{}

public class ServiceB: IServiceB
{}

public class ServiceC: IServiceC
{}

Wadah:

container.Register<IServiceA, ServiceA>();
container.Register<IServiceB, ServiceB>();
container.Register<IServiceC, ServiceC>();
jlc397
sumber
5

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:

System.Collections.Generic.Dictionary<string, IConnectionFactory> dict = 
    new System.Collections.Generic.Dictionary<string, IConnectionFactory>(
        System.StringComparer.OrdinalIgnoreCase);

dict.Add("ReadDB", new ConnectionFactory("connectionString1"));
dict.Add("WriteDB", new ConnectionFactory("connectionString2"));
dict.Add("TestDB", new ConnectionFactory("connectionString3"));
dict.Add("Analytics", new ConnectionFactory("connectionString4"));
dict.Add("LogDB", new ConnectionFactory("connectionString5"));

Dan kemudian daftarkan kamus dengan koleksi layanan:

services.AddSingleton<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(dict);

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)

services.AddTransient<Func<string, IConnectionFactory>>(
    delegate (IServiceProvider sp)
    {
        return
            delegate (string key)
            {
                System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService
 <System.Collections.Generic.Dictionary<string, IConnectionFactory>>(sp);

                if (dbs.ContainsKey(key))
                    return dbs[key];

                throw new System.Collections.Generic.KeyNotFoundException(key); // or maybe return null, up to you
            };
    });

Sekarang Anda dapat mengakses tipe Anda dengan baik

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<Func<string, IConnectionFactory>>(serviceProvider)("LogDB");
logDB.Connection

atau

System.Collections.Generic.Dictionary<string, IConnectionFactory> dbs = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider);
dbs["logDB"].Connection

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):

IConnectionFactory logDB = Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<System.Collections.Generic.Dictionary<string, IConnectionFactory>>(serviceProvider)["logDB"];
logDB.Connection

(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:

services.AddSingleton<IConnectionFactory>(new ConnectionFactory("ReadDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("WriteDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("TestDB"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("Analytics"));
services.AddSingleton<IConnectionFactory>(new ConnectionFactory("LogDB"));



// /programming/39174989/how-to-register-multiple-implementations-of-the-same-interface-in-asp-net-core
services.AddTransient<Func<string, IConnectionFactory>>(
    delegate(IServiceProvider sp)
    {
        return
            delegate(string key)
            {
                System.Collections.Generic.IEnumerable<IConnectionFactory> svs = 
                    sp.GetServices<IConnectionFactory>();

                foreach (IConnectionFactory thisService in svs)
                {
                    if (key.Equals(thisService.Name, StringComparison.OrdinalIgnoreCase))
                        return thisService;
                }

                return null;
            };
    });

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

Stefan Steiger
sumber
Jika layanan telah IDisposediterapkan, siapa yang bertanggung jawab untuk membuang layanan? Anda telah mendaftarkan kamus sebagaiSingleton
LP13
@ LP13: Anda juga bisa mendaftarkan kamus dengan delegasi sebagai nilai, kemudian Anda bisa mendaftarkannya di itransient, dan membuat contoh baru, misalnya. GetRequiredService <T> () ["logDB"] ()
Stefan Steiger
5

sejak posting saya di atas, saya telah pindah ke Generic Factory Class

Pemakaian

 services.AddFactory<IProcessor, string>()
         .Add<ProcessorA>("A")
         .Add<ProcessorB>("B");

 public MyClass(IFactory<IProcessor, string> processorFactory)
 {
       var x = "A"; //some runtime variable to select which object to create
       var processor = processorFactory.Create(x);
 }

Penerapan

public class FactoryBuilder<I, P> where I : class
{
    private readonly IServiceCollection _services;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public FactoryBuilder(IServiceCollection services)
    {
        _services = services;
        _factoryTypes = new FactoryTypes<I, P>();
    }
    public FactoryBuilder<I, P> Add<T>(P p)
        where T : class, I
    {
        _factoryTypes.ServiceList.Add(p, typeof(T));

        _services.AddSingleton(_factoryTypes);
        _services.AddTransient<T>();
        return this;
    }
}
public class FactoryTypes<I, P> where I : class
{
    public Dictionary<P, Type> ServiceList { get; set; } = new Dictionary<P, Type>();
}

public interface IFactory<I, P>
{
    I Create(P p);
}

public class Factory<I, P> : IFactory<I, P> where I : class
{
    private readonly IServiceProvider _serviceProvider;
    private readonly FactoryTypes<I, P> _factoryTypes;
    public Factory(IServiceProvider serviceProvider, FactoryTypes<I, P> factoryTypes)
    {
        _serviceProvider = serviceProvider;
        _factoryTypes = factoryTypes;
    }

    public I Create(P p)
    {
        return (I)_serviceProvider.GetService(_factoryTypes.ServiceList[p]);
    }
}

Perpanjangan

namespace Microsoft.Extensions.DependencyInjection
{
    public static class DependencyExtensions
    {
        public static IServiceCollection AddFactory<I, P>(this IServiceCollection services, Action<FactoryBuilder<I, P>> builder)
            where I : class
        {
            services.AddTransient<IFactory<I, P>, Factory<I, P>>();
            var factoryBuilder = new FactoryBuilder<I, P>(services);
            builder(factoryBuilder);
            return services;
        }
    }
}
T Brown
sumber
Bisakah Anda memberikan ekstensi metode .AddFactory ()?
pengembang
Maaf Baru saja melihat ini ... ditambahkan
T Brown
3

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 menyebutnya IServiceAccessorsebagai antarmuka dan kemudian saya harus menambahkan beberapa ekstensi ke IServiceCollection:

public static IServiceCollection AddSingleton<TService, TImplementation, TServiceAccessor>(
            this IServiceCollection services,
            string instanceName
        )
            where TService : class
            where TImplementation : class, TService
            where TServiceAccessor : class, IServiceAccessor<TService>
        {
            services.AddSingleton<TService, TImplementation>();
            services.AddSingleton<TServiceAccessor>();
            var provider = services.BuildServiceProvider();
            var implementationInstance = provider.GetServices<TService>().Last();
            var accessor = provider.GetServices<TServiceAccessor>().First();

            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(TServiceAccessor));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }

            accessor.SetService(implementationInstance, instanceName);
            services.AddSingleton<TServiceAccessor>(prvd => accessor);
            return services;
        }

Accessor layanan terlihat seperti:

 public interface IServiceAccessor<TService>
    {
         void Register(TService service,string name);
         TService Resolve(string name);

    }

Hasil akhirnya, Anda akan dapat mendaftarkan layanan dengan nama atau contoh bernama seperti yang biasa kami lakukan dengan wadah lain .. misalnya:

    services.AddSingleton<IEncryptionService, SymmetricEncryptionService, EncyptionServiceAccessor>("Symmetric");
    services.AddSingleton<IEncryptionService, AsymmetricEncryptionService, EncyptionServiceAccessor>("Asymmetric");

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.

 /// <summary>
    /// Adds the singleton.
    /// </summary>
    /// <typeparam name="TService">The type of the t service.</typeparam>
    /// <typeparam name="TImplementation">The type of the t implementation.</typeparam>
    /// <param name="services">The services.</param>
    /// <param name="instanceName">Name of the instance.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection AddSingleton<TService, TImplementation>(
        this IServiceCollection services,
        string instanceName
    )
        where TService : class
        where TImplementation : class, TService
    {
        var provider = services.BuildServiceProvider();
        var implementationInstance = provider.GetServices<TService>().LastOrDefault();
        if (implementationInstance.IsNull())
        {
            services.AddSingleton<TService, TImplementation>();
            provider = services.BuildServiceProvider();
            implementationInstance = provider.GetServices<TService>().Single();
        }
        return services.RegisterInternal(instanceName, provider, implementationInstance);
    }

    private static IServiceCollection RegisterInternal<TService>(this IServiceCollection services,
        string instanceName, ServiceProvider provider, TService implementationInstance)
        where TService : class
    {
        var accessor = provider.GetServices<IServiceAccessor<TService>>().LastOrDefault();
        if (accessor.IsNull())
        {
            services.AddSingleton<ServiceAccessor<TService>>();
            provider = services.BuildServiceProvider();
            accessor = provider.GetServices<ServiceAccessor<TService>>().Single();
        }
        else
        {
            var serviceDescriptors = services.Where(d => d.ServiceType == typeof(IServiceAccessor<TService>));
            while (serviceDescriptors.Any())
            {
                services.Remove(serviceDescriptors.First());
            }
        }
        accessor.Register(implementationInstance, instanceName);
        services.AddSingleton<TService>(prvd => implementationInstance);
        services.AddSingleton<IServiceAccessor<TService>>(prvd => accessor);
        return services;
    }

    //
    // Summary:
    //     Adds a singleton service of the type specified in TService with an instance specified
    //     in implementationInstance to the specified Microsoft.Extensions.DependencyInjection.IServiceCollection.
    //
    // Parameters:
    //   services:
    //     The Microsoft.Extensions.DependencyInjection.IServiceCollection to add the service
    //     to.
    //   implementationInstance:
    //     The instance of the service.
    //   instanceName:
    //     The name of the instance.
    //
    // Returns:
    //     A reference to this instance after the operation has completed.
    public static IServiceCollection AddSingleton<TService>(
        this IServiceCollection services,
        TService implementationInstance,
        string instanceName) where TService : class
    {
        var provider = services.BuildServiceProvider();
        return RegisterInternal(services, instanceName, provider, implementationInstance);
    }

    /// <summary>
    /// Registers an interface for a class
    /// </summary>
    /// <typeparam name="TInterface">The type of the t interface.</typeparam>
    /// <param name="services">The services.</param>
    /// <returns>IServiceCollection.</returns>
    public static IServiceCollection As<TInterface>(this IServiceCollection services)
         where TInterface : class
    {
        var descriptor = services.Where(d => d.ServiceType.GetInterface(typeof(TInterface).Name) != null).FirstOrDefault();
        if (descriptor.IsNotNull())
        {
            var provider = services.BuildServiceProvider();
            var implementationInstance = (TInterface)provider?.GetServices(descriptor?.ServiceType)?.Last();
            services?.AddSingleton(implementationInstance);
        }
        return services;
    }
Assil
sumber
1
Ini membantu menyelesaikan masalah saya di mana saya kehilangan registrasi jenis di accessor layanan. Caranya adalah dengan menghapus semua binding untuk accessor layanan dan kemudian menambahkannya lagi!
Umar Farooq Khawaja
3

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:

  1. Tambahkan paket nuget Dazinator.Extensions.DependencyInjection ke proyek Anda.
  2. Tambahkan registrasi Layanan Bernama Anda.
    var services = new ServiceCollection();
    services.AddNamed<AnimalService>(names =>
    {
        names.AddSingleton("A"); // will resolve to a singleton instance of AnimalService
        names.AddSingleton<BearService>("B"); // will resolve to a singleton instance of BearService (which derives from AnimalService)
        names.AddSingleton("C", new BearService()); will resolve to singleton instance provided yourself.
        names.AddSingleton("D", new DisposableTigerService(), registrationOwnsInstance = true); // will resolve to singleton instance provided yourself, but will be disposed for you (if it implements IDisposable) when this registry is disposed (also a singleton).

        names.AddTransient("E"); // new AnimalService() every time..
        names.AddTransient<LionService>("F"); // new LionService() every time..

        names.AddScoped("G");  // scoped AnimalService
        names.AddScoped<DisposableTigerService>("H");  scoped DisposableTigerService and as it implements IDisposable, will be disposed of when scope is disposed of.

    });

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:

public MyController(Func<string, AnimalService> namedServices)
{
   AnimalService serviceA = namedServices("A");
   AnimalService serviceB = namedServices("B"); // BearService derives from AnimalService
}

atau

public MyController(NamedServiceResolver<AnimalService> namedServices)
{
   AnimalService serviceA = namedServices["A"];
   AnimalService serviceB = namedServices["B"]; // instance of BearService returned derives from AnimalService
}

Saya telah secara khusus mendesain perpustakaan ini untuk bekerja dengan baik dengan Microsoft.Extensions.DependencyInjection - misalnya:

  1. 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<>dan AddSingleton<>metode bekerja secara normal.

  2. Untuk layanan bernama sementara dan ruang lingkup, registri membangun ObjectFactorysehingga 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.

Darrell
sumber
2

Solusi saya untuk apa nilainya ... dianggap beralih ke Castle Windsor karena tidak bisa mengatakan saya menyukai salah satu solusi di atas. Maaf!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Buat berbagai implementasi Anda

public class YourClassA : IStage<YouClassA> { 
    public void DoSomething() 
    {
        ...TODO
    }
}

public class YourClassB : IStage<YourClassB> { .....etc. }

Registrasi

services.AddTransient<IStage<YourClassA>, YourClassA>()
services.AddTransient<IStage<YourClassB>, YourClassB>()

Konstruktor dan penggunaan instance ...

public class Whatever
{
   private IStage ClassA { get; }

   public Whatever(IStage<YourClassA> yourClassA)
   {
         ClassA = yourClassA;
   }

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }
littgle
sumber
1

Memperluas solusi @rnrneverdies. Alih-alih ToString (), opsi berikut juga dapat digunakan- 1) Dengan implementasi properti bersama, 2) Layanan layanan yang disarankan oleh @Craig Brunetti.

public interface IService { }
public class ServiceA : IService
{
    public override string ToString()
    {
        return "A";
    }
}

public class ServiceB : IService
{
    public override string ToString()
    {
        return "B";
    }

}

/// <summary>
/// extension method that compares with ToString value of an object and returns an object if found
/// </summary>
public static class ServiceProviderServiceExtensions
{
    public static T GetService<T>(this IServiceProvider provider, string identifier)
    {
        var services = provider.GetServices<T>();
        var service = services.FirstOrDefault(o => o.ToString() == identifier);
        return service;
    }
}

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

    services.AddSingleton<IService, ServiceA>();
    services.AddSingleton<IService, ServiceB>();
    services.AddSingleton<IService, ServiceC>();

    var sp = services.BuildServiceProvider();
    var a = sp.GetService<IService>("A"); //returns instance of ServiceA
    var b = sp.GetService<IService>("B"); //returns instance of ServiceB

    //Remaining configurations....
}
vrluckyin
sumber
1

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.

// In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped(IService, ServiceA);
    services.AddScoped(IService, ServiceB);
    services.AddScoped(IService, ServiceC);
}

// Any class that uses the service(s)
public class Consumer
{
    private readonly IEnumerable<IService> _myServices;

    public Consumer(IEnumerable<IService> myServices)
    {
        _myServices = myServices;
    }

    public UseServiceA()
    {
        var serviceA = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceA));
        serviceA.DoTheThing();
    }

    public UseServiceB()
    {
        var serviceB = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceB));
        serviceB.DoTheThing();
    }

    public UseServiceC()
    {
        var serviceC = _myServices.FirstOrDefault(t => t.GetType() == typeof(ServiceC));
        serviceC.DoTheThing();
    }
}
Ciaran Bruen
sumber
Mengalahkan tujuan IoC. Anda mungkin juga cukup menulis:var serviceA = new ServiceA();
James Curran
2
@ JamesCurran bukan jika ServiceA memiliki dependensi, atau jika Anda ingin menguji unit kelas.
Jorn.Beyers
0

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

Andrew Stakhov
sumber
0

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:

public interface INamedService
{
    string Name { get; }
}

public static T GetService<T>(this IServiceProvider provider, string serviceName)
    where T : INamedService
{
    var candidates = provider.GetServices<T>();
    return candidates.FirstOrDefault(s => s.Name == serviceName);
}

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.

Craig Brunetti
sumber
2
Ini disebut pola locator layanan dan biasanya bukan rute terbaik untuk pergi kecuali Anda benar-benar harus
Joe Phillips
@ JoPhillips Apakah Anda punya beberapa masukan mengapa itu bukan solusi yang baik? Saya suka keanggunan itu. Satu-satunya downside yang dapat saya pikirkan adalah bahwa saya membuat contoh dari semua itu setiap kali Anda mendapatkannya.
Peter
2
@ Peter Alasan utamanya adalah karena sangat sulit untuk dikerjakan. Jika Anda melewati objek serviceLocator ke dalam kelas, sama sekali tidak jelas dependensi apa yang digunakan kelas karena ia mendapatkan semuanya dari objek "dewa" ajaib. Bayangkan harus menemukan referensi tipe yang ingin Anda ubah. Kemampuan itu pada dasarnya menghilang ketika Anda mendapatkan semuanya melalui objek pencari layanan. Injeksi konstruktor jauh lebih jelas dan dapat diandalkan
Joe Phillips
Saya tidak tahu. Jelasnya bukan minus bagi saya ... karena jika saya peduli tentang bagaimana komponen saya meningkatkan ketergantungan mereka, saya akan memiliki unit test untuk itu ... tes yang tidak hanya merujuk pada setiap ketergantungan, tetapi membantu kami memahami BAGAIMANA setiap ketergantungan diperlukan. Bagaimana lagi Anda akan menyadari hal itu, dengan membaca konstruktor?!?
Craig Brunetti
0

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:

 var serviceCollection = new ServiceCollection();
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), "A", ServiceLifetime.Transient);
 serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), "B", ServiceLifetime.Transient);

 var serviceProvider = serviceCollection.BuildServiceProvider();

 var myServiceA = serviceProvider.GetService<IMyService>("A");
 var myServiceB = serviceProvider.GetService<IMyService>("B");

Perpustakaan juga memungkinkan Anda untuk menerapkan "pola pabrik" dengan mudah seperti ini:

    [Test]
    public void FactoryPatternTest()
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceA), MyEnum.A.GetName(), ServiceLifetime.Transient);
        serviceCollection.Add(typeof(IMyService), typeof(MyServiceB), MyEnum.B.GetName(), ServiceLifetime.Transient);

        serviceCollection.AddTransient<IMyServiceFactoryPatternResolver, MyServiceFactoryPatternResolver>();

        var serviceProvider = serviceCollection.BuildServiceProvider();

        var factoryPatternResolver = serviceProvider.GetService<IMyServiceFactoryPatternResolver>();

        var myServiceA = factoryPatternResolver.Resolve(MyEnum.A);
        Assert.NotNull(myServiceA);
        Assert.IsInstanceOf<MyServiceA>(myServiceA);

        var myServiceB = factoryPatternResolver.Resolve(MyEnum.B);
        Assert.NotNull(myServiceB);
        Assert.IsInstanceOf<MyServiceB>(myServiceB);
    }

    public interface IMyServiceFactoryPatternResolver : IFactoryPatternResolver<IMyService, MyEnum>
    {
    }

    public class MyServiceFactoryPatternResolver : FactoryPatternResolver<IMyService, MyEnum>, IMyServiceFactoryPatternResolver
    {
        public MyServiceFactoryPatternResolver(IServiceProvider serviceProvider)
        : base(serviceProvider)
        {
        }
    }

    public enum MyEnum
    {
        A = 1,
        B = 2
    }

Semoga ini bisa membantu

Subgurim
sumber
0

Saya membuat ekstensi sendiri di atas ekstensi yang IServiceCollectiondigunakan WithName:

public static IServiceCollection AddScopedWithName<TService, TImplementation>(this IServiceCollection services, string serviceName)
        where TService : class
        where TImplementation : class, TService
    {
        Type serviceType = typeof(TService);
        Type implementationServiceType = typeof(TImplementation);
        ServiceCollectionTypeMapper.Instance.AddDefinition(serviceType.Name, serviceName, implementationServiceType.AssemblyQualifiedName);
        services.AddScoped<TImplementation>();
        return services;
    }

ServiceCollectionTypeMapperadalah contoh tunggal yang peta IService> NameOfService> Implementationdi 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.

 /// <summary>
/// Allows to set the service register mapping.
/// </summary>
public class ServiceCollectionTypeMapper
{
    private ServiceCollectionTypeMapper()
    {
        this.ServiceRegister = new Dictionary<string, Dictionary<string, string>>();
    }

    /// <summary>
    /// Gets the instance of mapper.
    /// </summary>
    public static ServiceCollectionTypeMapper Instance { get; } = new ServiceCollectionTypeMapper();

    private Dictionary<string, Dictionary<string, string>> ServiceRegister { get; set; }

    /// <summary>
    /// Adds new service definition.
    /// </summary>
    /// <param name="typeName">The name of the TService.</param>
    /// <param name="serviceName">The TImplementation name.</param>
    /// <param name="namespaceFullName">The TImplementation AssemblyQualifiedName.</param>
    public void AddDefinition(string typeName, string serviceName, string namespaceFullName)
    {
        if (this.ServiceRegister.TryGetValue(typeName, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out _))
            {
                throw new InvalidOperationException($"Exists an implementation with the same name [{serviceName}] to the type [{typeName}].");
            }
            else
            {
                services.Add(serviceName, namespaceFullName);
            }
        }
        else
        {
            Dictionary<string, string> serviceCollection = new Dictionary<string, string>
            {
                { serviceName, namespaceFullName },
            };
            this.ServiceRegister.Add(typeName, serviceCollection);
        }
    }

    /// <summary>
    /// Get AssemblyQualifiedName of implementation.
    /// </summary>
    /// <typeparam name="TService">The type of the service implementation.</typeparam>
    /// <param name="serviceName">The name of the service.</param>
    /// <returns>The AssemblyQualifiedName of the inplementation service.</returns>
    public string GetService<TService>(string serviceName)
    {
        Type serviceType = typeof(TService);

        if (this.ServiceRegister.TryGetValue(serviceType.Name, out Dictionary<string, string> services))
        {
            if (services.TryGetValue(serviceName, out string serviceImplementation))
            {
                return serviceImplementation;
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

Untuk mendaftarkan layanan baru:

services.AddScopedWithName<IService, MyService>("Name");

Untuk menyelesaikan layanan, kami membutuhkan ekstensi IServiceProviderseperti ini.

/// <summary>
    /// Gets the implementation of service by name.
    /// </summary>
    /// <typeparam name="T">The type of service.</typeparam>
    /// <param name="serviceProvider">The service provider.</param>
    /// <param name="serviceName">The service name.</param>
    /// <returns>The implementation of service.</returns>
    public static T GetService<T>(this IServiceProvider serviceProvider, string serviceName)
    {
        string fullnameImplementation = ServiceCollectionTypeMapper.Instance.GetService<T>(serviceName);
        if (fullnameImplementation == null)
        {
            throw new InvalidOperationException($"Unable to resolve service of type [{typeof(T)}] with name [{serviceName}]");
        }
        else
        {
            return (T)serviceProvider.GetService(Type.GetType(fullnameImplementation));
        }
    }

Ketika menyelesaikan:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

Ingatlah bahwa serviceProvider dapat disuntikkan ke dalam konstruktor di aplikasi kita sebagai IServiceProvider.

Saya harap ini membantu.

svladimirrc
sumber