Trong bài viết này, tôi sẽ chia sẻ các best practice, mẹo và thủ thuật về việc sử dụng Dependency Injection (DI) trong ứng dụng ASP.NET Core. Mục địch đằng sau những nguyên tắc này là:
Bài viết này giả định rằng bạn đã quen thuộc với Dependency Injection và ASP.NET Core ở mức cơ bản. Nếu không, trước tiên bạn hãy đọc bài viết về ASP.NET Core Dependency Injection.
Constructor injection được sử dụng để khai báo và lấy các phụ thuộc của một dịch vụ trên service construction. Ví dụ:
public class ProductService
{
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
}
public void Delete(int id)
{
_productRepository.Delete(id);
}
}
Trong ví dụ trên, ProductService inject IProductRepository trong phương thức khởi tạo của nó, sau đó sử dụng nó bên trong phương thức Delete.
Best practice:
Dependency Injection Container mặc định trong ASP.NET Core không hỗ trợ property injection. Nhưng bạn có thể sử dụng một Dependency Injection Container khác hỗ trợ property injection. Ví dụ:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace MyApp
{
public class ProductService
{
public ILogger<ProductService> Logger { get; set; }
private readonly IProductRepository _productRepository;
public ProductService(IProductRepository productRepository)
{
_productRepository = productRepository;
Logger = NullLogger<ProductService>.Instance;
}
public void Delete(int id)
{
_productRepository.Delete(id);
Logger.LogInformation(
$"Deleted a product with id = {id}");
}
}
}
ProductService đang khai báo thuộc tính Logger với bộ thiết lập công khai . Vùng chứa tiêm phụ thuộc có thể đặt Bộ ghi nhật ký nếu nó có sẵn (đã đăng ký với vùng chứa DI trước đó).
Best practice:
Mẫu định vị dịch vụ là một cách khác để lấy các phụ thuộc. Thí dụ:
public class ProductService
{
private readonly IProductRepository _productRepository;
private readonly ILogger<ProductService> _logger;
public ProductService(IServiceProvider serviceProvider)
{
_productRepository = serviceProvider.GetRequiredService<IProductRepository>();
_logger = serviceProvider.GetService<ILogger<ProductService>>() ??
NullLogger<ProductService>.Instance;
}
public void Delete(int id)
{
_productRepository.Delete(id);
_logger.LogInformation($"Deleted a product with id = {id}");
}
}
Trong ví dụ trên, ProductService inject IServiceProvider và giải quyết các phụ thuộc bằng cách sử dụng nó. Phương thức GetRequiredService sẽ ném ra một ngoại lệ nếu phụ thuộc được yêu cầu chưa được đăng ký trước đó. Còn phương thức GetService chỉ trả về null trong trường hợp đó.
Khi bạn khởi tạo các dịch vụ bên trong phương thức khởi tạo (constructor), chúng sẽ được giải phóng khi dịch vụ được giải phóng. Vì vậy, bạn không cần quan tâm đến việc giải phóng / hủy bỏ các dịch vụ được giải quyết bên trong phương thức khởi tạo (giống như constructor injection và property injection).
Best practice:
Có ba loại vòng đời dịch vụ trong ASP.NET Core Dependency Injection:
DI Container theo dõi tất cả các dịch vụ đã được khởi tạo. Dịch vụ sẽ được giải phóng và xử lý khi thời gian tồn tại của chúng kết thúc:
Best practice:
Trong một số trường hợp, bạn có thể cần inject một dịch vụ khác trong một phương thức của dịch vụ của bạn. Trong những trường hợp như vậy, hãy đảm bảo rằng bạn phải giải phóng dịch vụ sau khi sử dụng. Cách tốt nhất để đảm bảo điều đó là tạo phạm vi dịch vụ. Ví dụ:
public class PriceCalculator
{
private readonly IServiceProvider _serviceProvider;
public PriceCalculator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public float Calculate(Product product, int count,
Type taxStrategyServiceType)
{
using (var scope = _serviceProvider.CreateScope())
{
var taxStrategy = (ITaxStrategy)scope.ServiceProvider
.GetRequiredService(taxStrategyServiceType);
var price = product.Price * count;
return price + taxStrategy.CalculateTax(price);
}
}
}
PriceCalculator inject IServiceProvider trong hàm khởi tạo của nó và gán nó cho một trường. Sau đó, PriceCalculator sử dụng nó bên trong phương thức Calculate để tạo phạm vi dịch vụ con. Nó sử dụng phương thức scope.ServiceProvider để inject dịch vụ, thay vì sử dụng trường _serviceProvider. Do đó, tất cả các dịch vụ được inject trong phạm vi được tự động giải phóng / xử lý vào cuối câu lệnh using.
Best practice:
Các dịch vụ Singleton thường được thiết kế để giữ trạng thái ứng dụng. Bộ nhớ đệm là một ví dụ điển hình về trạng thái ứng dụng. Ví dụ:
public class FileService
{
private readonly ConcurrentDictionary<string, byte[]> _cache;
public FileService()
{
_cache = new ConcurrentDictionary<string, byte[]>();
}
public byte[] GetFileContent(string filePath)
{
return _cache.GetOrAdd(filePath, _ =>
{
return File.ReadAllBytes(filePath);
});
}
}
FileService chỉ cần lưu nội dung tệp vào bộ nhớ cache để giảm số lần đọc đĩa. Dịch vụ này nên được đăng ký dưới dạng Singleton. Nếu không, bộ nhớ đệm sẽ không hoạt động như mong đợi.
Best practice:
Scoped có vẻ là một ứng cử viên phù hợp để lưu trữ dữ liệu theo yêu cầu web. Vì ASP.NET Core tạo phạm vi dịch vụ cho mỗi yêu cầu web. Vì vậy, nếu bạn đăng ký một dịch vụ theo scoped, nó có thể được chia sẻ trong một yêu cầu web. Ví dụ:
public class RequestItemsService
{
private readonly Dictionary<string, object> _items;
public RequestItemsService()
{
_items = new Dictionary<string, object>();
}
public void Set(string name, object value)
{
_items[name] = value;
}
public object Get(string name)
{
return _items[name];
}
}
Nếu bạn đăng ký RequestItemsService dưới dạng scoped và đưa nó vào hai dịch vụ khác nhau, thì bạn có thể nhận được một mục được thêm vào từ một dịch vụ khác vì chúng sẽ chia sẻ cùng một thể hiện của RequestItemsService. Đó là những gì chúng tôi mong đợi từ các dịch vụ scoped.
Nhưng thực tế có thể không phải lúc nào cũng như vậy. Nếu bạn tạo một phạm vi dịch vụ con và inject RequestItemsService từ phạm vi con, thì bạn sẽ nhận được một phiên bản mới của RequestItemsService và nó sẽ không hoạt động như bạn mong đợi. Vì vậy, dịch vụ có phạm vi không phải lúc nào cũng có nghĩa là phiên bản cho mỗi yêu cầu web.
Bạn có thể nghĩ rằng bạn không phạm phải sai lầm rõ ràng như vậy (giải quyết một phạm vi trong phạm vi con). Tuy nhiên, đây không phải là một sai lầm (được sử dụng rất thường xuyên) và trường hợp có thể không đơn giản như vậy. Nếu có một sự phụ thuộc lớn giữa các dịch vụ của bạn, bạn không thể biết liệu có ai đã tạo phạm vi dịch vụ con và inject một dịch vụ đưa vào dịch vụ khác… cuối cùng inject một dịch vụ scoped.
Best practice:
Ban đầu, Dependency Injection có vẻ đơn giản để sử dụng, nhưng tiềm ẩn nhiều vấn đề về đa luồng và rò rỉ bộ nhớ nếu bạn không tuân theo một số nguyên tắc nghiêm ngặt. Tôi đã chia sẻ một số nguyên tắc tốt dựa trên kinh nghiệm của bản thân trong quá trình phát triển các ứng dụng doanh nghiệp dựa trên ASP.NET Core.
Bạn có thể vui lòng tắt trình chặn quảng cáo ❤️ để hỗ trợ chúng tôi duy trì hoạt động của trang web.
Trong bài viết này, chúng ta sẽ tìm hiểu cách đặt tên cho các endpoint để có thể tạo liên kết đến chúng từ bất kỳ đâu trong ứng dụng ASP.NET Core.
Trong bài viết này, chúng ta sẽ khám phá Model trong ASP.NET Core và các sub-framework khác như ASP.NET Core MVC, Razor Pages, Endpoints và Blazor.
Bài viết này sẽ giới thiệu cho bạn những điều cơ bản về việc thiết lập một dự án ASP.NET Core để sử dụng các component Svelte phía máy khách.
Bài viết này hướng dẫn cách triển khai một background service trong ứng dụng ASP.NET Core và cách giao tiếp với background service từ một HTTP request.