วิธีการแก้ไข Instance Inside ConfigureServices ใน ASP.NET Core


116

เป็นไปได้ไหมที่จะแก้ไขอินสแตนซ์IOptions<AppSettings>จากConfigureServicesเมธอดใน Startup? โดยปกติคุณสามารถใช้IServiceProviderเพื่อเริ่มต้นอินสแตนซ์ แต่คุณไม่มีในขั้นตอนนี้เมื่อคุณลงทะเบียนบริการ

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<AppSettings>(
        configuration.GetConfigurationSection(nameof(AppSettings)));

    // How can I resolve IOptions<AppSettings> here?
}

คำตอบ:


166

คุณสามารถสร้างผู้ให้บริการโดยใช้BuildServiceProvider()วิธีการในIServiceCollection:

public void ConfigureService(IServiceCollection services)
{
    // Configure the services
    services.AddTransient<IFooService, FooServiceImpl>();
    services.Configure<AppSettings>(configuration.GetSection(nameof(AppSettings)));

    // Build an intermediate service provider
    var sp = services.BuildServiceProvider();

    // Resolve the services from the service provider
    var fooService = sp.GetService<IFooService>();
    var options = sp.GetService<IOptions<AppSettings>>();
}

คุณต้องการMicrosoft.Extensions.DependencyInjectionแพ็คเกจนี้


ในกรณีที่คุณต้องการผูกตัวเลือกบางอย่างไว้ConfigureServicesคุณสามารถใช้Bindวิธีการ:

var appSettings = new AppSettings();
configuration.GetSection(nameof(AppSettings)).Bind(appSettings);

ฟังก์ชันนี้พร้อมใช้งานผ่านMicrosoft.Extensions.Configuration.Binderแพ็คเกจ


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

2
@Ray จากนั้นคุณสามารถใช้กลไกการฉีดพึ่งพาเริ่มต้นเช่นการฉีดตัวสร้าง คำถามนี้เกี่ยวกับการแก้ไขบริการภายในConfigureServicesเมธอดโดยเฉพาะ
Henk Mollema

@pcdev คุณจะได้รับ NULL เมื่อคุณทำเช่นนั้นแล้วลองแก้ไขอินสแตนซ์ คุณต้องเพิ่มบริการก่อน
IngoB

17
แม้ว่าวิธีนี้อาจมีประโยชน์ในกรณีที่วิธีการเพิ่มบริการไม่มีการใช้งานเกินพิกัดจากโรงงาน (เช่นที่นี่ ) แต่การใช้BuildServiceProviderจะทำให้เกิดคำเตือนหากใช้ในรหัสแอปพลิเคชันเช่นConfigureServicesส่งผลให้มีสำเนาเพิ่มเติมของบริการซิงเกิลตัน สร้างขึ้น คำตอบของ Ehsan Mirsaeedi นี่คือทางออกที่ดีที่สุดสำหรับกรณีเช่นนี้
นีโอ

1
คำตอบนี้ไม่ถูกต้องหากมีการลงทะเบียนแบบซิงเกิลตันอาจทำให้เกิดซิงเกิลตันหลายครั้ง @ ehsan-mirsaeedi คำตอบดีกว่าและควรเป็นคำตอบที่ยอมรับได้
Fab

78

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

ตัวอย่างต่อไปนี้แสดงให้เห็นว่าคุณสามารถใช้เกินนี้ในAddSingleton / AddTransientวิธี

services.AddSingleton(serviceProvider =>
{
    var options = serviceProvider.GetService<IOptions<AppSettings>>();
    var foo = new Foo(options);
    return foo ;
});


services.AddTransient(serviceProvider =>
{
    var options = serviceProvider.GetService<IOptions<AppSettings>>();
    var bar = new Bar(options);
    return bar;
});

20
ใช้โซลูชันนี้แทนคำตอบที่ยอมรับสำหรับ. Net Core 3 หรือสูงกว่า!
Joshit

12
@ Joshit ฉันไม่แน่ใจว่านี่เป็นการแทนที่คำตอบที่ยอมรับได้ในทุกสถานการณ์ IServiceProvider มีให้สำหรับเช่น AddSingleton, AddScoped, AddTransient แต่ยังมีวิธีการเพิ่มอื่น ๆ อีกมากมายที่ไม่ให้การโอเวอร์โหลดนี้ ได้แก่ AddCors, AddAuthentication, AddAuthorization
Jpsy

1
@Jpsy คุณผสมสิ่งที่ไม่เกี่ยวข้อง AddCors, AddAuthentication และอื่น ๆ เป็นตัวช่วยที่เรียกภายใต้ emthods การลงทะเบียนเพื่อเชื่อมต่อมิดเดิลแวร์พื้นฐาน AddTransient, AddSingleton, AddScoped เป็นการลงทะเบียนสามรายการ (โดยมีช่วงอายุการใช้งานที่ใช้กันทั่วไป 3 รายการ)
Fab

ทั้งนี้ไม่ครอบคลุมทุกกรณี โปรดอ้างอิงคำตอบของฉันสำหรับวิธีแก้ปัญหาที่ทำได้
Ian Kemp

12

วิธีที่ง่ายและถูกต้องที่สุดในการดำเนินการนี้ใน ASP.NET Core ทุกเวอร์ชันคือการติดตั้งIConfigureOptions<TOptions>อินเทอร์เฟซ แม้ว่าสิ่งนี้จะมีมาตั้งแต่. NET Core 1.0 แต่ดูเหมือนว่ามีเพียงไม่กี่คนที่รู้ว่ามันทำให้ Just Work ™เป็นอย่างไร

ตัวอย่างเช่นคุณต้องการเพิ่มเครื่องมือตรวจสอบโมเดลที่กำหนดเองซึ่งมีการพึ่งพาบริการอื่น ๆ ของแอปพลิเคชันของคุณ ในตอนแรกดูเหมือนจะเป็นไปไม่ได้ - ไม่มีวิธีแก้ไขIMyServiceDependencyเนื่องจากคุณไม่สามารถเข้าถึงIServiceProvider:

public class MyModelValidatorProvider : IModelValidatorProvider
{
    public MyModelValidatorProvider(IMyServiceDependency dependency)
    {
        ...
    }
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(options =>
    {
        options.ModelValidatorProviders.Add(new MyModelValidatorProvider(??????));
    });
}

แต่ "เวทมนตร์" ของIConfigureOptions<TOptions>มันทำให้ง่ายมาก:

public class ConfigureMvcOptions : IConfigureOptions<MvcOptions>
{
    private IMyServiceDependency _dependency;

    public MyMvcOptions(IMyServiceDependency dependency)
        => _dependency = dependency;

    public void Configure(MvcOptions options)
        => options.ModelValidatorProviders.Add(new MyModelValidatorProvider(_dependency));
}

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    ...

    // or scoped, or transient, as necessary for your service
    services.AddSingleton<IConfigureOptions<MvcOptions>, ConfigureMvcOptions>();
}

โดยพื้นฐานแล้วการตั้งค่าใด ๆ ที่คุณเคยทำในAdd***(***Options)ผู้รับมอบสิทธิ์ConfigureServicesจะถูกย้ายไปที่เมธอดIConfigureOptions<TOptions>ของชั้นเรียน Configureจากนั้นคุณลงทะเบียนตัวเลือกด้วยวิธีเดียวกับที่คุณลงทะเบียนบริการอื่น ๆ และไปเลย!

สำหรับรายละเอียดเพิ่มเติมรวมถึงข้อมูลเกี่ยวกับวิธีการทำงานเบื้องหลังฉันขอแนะนำให้คุณรู้จักกับ Andrew Lock ที่ยอดเยี่ยมเสมอ


จะเกิดอะไรขึ้นถ้าคุณต้องการเพียงออบเจ็กต์คอนฟิกูเรชันเพื่ออ่านในขณะที่เรียกวิธีการขยายไลบรารีอื่น ๆ มีวิธีใดในการแสดงผล / แก้ไขการกำหนดค่าเพื่อแจ้งค่าที่คุณระบุขณะลงทะเบียนบริการอื่น ๆ หรือไม่?
Alexander Trauzzi

@AlexanderTrauzzi เพียงแค่ส่ง IConfiguration ไปยัง ConfigureServices - IOC จะจัดเตรียมให้
Chazt3n

วิธีใดในการเป็นผู้เขียนห้องสมุดเนื่องจากมันจะอยู่ในวิธีการขยาย?
Alexander Trauzzi

@AlexanderTrauzzi ฉันขอแนะนำให้คุณถามคำถามที่มีรายละเอียดว่าคุณพยายามทำอะไรให้สำเร็จ
Ian Kemp

1

คุณกำลังมองหาสิ่งที่ต้องการต่อไปนี้หรือไม่? คุณสามารถดูความคิดเห็นของฉันในรหัส:

// this call would new-up `AppSettings` type
services.Configure<AppSettings>(appSettings =>
{
    // bind the newed-up type with the data from the configuration section
    ConfigurationBinder.Bind(appSettings, Configuration.GetConfigurationSection(nameof(AppSettings)));

    // modify these settings if you want to
});

// your updated app settings should be available through DI now

-1

ต้องการช่วยเหลือผู้อื่นที่มองเหมือนกัน แต่เมื่อใช้ Autofac ด้วย

หากคุณต้องการรับ ILifetimeScope (เช่นคอนเทนเนอร์ของขอบเขตปัจจุบัน) คุณต้องเรียกapp.ApplicationServices.GetAutofacRoot()เมธอดในConfigure(IApplicationBuilder app)สิ่งนี้จะส่งคืนอินสแตนซ์ ILifetimeScope ที่คุณสามารถใช้เพื่อแก้ไขเซอร์วิส

public void Configure(IApplicationBuilder app)
    {
        //app middleware registrations 
        //...
        //

        ILifetimeScope autofacRoot = app.ApplicationServices.GetAutofacRoot();
        var repository = autofacRoot.Resolve<IRepository>();
    }

2
คำตอบนี้เฉพาะเจาะจงเกินไปสำหรับ AutoFac ซึ่งไม่อยู่ในขอบเขตของคำถามนี้
Pure.Krome

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