.NET Core DI วิธีการส่งผ่านพารามิเตอร์ไปยังตัวสร้าง


102

มีตัวสร้างบริการต่อไปนี้

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

     }
}

ทางเลือกในการส่งผ่านพารามิเตอร์โดยใช้กลไก. NET Core IOC คืออะไร

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

มีวิธีอื่น ๆ ?


3
เปลี่ยนการออกแบบของคุณ แยกอาร์กิวเมนต์ลงในออบเจ็กต์พารามิเตอร์และฉีดเข้าไป
Steven

คำตอบ:


121

พารามิเตอร์นิพจน์ ( xในกรณีนี้) ของตัวแทนจากโรงงานคือ a IServiceProvider.

ใช้เพื่อแก้ไขการอ้างอิง

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

ผู้รับมอบอำนาจจากโรงงานเป็นผู้ร้องขอล่าช้า เมื่อใดก็ตามที่จะแก้ไขประเภทจะส่งผ่านผู้ให้บริการที่เสร็จสมบูรณ์เป็นพารามิเตอร์มอบหมาย


1
ใช่นี่เป็นวิธีที่ฉันทำอยู่ตอนนี้ แต่มีวิธีอื่นอีกไหม อาจจะสง่างามกว่านี้? ฉันหมายความว่ามันจะดูแปลกเล็กน้อยที่มีพารามิเตอร์อื่น ๆ ที่ลงทะเบียนบริการ ฉันกำลังมองหาบางอย่างเพิ่มเติมเช่นลงทะเบียนบริการตามปกติและส่งผ่านอาร์กิวเมนต์ที่ไม่ใช่บริการเท่านั้นในกรณีนี้คืออาร์กิวเมนต์ สิ่งที่ Autofac ทำ .WithParameter("argument", "");
boris

1
ไม่มีคุณกำลังสร้างผู้ให้บริการด้วยตนเองซึ่งไม่ดี ผู้รับมอบสิทธิ์คือการร้องขอที่ล่าช้า เมื่อใดก็ตามที่จะแก้ไขประเภทจะส่งผ่านผู้ให้บริการที่เสร็จสมบูรณ์เป็นพารามิเตอร์มอบหมาย
Nkosi

@MCR ซึ่งเป็นแนวทางเริ่มต้นกับ Core DI นอกกรอบ
Nkosi

11
@Nkosi: ดูที่ActivatorUtilities.CreateInstanceซึ่งเป็นส่วนหนึ่งของMicrosoft.Extensions.DependencyInjection.Abstractionsแพ็คเกจ (ดังนั้นจึงไม่มีการพึ่งพาเฉพาะคอนเทนเนอร์)
Tseng

ขอบคุณ @Tseng ที่ดูเหมือนคำตอบจริงที่เรากำลังมองหาที่นี่
BrainSlugs83

59

มันควรจะตั้งข้อสังเกตว่าวิธีที่แนะนำคือการใช้รูปแบบตัวเลือก แต่มีกรณีการใช้งานที่ไม่สามารถใช้งานได้จริง (เมื่อทราบพารามิเตอร์ที่รันไทม์เท่านั้นไม่ใช่ในเวลาเริ่มต้น / คอมไพล์) หรือคุณต้องแทนที่การอ้างอิงแบบไดนามิก

มีประโยชน์มากเมื่อคุณต้องการแทนที่การอ้างอิงเดียว (ไม่ว่าจะเป็นสตริงจำนวนเต็มหรือการอ้างอิงประเภทอื่น) หรือเมื่อใช้ไลบรารีของบุคคลที่สามซึ่งยอมรับเฉพาะพารามิเตอร์สตริง / จำนวนเต็มและคุณต้องการพารามิเตอร์รันไทม์

คุณสามารถลองCreateInstance (IServiceProvider, Object [])เป็นทางลัด(ไม่แน่ใจว่าใช้งานได้กับพารามิเตอร์สตริง / ประเภทค่า / primitives (int, float, string), ยังไม่ทดสอบ) (เพิ่งทดลองใช้และยืนยันว่าใช้งานได้แม้จะมี พารามิเตอร์สตริงหลายตัว)แทนที่จะแก้ไขทุกการพึ่งพาด้วยมือ:

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

พารามิเตอร์ (พารามิเตอร์สุดท้ายของCreateInstance<T>/ CreateInstance) กำหนดพารามิเตอร์ที่ควรเปลี่ยน (ไม่ได้รับการแก้ไขจากผู้ให้บริการ) โดยจะใช้จากซ้ายไปขวาตามที่ปรากฏ (เช่นสตริงแรกจะถูกแทนที่ด้วยพารามิเตอร์ที่พิมพ์สตริงแรกของประเภทที่จะสร้างอินสแตนซ์)

ActivatorUtilities.CreateInstance<Service> ถูกใช้ในหลาย ๆ ที่เพื่อแก้ไขบริการและแทนที่หนึ่งในการลงทะเบียนเริ่มต้นสำหรับการเปิดใช้งานครั้งเดียวนี้

ตัวอย่างเช่นถ้าคุณมีระดับมีชื่อMyServiceและมีIOtherService, ILogger<MyService>เป็นการอ้างอิงและคุณต้องการที่จะแก้ปัญหาการให้บริการ แต่แทนที่บริการเริ่มต้นของIOtherService(พูดของมันOtherServiceA) ด้วยOtherServiceBคุณสามารถทำสิ่งที่ชอบ:

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

จากนั้นพารามิเตอร์แรกของIOtherServiceจะได้รับการOtherServiceBฉีดแทนที่จะOtherServiceAเป็นพารามิเตอร์ที่เหลือจะมาจากคอนเทนเนอร์

สิ่งนี้มีประโยชน์เมื่อคุณมีการอ้างอิงจำนวนมากและต้องการเพียงแค่ปฏิบัติต่อผู้ให้บริการรายเดียวเป็นพิเศษ (เช่นแทนที่ผู้ให้บริการเฉพาะฐานข้อมูลด้วยค่าที่กำหนดค่าในระหว่างการร้องขอหรือสำหรับผู้ใช้เฉพาะบางสิ่งบางอย่างที่คุณรู้ในเวลาดำเนินการและระหว่างการร้องขอและ ไม่ใช่เมื่อแอปพลิเคชันถูกสร้าง / เริ่มต้น)

นอกจากนี้คุณยังสามารถใช้ActivatorUtilities.CreateFactory (ชนิดประเภท []) วิธีการสร้างโรงงานวิธีแทนเพราะมันมีประสิทธิภาพที่ดีขึ้นGitHub อ้างอิงและเกณฑ์มาตรฐาน

ประการต่อมามีประโยชน์เมื่อประเภทได้รับการแก้ไขบ่อยมาก (เช่นใน SignalR และสถานการณ์การร้องขอสูงอื่น ๆ ) โดยทั่วไปคุณจะต้องสร้างObjectFactoryผ่าน

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

จากนั้นแคช (เป็นตัวแปร ฯลฯ ) และเรียกมันเมื่อจำเป็น

MyService myService = myServiceFactory(serviceProvider, myServiceOrParameterTypeToReplace);

## อัปเดต: เพิ่งลองใช้ตัวเองเพื่อยืนยันว่ามันยังทำงานกับสตริงและจำนวนเต็มและมันใช้งานได้ นี่คือตัวอย่างที่เป็นรูปธรรมที่ฉันทดสอบด้วย:

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

พิมพ์

Output: Hello Tseng Stackoverflow

6
นี่เป็นวิธีที่ ASP.NET Core สร้างอินสแตนซ์คอนโทรลเลอร์ตามค่าเริ่มต้นControllerActivatorProviderพวกเขาไม่ได้รับการแก้ไขโดยตรงจาก IoC (เว้นแต่.AddControllersAsServicesจะใช้ซึ่งแทนที่ControllerActivatorProviderด้วยServiceBasedControllerActivator
Tseng

1
ActivatorUtilities.CreateInstance()คือสิ่งที่ฉันต้องการ ขอบคุณ!
Billy Jo

1
@Tseng คุณจะกรุณาตรวจสอบรหัสที่โพสต์ของคุณและโพสต์การปรับปรุง หลังจากสร้างส่วนขยายและคลาสระดับบนสุดของ HellloWorldService แล้วฉันยังต้องเผชิญกับบริการสาธิต HelloWorld ในฐานะที่ไม่ได้กำหนด ฉันไม่เข้าใจว่าสิ่งนี้ควรจะใช้งานได้ดีพอที่จะแก้ไขได้อย่างไร เป้าหมายของฉันคือการเข้าใจว่ากลไกนี้ทำงานอย่างไรตามที่ฉันต้องการ
SOHO Developer

1
@SOHODeveloper: เห็นได้ชัดว่าpublic string HelloWorld()การใช้งานเมธอดหายไป
Tseng

คำตอบนี้สวยหรูกว่าและควรได้รับการยอมรับ ... ขอบคุณ!
Exodium

15

หากคุณรู้สึกไม่สบายใจกับการปรับปรุงบริการใหม่คุณสามารถใช้Parameter Objectรูปแบบนี้ได้

ดังนั้นแยกพารามิเตอร์สตริงเป็นประเภทของตัวเอง

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

และตัวสร้างตอนนี้จะมีลักษณะดังนี้

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

}

และการตั้งค่า

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

ประโยชน์ประการแรกคือหากคุณต้องการเปลี่ยนตัวสร้างบริการและเพิ่มบริการใหม่เข้าไปคุณก็ไม่ต้องเปลี่ยนnew Service(...สาย ข้อดีอีกประการหนึ่งคือการตั้งค่าให้สะอาดขึ้นเล็กน้อย

สำหรับตัวสร้างที่มีพารามิเตอร์เดียวหรือสองตัวสิ่งนี้อาจมากเกินไป


2
มันจะง่ายกว่าสำหรับพารามิเตอร์ที่ซับซ้อนในการใช้รูปแบบตัวเลือกและเป็นวิธีที่แนะนำสำหรับรูปแบบตัวเลือกอย่างไรก็ตามไม่เหมาะสำหรับพารามิเตอร์ที่รู้เฉพาะในรันไทม์เท่านั้น (เช่นจากการร้องขอหรือการอ้างสิทธิ์)
Tseng
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.