.NET Core DI, parametreleri oluşturucuya aktarmanın yolları


102

Aşağıdaki hizmet kurucusuna sahip olmak

public class Service : IService
{
     public Service(IOtherService service1, IAnotherOne service2, string arg)
     {

     }
}

.NET Core IOC mekanizmasını kullanarak parametreleri geçirme seçenekleri nelerdir?

_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService>(x=>new Service( _serviceCollection.BuildServiceProvider().GetService<IOtherService>(), _serviceCollection.BuildServiceProvider().GetService<IAnotherOne >(), "" ));

Başka bir yolu var mı?


3
Tasarımınızı değiştirin. Arg'yi bir Parametre Nesnesine çıkarın ve bunu enjekte edin.
Steven

Yanıtlar:


121

Fabrika temsilcisinin ifade parametresi ( bu durumda x ) a'dır IServiceProvider.

Bağımlılıkları çözmek için bunu kullanın,

_serviceCollection.AddSingleton<IService>(x => 
    new Service(x.GetRequiredService<IOtherService>(),
                x.GetRequiredService<IAnotherOne>(), 
                ""));

Fabrika temsilcisi, gecikmiş bir çağrıdır. Tür çözülecek olduğunda, tamamlanan sağlayıcıyı temsilci parametresi olarak iletecektir.


1
evet, şu anda bunu yapıyorum, ama başka bir yolu var mı? belki daha zarif? Demek istediğim, kayıtlı hizmetler olan diğer parametrelere sahip olmak biraz tuhaf görünebilir. Hizmetleri normal olarak kaydettirmek ve yalnızca hizmet dışı argümanları iletmek gibi bir şey arıyorum, bu durumda arg. AutoFac gibi bir şey yapar .WithParameter("argument", "");
boris

1
Hayır, sağlayıcıyı manuel olarak oluşturuyorsunuz, bu kötü. Temsilci, gecikmiş bir çağrıdır. Tür çözüldüğünde, tamamlanan sağlayıcıyı temsilci parametresi olarak iletecektir.
Nkosi

@MCR, Core DI ile kutudan çıkan varsayılan yaklaşımdır.
Nkosi

11
@Nkosi: ActivatorUtilities.CreateInstance'a bir göz atın , Microsoft.Extensions.DependencyInjection.Abstractionspaketin parçası (yani kapsayıcıya özgü bağımlılıklar yok)
Tseng

Teşekkürler, @Tseng burada aradığımız gerçek cevap gibi görünüyor.
BrainSlugs83

59

Tavsiye edilen yolun Seçenekler Modelini kullanmak olduğuna dikkat edilmelidir . Ancak, pratik olmadığı (parametreler başlangıç ​​/ derleme zamanında değil, yalnızca çalışma zamanında bilindiğinde) veya bir bağımlılığı dinamik olarak değiştirmeniz gereken kullanım durumları vardır.

Tek bir bağımlılığı değiştirmeniz gerektiğinde (bir dize, tam sayı veya başka bir bağımlılık türü) veya yalnızca dize / tamsayı parametrelerini kabul eden bir 3. taraf kitaplığı kullanırken ve çalışma zamanı parametresine ihtiyaç duyduğunuzda çok kullanışlıdır.

CreateInstance'ı (IServiceProvider, Object []) bir kısayol eli olarak deneyebilirsiniz ( dizge parametreleri / değer türleri / ilkeller (int, float, string) ile çalıştığından emin değilim, test edilmemiştir) (Sadece denedim ve çalıştığını doğruladı, her bir bağımlılığı elle çözmek yerine birden çok dize parametresi) :

_serviceCollection.AddSingleton<IService>(x => 
    ActivatorUtilities.CreateInstance<Service>(x, "");
);

Parametreler (son parametre CreateInstance<T>/ ' CreateInstance) değiştirilmelidir parametreleri (sağlayıcıdan çözülmediği) tanımlar. Göründükleri anda soldan sağa uygulanırlar (yani ilk dize, somutlaştırılacak türün ilk dize-tipli parametresi ile değiştirilecektir).

ActivatorUtilities.CreateInstance<Service> birçok yerde bir hizmeti çözümlemek ve bu tek etkinleştirme için varsayılan kayıtlardan birini değiştirmek için kullanılır.

Örneğin bir sınıf adında varsa MyService, ve sahip olduğu IOtherService, ILogger<MyService>bağımlılıklar olarak ve hizmetin çözümlenmesinde ancak varsayılan hizmetini değiştirmek istiyor IOtherService(ITS söylemek OtherServiceAile birlikte) OtherServiceBgibi bir şey yapabileceğini,:

myService = ActivatorUtilities.CreateInstance<Service>(serviceProvider, new OtherServiceB())

Daha sonra ilk parametresi yerine enjekte IOtherServiceedilecek OtherServiceB, OtherServiceAancak kalan parametreler kaptan gelecektir.

Bu, çok sayıda bağımlılığınız olduğunda ve yalnızca tek birini özel olarak ele almak istediğinizde yararlıdır (yani, veritabanına özgü bir sağlayıcıyı, istek sırasında veya belirli bir kullanıcı için yapılandırılan bir değerle değiştirmek, yalnızca çalışma zamanında ve bir istek sırasında bildiğiniz bir şey ve uygulama oluşturulduğunda / başlatıldığında değil).

Daha iyi performans GitHub Reference ve Benchmark sunduğundan, bunun yerine fabrika yöntemi oluşturmak için ActivatorUtilities.CreateFactory (Type, Type []) Yöntemini de kullanabilirsiniz .

Daha sonra, tür çok sık çözüldüğünde (SignalR ve diğer yüksek istek senaryolarında olduğu gibi) kullanışlıdır. Temel olarak bir oluşturmak istiyorum ObjectFactoryaracılığıyla

var myServiceFactory = ActivatorUtilities.CreateFactory(typeof(MyService), new[] { typeof(IOtherService) });

daha sonra önbelleğe alın (değişken olarak vb.) ve gerektiğinde çağırın

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

## Güncelleme: Dizeler ve tamsayılarla da çalıştığını doğrulamak için kendim denedim ve gerçekten işe yarıyor. İşte test ettiğim somut örnek:

class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddTransient<HelloWorldService>();
        services.AddTransient(p => p.ResolveWith<DemoService>("Tseng", "Stackoverflow"));

        var provider = services.BuildServiceProvider();

        var demoService = provider.GetRequiredService<DemoService>();

        Console.WriteLine($"Output: {demoService.HelloWorld()}");
        Console.ReadKey();
    }
}

public class DemoService
{
    private readonly HelloWorldService helloWorldService;
    private readonly string firstname;
    private readonly string lastname;

    public DemoService(HelloWorldService helloWorldService, string firstname, string lastname)
    {
        this.helloWorldService = helloWorldService ?? throw new ArgumentNullException(nameof(helloWorldService));
        this.firstname = firstname ?? throw new ArgumentNullException(nameof(firstname));
        this.lastname = lastname ?? throw new ArgumentNullException(nameof(lastname));
    }

    public string HelloWorld()
    {
        return this.helloWorldService.Hello(firstName, lastName);
    }
}

public class HelloWorldService
{
    public string Hello(string name) => $"Hello {name}";
    public string Hello(string firstname, string lastname) => $"Hello {firstname} {lastname}";
}

// Just a helper method to shorten code registration code
static class ServiceProviderExtensions
{
    public static T ResolveWith<T>(this IServiceProvider provider, params object[] parameters) where T : class => 
        ActivatorUtilities.CreateInstance<T>(provider, parameters);
}

Baskılar

Output: Hello Tseng Stackoverflow

6
Bu da ASP.NET Çekirdek varsayılan tarafından Kontrolörleri başlatır nasıl ControllerActivatorProvider , doğrudan sürece IoC (den çözülmediği .AddControllersAsServicesyerini kullanılır ControllerActivatorProviderileServiceBasedControllerActivator
Tseng

1
ActivatorUtilities.CreateInstance()tam da ihtiyacım olan şey. Teşekkürler!
Billy Jo

1
@Tseng Gönderilen kodunuzu incelemek ve bir güncelleme göndermek için çok nazik misiniz? Uzantı ve HellloWorldService üst düzey sınıflarını yaptıktan sonra, hala demoservice.HelloWorld ile tanımsız olarak karşı karşıyayım. Bunun düzeltmek için yeterince işe yaraması gerektiğini anlamıyorum. Amacım, ihtiyacım olan bu mekanizmanın nasıl çalıştığını anlamak.
SOHO Developer

1
@SOHODeveloper: Açıkçası public string HelloWorld()yöntem uygulaması eksikti
Tseng

Bu cevap daha zarif ve kabul edilmelidir ... Teşekkürler!
Exodium

15

Hizmeti yenilemekten rahatsızsanız, Parameter Objectkalıbı kullanabilirsiniz .

Dize parametresini kendi türüne çıkarın

public class ServiceArgs
{
   public string Arg1 {get; set;}
}

Ve kurucu şimdi şöyle görünecek

public Service(IOtherService service1, 
               IAnotherOne service2, 
               ServiceArgs args)
{

}

Ve kurulum

_serviceCollection.AddSingleton<ServiceArgs>(_ => new ServiceArgs { Arg1 = ""; });
_serviceCollection.AddSingleton<IOtherService , OtherService>();
_serviceCollection.AddSingleton<IAnotherOne , AnotherOne>();
_serviceCollection.AddSingleton<IService, Service>();

İlk faydası, Hizmet oluşturucuyu değiştirmeniz ve ona yeni hizmetler eklemeniz gerekirse, new Service(...aramaları değiştirmeniz gerekmez . Diğer bir faydası da kurulumun biraz daha temiz olmasıdır.

Tek bir veya iki parametresi olan bir kurucu için bu çok fazla olabilir.


2
Karmaşık parametrelerin Seçenekler modelini kullanması daha sezgisel olacaktır ve seçenekler modeli için önerilen yoldur, ancak yalnızca çalışma zamanında bilinen parametreler için daha az uygundur (yani bir talep veya talepten)
Tseng
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.