Dependency Injection in ASP.NET Core

ASP.NET Core Dependency Injection

Overview of how to use AspNet Core Dependency Injection

Quang Trong VU
Old Dev

--

Dependency Injection

Bài viết này mục đích mô tả cách thức hoạt động cơ bản của DI trong ASP.NET Core.

Mặc dù ASP.NET Core cung cấp tập tính năng tối thiểu để có thể sử dụng trên Service Container mặc định, tuy nhiên có thể các chức năng của Service Container mặc định chưa đủ đáp ứng nhu cầu sử dụng. Do vậy bạn có thể muốn sử dụng các DI Container khác ví dụ Autofac hoặc Castle Windor bởi chúng sẽ cung cấp những tính năng cao cấp hơn(?).

Dưới đây là danh mục trong bài này:

  1. Service Lifetimes
    -
    Transient
    - Scoped
    - Singleton
  2. IServiceCollection và IServiceProvider
    - Factory Method
    - Instance Registration
    - Generic Type Registration
    - Multiple Registration
  3. Using Castle Windsor và Autofac
    - Castle Windsor
    - Autofac

Và bây giờ là các bước chi tiết.

Trước tiên là một ví dụ đơn giản về liên hệ giữa các khái niệm:

using Microsoft.Extensions.DependencyInjection;class Program
{
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<FooService>(); var serviceProvider = services.BuildServiceProvider();
var fooService = serviceProvider.GetService<FooService>();
fooService.DoIt();
}
}
public class FooService
{
public void DoIt()
{
Console.WriteLine("This is Microsoft DI!");
}
}
Demo 1

*Muốn chạy ví dụ trên bạn có thể tạo ứng dụng Console trên Visual Studio, macOS hoặc PC đều được. Sau đó cài đặt thêm nuget package Microsoft.Extensions.DependencyInjection.

IServiceCollection là một tập hợp (collection) các service descriptor (mô tả, định nghĩa các service). Chúng ta có thể Register Service của mình trong tập hợp này với những lifetime khác nhau: Transient, Scoped, Singleton.

IServiceProvider là một Built-in Container đơn giản được cung cấp sẵn trong ASP.NET Core. Nó hỗ trợ Constructor Injection mặc định. Chúng ta sẽ lấy ra các Registered Service (đã Register trong IServiceCollection) bằng việc sử dụng Service Provider.

1. Service Lifetimes

Định nghĩa vòng đời của các Service Instance được tạo ra bởi Service Provider.

Transient: Service Instance được tạo mỗi khi chúng được gọi. (Kiểu mỗi câu lệnh truy suất đến Service Object)

Scoped: Service Instance được tạo một lần cho một Request. Kiểu một HTTP Request. trong một HTTP Request mọi câu lệnh gọi đến Service Object đều sử dụng chung một Service Instance.

Singleton: Service Instance được tạo khi được gọi lần đầu tiên, và tồn tại cùng vòng đời của Application.

Bạn có thể thử trên ví dụ sau:

Bước 1: Định nghĩa các Service Class

public class TransientDateOperation
{
public TransientDateOperation()
{
Console.WriteLine("Transient service is created!");
}
}
public class ScopedDateOperation
{
public ScopedDateOperation()
{
Console.WriteLine("Scoped service is created!");
}
}
public class SingletonDateOperation
{
public SingletonDateOperation()
{
Console.WriteLine("Singleton service is created!");
}
}

Bước 2: Register, Call

class Program
{
static void Main(string[] args)
{
DemoServiceLifecycle();
}
private static void DemoServiceLifecycle()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<TransientDateOperation>();
services.AddScoped<ScopedDateOperation>();
services.AddSingleton<SingletonDateOperation>();
var serviceProvider = services.BuildServiceProvider(); Console.WriteLine();
Console.WriteLine("-------- 1st Request --------");
Console.WriteLine();
var transientService = serviceProvider.GetService<TransientDateOperation>();
var scopedService = serviceProvider.GetService<ScopedDateOperation>();
var singletonService = serviceProvider.GetService<SingletonDateOperation>();
Console.WriteLine();
Console.WriteLine("-------- 2nd Request --------");
Console.WriteLine();
var transientService2 = serviceProvider.GetService<TransientDateOperation>();
var scopedService2 = serviceProvider.GetService<ScopedDateOperation>();
var singletonService2 = serviceProvider.GetService<SingletonDateOperation>();
Console.WriteLine();
Console.WriteLine("-----------------------------");
Console.WriteLine();
}
}
Demo 2

Tuy nhiên ví dụ trên chưa thể hiện rõ vòng đời Scoped. Nên bạn có thể tham khảo ví dụ sau:

private static void DemoServiceLifecycle()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<TransientDateOperation>();
services.AddScoped<ScopedDateOperation>();
services.AddSingleton<SingletonDateOperation>();
var serviceProvider = services.BuildServiceProvider(); Console.WriteLine();
Console.WriteLine("-------- 1st Request --------");
Console.WriteLine();
var transientService = serviceProvider.GetService<TransientDateOperation>();
var scopedService = serviceProvider.GetService<ScopedDateOperation>();
var singletonService = serviceProvider.GetService<SingletonDateOperation>();
Console.WriteLine();
Console.WriteLine("-------- 2nd Request --------");
Console.WriteLine();
var transientService2 = serviceProvider.GetService<TransientDateOperation>();
var scopedService2 = serviceProvider.GetService<ScopedDateOperation>();
var singletonService2 = serviceProvider.GetService<SingletonDateOperation>();
Console.WriteLine();
Console.WriteLine("-----------------------------");
Console.WriteLine();
}
Demo 3

Bằng việc sử dụng using thì sẽ kết thúc scope sau khi ra khỏi khối using.

Tiếp tục chúng ta sẽ tìm hiểu ASP.NET Core DI Service Provider, Service Collection (đây là DI mặc định của ASP.NET Core nhé — như cách đặt tên ASP.NET Core DI)

2. IServiceCollection và IServiceProvider

Trong phần này chúng ta sẽ làm thêm một số ví dụ để hiểu hơn về cách thức hoạt động của cơ chế DI trong ASP.NET Core. Các ví dụ sẽ kết hợp với các khái niệm mới.

Factory Medthod:

Chúng ta sẽ xem Factory Method pattern được sử dụng như thế nào. Đầu tiên tạo định nghĩ Service interface và Service implementation.

public class FooService
{
// this is a dependency of this Service
private readonly ISendMail _sendMail;
// inject by constructor
public FooService(ISendMail sendMail)
{
_sendMail = sendMail;
}
public void DoIt()
{
_sendMail.Send();
}
}
public class SendMail : ISendMail
{
public void Send()
{
Console.WriteLine("Sent");
}
}
public interface ISendMail
{
void Send();
}
// Register & Callclass Program
{
static void Main(string[] args)
{
FooFactoryMethod();
}
public static void FooFactoryMethod()
{
IServiceCollection services = new ServiceCollection();
// Register
// Register Interface with Implementation.
services.AddTransient<ISendMail, SendMail>();
// Directly register Class and Instance it.
services.AddTransient(
provider => new FooService(provider.GetService<ISendMail>())
);
// Call
var serviceProvider = services.BuildServiceProvider();
var instance = serviceProvider.GetService<FooService>();
instance.DoIt();
}
}
Demo 4

Instance Registration

Như ví dụ, Service Instance được tạo Anonymous ngay khi đăng ký Service: provider => new FooService(provider.GetService<ISendMail>()); . Tuy nhiên bạn có thể tạo Service Instance sau đó Register nó.

public class FooInstance
{
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
InstanceRegistrationDemo();
}
public static void InstanceRegistrationDemo()
{
var instance = new FooInstance { Value = 10 };
IServiceCollection services = new ServiceCollection();
// Register an Instance
services.AddSingleton(instance);
foreach (ServiceDescriptor service in services)
{
if (service.ServiceType == typeof(FooInstance))
{
var registeredInstance = (FooInstance)service.ImplementationInstance;
Console.WriteLine("Registered instance: " + registeredInstance.Value);
}
}
var serviceProvider = services.BuildServiceProvider();
var fooInstance = serviceProvider.GetService<FooInstance>();
Console.WriteLine("Registered service by instance registration : " + fooInstance.Value);
}
}
Demo 5

Vừa rồi chúng ta đã sử dụng 4 kiểu register:

services.AddTransient<TransientDateOperation>();

services.AddTransient<ISendMail, SendMail>();

service.AddTransient(
provider => new FooService(provider.GetService<ISendMail>());
);

services.AddSingleton(instance);

Tiếp theo sẽ là

Generic Type Registration

IServiceCollection services = new ServiceCollection();

services.AddTransient<FooClassWithValue>();
services.AddTransient(typeof(IFooGeneric<>), typeof(FooGeneric<>));

var serviceProvider = services.BuildServiceProvider();

var service = serviceProvider.GetService<IFooGeneric<FooClassWithValue>>();

Multiple Registration

ASP.NET Core DI hỗ trợ multiple registration với những option khác nhau.

public interface IHasValue
{
object Value { get; set; }
}
public class FooClassWithValue : IHasValue
{
public object Value { get; set; }
public FooClassWithValue()
{
Value = 11;
}
}
public class BarClassWithValue : IHasValue
{
public object Value { get; set; }
public BarClassWithValue()
{
Value = 12;
}
}

Ví dụ sử dụng

class Program
{
static void Main(string[] args)
{
MultipleImplementation();
MultipleImplementationWithTry();
MultipleImplementationWithReplace();
}
private static void MultipleImplementation()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IHasValue, FooClassWithValue>();
services.AddTransient<IHasValue, BarClassWithValue>();
var serviceProvider = services.BuildServiceProvider();
var listServices = serviceProvider.GetServices<IHasValue>().ToList();
var gotService = serviceProvider.GetService<IHasValue>();
Console.WriteLine("----- Multiple Implemantation Services -----------"); foreach (var service in listServices)
{
Console.WriteLine(service.Value);
}
Console.WriteLine("----- Multiple Implemantation Service ------------");
Console.WriteLine(gotService.Value);
}
private static void MultipleImplementationWithTry()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IHasValue, FooClassWithValue>();
services.TryAddTransient<IHasValue, BarClassWithValue>();
var serviceProvider = services.BuildServiceProvider();
var listServices = serviceProvider.GetServices<IHasValue>().ToList();
Console.WriteLine("----- Multiple Implemantation Try ----------------"); foreach (var service in listServices)
{
Console.WriteLine(service.Value);
}
}
private static void MultipleImplementationWithReplace()
{
IServiceCollection services = new ServiceCollection();
services.AddTransient<IHasValue, FooClassWithValue>();
services.Replace(ServiceDescriptor.Transient<IHasValue, BarClassWithValue>());
var serviceProvider = services.BuildServiceProvider();
var listServices = serviceProvider.GetServices<IHasValue>().ToList();
Console.WriteLine("----- Multiple Implemantation Replace ------------"); foreach (var service in listServices)
{
Console.WriteLine(service.Value);
}
Console.WriteLine("--------------------------------------------------");
}
}

Như vậy nếu dùng thuần AddTransient thì chúng ta sẽ nhận được đối tượng Register cuối. Nếu dùng TryAdd- thì nó sẽ không thực hiện Register nếu đối tượng đã được đăng ký. Còn nếu dùng Replace thì sẽ thay thế đối tượng cũ.

3. Sử dụng Autofac, Castle Windsor.

Castle Windsor

Đầu tiên bạn cần cài đặt nuget package Castle.Windsor.MsDependencyInjection.

IServiceCollection services = new ServiceCollection();
services.AddTransient<FooService>();

var windsorContainer = new WindsorContainer();
windsorContainer.Register(
Component.For<FooService>()
);

var serviceProvider = WindsorRegistrationHelper.CreateServiceProvider(
windsorContainer,
services
);

var fooService = serviceProvider.GetService<FooService>();

Autofac

Để sử dụng Autofac, đầu tiên bạn cài đặt nuget package: Autofac.Extensions.DependencyInjection.

IServiceCollection services = new ServiceCollection();
services.AddTransient<FooService>();

var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<FooService>();
containerBuilder.Populate(services);

var container = containerBuilder.Build();
var serviceProvider = new AutofacServiceProvider(container);
var fooService = serviceProvider.GetService<FooService>();

Bài viết tới sẽ mô tả chi tiết các sử dụng Castle Windsor trong ASP.NET Core Zero template project.

Các tham khảo.

--

--

Quang Trong VU
Old Dev
Editor for

Software Developer — Life’s a journey — Studying and Sharing