Injeksi Dependensi di ASP.NET Core

Catatan

Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Penting

Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.

Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.

Oleh Kirk Larkin, Steve Smith, dan Brandon Dahler

ASP.NET Core mendukung pola desain perangkat lunak injeksi dependensi (DI), yang merupakan teknik untuk mencapai Inversion of Control (IoC) antara kelas dan dependensinya.

Untuk informasi selengkapnya khusus untuk injeksi dependensi dalam pengontrol MVC, lihat Injeksi dependensi ke pengontrol di ASP.NET Core.

Untuk informasi tentang menggunakan injeksi dependensi dalam aplikasi selain aplikasi web, lihat Injeksi dependensi di .NET.

Untuk informasi selengkapnya tentang injeksi dependensi opsi, lihat Pola opsi di ASP.NET Core.

Topik ini menyediakan informasi tentang injeksi dependensi di ASP.NET Core. Dokumentasi utama tentang menggunakan injeksi dependensi terkandung dalam injeksi Dependensi di .NET.

Melihat atau mengunduh kode sampel (cara mengunduh)

Gambaran umum injeksi dependensi

Dependensi adalah objek yang bergantung pada objek lain. Periksa kelas berikut MyDependency dengan WriteMessage metode yang bergantung pada kelas lain:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Kelas dapat membuat instans MyDependency kelas untuk menggunakan metodenya WriteMessage . Dalam contoh berikut, MyDependency kelas adalah dependensi dari IndexModel kelas :


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

Kelas membuat dan secara langsung tergantung pada MyDependency kelas . Dependensi kode, seperti dalam contoh sebelumnya, bermasalah dan harus dihindari karena alasan berikut:

  • Untuk mengganti MyDependency dengan implementasi yang berbeda, IndexModel kelas harus dimodifikasi.
  • Jika MyDependency memiliki dependensi, dependensi juga harus dikonfigurasi oleh IndexModel kelas. Dalam proyek besar dengan beberapa kelas tergantung pada MyDependency, kode konfigurasi menjadi tersebar di seluruh aplikasi.
  • Implementasi ini sulit untuk pengujian unit.

Injeksi dependensi mengatasi masalah ini melalui:

  • Penggunaan antarmuka atau kelas dasar untuk mengabstraksi implementasi dependensi.
  • Pendaftaran dependensi dalam kontainer layanan. ASP.NET Core menyediakan kontainer layanan bawaan, IServiceProvider. Layanan biasanya terdaftar dalam file aplikasi Program.cs .
  • Injeksi layanan ke konstruktor kelas tempat layanan digunakan. Kerangka kerja mengambil tanggung jawab untuk membuat instans dependensi dan membuangnya ketika tidak lagi diperlukan.

Di aplikasi sampel, IMyDependency antarmuka menentukan WriteMessage metode :

public interface IMyDependency
{
    void WriteMessage(string message);
}

Antarmuka ini diimplementasikan oleh jenis beton, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Aplikasi sampel mendaftarkan IMyDependency layanan dengan jenis MyDependencybeton . Metode ini AddScoped mendaftarkan layanan dengan masa pakai terlingkup, seumur hidup satu permintaan. Masa pakai layanan dijelaskan nanti dalam topik ini.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

Di aplikasi sampel, IMyDependency layanan diminta dan digunakan untuk memanggil WriteMessage metode :

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Dengan menggunakan pola DI, pengontrol atau Razor Halaman:

  • Tidak menggunakan jenis MyDependencybeton , hanya antarmuka yang diimplementasikannya IMyDependency . Itu memudahkan untuk mengubah implementasi tanpa memodifikasi pengontrol atau Razor Halaman.
  • Tidak membuat instans MyDependency, instans dibuat oleh kontainer DI.

Implementasi IMyDependency antarmuka dapat ditingkatkan dengan menggunakan API pengelogan bawaan:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Yang diperbarui Program.cs mendaftarkan implementasi baru IMyDependency :

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 tergantung pada ILogger<TCategoryName>, yang dimintanya di konstruktor. ILogger<TCategoryName>adalah layanan yang disediakan kerangka kerja.

Tidak biasa menggunakan injeksi dependensi dengan cara berantai. Setiap dependensi yang diminta pada gilirannya meminta dependensinya sendiri. Kontainer menyelesaikan dependensi dalam grafik dan mengembalikan layanan yang diselesaikan sepenuhnya. Kumpulan dependensi kolektif yang harus diselesaikan biasanya disebut sebagai pohon dependensi, grafik dependensi, atau grafik objek.

Kontainer menyelesaikan ILogger<TCategoryName> dengan memanfaatkan jenis terbuka (generik), menghilangkan kebutuhan untuk mendaftarkan setiap jenis yang dibangun (generik).

Dalam terminologi injeksi dependensi, layanan:

  • Biasanya merupakan objek yang menyediakan layanan ke objek lain, seperti IMyDependency layanan.
  • Tidak terkait dengan layanan web, meskipun layanan dapat menggunakan layanan web.

Kerangka kerja ini menyediakan sistem pengelogan yang kuat. Implementasi IMyDependency yang ditunjukkan dalam contoh sebelumnya ditulis untuk menunjukkan DI dasar, bukan untuk menerapkan pengelogan. Sebagian besar aplikasi tidak perlu menulis pencatat. Kode berikut menunjukkan penggunaan pengelogan default, yang tidak memerlukan layanan apa pun untuk didaftarkan:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Menggunakan kode sebelumnya, tidak perlu memperbarui Program.cs, karena pengelogan disediakan oleh kerangka kerja.

Mendaftarkan grup layanan dengan metode ekstensi

Kerangka kerja ASP.NET Core menggunakan konvensi untuk mendaftarkan sekelompok layanan terkait. Konvensi ini menggunakan metode ekstensi tunggal Add{GROUP_NAME} untuk mendaftarkan semua layanan yang diperlukan oleh fitur kerangka kerja. Misalnya, AddControllers metode ekstensi mendaftarkan layanan yang diperlukan untuk pengontrol MVC.

Kode berikut dihasilkan oleh Razor templat Pages menggunakan akun pengguna individual dan menunjukkan cara menambahkan layanan tambahan ke kontainer menggunakan metode AddDbContext ekstensi dan AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Pertimbangkan hal berikut yang mendaftarkan layanan dan mengonfigurasikan opsi:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grup pendaftaran terkait dapat dipindahkan ke metode ekstensi untuk mendaftarkan layanan. Misalnya, layanan konfigurasi ditambahkan ke kelas berikut:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Layanan yang tersisa akan didaftarkan di kelas yang sama. Kode berikut menggunakan metode ekstensi baru untuk mendaftarkan layanan:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Catatan: Setiap metode ekstensi services.Add{GROUP_NAME} menambahkan dan berpotensi mengonfigurasikan layanan. Misalnya, AddControllersWithViews menambahkan pengontrol MVC pada layanan dengan tampilan yang diperlukan, dan AddRazorPages menambahkan Razor Pages yang diperlukan pada layanan.

Masa pakai layanan

Lihat Masa pakai layanan dalam injeksi Dependensi di .NET

Untuk menggunakan layanan terlingkup di middleware, gunakan salah satu pendekatan berikut:

  • Masukkan layanan ke dalam middleware Invoke atau InvokeAsync metode. Menggunakan injeksi konstruktor melempar pengecualian runtime karena memaksa layanan terlingkup berperilaku seperti singleton. Sampel di bagian Opsi seumur hidup dan pendaftaran menunjukkan InvokeAsync pendekatan.
  • Gunakan middleware berbasis Pabrik. Middleware yang terdaftar menggunakan pendekatan ini diaktifkan per permintaan klien (koneksi), yang memungkinkan layanan terlingkup disuntikkan ke konstruktor middleware.

Untuk informasi selengkapnya, lihat Menulis middleware ASP.NET Core kustom.

Metode pendaftaran layanan

Lihat Metode pendaftaran layanan dalam injeksi Dependensi di .NET

Adalah umum untuk menggunakan beberapa implementasi saat mengejek jenis untuk pengujian.

Mendaftarkan layanan hanya dengan jenis implementasi setara dengan mendaftarkan layanan tersebut dengan implementasi dan jenis layanan yang sama. Inilah sebabnya mengapa beberapa implementasi layanan tidak dapat didaftarkan menggunakan metode yang tidak mengambil jenis layanan eksplisit. Metode ini dapat mendaftarkan beberapa instans layanan, tetapi semuanya akan memiliki jenis implementasi yang sama.

Salah satu metode pendaftaran layanan di atas dapat digunakan untuk mendaftarkan beberapa instans layanan dengan jenis layanan yang sama. Dalam contoh berikut, AddSingleton dipanggil dua kali dengan IMyDependency sebagai jenis layanan. Panggilan kedua untuk AddSingleton mengambil alih yang sebelumnya ketika diselesaikan sebagai IMyDependency dan ditambahkan ke yang sebelumnya ketika beberapa layanan diselesaikan melalui IEnumerable<IMyDependency>. Layanan muncul dalam urutan terdaftar ketika diselesaikan melalui IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Layanan utama

Layanan utama mengacu pada mekanisme untuk mendaftarkan dan mengambil layanan Dependency Injection (DI) menggunakan kunci. Layanan dikaitkan dengan kunci dengan memanggil AddKeyedSingleton (atau AddKeyedScoped atau AddKeyedTransient) untuk mendaftarkannya. Akses layanan terdaftar dengan menentukan kunci dengan [FromKeyedServices] atribut . Kode berikut menunjukkan cara menggunakan layanan kunci:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");
builder.Services.AddControllers();

var app = builder.Build();

app.MapGet("/big", ([FromKeyedServices("big")] ICache bigCache) => bigCache.Get("date"));
app.MapGet("/small", ([FromKeyedServices("small")] ICache smallCache) =>
                                                               smallCache.Get("date"));

app.MapControllers();

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

[ApiController]
[Route("/cache")]
public class CustomServicesApiController : Controller
{
    [HttpGet("big-cache")]
    public ActionResult<object> GetOk([FromKeyedServices("big")] ICache cache)
    {
        return cache.Get("data-mvc");
    }
}

public class MyHub : Hub
{
    public void Method([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

Perilaku injeksi konstruktor

Lihat Perilaku injeksi konstruktor dalam injeksi Dependensi di .NET

Konteks Kerangka Kerja Entitas

Secara default, konteks Kerangka Kerja Entitas ditambahkan ke kontainer layanan menggunakan masa pakai terlingkup karena operasi database aplikasi web biasanya dilingkup ke permintaan klien. Untuk menggunakan masa pakai yang berbeda, tentukan masa pakai dengan menggunakan AddDbContext kelebihan beban. Layanan seumur hidup tertentu tidak boleh menggunakan konteks database dengan masa pakai yang lebih pendek dari masa pakai layanan.

Opsi masa pakai dan pendaftaran

Untuk menunjukkan perbedaan antara masa pakai layanan dan opsi pendaftarannya, pertimbangkan antarmuka berikut yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId. Bergantung pada bagaimana masa pakai layanan operasi dikonfigurasi untuk antarmuka berikut, kontainer menyediakan instans layanan yang sama atau berbeda saat diminta oleh kelas:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Kelas berikut Operation mengimplementasikan semua antarmuka sebelumnya. Operation Konstruktor menghasilkan GUID dan menyimpan 4 karakter terakhir dalam OperationId properti:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Kode berikut membuat beberapa pendaftaran Operation kelas sesuai dengan masa pakai bernama:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Aplikasi sampel menunjukkan masa pakai objek baik di dalam maupun di antara permintaan. IndexModel dan middleware meminta setiap jenis IOperation jenis dan mencatat OperationId untuk masing-masing:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Mirip IndexModeldengan , middleware menyelesaikan layanan yang sama:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Layanan terlingkup dan sementara harus diselesaikan dalam InvokeAsync metode :

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Output pencatat menunjukkan:

  • Objek sementara selalu berbeda. Nilai sementara OperationId berbeda di IndexModel dan di middleware.
  • Objek tercakup sama untuk permintaan tertentu tetapi berbeda di setiap permintaan baru.
  • Objek singleton sama untuk setiap permintaan.

Untuk mengurangi output pengelogan, atur "Logging:LogLevel:Microsoft:Error" dalam appsettings.Development.json file:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Mengatasi layanan saat memulai aplikasi

Kode berikut menunjukkan cara mengatasi layanan tercakup selama durasi terbatas saat aplikasi dimulai:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validasi cakupan

Lihat Perilaku injeksi konstruktor dalam injeksi Dependensi di .NET

Untuk informasi selengkapnya, lihat Validasi cakupan.

Layanan Permintaan

Layanan dan dependensinya dalam permintaan ASP.NET Core diekspos melalui HttpContext.RequestServices.

Kerangka kerja membuat cakupan per permintaan, dan RequestServices mengekspos penyedia layanan terlingkup. Semua layanan terlingkup berlaku selama permintaan aktif.

Catatan

Lebih suka meminta dependensi sebagai parameter konstruktor daripada menyelesaikan layanan dari RequestServices. Meminta dependensi sebagai parameter konstruktor menghasilkan kelas yang lebih mudah diuji.

Layanan desain untuk injeksi dependensi

Saat merancang layanan untuk injeksi dependensi:

  • Hindari kelas dan anggota statis yang stateful. Hindari membuat status global dengan merancang aplikasi untuk menggunakan layanan singleton sebagai gantinya.
  • Hindari instansiasi langsung kelas dependen dalam layanan. Instansiasi langsung menggabungkan kode ke implementasi tertentu.
  • Membuat layanan kecil, diperhitungkan dengan baik, dan mudah diuji.

Jika kelas memiliki banyak dependensi yang disuntikkan, mungkin merupakan tanda bahwa kelas memiliki terlalu banyak tanggung jawab dan melanggar Prinsip Tanggung Jawab Tunggal (SRP). Coba refaktor kelas dengan memindahkan beberapa tanggung jawabnya ke kelas baru. Perlu diingat bahwa Razor kelas model halaman Halaman dan kelas pengontrol MVC harus berfokus pada masalah UI.

Pembuangan layanan

Kontainer memanggil Dispose jenis yang IDisposable dibuatnya. Layanan yang diselesaikan dari kontainer tidak boleh dibuang oleh pengembang. Jika jenis atau pabrik terdaftar sebagai singleton, kontainer akan membuang singleton secara otomatis.

Dalam contoh berikut, layanan dibuat oleh kontainer layanan dan dibuang secara otomatis: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

Konsol debug menunjukkan output berikut setelah setiap refresh halaman Indeks:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Layanan tidak dibuat oleh kontainer layanan

Pertimbangkan gambar berikut:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

Dalam kode sebelumnya:

  • Instans layanan tidak dibuat oleh kontainer layanan.
  • Kerangka kerja tidak membuang layanan secara otomatis.
  • Pengembang bertanggung jawab untuk membuang layanan.

Panduan IDisposable untuk Instans sementara dan bersama

Lihat panduan IDisposable untuk Instans sementara dan bersama dalam injeksi Dependensi di .NET

Penggantian kontainer layanan default

Lihat Penggantian kontainer layanan default dalam injeksi Dependensi di .NET

Rekomendasi

Lihat Rekomendasi dalam injeksi Dependensi di .NET

  • Hindari menggunakan pola pencari lokasi layanan. Misalnya, jangan panggil GetService untuk mendapatkan instans layanan saat Anda dapat menggunakan DI sebagai gantinya:

    Salah:

    Kode yang salah

    Benar:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Variasi pencari layanan lain yang harus dihindari adalah menyuntikkan pabrik yang menyelesaikan dependensi saat runtime. Kedua praktik ini mencampur strategi Inversi Kontrol .

  • Hindari akses statis ke HttpContext (misalnya, IHttpContextAccessor.HttpContext).

DI adalah alternatif untuk pola akses objek statis/global. Anda mungkin tidak dapat mewujudkan manfaat DI jika Anda mencampurnya dengan akses objek statis.

Orchard Core adalah kerangka kerja aplikasi untuk membangun aplikasi modular dan multi-penyewa di ASP.NET Core. Untuk informasi selengkapnya, lihat Dokumentasi Orchard Core.

Lihat sampel Orchard Core untuk contoh cara membuat aplikasi modular dan multi-penyewa hanya menggunakan Orchard Core Framework tanpa fitur khusus CMS-nya.

Layanan yang disediakan kerangka kerja

Program.cs mendaftarkan layanan yang digunakan aplikasi, termasuk fitur platform, seperti Entity Framework Core dan ASP.NET Core MVC. Awalnya, yang IServiceCollection disediakan untuk Program.cs memiliki layanan yang ditentukan oleh kerangka kerja tergantung pada bagaimana host dikonfigurasi. Untuk aplikasi berdasarkan templat ASP.NET Core, kerangka kerja mendaftarkan lebih dari 250 layanan.

Tabel berikut mencantumkan sampel kecil layanan terdaftar kerangka kerja ini:

Jenis Layanan Seumur hidup
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Perubahan sementara
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Perubahan sementara
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Perubahan sementara
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Perubahan sementara
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Sumber Daya Tambahan:

Oleh Kirk Larkin, Steve Smith, dan Brandon Dahler

ASP.NET Core mendukung pola desain perangkat lunak injeksi dependensi (DI), yang merupakan teknik untuk mencapai Inversion of Control (IoC) antara kelas dan dependensinya.

Untuk informasi selengkapnya khusus untuk injeksi dependensi dalam pengontrol MVC, lihat Injeksi dependensi ke pengontrol di ASP.NET Core.

Untuk informasi tentang menggunakan injeksi dependensi dalam aplikasi selain aplikasi web, lihat Injeksi dependensi di .NET.

Untuk informasi selengkapnya tentang injeksi dependensi opsi, lihat Pola opsi di ASP.NET Core.

Topik ini menyediakan informasi tentang injeksi dependensi di ASP.NET Core. Dokumentasi utama tentang menggunakan injeksi dependensi terkandung dalam injeksi Dependensi di .NET.

Melihat atau mengunduh kode sampel (cara mengunduh)

Gambaran umum injeksi dependensi

Dependensi adalah objek yang bergantung pada objek lain. Periksa kelas berikut MyDependency dengan WriteMessage metode yang bergantung pada kelas lain:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Kelas dapat membuat instans MyDependency kelas untuk menggunakan metodenya WriteMessage . Dalam contoh berikut, MyDependency kelas adalah dependensi dari IndexModel kelas :


public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet");
    }
}

Kelas membuat dan secara langsung tergantung pada MyDependency kelas . Dependensi kode, seperti dalam contoh sebelumnya, bermasalah dan harus dihindari karena alasan berikut:

  • Untuk mengganti MyDependency dengan implementasi yang berbeda, IndexModel kelas harus dimodifikasi.
  • Jika MyDependency memiliki dependensi, dependensi juga harus dikonfigurasi oleh IndexModel kelas. Dalam proyek besar dengan beberapa kelas tergantung pada MyDependency, kode konfigurasi menjadi tersebar di seluruh aplikasi.
  • Implementasi ini sulit untuk pengujian unit.

Injeksi dependensi mengatasi masalah ini melalui:

  • Penggunaan antarmuka atau kelas dasar untuk mengabstraksi implementasi dependensi.
  • Pendaftaran dependensi dalam kontainer layanan. ASP.NET Core menyediakan kontainer layanan bawaan, IServiceProvider. Layanan biasanya terdaftar dalam file aplikasi Program.cs .
  • Injeksi layanan ke konstruktor kelas tempat layanan digunakan. Kerangka kerja mengambil tanggung jawab untuk membuat instans dependensi dan membuangnya ketika tidak lagi diperlukan.

Di aplikasi sampel, IMyDependency antarmuka menentukan WriteMessage metode :

public interface IMyDependency
{
    void WriteMessage(string message);
}

Antarmuka ini diimplementasikan oleh jenis beton, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Aplikasi sampel mendaftarkan IMyDependency layanan dengan jenis MyDependencybeton . Metode ini AddScoped mendaftarkan layanan dengan masa pakai terlingkup, seumur hidup satu permintaan. Masa pakai layanan dijelaskan nanti dalam topik ini.

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

Di aplikasi sampel, IMyDependency layanan diminta dan digunakan untuk memanggil WriteMessage metode :

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Dengan menggunakan pola DI, pengontrol atau Razor Halaman:

  • Tidak menggunakan jenis MyDependencybeton , hanya antarmuka yang diimplementasikannya IMyDependency . Itu memudahkan untuk mengubah implementasi tanpa memodifikasi pengontrol atau Razor Halaman.
  • Tidak membuat instans MyDependency, instans dibuat oleh kontainer DI.

Implementasi IMyDependency antarmuka dapat ditingkatkan dengan menggunakan API pengelogan bawaan:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Yang diperbarui Program.cs mendaftarkan implementasi baru IMyDependency :

using DependencyInjectionSample.Interfaces;
using DependencyInjectionSample.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<IMyDependency, MyDependency2>();

var app = builder.Build();

MyDependency2 tergantung pada ILogger<TCategoryName>, yang dimintanya di konstruktor. ILogger<TCategoryName>adalah layanan yang disediakan kerangka kerja.

Tidak biasa menggunakan injeksi dependensi dengan cara berantai. Setiap dependensi yang diminta pada gilirannya meminta dependensinya sendiri. Kontainer menyelesaikan dependensi dalam grafik dan mengembalikan layanan yang diselesaikan sepenuhnya. Kumpulan dependensi kolektif yang harus diselesaikan biasanya disebut sebagai pohon dependensi, grafik dependensi, atau grafik objek.

Kontainer menyelesaikan ILogger<TCategoryName> dengan memanfaatkan jenis terbuka (generik), menghilangkan kebutuhan untuk mendaftarkan setiap jenis yang dibangun (generik).

Dalam terminologi injeksi dependensi, layanan:

  • Biasanya merupakan objek yang menyediakan layanan ke objek lain, seperti IMyDependency layanan.
  • Tidak terkait dengan layanan web, meskipun layanan dapat menggunakan layanan web.

Kerangka kerja ini menyediakan sistem pengelogan yang kuat. Implementasi IMyDependency yang ditunjukkan dalam contoh sebelumnya ditulis untuk menunjukkan DI dasar, bukan untuk menerapkan pengelogan. Sebagian besar aplikasi tidak perlu menulis pencatat. Kode berikut menunjukkan penggunaan pengelogan default, yang tidak memerlukan layanan apa pun untuk didaftarkan:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; } = string.Empty;

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Menggunakan kode sebelumnya, tidak perlu memperbarui Program.cs, karena pengelogan disediakan oleh kerangka kerja.

Mendaftarkan grup layanan dengan metode ekstensi

Kerangka kerja ASP.NET Core menggunakan konvensi untuk mendaftarkan sekelompok layanan terkait. Konvensi ini menggunakan metode ekstensi tunggal Add{GROUP_NAME} untuk mendaftarkan semua layanan yang diperlukan oleh fitur kerangka kerja. Misalnya, AddControllers metode ekstensi mendaftarkan layanan yang diperlukan untuk pengontrol MVC.

Kode berikut dihasilkan oleh Razor templat Pages menggunakan akun pengguna individual dan menunjukkan cara menambahkan layanan tambahan ke kontainer menggunakan metode AddDbContext ekstensi dan AddDefaultIdentity:

using DependencyInjectionSample.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();

var app = builder.Build();

Pertimbangkan hal berikut yang mendaftarkan layanan dan mengonfigurasikan opsi:

using ConfigSample.Options;
using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.Configure<PositionOptions>(
    builder.Configuration.GetSection(PositionOptions.Position));
builder.Services.Configure<ColorOptions>(
    builder.Configuration.GetSection(ColorOptions.Color));

builder.Services.AddScoped<IMyDependency, MyDependency>();
builder.Services.AddScoped<IMyDependency2, MyDependency2>();

var app = builder.Build();

Grup pendaftaran terkait dapat dipindahkan ke metode ekstensi untuk mendaftarkan layanan. Misalnya, layanan konfigurasi ditambahkan ke kelas berikut:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Layanan yang tersisa akan didaftarkan di kelas yang sama. Kode berikut menggunakan metode ekstensi baru untuk mendaftarkan layanan:

using Microsoft.Extensions.DependencyInjection.ConfigSample.Options;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddConfig(builder.Configuration)
    .AddMyDependencyGroup();

builder.Services.AddRazorPages();

var app = builder.Build();

Catatan: Setiap metode ekstensi services.Add{GROUP_NAME} menambahkan dan berpotensi mengonfigurasikan layanan. Misalnya, AddControllersWithViews menambahkan pengontrol MVC pada layanan dengan tampilan yang diperlukan, dan AddRazorPages menambahkan Razor Pages yang diperlukan pada layanan.

Masa pakai layanan

Lihat Masa pakai layanan dalam injeksi Dependensi di .NET

Untuk menggunakan layanan terlingkup di middleware, gunakan salah satu pendekatan berikut:

  • Masukkan layanan ke dalam middleware Invoke atau InvokeAsync metode. Menggunakan injeksi konstruktor melempar pengecualian runtime karena memaksa layanan terlingkup berperilaku seperti singleton. Sampel di bagian Opsi seumur hidup dan pendaftaran menunjukkan InvokeAsync pendekatan.
  • Gunakan middleware berbasis Pabrik. Middleware yang terdaftar menggunakan pendekatan ini diaktifkan per permintaan klien (koneksi), yang memungkinkan layanan terlingkup disuntikkan ke konstruktor middleware.

Untuk informasi selengkapnya, lihat Menulis middleware ASP.NET Core kustom.

Metode pendaftaran layanan

Lihat Metode pendaftaran layanan dalam injeksi Dependensi di .NET

Adalah umum untuk menggunakan beberapa implementasi saat mengejek jenis untuk pengujian.

Mendaftarkan layanan hanya dengan jenis implementasi setara dengan mendaftarkan layanan tersebut dengan implementasi dan jenis layanan yang sama. Inilah sebabnya mengapa beberapa implementasi layanan tidak dapat didaftarkan menggunakan metode yang tidak mengambil jenis layanan eksplisit. Metode ini dapat mendaftarkan beberapa instans layanan, tetapi semuanya akan memiliki jenis implementasi yang sama.

Salah satu metode pendaftaran layanan di atas dapat digunakan untuk mendaftarkan beberapa instans layanan dengan jenis layanan yang sama. Dalam contoh berikut, AddSingleton dipanggil dua kali dengan IMyDependency sebagai jenis layanan. Panggilan kedua untuk AddSingleton mengambil alih yang sebelumnya ketika diselesaikan sebagai IMyDependency dan ditambahkan ke yang sebelumnya ketika beberapa layanan diselesaikan melalui IEnumerable<IMyDependency>. Layanan muncul dalam urutan terdaftar ketika diselesaikan melalui IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Perilaku injeksi konstruktor

Lihat Perilaku injeksi konstruktor dalam injeksi Dependensi di .NET

Konteks Kerangka Kerja Entitas

Secara default, konteks Kerangka Kerja Entitas ditambahkan ke kontainer layanan menggunakan masa pakai terlingkup karena operasi database aplikasi web biasanya dilingkup ke permintaan klien. Untuk menggunakan masa pakai yang berbeda, tentukan masa pakai dengan menggunakan AddDbContext kelebihan beban. Layanan seumur hidup tertentu tidak boleh menggunakan konteks database dengan masa pakai yang lebih pendek dari masa pakai layanan.

Opsi masa pakai dan pendaftaran

Untuk menunjukkan perbedaan antara masa pakai layanan dan opsi pendaftarannya, pertimbangkan antarmuka berikut yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId. Bergantung pada bagaimana masa pakai layanan operasi dikonfigurasi untuk antarmuka berikut, kontainer menyediakan instans layanan yang sama atau berbeda saat diminta oleh kelas:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Kelas berikut Operation mengimplementasikan semua antarmuka sebelumnya. Operation Konstruktor menghasilkan GUID dan menyimpan 4 karakter terakhir dalam OperationId properti:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Kode berikut membuat beberapa pendaftaran Operation kelas sesuai dengan masa pakai bernama:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddTransient<IOperationTransient, Operation>();
builder.Services.AddScoped<IOperationScoped, Operation>();
builder.Services.AddSingleton<IOperationSingleton, Operation>();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseMyMiddleware();
app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

Aplikasi sampel menunjukkan masa pakai objek baik di dalam maupun di antara permintaan. IndexModel dan middleware meminta setiap jenis IOperation jenis dan mencatat OperationId untuk masing-masing:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Mirip IndexModeldengan , middleware menyelesaikan layanan yang sama:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationTransient transientOperation, IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + transientOperation.OperationId);
        _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Layanan terlingkup dan sementara harus diselesaikan dalam InvokeAsync metode :

public async Task InvokeAsync(HttpContext context,
    IOperationTransient transientOperation, IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + transientOperation.OperationId);
    _logger.LogInformation("Scoped: " + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Output pencatat menunjukkan:

  • Objek sementara selalu berbeda. Nilai sementara OperationId berbeda di IndexModel dan di middleware.
  • Objek tercakup sama untuk permintaan tertentu tetapi berbeda di setiap permintaan baru.
  • Objek singleton sama untuk setiap permintaan.

Untuk mengurangi output pengelogan, atur "Logging:LogLevel:Microsoft:Error" dalam appsettings.Development.json file:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Mengatasi layanan saat memulai aplikasi

Kode berikut menunjukkan cara mengatasi layanan tercakup selama durasi terbatas saat aplikasi dimulai:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<IMyDependency, MyDependency>();

var app = builder.Build();

using (var serviceScope = app.Services.CreateScope())
{
    var services = serviceScope.ServiceProvider;

    var myDependency = services.GetRequiredService<IMyDependency>();
    myDependency.WriteMessage("Call services from main");
}

app.MapGet("/", () => "Hello World!");

app.Run();

Validasi cakupan

Lihat Perilaku injeksi konstruktor dalam injeksi Dependensi di .NET

Untuk informasi selengkapnya, lihat Validasi cakupan.

Layanan Permintaan

Layanan dan dependensinya dalam permintaan ASP.NET Core diekspos melalui HttpContext.RequestServices.

Kerangka kerja membuat cakupan per permintaan, dan RequestServices mengekspos penyedia layanan terlingkup. Semua layanan terlingkup berlaku selama permintaan aktif.

Catatan

Lebih suka meminta dependensi sebagai parameter konstruktor daripada menyelesaikan layanan dari RequestServices. Meminta dependensi sebagai parameter konstruktor menghasilkan kelas yang lebih mudah diuji.

Layanan desain untuk injeksi dependensi

Saat merancang layanan untuk injeksi dependensi:

  • Hindari kelas dan anggota statis yang stateful. Hindari membuat status global dengan merancang aplikasi untuk menggunakan layanan singleton sebagai gantinya.
  • Hindari instansiasi langsung kelas dependen dalam layanan. Instansiasi langsung menggabungkan kode ke implementasi tertentu.
  • Membuat layanan kecil, diperhitungkan dengan baik, dan mudah diuji.

Jika kelas memiliki banyak dependensi yang disuntikkan, mungkin merupakan tanda bahwa kelas memiliki terlalu banyak tanggung jawab dan melanggar Prinsip Tanggung Jawab Tunggal (SRP). Coba refaktor kelas dengan memindahkan beberapa tanggung jawabnya ke kelas baru. Perlu diingat bahwa Razor kelas model halaman Halaman dan kelas pengontrol MVC harus berfokus pada masalah UI.

Pembuangan layanan

Kontainer memanggil Dispose jenis yang IDisposable dibuatnya. Layanan yang diselesaikan dari kontainer tidak boleh dibuang oleh pengembang. Jika jenis atau pabrik terdaftar sebagai singleton, kontainer akan membuang singleton secara otomatis.

Dalam contoh berikut, layanan dibuat oleh kontainer layanan dan dibuang secara otomatis: dependency-injection\samples\6.x\DIsample2\DIsample2\Services\Service1.cs

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
using DIsample2.Services;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddScoped<Service1>();
builder.Services.AddSingleton<Service2>();

var myKey = builder.Configuration["MyKey"];
builder.Services.AddSingleton<IService3>(sp => new Service3(myKey));

var app = builder.Build();
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

Konsol debug menunjukkan output berikut setelah setiap refresh halaman Indeks:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = MyKey from appsettings.Developement.json
Service1.Dispose

Layanan tidak dibuat oleh kontainer layanan

Pertimbangkan gambar berikut:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddSingleton(new Service1());
builder.Services.AddSingleton(new Service2());

Dalam kode sebelumnya:

  • Instans layanan tidak dibuat oleh kontainer layanan.
  • Kerangka kerja tidak membuang layanan secara otomatis.
  • Pengembang bertanggung jawab untuk membuang layanan.

Panduan IDisposable untuk Instans sementara dan bersama

Lihat panduan IDisposable untuk Instans sementara dan bersama dalam injeksi Dependensi di .NET

Penggantian kontainer layanan default

Lihat Penggantian kontainer layanan default dalam injeksi Dependensi di .NET

Rekomendasi

Lihat Rekomendasi dalam injeksi Dependensi di .NET

  • Hindari menggunakan pola pencari lokasi layanan. Misalnya, jangan panggil GetService untuk mendapatkan instans layanan saat Anda dapat menggunakan DI sebagai gantinya:

    Salah:

    Kode yang salah

    Benar:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Variasi pencari layanan lain yang harus dihindari adalah menyuntikkan pabrik yang menyelesaikan dependensi saat runtime. Kedua praktik ini mencampur strategi Inversi Kontrol .

  • Hindari akses statis ke HttpContext (misalnya, IHttpContextAccessor.HttpContext).

DI adalah alternatif untuk pola akses objek statis/global. Anda mungkin tidak dapat mewujudkan manfaat DI jika Anda mencampurnya dengan akses objek statis.

Orchard Core adalah kerangka kerja aplikasi untuk membangun aplikasi modular dan multi-penyewa di ASP.NET Core. Untuk informasi selengkapnya, lihat Dokumentasi Orchard Core.

Lihat sampel Orchard Core untuk contoh cara membuat aplikasi modular dan multi-penyewa hanya menggunakan Orchard Core Framework tanpa fitur khusus CMS-nya.

Layanan yang disediakan kerangka kerja

Program.cs mendaftarkan layanan yang digunakan aplikasi, termasuk fitur platform, seperti Entity Framework Core dan ASP.NET Core MVC. Awalnya, yang IServiceCollection disediakan untuk Program.cs memiliki layanan yang ditentukan oleh kerangka kerja tergantung pada bagaimana host dikonfigurasi. Untuk aplikasi berdasarkan templat ASP.NET Core, kerangka kerja mendaftarkan lebih dari 250 layanan.

Tabel berikut mencantumkan sampel kecil layanan terdaftar kerangka kerja ini:

Jenis Layanan Seumur hidup
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Perubahan sementara
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Perubahan sementara
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Perubahan sementara
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Perubahan sementara
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Sumber Daya Tambahan:

Oleh Kirk Larkin, Steve Smith, Scott Addie, dan Brandon Dahler

ASP.NET Core mendukung pola desain perangkat lunak injeksi dependensi (DI), yang merupakan teknik untuk mencapai Inversion of Control (IoC) antara kelas dan dependensinya.

Untuk informasi selengkapnya khusus untuk injeksi dependensi dalam pengontrol MVC, lihat Injeksi dependensi ke pengontrol di ASP.NET Core.

Untuk informasi tentang menggunakan injeksi dependensi dalam aplikasi selain aplikasi web, lihat Injeksi dependensi di .NET.

Untuk informasi selengkapnya tentang injeksi dependensi opsi, lihat Pola opsi di ASP.NET Core.

Topik ini menyediakan informasi tentang injeksi dependensi di ASP.NET Core. Dokumentasi utama tentang menggunakan injeksi dependensi terkandung dalam injeksi Dependensi di .NET.

Melihat atau mengunduh kode sampel (cara mengunduh)

Gambaran umum injeksi dependensi

Dependensi adalah objek yang bergantung pada objek lain. Periksa kelas berikut MyDependency dengan WriteMessage metode yang bergantung pada kelas lain:

public class MyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage called. Message: {message}");
    }
}

Kelas dapat membuat instans MyDependency kelas untuk menggunakan metodenya WriteMessage . Dalam contoh berikut, MyDependency kelas adalah dependensi dari IndexModel kelas :

public class IndexModel : PageModel
{
    private readonly MyDependency _dependency = new MyDependency();

    public void OnGet()
    {
        _dependency.WriteMessage("IndexModel.OnGet created this message.");
    }
}

Kelas membuat dan secara langsung tergantung pada MyDependency kelas . Dependensi kode, seperti dalam contoh sebelumnya, bermasalah dan harus dihindari karena alasan berikut:

  • Untuk mengganti MyDependency dengan implementasi yang berbeda, IndexModel kelas harus dimodifikasi.
  • Jika MyDependency memiliki dependensi, dependensi juga harus dikonfigurasi oleh IndexModel kelas. Dalam proyek besar dengan beberapa kelas tergantung pada MyDependency, kode konfigurasi menjadi tersebar di seluruh aplikasi.
  • Implementasi ini sulit untuk pengujian unit. Aplikasi harus menggunakan kelas tiruan atau stub MyDependency , yang tidak dimungkinkan dengan pendekatan ini.

Injeksi dependensi mengatasi masalah ini melalui:

  • Penggunaan antarmuka atau kelas dasar untuk mengabstraksi implementasi dependensi.
  • Pendaftaran dependensi dalam kontainer layanan. ASP.NET Core menyediakan kontainer layanan bawaan, IServiceProvider. Layanan biasanya terdaftar dalam metode aplikasi Startup.ConfigureServices .
  • Injeksi layanan ke konstruktor kelas tempat layanan digunakan. Kerangka kerja mengambil tanggung jawab untuk membuat instans dependensi dan membuangnya ketika tidak lagi diperlukan.

Di aplikasi sampel, IMyDependency antarmuka menentukan WriteMessage metode :

public interface IMyDependency
{
    void WriteMessage(string message);
}

Antarmuka ini diimplementasikan oleh jenis beton, MyDependency:

public class MyDependency : IMyDependency
{
    public void WriteMessage(string message)
    {
        Console.WriteLine($"MyDependency.WriteMessage Message: {message}");
    }
}

Aplikasi sampel mendaftarkan IMyDependency layanan dengan jenis MyDependencybeton . Metode ini AddScoped mendaftarkan layanan dengan masa pakai terlingkup, seumur hidup satu permintaan. Masa pakai layanan dijelaskan nanti dalam topik ini.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency>();

    services.AddRazorPages();
}

Di aplikasi sampel, IMyDependency layanan diminta dan digunakan untuk memanggil WriteMessage metode :

public class Index2Model : PageModel
{
    private readonly IMyDependency _myDependency;

    public Index2Model(IMyDependency myDependency)
    {
        _myDependency = myDependency;            
    }

    public void OnGet()
    {
        _myDependency.WriteMessage("Index2Model.OnGet");
    }
}

Dengan menggunakan pola DI, pengontrol:

  • Tidak menggunakan jenis MyDependencybeton , hanya antarmuka yang diimplementasikannya IMyDependency . Itu memudahkan untuk mengubah implementasi yang digunakan pengontrol tanpa memodifikasi pengontrol.
  • Tidak membuat instans MyDependency, instans dibuat oleh kontainer DI.

Implementasi IMyDependency antarmuka dapat ditingkatkan dengan menggunakan API pengelogan bawaan:

public class MyDependency2 : IMyDependency
{
    private readonly ILogger<MyDependency2> _logger;

    public MyDependency2(ILogger<MyDependency2> logger)
    {
        _logger = logger;
    }

    public void WriteMessage(string message)
    {
        _logger.LogInformation( $"MyDependency2.WriteMessage Message: {message}");
    }
}

Metode yang diperbarui ConfigureServices mendaftarkan implementasi baru IMyDependency :

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IMyDependency, MyDependency2>();

    services.AddRazorPages();
}

MyDependency2 tergantung pada ILogger<TCategoryName>, yang dimintanya di konstruktor. ILogger<TCategoryName>adalah layanan yang disediakan kerangka kerja.

Tidak biasa menggunakan injeksi dependensi dengan cara berantai. Setiap dependensi yang diminta pada gilirannya meminta dependensinya sendiri. Kontainer menyelesaikan dependensi dalam grafik dan mengembalikan layanan yang diselesaikan sepenuhnya. Kumpulan dependensi kolektif yang harus diselesaikan biasanya disebut sebagai pohon dependensi, grafik dependensi, atau grafik objek.

Kontainer menyelesaikan ILogger<TCategoryName> dengan memanfaatkan jenis terbuka (generik), menghilangkan kebutuhan untuk mendaftarkan setiap jenis yang dibangun (generik).

Dalam terminologi injeksi dependensi, layanan:

  • Biasanya merupakan objek yang menyediakan layanan ke objek lain, seperti IMyDependency layanan.
  • Tidak terkait dengan layanan web, meskipun layanan dapat menggunakan layanan web.

Kerangka kerja ini menyediakan sistem pengelogan yang kuat. Implementasi IMyDependency yang ditunjukkan dalam contoh sebelumnya ditulis untuk menunjukkan DI dasar, bukan untuk menerapkan pengelogan. Sebagian besar aplikasi tidak perlu menulis pencatat. Kode berikut menunjukkan menggunakan pengelogan default, yang tidak memerlukan layanan apa pun untuk didaftarkan di ConfigureServices:

public class AboutModel : PageModel
{
    private readonly ILogger _logger;

    public AboutModel(ILogger<AboutModel> logger)
    {
        _logger = logger;
    }
    
    public string Message { get; set; }

    public void OnGet()
    {
        Message = $"About page visited at {DateTime.UtcNow.ToLongTimeString()}";
        _logger.LogInformation(Message);
    }
}

Menggunakan kode sebelumnya, tidak perlu memperbarui ConfigureServices, karena pengelogan disediakan oleh kerangka kerja.

Layanan yang disuntikkan ke Startup

Layanan dapat disuntikkan ke Startup konstruktor dan Startup.Configure metode .

Hanya layanan berikut yang dapat disuntikkan ke Startup konstruktor saat menggunakan Host Generik (IHostBuilder):

Setiap layanan yang terdaftar dengan kontainer DI dapat disuntikkan ke Startup.Configure dalam metode :

public void Configure(IApplicationBuilder app, ILogger<Startup> logger)
{
    ...
}

Untuk informasi selengkapnya, lihat Pengaktifan aplikasi di konfigurasi ASP.NET Core dan Access di Startup.

Mendaftarkan grup layanan dengan metode ekstensi

Kerangka kerja ASP.NET Core menggunakan konvensi untuk mendaftarkan sekelompok layanan terkait. Konvensi ini menggunakan metode ekstensi tunggal Add{GROUP_NAME} untuk mendaftarkan semua layanan yang diperlukan oleh fitur kerangka kerja. Misalnya, AddControllers metode ekstensi mendaftarkan layanan yang diperlukan untuk pengontrol MVC.

Kode berikut dihasilkan oleh Razor templat Pages menggunakan akun pengguna individual dan menunjukkan cara menambahkan layanan tambahan ke kontainer menggunakan metode AddDbContext ekstensi dan AddDefaultIdentity:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlServer(
            Configuration.GetConnectionString("DefaultConnection")));
    services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    services.AddRazorPages();
}

Pertimbangkan metode ConfigureServices berikut yang mendaftarkan layanan dan mengonfigurasikan opsi:

public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
        Configuration.GetSection(PositionOptions.Position));
    services.Configure<ColorOptions>(
        Configuration.GetSection(ColorOptions.Color));

    services.AddScoped<IMyDependency, MyDependency>();
    services.AddScoped<IMyDependency2, MyDependency2>();

    services.AddRazorPages();
}

Grup pendaftaran terkait dapat dipindahkan ke metode ekstensi untuk mendaftarkan layanan. Misalnya, layanan konfigurasi ditambahkan ke kelas berikut:

using ConfigSample.Options;
using Microsoft.Extensions.Configuration;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class MyConfigServiceCollectionExtensions
    {
        public static IServiceCollection AddConfig(
             this IServiceCollection services, IConfiguration config)
        {
            services.Configure<PositionOptions>(
                config.GetSection(PositionOptions.Position));
            services.Configure<ColorOptions>(
                config.GetSection(ColorOptions.Color));

            return services;
        }

        public static IServiceCollection AddMyDependencyGroup(
             this IServiceCollection services)
        {
            services.AddScoped<IMyDependency, MyDependency>();
            services.AddScoped<IMyDependency2, MyDependency2>();

            return services;
        }
    }
}

Layanan yang tersisa akan didaftarkan di kelas yang sama. Metode ConfigureServices berikut menggunakan metode ekstensi baru untuk mendaftarkan layanan:

public void ConfigureServices(IServiceCollection services)
{
    services.AddConfig(Configuration)
            .AddMyDependencyGroup();

    services.AddRazorPages();
}

Catatan: Setiap metode ekstensi services.Add{GROUP_NAME} menambahkan dan berpotensi mengonfigurasikan layanan. Misalnya, AddControllersWithViews menambahkan pengontrol MVC pada layanan dengan tampilan yang diperlukan, dan AddRazorPages menambahkan Razor Pages yang diperlukan pada layanan. Sebaiknya aplikasi mengikuti konvensi penamaan pembuatan metode ekstensi di namespace layanan Microsoft.Extensions.DependencyInjection. Membuat metode ekstensi di namespace layanan Microsoft.Extensions.DependencyInjection:

  • Merangkum grup pendaftaran layanan.
  • Menyediakan akses IntelliSense yang nyaman ke layanan.

Masa pakai layanan

Lihat Masa pakai layanan dalam injeksi Dependensi di .NET

Untuk menggunakan layanan terlingkup di middleware, gunakan salah satu pendekatan berikut:

  • Masukkan layanan ke dalam middleware Invoke atau InvokeAsync metode. Menggunakan injeksi konstruktor melempar pengecualian runtime karena memaksa layanan terlingkup berperilaku seperti singleton. Sampel di bagian Opsi seumur hidup dan pendaftaran menunjukkan InvokeAsync pendekatan.
  • Gunakan middleware berbasis Pabrik. Middleware yang terdaftar menggunakan pendekatan ini diaktifkan per permintaan klien (koneksi), yang memungkinkan layanan terlingkup disuntikkan ke dalam metode middleware InvokeAsync .

Untuk informasi selengkapnya, lihat Menulis middleware ASP.NET Core kustom.

Metode pendaftaran layanan

Lihat Metode pendaftaran layanan dalam injeksi Dependensi di .NET

Adalah umum untuk menggunakan beberapa implementasi saat mengejek jenis untuk pengujian.

Mendaftarkan layanan hanya dengan jenis implementasi setara dengan mendaftarkan layanan tersebut dengan implementasi dan jenis layanan yang sama. Inilah sebabnya mengapa beberapa implementasi layanan tidak dapat didaftarkan menggunakan metode yang tidak mengambil jenis layanan eksplisit. Metode ini dapat mendaftarkan beberapa instans layanan, tetapi semuanya akan memiliki jenis implementasi yang sama.

Salah satu metode pendaftaran layanan di atas dapat digunakan untuk mendaftarkan beberapa instans layanan dengan jenis layanan yang sama. Dalam contoh berikut, AddSingleton dipanggil dua kali dengan IMyDependency sebagai jenis layanan. Panggilan kedua untuk AddSingleton mengambil alih yang sebelumnya ketika diselesaikan sebagai IMyDependency dan ditambahkan ke yang sebelumnya ketika beberapa layanan diselesaikan melalui IEnumerable<IMyDependency>. Layanan muncul dalam urutan terdaftar ketika diselesaikan melalui IEnumerable<{SERVICE}>.

services.AddSingleton<IMyDependency, MyDependency>();
services.AddSingleton<IMyDependency, DifferentDependency>();

public class MyService
{
    public MyService(IMyDependency myDependency, 
       IEnumerable<IMyDependency> myDependencies)
    {
        Trace.Assert(myDependency is DifferentDependency);

        var dependencyArray = myDependencies.ToArray();
        Trace.Assert(dependencyArray[0] is MyDependency);
        Trace.Assert(dependencyArray[1] is DifferentDependency);
    }
}

Perilaku injeksi konstruktor

Lihat Perilaku injeksi konstruktor dalam injeksi Dependensi di .NET

Konteks Kerangka Kerja Entitas

Secara default, konteks Kerangka Kerja Entitas ditambahkan ke kontainer layanan menggunakan masa pakai terlingkup karena operasi database aplikasi web biasanya dilingkup ke permintaan klien. Untuk menggunakan masa pakai yang berbeda, tentukan masa pakai dengan menggunakan AddDbContext kelebihan beban. Layanan seumur hidup tertentu tidak boleh menggunakan konteks database dengan masa pakai yang lebih pendek dari masa pakai layanan.

Opsi masa pakai dan pendaftaran

Untuk menunjukkan perbedaan antara masa pakai layanan dan opsi pendaftarannya, pertimbangkan antarmuka berikut yang mewakili tugas sebagai operasi dengan pengidentifikasi, OperationId. Bergantung pada bagaimana masa pakai layanan operasi dikonfigurasi untuk antarmuka berikut, kontainer menyediakan instans layanan yang sama atau berbeda saat diminta oleh kelas:

public interface IOperation
{
    string OperationId { get; }
}

public interface IOperationTransient : IOperation { }
public interface IOperationScoped : IOperation { }
public interface IOperationSingleton : IOperation { }

Kelas berikut Operation mengimplementasikan semua antarmuka sebelumnya. Operation Konstruktor menghasilkan GUID dan menyimpan 4 karakter terakhir dalam OperationId properti:

public class Operation : IOperationTransient, IOperationScoped, IOperationSingleton
{
    public Operation()
    {
        OperationId = Guid.NewGuid().ToString()[^4..];
    }

    public string OperationId { get; }
}

Metode ini Startup.ConfigureServices membuat beberapa pendaftaran Operation kelas sesuai dengan masa pakai bernama:

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IOperationTransient, Operation>();
    services.AddScoped<IOperationScoped, Operation>();
    services.AddSingleton<IOperationSingleton, Operation>();

    services.AddRazorPages();
}

Aplikasi sampel menunjukkan masa pakai objek baik di dalam maupun di antara permintaan. IndexModel dan middleware meminta setiap jenis IOperation jenis dan mencatat OperationId untuk masing-masing:

public class IndexModel : PageModel
{
    private readonly ILogger _logger;
    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;
    private readonly IOperationScoped _scopedOperation;

    public IndexModel(ILogger<IndexModel> logger,
                      IOperationTransient transientOperation,
                      IOperationScoped scopedOperation,
                      IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _scopedOperation    = scopedOperation;
        _singletonOperation = singletonOperation;
    }

    public void  OnGet()
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + _scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);
    }
}

Mirip IndexModeldengan , middleware menyelesaikan layanan yang sama:

public class MyMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;

    private readonly IOperationTransient _transientOperation;
    private readonly IOperationSingleton _singletonOperation;

    public MyMiddleware(RequestDelegate next, ILogger<MyMiddleware> logger,
        IOperationTransient transientOperation,
        IOperationSingleton singletonOperation)
    {
        _logger = logger;
        _transientOperation = transientOperation;
        _singletonOperation = singletonOperation;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context,
        IOperationScoped scopedOperation)
    {
        _logger.LogInformation("Transient: " + _transientOperation.OperationId);
        _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
        _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

        await _next(context);
    }
}

public static class MyMiddlewareExtensions
{
    public static IApplicationBuilder UseMyMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyMiddleware>();
    }
}

Layanan terlingkup harus diselesaikan dalam InvokeAsync metode :

public async Task InvokeAsync(HttpContext context,
    IOperationScoped scopedOperation)
{
    _logger.LogInformation("Transient: " + _transientOperation.OperationId);
    _logger.LogInformation("Scoped: "    + scopedOperation.OperationId);
    _logger.LogInformation("Singleton: " + _singletonOperation.OperationId);

    await _next(context);
}

Output pencatat menunjukkan:

  • Objek sementara selalu berbeda. Nilai sementara OperationId berbeda di IndexModel dan di middleware.
  • Objek tercakup sama untuk permintaan tertentu tetapi berbeda di setiap permintaan baru.
  • Objek singleton sama untuk setiap permintaan.

Untuk mengurangi output pengelogan, atur "Logging:LogLevel:Microsoft:Error" dalam appsettings.Development.json file:

{
  "MyKey": "MyKey from appsettings.Developement.json",
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "System": "Debug",
      "Microsoft": "Error"
    }
  }
}

Memanggil layanan dari utama

IServiceScope Buat dengan IServiceScopeFactory.CreateScope untuk menyelesaikan layanan terlingkup dalam cakupan aplikasi. Pendekatan ini berguna untuk mengakses layanan tercakup saat startup untuk menjalankan tugas inisialisasi.

Contoh berikut menunjukkan cara mengakses layanan terlingkup IMyDependency dan memanggil metodenya WriteMessage di Program.Main:

public class Program
{
    public static void Main(string[] args)
    {
        var host = CreateHostBuilder(args).Build();

        using (var serviceScope = host.Services.CreateScope())
        {
            var services = serviceScope.ServiceProvider;

            try
            {
                var myDependency = services.GetRequiredService<IMyDependency>();
                myDependency.WriteMessage("Call services from main");
            }
            catch (Exception ex)
            {
                var logger = services.GetRequiredService<ILogger<Program>>();
                logger.LogError(ex, "An error occurred.");
            }
        }

        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

Validasi cakupan

Lihat Perilaku injeksi konstruktor dalam injeksi Dependensi di .NET

Untuk informasi selengkapnya, lihat Validasi cakupan.

Layanan Permintaan

Layanan dan dependensinya dalam permintaan ASP.NET Core diekspos melalui HttpContext.RequestServices.

Kerangka kerja membuat cakupan per permintaan, dan RequestServices mengekspos penyedia layanan terlingkup. Semua layanan terlingkup berlaku selama permintaan aktif.

Catatan

Lebih suka meminta dependensi sebagai parameter konstruktor daripada menyelesaikan layanan dari RequestServices. Meminta dependensi sebagai parameter konstruktor menghasilkan kelas yang lebih mudah diuji.

Layanan desain untuk injeksi dependensi

Saat merancang layanan untuk injeksi dependensi:

  • Hindari kelas dan anggota statis yang stateful. Hindari membuat status global dengan merancang aplikasi untuk menggunakan layanan singleton sebagai gantinya.
  • Hindari instansiasi langsung kelas dependen dalam layanan. Instansiasi langsung menggabungkan kode ke implementasi tertentu.
  • Membuat layanan kecil, diperhitungkan dengan baik, dan mudah diuji.

Jika kelas memiliki banyak dependensi yang disuntikkan, mungkin merupakan tanda bahwa kelas memiliki terlalu banyak tanggung jawab dan melanggar Prinsip Tanggung Jawab Tunggal (SRP). Coba refaktor kelas dengan memindahkan beberapa tanggung jawabnya ke kelas baru. Perlu diingat bahwa Razor kelas model halaman Halaman dan kelas pengontrol MVC harus berfokus pada masalah UI.

Pembuangan layanan

Kontainer memanggil Dispose jenis yang IDisposable dibuatnya. Layanan yang diselesaikan dari kontainer tidak boleh dibuang oleh pengembang. Jika jenis atau pabrik terdaftar sebagai singleton, kontainer akan membuang singleton secara otomatis.

Dalam contoh berikut, layanan dibuat oleh kontainer layanan dan dibuang secara otomatis:

public class Service1 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable
{
    private bool _disposed;

    public void Write(string message)
    {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3
{
    public void Write(string message);
}

public class Service3 : IService3, IDisposable
{
    private bool _disposed;

    public Service3(string myKey)
    {
        MyKey = myKey;
    }

    public string MyKey { get; }

    public void Write(string message)
    {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose()
    {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<Service1>();
    services.AddSingleton<Service2>();
    
    var myKey = Configuration["MyKey"];
    services.AddSingleton<IService3>(sp => new Service3(myKey));

    services.AddRazorPages();
}
public class IndexModel : PageModel
{
    private readonly Service1 _service1;
    private readonly Service2 _service2;
    private readonly IService3 _service3;

    public IndexModel(Service1 service1, Service2 service2, IService3 service3)
    {
        _service1 = service1;
        _service2 = service2;
        _service3 = service3;
    }

    public void OnGet()
    {
        _service1.Write("IndexModel.OnGet");
        _service2.Write("IndexModel.OnGet");
        _service3.Write("IndexModel.OnGet");
    }
}

Konsol debug menunjukkan output berikut setelah setiap refresh halaman Indeks:

Service1: IndexModel.OnGet
Service2: IndexModel.OnGet
Service3: IndexModel.OnGet, MyKey = My Key from config
Service1.Dispose

Layanan tidak dibuat oleh kontainer layanan

Pertimbangkan gambar berikut:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new Service1());
    services.AddSingleton(new Service2());

    services.AddRazorPages();
}

Dalam kode sebelumnya:

  • Instans layanan tidak dibuat oleh kontainer layanan.
  • Kerangka kerja tidak membuang layanan secara otomatis.
  • Pengembang bertanggung jawab untuk membuang layanan.

Panduan IDisposable untuk Instans sementara dan bersama

Lihat panduan IDisposable untuk Instans sementara dan bersama dalam injeksi Dependensi di .NET

Penggantian kontainer layanan default

Lihat Penggantian kontainer layanan default dalam injeksi Dependensi di .NET

Rekomendasi

Lihat Rekomendasi dalam injeksi Dependensi di .NET

  • Hindari menggunakan pola pencari lokasi layanan. Misalnya, jangan panggil GetService untuk mendapatkan instans layanan saat Anda dapat menggunakan DI sebagai gantinya:

    Salah:

    Kode yang salah

    Benar:

    public class MyClass
    {
        private readonly IOptionsMonitor<MyOptions> _optionsMonitor;
    
        public MyClass(IOptionsMonitor<MyOptions> optionsMonitor)
        {
            _optionsMonitor = optionsMonitor;
        }
    
        public void MyMethod()
        {
            var option = _optionsMonitor.CurrentValue.Option;
    
            ...
        }
    }
    
  • Variasi pencari layanan lain yang harus dihindari adalah menyuntikkan pabrik yang menyelesaikan dependensi saat runtime. Kedua praktik ini mencampur strategi Inversi Kontrol .

  • Hindari akses statis ke HttpContext (misalnya, IHttpContextAccessor.HttpContext).

  • Hindari panggilan ke BuildServiceProvider dalam ConfigureServices. BuildServiceProvider Panggilan biasanya terjadi ketika pengembang ingin menyelesaikan layanan di ConfigureServices. Misalnya, pertimbangkan kasus di mana LoginPath dimuat dari konfigurasi. Hindari pendekatan berikut:

    kode buruk memanggil BuildServiceProvider

    Pada gambar sebelumnya, memilih garis bergelombang hijau di bawah services.BuildServiceProvider menunjukkan peringatan ASP0000 berikut:

    ASP0000 Memanggil 'BuildServiceProvider' dari kode aplikasi menghasilkan salinan tambahan layanan singleton yang dibuat. Pertimbangkan alternatif seperti layanan injeksi dependensi sebagai parameter untuk 'Konfigurasi'.

    Panggilan BuildServiceProvider membuat kontainer kedua, yang dapat membuat singleton robek dan menyebabkan referensi ke grafik objek di beberapa kontainer.

    Cara yang benar untuk mendapatkan LoginPath adalah dengan menggunakan dukungan bawaan pola opsi untuk DI:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
            .AddCookie();
    
        services.AddOptions<CookieAuthenticationOptions>(
                            CookieAuthenticationDefaults.AuthenticationScheme)
            .Configure<IMyService>((options, myService) =>
            {
                options.LoginPath = myService.GetLoginPath();
            });
    
        services.AddRazorPages();
    }
    
  • Layanan sementara sekali pakai ditangkap oleh kontainer untuk dibuang. Ini dapat berubah menjadi kebocoran memori jika diselesaikan dari kontainer tingkat atas.

  • Aktifkan validasi cakupan untuk memastikan aplikasi tidak memiliki singleton yang menangkap layanan terlingkup. Untuk informasi selengkapnya, lihat Validasi cakupan.

Seperti semua set rekomendasi, Anda mungkin mengalami situasi di mana mengabaikan rekomendasi diperlukan. Pengecualian jarang terjadi, sebagian besar kasus khusus dalam kerangka kerja itu sendiri.

DI adalah alternatif untuk pola akses objek statis/global. Anda mungkin tidak dapat mewujudkan manfaat DI jika Anda mencampurnya dengan akses objek statis.

Orchard Core adalah kerangka kerja aplikasi untuk membangun aplikasi modular dan multi-penyewa di ASP.NET Core. Untuk informasi selengkapnya, lihat Dokumentasi Orchard Core.

Lihat sampel Orchard Core untuk contoh cara membuat aplikasi modular dan multi-penyewa hanya menggunakan Orchard Core Framework tanpa fitur khusus CMS-nya.

Layanan yang disediakan kerangka kerja

Metode ini Startup.ConfigureServices mendaftarkan layanan yang digunakan aplikasi, termasuk fitur platform, seperti Entity Framework Core dan ASP.NET Core MVC. Awalnya, yang IServiceCollection disediakan untuk ConfigureServices memiliki layanan yang ditentukan oleh kerangka kerja tergantung pada bagaimana host dikonfigurasi. Untuk aplikasi berdasarkan templat ASP.NET Core, kerangka kerja mendaftarkan lebih dari 250 layanan.

Tabel berikut mencantumkan sampel kecil layanan terdaftar kerangka kerja ini:

Jenis Layanan Seumur hidup
Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory Perubahan sementara
IHostApplicationLifetime Singleton
IWebHostEnvironment Singleton
Microsoft.AspNetCore.Hosting.IStartup Singleton
Microsoft.AspNetCore.Hosting.IStartupFilter Perubahan sementara
Microsoft.AspNetCore.Hosting.Server.IServer Singleton
Microsoft.AspNetCore.Http.IHttpContextFactory Perubahan sementara
Microsoft.Extensions.Logging.ILogger<TCategoryName> Singleton
Microsoft.Extensions.Logging.ILoggerFactory Singleton
Microsoft.Extensions.ObjectPool.ObjectPoolProvider Singleton
Microsoft.Extensions.Options.IConfigureOptions<TOptions> Perubahan sementara
Microsoft.Extensions.Options.IOptions<TOptions> Singleton
System.Diagnostics.DiagnosticSource Singleton
System.Diagnostics.DiagnosticListener Singleton

Sumber Daya Tambahan: