Asp.Net Core'da aynı arayüzün birden fazla uygulamasını nasıl kaydedebilirim?


239

Aynı arabirimden türetilen hizmetlerim var.

public interface IService { }
public class ServiceA : IService { }
public class ServiceB : IService { } 
public class ServiceC : IService { }

Tipik olarak, gibi diğer IoC kapları, Unitysomut uygulamaları Keyonları ayıran bazı kişiler tarafından kaydetmenize izin verir .

ASP.NET Core'da, bu hizmetleri nasıl kaydedebilirim ve bazı anahtarlara dayanarak çalışma zamanında nasıl çözebilirim?

Genellikle somut uygulamayı ayırt etmek için kullanılacak Addbir keyveya nameparametre alan herhangi bir Hizmet yöntemi görmüyorum .

    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?
       }
    }

Fabrika kalıbı burada tek seçenek mi?

Update1
ben makalesinde rağmen gitti burada biz birden beton uygulamaları varken hizmet örneklerini almak için fabrika deseni nasıl kullanılacağını gösterir. Ancak, hala tam bir çözüm değildir. _serviceProvider.GetService()Yöntemi çağırdığımda, yapıcıya veri enjekte edemiyorum.

Örneğin şunu düşünün:

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

_serviceProvider.GetService()Uygun bağlantı dizesini nasıl enjekte edebilirim ? Unity'de veya başka bir IoC kütüphanesinde, bunu tür kaydında yapabiliriz. Ancak, tüm ayarları enjekte etmemi gerektiren IOption'u kullanabilirim . Hizmete belirli bir bağlantı dizesi enjekte edemiyorum.

Ayrıca, diğer kapları (Unity dahil) kullanmaktan kaçınmaya çalıştığımı unutmayın çünkü o zaman başka her şeyi (örneğin, Denetleyiciler) yeni kapsayıcıya da kaydetmeliyim.

Ayrıca, hizmet örnekleri oluşturmak için fabrika modelini kullanmak, istemcinin burada ayrıntıları verdiği bağımlılık sayısını artırdığından DIP'ye aykırıdır .

ASP.NET Core varsayılan DI iki şey eksik düşünüyorum:

  1. Anahtar kullanarak örnekleri kaydetme yeteneği
  2. Kayıt sırasında yapıcılara statik veri enjekte etme yeteneği

4
İsme

2
Sonunda nuget'te isim tabanlı kayıtlar için bir uzantı var , umarım yardımcı olabilir
neleus

Merhaba, aptalca sorum için üzgünüm, ancak Microsoft.Extensions.DependencyInjection ile ilgili yeni bir şeyim var mı? Iservice'i "ortak arabirim IServiceA: IService" ve "public class ServiceA: IServiceA" gibi genişleten 3 boş arabirim oluşturduğunu düşünüyor musunuz? “... iyi bir uygulama seçeneği olabilir mi?
Emiliano Magliocca

bu makalenin herhangi bir kullanımı var mı? stevejgordon.co.uk/…
Mike B

Can Update1kurucular şeyler enjekte gibi farklı bir soruya taşınacak inşa itiraz hangi çalışma dışarı çok farklıdır
Neil

Yanıtlar:


246

FuncKendimi bu durumda bulduğum zaman kullanarak basit bir çözüm yaptım .

Öncelikle paylaşılan bir temsilci beyan edin:

public delegate IService ServiceResolver(string key);

Ardından Startup.cs, çoklu beton kayıtlarını ve bu türlerin manuel eşlemesini ayarlayın:

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

Ve DI ile kayıtlı herhangi bir sınıftan kullanın:

public class Consumer
{
    private readonly IService _aService;

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

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

Bu örnekte, çözüm anahtarının basitlik uğruna bir dize olduğunu ve OP'nin özellikle bu durumu istediğini unutmayın.

Ancak, genellikle kodunuzu çürüten büyük bir n-case anahtarı istemediğiniz için, herhangi bir özel çözünürlük türünü anahtar olarak kullanabilirsiniz. Uygulamanızın nasıl ölçeklendiğine bağlıdır.


1
@MatthewStevenMonkan cevabımı bir örnekle güncelledi
Miguel A. Arilla

2
Bunun gibi bir fabrika deseni kullanmak en iyi yoldur. Paylaşım için teşekkürler!
Sergey Akopov

2
+1 Çok temiz ve temiz, çünkü başka bir di-konteyner kullandığımızda, bağımlılıkları çözmemiz gerektiğinde paketlerini eklemeliyiz, örn. AutoFac'da ILifetimeScope.
Anupam Singh

1
Bence, .NET Core üzerinde çalışan küçük ve orta ölçekli uygulamaların çoğu herhangi bir DI çerçevesine ihtiyaç duymaz, sadece karmaşıklık ve istenmeyen bağımlılıklar ekler, yerleşik DI'nin güzelliği ve basitliği fazlasıyla yeterlidir ve ayrıca kolaylıkla genişletilebilir.
Miguel A. Arilla

7
Aşağı oy açıklaması - Çok ilginç ama şu anda birileri birkaç yıl önce (MS DI devriminden önce) yaptığı tüm bu Func büyü kaldırmak için büyük bir kod tabanı yeniden düzenleme bu çizgide daha fazla kıvrık DI çözünürlüğüne neden olabilir. Örneğin bir Windows servis işleyicisi üzerinde çalıştım Func ile yapmak için 1.6k kod üzerinde satır vardı ve bunu yaptıktan sonra DI önerilen yolu 0.2k satır indirdi. OK-Kod satırları hiçbir şey ifade etmiyor .. şimdi okumak ve tekrar kullanmak daha kolay ...
Piotr Kula

79

Başka bir seçenek uzatma yöntemini kullanmaktır GetServicesdan Microsoft.Extensions.DependencyInjection.

Hizmetlerinizi şu şekilde kaydedin:

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

Sonra biraz Linq ile çözün:

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

veya

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

( IService"Ad" adlı bir dize özelliğine sahip olduğunu varsayarsak )

Emin olun using Microsoft.Extensions.DependencyInjection;

Güncelleme

AspNet 2.1 kaynağı: GetServices


6
Emin değilim ama bence deterministik değil. Bugün alacağınız sonuçlar yarın değişebilir, iyi bir uygulama gibi görünmüyor.
rnrneverdies

4
GetServices için bağlantıyı oylayın, hangi servislerin bir istekte bulunabileceğinizi gösteren, istek üzerine bana gösterdiIEnumerable<IService>
johnny 5

20
serviceProvider.GetServices <IService> (), ServiceA, ServiceB ve ServiceC öğelerinin her birini başlatır. Sadece bir hizmetin yapıcısını aramak istersiniz - gerçekten ihtiyacınız olanı. Uygulamalar hafif değilse veya birçok IService uygulamanız varsa (örneğin, her model için otomatik olarak IRepository uygulamalarınız varsa) bu büyük bir sorundur.
Uros

6
@Uros ile hemfikirim. Bu iyi bir çözüm değil. 10 IService uygulaması kaydederseniz ve gerçekten ihtiyacınız olan sonuncusuysa ne olacağını düşünün. Bu durumda, DI tarafından hiçbir zaman kullanılmayan 9 örnek oluşturulur.
thomai

4
Kötü fikir: Birden fazla kullanılmayan örnek, servis bulucu anti deseni ve gerçek uygulamaya doğrudan bağlantı (<ServiceA> tipi).
Rico Suter

20

Tarafından desteklenmiyor Microsoft.Extensions.DependencyInjection.

Ancak, StructureMap Bakın Ana sayfası ve GitHub Projesi gibi başka bir bağımlılık enjeksiyon mekanizması ekleyebilirsiniz .

Hiç zor değil:

  1. Şurada StructureMap'e bir bağımlılık ekleyin project.json:

    "Structuremap.Microsoft.DependencyInjection" : "1.0.1",
  2. İçindeki ASP.NET boru hattına enjekte edin ConfigureServicesve sınıflarınızı kaydedin (bkz. Dokümanlar)

    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. Ardından, adlandırılmış bir örnek almak için, IContainer

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

Bu kadar.

Örnek oluşturmak için ihtiyacınız var

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

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

        public string Name {get; set; }
    }

Bu yaklaşımı denedim, ancak denetleyicimde çalışma zamanı hataları alıyorum çünkü IContainer yapı planlarında bulunamadı. IContainer'ın otomatik olarak enjekte edilmesini istemem gereken bir şey var mı?
Mart'ta

BTW, StructureMap.Micorosoft.DependencyInjection 1.3.0 kullanıyorum.
2017'de

Yeni kapsayıcıyı ConfigureServices'te mi iade ediyorsunuz?
Gerardo Grignoli

Yukarıdaki # 2 adımda gösterildiği gibi yeni kabın IServiceProviderInstance iade ediyorum. Bunu sadece türlerim için değiştirerek kopyaladım. Bu iyi bir çözüm ve mükemmel çalışıyor. Tek dezavantajı enjekte edilen bir kap kullanamıyorum ve yapmak istemiyorum statik bir kap için başvuruyorum.
mohrtan

1
Eserleri benim için teşekkürler GerardoGrignoli. @mohrtan hala buna bakıyorsanız örnek kod burada. github.com/Yawarmurtaza/AspNetCoreStructureMap
Yawar Murtaza

13

Doğru, yerleşik ASP.NET Core kapsayıcısı birden fazla hizmeti kaydetme ve daha sonra belirli bir hizmeti alma kavramına sahip değildir, önerdiğiniz gibi bir fabrika bu durumda tek gerçek çözümdür.

Alternatif olarak, ihtiyacınız olan çözümü sağlayan Unity veya StructureMap gibi üçüncü taraf bir konteynere geçebilirsiniz (burada belgelenmiştir: https://docs.asp.net/en/latest/fundamentals/dependency-injection.html?#replacing- the-default-services-container ).


13

Aynı sorunla karşılaştım ve nasıl çözdüğümü ve nedenini paylaşmak istiyorum.

Bahsettiğiniz gibi iki sorun var. İlk:

Asp.Net Core'da bu hizmetleri nasıl kaydedebilirim ve bazı anahtarlara dayanarak çalışma zamanında nasıl çözebilirim?

Peki hangi seçeneklerimiz var? İnsanlar iki öneride bulunur:

  • Özel bir fabrika kullanın (gibi _myFactory.GetServiceByKey(key))

  • Başka bir DI motoru kullanın (gibi _unityContainer.Resolve<IService>(key))

Fabrika kalıbı burada tek seçenek mi?

Aslında her iki seçenek de fabrikalardır çünkü her IoC Container aynı zamanda bir fabrikadır (yine de yapılandırılabilir ve karmaşıktır). Bana öyle geliyor ki diğer seçenekler de Fabrika modelinin varyasyonları.

Peki hangi seçenek daha iyi? Burada özel fabrika kullanmayı öneren @Sock ile hemfikirim ve bu yüzden.

İlk olarak, gerçekten ihtiyaç duyulmadığında yeni bağımlılıklar eklemekten kaçınırım. Bu noktada size katılıyorum. Ayrıca, iki DI çerçevesi kullanmak özel fabrika soyutlaması oluşturmaktan daha kötüdür. İkinci durumda, yeni paket bağımlılığı (Unity gibi) eklemelisiniz, ancak yeni bir fabrika arayüzüne bağlı olarak burada daha az kötülük var. ASP.NET Core DI'nin ana fikri, basitlik olduğuna inanıyorum. KISS prensibini takip eden minimal bir özellik setini korur . Ekstra bir özelliğe ihtiyacınız varsa, DIY yapın veya istenen özelliği uygulayan bir Plungin kullanın (Açık Kapalı Prensibi).

İkincisi, genellikle tek bir hizmet için adlandırılmış birçok bağımlılık enjekte etmemiz gerekir. Birlik durumunda yapıcı parametreleri için adlar belirtmeniz gerekebilir (kullanarak InjectionConstructor). Bu kayıt , yapıcı için argümanları tahmin etmek için yansıma ve bazı akıllı mantık kullanır . Bu, kayıt yapıcı bağımsız değişkenleriyle eşleşmezse çalışma zamanı hatalarına da neden olabilir. Diğer taraftan, kendi fabrikanızı kullanırken, yapıcı parametrelerini nasıl sağlayacağınız konusunda tam kontrole sahipsiniz. Daha okunabilir ve derleme zamanında çözüldü. Yine KISS prensibi .

İkinci sorun:

_ServiceProvider.GetService () uygun bağlantı dizesini nasıl enjekte edebilir?

İlk olarak, IOptions(ve bu nedenle pakette Microsoft.Extensions.Options.ConfigurationExtensions) gibi yeni şeylere bağlı olarak iyi bir fikir olmadığını kabul ediyorum . IOptionsYararları hakkında farklı fikirlerin olduğu yerler hakkında bazı tartışmalar gördüm . Yine, gerçekten ihtiyaç duyulmadığında yeni bağımlılıklar eklemekten kaçınmaya çalışıyorum. Gerçekten gerekli mi? Bence hayır. Aksi takdirde her uygulamanın, bu uygulamadan net bir ihtiyaç duyulmadan ona bağlı olması gerekir (benim için de sizinle aynı fikirde olduğum ISS ihlali gibi görünüyor). Bu aynı zamanda fabrikaya bağlı ama bu durumda yaklaşık doğrudur olabilir kaçınılmalıdır.

ASP.NET Core DI bu amaç için çok güzel bir aşırı yükleme sağlar:

var mongoConnection = //...
var efConnection = //...
var otherConnection = //...
services.AddTransient<IMyFactory>(
             s => new MyFactoryImpl(
                 mongoConnection, efConnection, otherConnection, 
                 s.GetService<ISomeDependency1>(), s.GetService<ISomeDependency2>())));

Merhaba, aptalca sorum için özür dilerim, ama Microsoft.Extensions.DependencyInjection ile ilgili yeniyim ... Iservice'i "ortak arabirim IServiceA: IService" ve "public class ServiceA: IServiceA" gibi genişleten 3 arabirim oluşturduğunu düşünüyor musunuz? ... iyi bir uygulama seçeneği olabilir mi?
Emiliano Magliocca

1
@ emiliano-magliocca Genel olarak, IServiceAsizin durumunuzda kullanmadığınız arabirimlere (ISS) bağımlı olmamalısınız . IServiceSadece metodları kullandığınız için IServicesadece bağımlılığınız olmalıdır .
neleus

1
@ cagatay-kalan OP'nin sorusu durumunda ASP.NET Core DI ile kolayca hedefine ulaşabilir. Başka DI çerçevelerine gerek yok.
neleus

1
@EmilianoMagliocca Bu şekilde kolayca çözülebilir: services.AddTransient<MyFirstClass>( s => new MyFirstClass(s.GetService<Escpos>()));birinci sınıf ve services.AddTransient<MySecondClass>( s => new MySecondClass(s.GetService<Usbpos>()));ikinci sınıf için.
neleus

1
Örneğimdeki @EmilianoMagliocca, hem 'MyFirstClass' hem de 'MySecondClass', hem Escpos hem de Usbpos'un uyguladığı arabirim türünün aynı ctor parametresine sahip. Dolayısıyla yukarıdaki kod yalnızca IoC kapsayıcısına 'MyFirstClass' ve 'MySecondClass' örneklerinin nasıl oluşturulacağını bildirir. Başka bir şey yok. Bu nedenle, başka arayüzleri 'MyFirstClass' ve 'MySecondClass' ile eşlemeniz gerekebilir. Bu sizin ihtiyaçlarınıza bağlıdır ve bunu benim örneğimde ele almadım.
neleus

13

Sadece bir IEnumerable enjekte ediyorum

Startup.cs içindeki ConfigureServices

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

Hizmetler Klasörü

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

Denetleyicinin DoSomething () yönteminde, istediğiniz hizmeti çözümlemek için typeof kullanabilirsiniz: var service = _services.FirstOrDefault (t => t.GetType () == typeof (ServiceA));
Ciaran Bruen

Kelimenin tam anlamıyla her şeyi denedim ve bu benim için çalışan tek çözüm. Teşekkürler!
Skatz1990

@ Skatz1990 Başka bir gönderide aşağıda oluşturduğum çözümü deneyin. Bence kullanımı daha temiz ve basit.
T Brown

12

Buradaki cevapların çoğu tek sorumluluk ilkesini (bir hizmet sınıfı bağımlılıkların kendisini çözmemelidir) ihlal eder ve / veya hizmet bulucu anti-desenini kullanır.

Bu sorunlardan kaçınmak için başka bir seçenek:

  • arabirimde ek bir genel tür parametresi veya genel olmayan arabirimi uygulayan yeni bir arabirim kullanın,
  • işaretleyici türünü eklemek için bir bağdaştırıcı / önleme sınıfı uygulayın ve ardından
  • genel türü "ad" olarak kullan

Daha fazla ayrıntı içeren bir makale yazdım: .NET'te Bağımlılık Enjeksiyonu: Eksik adlandırılmış kayıtlarla çalışmanın bir yolu


kabul edilen cevap tek sorumluluk ilkesini nasıl ihlal eder?
LP13

Stackoverflow.com/a/52066039/876814 yorumlarına bakın ve kabul edilen cevapta hizmet tembel bir şekilde çözülür, yani yalnızca çalışma zamanında başarısız olup olmadığını ve kapsayıcı derlendikten sonra bunu başlangıçta statik olarak kontrol etmenin bir yolu olmadığını bilirsiniz. yorumdaki cevap). SRP, çünkü hizmet sadece iş mantığından değil, aynı zamanda bağımlılık çözümlemesinden de sorumludur
Rico Suter

@RicoSuter Blogunuzdaki çözümü gerçekten çok seviyorum, ancak Başlangıç ​​sınıfındaki DI'nizle kafam karıştı. Özellikle, bu imzalı bir yapıcı görmüyorum çünkü ben MessagePublisher ("MyOrderCreatedQueue") satır anlamıyorum. services.AddSingleton <IMessagePublisher <OrderCreatedMessage>> (yeni MessagePublisher <OrderCreatedMessage> (yeni MessagePublisher ("MyOrderCreatedQueue")));
Lee Z

Teşekkürler, makaleyi güncelleyin ve IMessagePublisher örnek bir uygulama olarak MyMessagePublisher kullanın
Rico Suter

7

Bu partiye biraz geç, ama işte benim çözümüm: ...

Genel İşleyici ise Startup.cs veya Program.cs ...

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

IMyInter Interface T Arabirimi

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

IMyIntface'nin somut uygulamaları

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

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

Umarım bu şekilde yapmakla ilgili herhangi bir sorun varsa, birileri bunun neden yanlış bir yol olduğunu gösterecektir.


2
IMyInterface<CustomerSavedConsumer>ve IMyInterface<ManagerSavedConsumer>olan farklı hizmet türleri - bu hiç de OP soruya cevap vermez.
Richard Hauer

2
OP, aynı arayüzün birden fazla uygulamasını Asp.net çekirdeğine kaydetmenin bir yolunu istedi. Eğer bunu yapmadıysam, nasıl olduğunu açıklayınız.
Gri

1
Doğru olduğunuzda, bu desen op'un istediği etkiye izin verir. En azından bunu kendim yapmaya çalışırken bu yazıya rastladım ve çözümüm durumum için en iyi şekilde çalıştı.
Gri

1
Sorun daha çok tek bir arabirim (MS DI kullanarak) için birden çok uygulama kaydetme kapsayıcı bir uygulama diğerinden ayırt etmek için izin vermiyor olduğunu umuyordum. Diğer DI'lerde bunları kapatabilirsiniz, böylece konteyner hangisini seçeceğinizi bilir. MS size sahip bir temsilci kullanmak ve el seçin. Arabirimleriniz farklı olduğundan çözümünüz bu senaryoyu ele almaz, bu nedenle kapsayıcıda doğru uygulamayı seçmede sorun yoktur. Numuneniz açık bir şekilde çalışıyor olsa da, belirtildiği gibi sorunun çözümü değildir.
Richard Hauer

3
@Gray Gönderiniz kötü bir basına sahip olsa da, bu çözümü öne çıkardığınız için teşekkür ederim. .Net çekirdeklerinde DI sınırlamalarını aşmak için okuyuculara başka bir seçenek sunar. OP sorusuna doğrudan cevap vermese de, mükemmel bir alternatif çözüm sunar, bu SO'nun ne olduğuyla ilgilidir, değil mi?
Neil Watson

5

Görünüşe göre, sadece servis arayüzünüzden IEnumerable enjekte edebilirsiniz! Ve sonra LINQ kullanarak istediğiniz örneği bulun.

Örneğim AWS SNS servisi için ama aynı şeyi enjekte edilen herhangi bir servis için gerçekten yapabilirsiniz.

Başlamak

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",

SNS Fabrikası

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

Artık özel servisinizde veya kontrol cihazınızda istediğiniz bölge için SNS servisini alabilirsiniz

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

     .........
}

5

Fabrika yaklaşımı kesinlikle uygulanabilir. Başka bir yaklaşım, IService'den devralınan, devralınan arabirimleri IService uygulamalarınızda uygulayan ve devralınan arabirimleri taban yerine kaydettirmek için kalıtım kullanmaktır. Kalıtım hiyerarşisi veya fabrikaları eklemek "doğru" kalıp mı, hepsi kime konuştuğunuza bağlıdır. IRepository<T>Veri erişiminin temeli gibi genel bir uygulama kullanan aynı uygulamada birden çok veritabanı sağlayıcısıyla uğraşırken genellikle bu kalıbı kullanmam gerekir .

Örnek arayüzler ve uygulamalar:

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

Konteyner:

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

5

Necromancing.
Sanırım buradaki insanlar tekerleği yeniden icat
ediyorlar - ve kötü, eğer böyle söyleyebilirsem ... Bir bileşeni anahtarla kaydetmek istiyorsanız, sadece bir sözlük kullanın:

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

Ardından sözlüğü servis koleksiyonuna kaydedin:

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

sözlüğü almak ve anahtarla erişmek istemiyorsanız, hizmet koleksiyonuna ek bir anahtar arama yöntemi ekleyerek sözlüğü gizleyebilirsiniz:
(delege / closure kullanımı olası bir bakıcıya neler olduğunu anlamak - ok gösterimi biraz şifreli)

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

Artık türlerinize aşağıdakilerden birini kullanarak erişebilirsiniz:

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

veya

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

Gördüğümüz gibi, birincisi tamamen gereksizdir, çünkü tam olarak bunu bir sözlükle, kapanışlara ve AddTransient'e gerek kalmadan yapabilirsiniz (ve VB kullanırsanız, diş telleri bile farklı olmayacaktır):

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

(daha basit - yine de uzantı yöntemi olarak kullanmak isteyebilirsiniz)

Tabii ki, sözlüğü beğenmediyseniz, arayüzünüzü bir özellikle Name(veya herhangi bir şeyle) düzenleyebilir ve bunu anahtarla arayabilirsiniz :

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

Ancak bu, özelliği barındırmak için arayüzünüzü değiştirmeyi gerektirir ve çok sayıda öğe arasında döngü yapmak, ilişkilendirilebilir dizi aramasından (sözlük) çok daha yavaş olmalıdır.
Yine de, sözlük olmadan yapılabileceğini bilmek güzel.

Bunlar sadece benim 0,05 dolarım


Hizmet IDisposeuygulanmışsa, hizmeti elden çıkarmak kimin sorumluluğundadır? Sen Sözlük kayıt yaptıranSingleton
LP13

@ LP13: Ayrıca sözlüğü bir temsilci ile değer olarak kaydedebilir, daha sonra onu geçici olarak kaydedebilir ve yeni bir örnek oluşturabilirsiniz, örn. GetRequiredService <T> () ["logDB"] ()
Stefan Steiger

5

Yukarıdaki yazımdan beri Genel Fabrika Sınıfına geçtim

kullanım

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

uygulama

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

Uzantı

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

.AddFactory () yöntemi uzantısı sağlayabilir misiniz?
geliştirici

Üzgünüm Sadece gördüm ... eklendi
T Brown

3

@Miguel A. Arilla bunu açıkça belirtti ve ona oy verdim, yararlı çözümünün üstünde düzgün görünen ama çok daha fazla çalışma gerektiren başka bir çözüm oluşturdum.

Kesinlikle yukarıdaki çözüme bağlıdır. Yani temelde benzer bir şey yarattım Func<string, IService>>ve ben IServiceAccessorbir arayüz olarak adlandırdım ve sonra IServiceCollectiongibi bazı daha uzantıları eklemek zorunda kaldı :

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

Hizmet Accessor şöyle görünür:

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

    }

Sonuç olarak, diğer kapsayıcılarla yaptığımız gibi adlara veya adlandırılmış örneklere sahip hizmetleri kaydedebileceksiniz ... örneğin:

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

Şimdilik bu yeterli, ancak çalışmanızı tamamlamak için, aynı yaklaşımı izleyerek her türlü kayıt türünü kapsayacak şekilde daha fazla uzantı yöntemi eklemek daha iyidir.

Stackoverflow üzerinde başka bir yazı vardı, ancak bulamıyorum, posterin bu özelliğin neden desteklenmediğini ve nasıl çalışılacağını ayrıntılarıyla açıkladığı yerde, temelde @Miguel'in belirttiği gibi. Her noktaya katılmama rağmen güzel bir yazıydı çünkü gerçekten adlandırılmış örneklere ihtiyacınız olan bir durum olduğunu düşünüyorum. Tekrar bulduğumda bu bağlantıyı buraya göndereceğim.

Aslında, bu Seçiciyi veya Erişimciyi geçmenize gerek yoktur:

Projemde aşağıdaki kodu kullanıyorum ve şimdiye kadar iyi çalıştı.

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

1
Bu, hizmet erişimcisinde türlerin kaydını kaybettiğim sorunumu çözmemde yardımcı oldu. Hile, hizmet erişimcisi için tüm bağları kaldırmak ve sonra tekrar eklemek oldu!
Umar Farooq Khawaja

3

Bunun için bazı güzel özellikler uygulayan bir kütüphane oluşturdum. Kod GitHub'da bulunabilir: https://github.com/dazinator/Dazinator.Extensions.DependencyInjection NuGet: https://www.nuget.org/packages/Dazinator.Extensions.DependencyInjection/

Kullanımı basittir:

  1. Dazinator.Extensions.DependencyInjection nuget paketini projenize ekleyin.
  2. Adlandırılmış Hizmet kayıtlarınızı ekleyin.
    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.

    });

Yukarıdaki örnekte, adlandırılan her bir kayıt için, ömür boyu veya Singleton, Scoped veya Transient belirttiğinizi de unutmayın.

Hizmetlerin aşağıdaki paketlere bağımlı olmama konusunda rahat olup olmadığınıza bağlı olarak hizmetleri iki yoldan biriyle çözebilirsiniz:

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

veya

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

Bu kitaplığı özellikle Microsoft.Extensions.DependencyInjection ile iyi çalışacak şekilde tasarladım - örneğin:

  1. Adlandırılmış hizmetlerini kayıt olduğunda, kayıt herhangi tipleri parametrelerle kurucuya sahip olabilir - onlar aynı şekilde, DI aracılığıyla memnun olacak AddTransient<>, AddScoped<>ve AddSingleton<>yöntemler genellikle çalışır.

  2. Geçici ve kapsamlı adlandırılmış hizmetler için kayıt defteri, ObjectFactorygerektiğinde türün yeni örneklerini çok hızlı bir şekilde etkinleştirebilmesi için bir yapı oluşturur . Bu, diğer yaklaşımlardan çok daha hızlıdır ve Microsoft.Extensions.DependencyInjection'ın işleri nasıl yaptığına uygundur.


2

Değeri için çözümüm ... Yukarıdaki çözümlerden herhangi birini sevdiğimi söyleyemediğim için Windsor Kalesi'ne geçmeyi düşündüm. Afedersiniz!!

public interface IStage<out T> : IStage { }

public interface IStage {
      void DoSomething();
}

Çeşitli uygulamalarınızı oluşturun

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

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

kayıt

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

Yapıcı ve örnek kullanımı ...

public class Whatever
{
   private IStage ClassA { get; }

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

   public void SomeWhateverMethod()
   {
        ClassA.DoSomething();
        .....
   }

1

@Rnrneverdies çözümünün genişletilmesi. ToString () yerine, aşağıdaki seçenekler de kullanılabilir- 1) Ortak mülkiyet uygulamasıyla, 2) @Craig Brunetti tarafından önerilen servisler.

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

1

Burada ve başka yerlerde makaleleri okuduktan sonra dizeleri olmadan çalışmasını başardı. Aynı arayüzde birden fazla uygulamanız olduğunda DI bunları bir koleksiyona ekleyecektir, bu nedenle koleksiyondan istediğiniz sürümü almak mümkündür 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();
    }
}

IoC'nin amacını yener. Siz de yazabilirsiniz:var serviceA = new ServiceA();
James Curran

2
@JamesCurran, ServiceA'nın bağımlılıkları varsa veya sınıfı test etmek istiyorsanız değil.
Jorn.Beyers

0

Kutudan çıktığı uygulama bunu sunmasa da, burada adlandırılmış örnekleri kaydetmenizi ve ardından kodunuza INamedServiceFactory'yi enjekte etmenizi ve örnekleri ada göre çekmenizi sağlayan örnek bir proje. Buradaki diğer facory çözümlerinden farklı olarak, aynı uygulamanın birden fazla örneğini kaydetmenize izin verir, ancak farklı yapılandırılır

https://github.com/macsux/DotNetDINamedInstances


0

Hizmetler için bir hizmete ne dersiniz?

Bir INamedService arabirimimiz (.Name özelliği ile) olsaydı, .GetService (dize adı) için bir IServiceCollection uzantısı yazabiliriz, burada uzantının bu dize parametresini alacağını ve kendi başına ve her birinde bir .GetServices () gerçekleştirebiliriz. örneğinde, INamedService.Name belirtilen adla eşleşen örneği bulun.

Bunun gibi:

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

Bu nedenle, IMyService'inizin INamedService uygulamasını uygulaması gerekir, ancak istediğiniz anahtar tabanlı çözünürlüğü alırsınız, değil mi?

Adil olmak gerekirse, bu INamedService arayüzüne bile sahip olmak çirkin görünüyor, ancak daha ileri gitmek ve işleri daha zarif hale getirmek istiyorsanız, uygulama / sınıftaki bir [NamedServiceAttribute ("A")] buradaki kod tarafından bulunabilir. ve aynı şekilde çalışır. Daha adil olmak gerekirse, Yansıma yavaştır, bu nedenle bir optimizasyon yapılabilir, ancak dürüst olmak gerekirse, DI motorunun yardımcı olması gereken bir şeydir. Hız ve basitlik, toplam sahip olma maliyetine büyük katkıda bulunur.

Sonuç olarak, açık bir fabrikaya gerek yoktur, çünkü "adlandırılmış bir hizmet bulmak" çok yeniden kullanılabilir bir kavramdır ve fabrika sınıfları bir çözüm olarak ölçeklenmez. Ve bir Func <> cezası gibi görünüyor, ama bir switch bloğu böyledir bleh sık sık yazmakta olurdum olarak Fabrikaları olarak Funcs yazılı olacak, yine, ve. Basit, yeniden kullanılabilir, daha az kodla başlayın ve bu sizin için yapmazsa, karmaşık olun.


2
Bu servis bulucu desen denir ve kesinlikle gerekmedikçe gitmek için en iyi yol değildir
Joe Phillips

@JoePhillips Neden iyi bir çözüm olmadığına dair bir bilginiz var mı? onun zarafetini seviyorum. I-ebilmek düşün tek dezavantajı ben bir olsun her hepsinin bir örneğini oluşturmak olmasıdır.
Peter

2
@Peter Temel nedeni, çalışmak çok zor olmasıdır. Bir serviceLocator nesnesini bir sınıfa geçiriyorsanız, sınıfın sihirli bir "tanrı" nesnesinden aldığı için hangi bağımlılıkların kullandığı açık değildir. Değiştirmek istediğiniz türün referanslarını bulmak zorunda olduğunuzu düşünün. Bu özellik, her şeyi bir servis bulucu nesnesi aracılığıyla aldığınızda temel olarak kaybolur. Yapıcı enjeksiyonu çok daha net ve güvenilir
Joe Phillips

Bilmiyorum. Açıklık benim için bir eksi değil ... çünkü bileşenlerimin bağımlılıklarından nasıl yararlandığını takip edersem, bunun için birim testlerim olurdu ... sadece her bağımlılığa atıfta bulunmayan, aynı zamanda anlamamıza yardımcı olan testler Her bağımlılık NASIL gereklidir. Yapıcıları okuyarak bundan başka nasıl haberdar olacaksınız?!?
Craig Brunetti

0

Aynı sorunla karşılaştım ve Adlandırılmış hizmetlere izin vermek için basit bir uzantıyla çalıştım. Burada bulabilirsiniz:

İstediğiniz sayıda (adlandırılmış) hizmet eklemenize olanak tanır:

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

Kütüphane ayrıca aşağıdaki gibi bir "fabrika modeli" uygulamasını kolayca yapmanızı sağlar:

    [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
    }

Umarım yardımcı olur


0

IServiceCollectionKullanılan WithNameuzantı üzerinden kendi uzantımı oluşturdum :

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

ServiceCollectionTypeMapperharitalar bir tekil örneğidir IService> NameOfService> Implementationbiz çözebilirsiniz zaman çiş ihtiyaç ve ne istediğimizi seçmek için kararlılık Birden fazla hizmet farklı bir yaklaşımdır daha bir arayüz farklı adlarla birçok uygulamalar olabilir, bu tip kaydedilmesini sağlar.

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

Yeni bir hizmet kaydetmek için:

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

Hizmeti çözmek için bunun IServiceProvidergibi bir uzantıya ihtiyacımız var .

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

Çözüldüğünde:

serviceProvider.GetService<IWithdrawalHandler>(serviceName);

ServiceProvider uygulamamızdaki bir kurucuya enjekte edilebilir IServiceProvider.

Umarım bu yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.