การแก้ไขอินสแตนซ์ด้วย ASP.NET Core DI


302

ฉันจะแก้ไขชนิดด้วยตนเองโดยใช้เฟรมเวิร์กการฉีดพึ่งพาของ ASP.NET Core MVC ได้อย่างไร

การตั้งค่าคอนเทนเนอร์นั้นง่ายพอ:

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

    services.AddTransient<ISomeService, SomeConcreteService>();
}

แต่ฉันจะแก้ไขISomeServiceได้อย่างไรถ้าไม่ฉีดยา? ตัวอย่างเช่นฉันต้องการทำสิ่งนี้:

ISomeService service = services.Resolve<ISomeService>();

IServiceCollectionไม่มีวิธีการดังกล่าวในการเป็น



3
คุณต้องการที่จะแก้ไขพวกเขาในConfigureServices()วิธีการ (พร้อมIServiceCollection) หรือที่ใดก็ได้ในแอปพลิเคชันหรือไม่
Henk Mollema

2
@HenkMollema: ทุกที่ในการเริ่มต้นจริง
เดฟใหม่

คำตอบ:


484

IServiceCollectionอินเตอร์เฟซที่ใช้สำหรับการสร้างภาชนะฉีดพึ่งพา หลังจากสร้างเสร็จสมบูรณ์แล้วมันจะประกอบไปด้วยIServiceProviderอินสแตนซ์ที่คุณสามารถใช้เพื่อแก้ไขบริการ คุณสามารถฉีดIServiceProviderเข้าไปในชั้นเรียนใดก็ได้ IApplicationBuilderและHttpContextเรียนสามารถให้ผู้ให้บริการเช่นกันผ่านทางของพวกเขาApplicationServicesหรือRequestServicesคุณสมบัติตามลำดับ

IServiceProviderกำหนดGetService(Type type)วิธีการแก้ปัญหาบริการ:

var service = (IFooService)serviceProvider.GetService(typeof(IFooService));

นอกจากนี้ยังมีวิธีการขยายความสะดวกหลายอย่างเช่นserviceProvider.GetService<IFooService>()(เพิ่มusingสำหรับMicrosoft.Extensions.DependencyInjection)

การแก้ไขบริการภายในคลาสเริ่มต้น

การฉีดการพึ่งพา

โฮสติ้งผู้ให้บริการรันไทม์สามารถฉีดบริการบางอย่างลงในตัวสร้างของStartupระดับเช่นIConfiguration, IWebHostEnvironment( IHostingEnvironmentใน pre-3.0 รุ่น) และILoggerFactory IServiceProviderหมายเหตุว่าหลังเป็นตัวอย่างที่สร้างขึ้นโดยชั้นโฮสติ้งและมีเพียงบริการที่จำเป็นสำหรับการเริ่มต้นขึ้นแอพลิเคชัน

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

public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }

public void ConfigureServices(IServiceCollection services)
{
    // Use Configuration here
}

บริการใด ๆ ที่ลงทะเบียนในConfigureServices()นั้นจะถูกฉีดเข้าไปในConfigure()วิธีการนั้น คุณสามารถเพิ่มจำนวนบริการโดยพลการหลังจากIApplicationBuilderพารามิเตอร์:

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IFooService>();
}

public void Configure(IApplicationBuilder app, IFooService fooService)
{
    fooService.Bar();
}

การแก้ไขการอ้างอิงด้วยตนเอง

หากคุณต้องการให้การบริการและการแก้ปัญหาด้วยตนเองคุณควรใช้ApplicationServicesให้บริการโดยIApplicationBuilderในConfigure()วิธีการ:

public void Configure(IApplicationBuilder app)
{
    var serviceProvider = app.ApplicationServices;
    var hostingEnv = serviceProvider.GetService<IHostingEnvironment>();
}

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

public Startup(IServiceProvider serviceProvider)
{
    var hostingEnv = serviceProvider.GetService<IWebHostEnvironment>();
}

หากคุณต้องแก้ไขบริการในConfigureServices()วิธีการนั้นจำเป็นต้องใช้วิธีการอื่น คุณสามารถสร้างสื่อกลางIServiceProviderจากIServiceCollectionอินสแตนซ์ที่มีบริการที่ลงทะเบียนจนถึงจุดนั้น :

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IFooService, FooService>();

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

    // This will succeed.
    var fooService = sp.GetService<IFooService>();
    // This will fail (return null), as IBarService hasn't been registered yet.
    var barService = sp.GetService<IBarService>();
}

โปรดทราบ: โดยทั่วไปคุณควรหลีกเลี่ยงการแก้ไขบริการภายในConfigureServices()วิธีนี้เนื่องจากเป็นสถานที่ที่คุณกำหนดค่าแอปพลิเคชันบริการ บางครั้งคุณต้องเข้าถึงIOptions<MyOptions>อินสแตนซ์ คุณสามารถทำได้โดยผูกค่าจากIConfigurationอินสแตนซ์กับอินสแตนซ์ของMyOptions(ซึ่งเป็นสิ่งสำคัญที่เฟรมเวิร์กตัวเลือกทำ):

public void ConfigureServices(IServiceCollection services)
{
    var myOptions = new MyOptions();
    Configuration.GetSection("SomeSection").Bind(myOptions);
}

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


14
@ HenkMollema แต่ถ้าฉันไม่สามารถฉีดอะไรได้ฉันหมายความว่าฉันไม่สามารถIServiceCollectionฉีดได้บางคลาสที่สร้างขึ้นด้วยตนเอง ( นอกขอบเขตของเครื่องพัสดุ ) ตัวจัดตารางเวลาในกรณีของฉันซึ่งต้องการบริการบางอย่างในการสร้างเป็นระยะ และส่งอีเมล
Merdan Gochmuradov

52
เตือนถ้าคุณต้องการแก้ไขบริการConfigureServicesและบริการนั้นเป็นซิงเกิลมันจะเป็นซิงเกิลที่แตกต่างจากที่คุณControllerใช้! ฉันถือว่านี่เป็นเพราะมันใช้ที่แตกต่างกันIServiceProvider- เพื่อหลีกเลี่ยงสิ่งนี้ไม่ได้แก้ไขผ่านBuildServiceProviderและแทนที่จะย้ายการค้นหาของคุณจากเดี่ยวConfigureServicesไปยังConfigure(..other params, IServiceProvider serviceProvider)ในStartup.cs
wal

3
@ เป็นจุดที่ดี เพราะมันเป็นIServiceProviderอินสแตนซ์ที่แตกต่างกันมันจะสร้างอินสแตนซ์ซิงเกิลใหม่ คุณสามารถหลีกเลี่ยงปัญหานี้ได้โดยส่งคืนอินสแตนซ์ของผู้ให้บริการจากConfigureServicesวิธีการเพื่อให้เป็นคอนเทนเนอร์ของแอปพลิเคชันของคุณเช่นกัน
Henk Mollema

1
การกล่าวอ้างcollection.BuildServiceProvider();เป็นสิ่งที่ฉันต้องการขอบคุณ!
Chris Marisic

2
@HenkMollema คุณจะทำงานกับผู้ให้บริการเพียงรายเดียวได้อย่างไร โดยทั่วไปแล้วคุณจะ 1) ลงทะเบียนการพึ่งพาของคุณ 2) สร้างอินสแตนซ์ของผู้ให้บริการชั่วคราว 3) ใช้ผู้ให้บริการนั้นเพื่อแก้ไขบางสิ่งที่คุณต้องลงทะเบียนการพึ่งพาอื่น ๆ หลังจากนั้นคุณไม่สามารถส่งคืนอินสแตนซ์ชั่วคราวเนื่องจากไม่มีการอ้างอิงบางส่วนของคุณ (ลงทะเบียนใน 3) ฉันพลาดอะไรไปรึเปล่า?
ฟิลิป

109

การแก้ไขอินสแตนซ์ด้วยตนเองนั้นเกี่ยวข้องกับการใช้IServiceProviderอินเตอร์เฟส:

การแก้ไขการพึ่งพาใน Startup.ConfigureServices

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IMyService, MyService>();

    var serviceProvider = services.BuildServiceProvider();
    var service = serviceProvider.GetService<IMyService>();
}

การแก้ไขการอ้างอิงใน Startup.Configure

public void Configure(
    IApplicationBuilder application,
    IServiceProvider serviceProvider)
{
    // By type.
    var service1 = (MyService)serviceProvider.GetService(typeof(MyService));

    // Using extension method.
    var service2 = serviceProvider.GetService<MyService>();

    // ...
}

การแก้ไขการอ้างอิงใน Startup.Configure ใน ASP.NET Core 3

public void Configure(
    IApplicationBuilder application,
    IWebHostEnvironment webHostEnvironment)
{
    app.ApplicationServices.GetService<MyService>();
}

การใช้บริการ Injected Runtime

บางประเภทสามารถฉีดเป็นพารามิเตอร์วิธี:

public class Startup
{
    public Startup(
        IHostingEnvironment hostingEnvironment,
        ILoggerFactory loggerFactory)
    {
    }

    public void ConfigureServices(
        IServiceCollection services)
    {
    }

    public void Configure(
        IApplicationBuilder application,
        IHostingEnvironment hostingEnvironment,
        IServiceProvider serviceProvider,
        ILoggerFactory loggerfactory,
        IApplicationLifetime applicationLifetime)
    {
    }
}

การแก้ไขการอ้างอิงในการดำเนินการของคอนโทรลเลอร์

[HttpGet("/some-action")]
public string SomeAction([FromServices] IMyService myService) => "Hello";

1
@AfsharMohebbi GetServiceซึ่งเป็น generic เป็นวิธีการขยายในMicrosoft.Extensions.DependencyInjectionnamespace
ahmadali shafiee

เกี่ยวกับวิธีการขยาย: วิธีการขยายเป็นวิธีคงที่ที่เพิ่ม funcionality ให้กับคลาสคุณสามารถประกาศสาธารณะคงที่ TheReturnType TheMethodName (TheTypeYouExtend theTypeYouExtend {// BODY} นี้แล้วคุณสามารถใช้มันเช่น: TheTypeYouExtend.TheMethodName (); กลายเป็น Aproach พบบ่อยมากกับ .NET หลักเพื่อให้นักพัฒนาสามารถขยายการทำงานฐาน ... ตัวอย่างที่ดีที่นี่: docs.microsoft.com/en-us/dotnet/csharp/programming-guide/...

17

หากคุณสร้างแอปพลิเคชันด้วยเทมเพลตคุณจะมีสิ่งนี้ในStartupชั้นเรียน:

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddApplicationInsightsTelemetry(Configuration);

    services.AddMvc();
}

จากนั้นคุณสามารถเพิ่มการอ้างอิงที่นั่นตัวอย่างเช่น:

services.AddTransient<ITestService, TestService>();

หากคุณต้องการเข้าถึงITestServiceตัวควบคุมของคุณคุณสามารถเพิ่มIServiceProviderตัวสร้างและมันจะถูกฉีด:

public HomeController(IServiceProvider serviceProvider)

จากนั้นคุณสามารถแก้ไขบริการที่คุณเพิ่ม:

var service = serviceProvider.GetService<ITestService>();

โปรดทราบว่าในการใช้รุ่นทั่วไปคุณจะต้องรวมเนมสเปซกับส่วนขยาย:

using Microsoft.Extensions.DependencyInjection;

ITestService.cs

public interface ITestService
{
    int GenerateRandom();
}

TestService.cs

public class TestService : ITestService
{
    public int GenerateRandom()
    {
        return 4;
    }
}

Startup.cs (ConfigureServices)

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(Configuration);
    services.AddMvc();

    services.AddTransient<ITestService, TestService>();
}

HomeController.cs

using Microsoft.Extensions.DependencyInjection;

namespace Core.Controllers
{
    public class HomeController : Controller
    {
        public HomeController(IServiceProvider serviceProvider)
        {
            var service = serviceProvider.GetService<ITestService>();
            int rnd = service.GenerateRandom();
        }

10

หากคุณเพียงแค่ต้องแก้ปัญหาการอ้างอิงหนึ่งเพื่อจุดประสงค์ในการส่งไปยังผู้สร้างของการอ้างอิงอื่นที่คุณกำลังลงทะเบียนคุณสามารถทำได้

สมมติว่าคุณมีบริการที่รับสายและ ISomeService

public class AnotherService : IAnotherService
{
    public AnotherService(ISomeService someService, string serviceUrl)
    {
        ...
    }
}

เมื่อคุณไปลงทะเบียนใน Startup.cs คุณจะต้องทำสิ่งนี้:

services.AddScoped<IAnotherService>(ctx => 
      new AnotherService(ctx.GetService<ISomeService>(), "https://someservice.com/")
);

สหกรณ์ไม่ได้ระบุเหตุผลที่จำเป็นต้องแก้ไขปัญหาการให้บริการในวิธี ConfigureService แต่เป็นส่วนใหญ่มีแนวโน้มที่ใครสักคนเหตุผลที่จะคิดจะทำอย่างนั้น
kilkfoe

1
ในความเป็นจริงนี้ควรเป็นคำตอบที่ยอมรับ ... แม้ว่าคำตอบของ Henk Mollema นั้นเป็นตัวอย่างที่ดีมาก แต่ทุกวันนี้คำตอบของคุณสะอาดขึ้นและไม่แนะนำปัญหาที่เกี่ยวข้องกับการสร้าง IServiceProvider ระดับกลาง อาจเป็นไปได้ว่าโซลูชั่นนี้ไม่สามารถใช้ได้ในปี 2558 เมื่อ Henk เชื่อมโยง แต่ตอนนี้มันเป็นวิธีที่จะไป
Vi100

พยายามนี้ แต่ISomeServiceก็ยังเป็นโมฆะสำหรับฉัน
ajbeaven

2 คำถาม: 1) ถ้าตัวสร้างพารามิเตอร์ของคลาสบริการ AnotherService เปลี่ยนแปลง (ลบหรือเพิ่มบริการ) แล้วฉันต้องแก้ไขเซ็กเมนต์รีจิสเตอร์ของบริการ IAnotherService และมันเปลี่ยนแปลงตลอดเวลา? 2) แทนฉันสามารถเพิ่มเพียงหนึ่งคอนสตรัคเตอร์สำหรับ AnotherService ที่มีพารามิเตอร์ 1 ตัวเช่น public AnotherService (IServiceProvider serviceProvider) และรับบริการที่ฉันต้องการจากตัวสร้าง และฉันต้องการลงทะเบียนคลาสบริการ AnotherService ในคลาสเริ่มต้นเช่น services.AddTransient <IAnotherService, AnotherService> (sp => {var service = new AnotherService (sp); service return;});
Thomas.Benz

2

คุณสามารถฉีดการพึ่งพาในคุณลักษณะเช่น AuthorizeAttribute ด้วยวิธีนี้

var someservice = (ISomeService)context.HttpContext.RequestServices.GetService(typeof(ISomeService));

นี่คือสิ่งที่ฉันกำลังค้นหา .. ขอบคุณ
Reyan Chougle

0

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันประหลาดใจที่แฮ็คค่อนข้างชัดเจนและน่าขยะแขยงไม่ได้อยู่ที่นี่

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

วิธีนี้มีข้อดีที่ไม่ต้องการให้คุณสร้างแผนผังบริการหรือใช้ระหว่างการกำหนดค่าบริการ คุณยังคงกำหนดวิธีการกำหนดค่าบริการ

public void ConfigureServices(IServiceCollection services)
{
    //Prey this doesn't get GC'd or promote to a static class var
    string? somevalue = null;

    services.AddSingleton<IServiceINeedToUse, ServiceINeedToUse>(scope => {
         //create service you need
         var service = new ServiceINeedToUse(scope.GetService<IDependantService>())
         //get the values you need
         somevalue = somevalue ?? service.MyDirtyHack();
         //return the instance
         return service;
    });
    services.AddTransient<IOtherService, OtherService>(scope => {
         //Explicitly ensuring the ctor function above is called, and also showcasing why this is an anti-pattern.
         scope.GetService<IServiceINeedToUse>();
         //TODO: Clean up both the IServiceINeedToUse and IOtherService configuration here, then somehow rebuild the service tree.
         //Wow!
         return new OtherService(somevalue);
    });
}

วิธีการแก้ไขรูปแบบนี้จะเป็นการให้OtherServiceการพึ่งพาอย่างชัดเจนIServiceINeedToUseมากกว่าโดยปริยายขึ้นอยู่กับมันหรือค่าตอบแทนของเมธอด ... หรือการแก้ไขการพึ่งพานั้นอย่างชัดเจนในแบบอื่น


-4
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();

    services.AddDbContext<ConfigurationRepository>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("SqlConnectionString")));

    services.AddScoped<IConfigurationBL, ConfigurationBL>();
    services.AddScoped<IConfigurationRepository, ConfigurationRepository>();
}

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

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