Khởi tạo service trong Startup ASP.NET Core
Đôi khi chúng ta cần khởi tạo các service trong giai đoạn khởi động ứng dụng của mình. Chúng ta sẽ xem nhanh nhiều cách tiếp cận khả thi dành cho nhà phát triển xây dựng ứng dụng ASP.NET Core.
Tại sao phải khởi tạo dịch vụ trong Startup ASP.NET Core?
Không có gì bí mật khi cơ sở hạ tầng ASP.NET mới nhất phụ thuộc nhiều vào dependency injection.
Khi làm việc với dependency injection, chúng ta sẽ cấu hình các service của mình một lần khi bắt đầu vòng đời của ứng dụng và sau đó gọi ASP.NET Core để khởi tạo các phụ thuộc của chúng ta.
Theo tôi, việc thiết lập một lần và sử dụng ở mọi nơi là một lợi thế lớn khi sử dụng dependency injection.
Khởi tạo service trong phương thức ConfigureServices
Vị trí đầu tiên chúng ta có thể khởi tạo một service nằm trong phương thức ConfigureServices
của chúng ta. Lý do để làm điều này có thể là để khởi tạo và đăng ký một thể hiện singleton với bộ sưu tập dịch vụ (services collection) của ứng dụng.
Tôi không khuyên bạn nên khởi tạo bất kỳ service nào trongphương thức
ConfigureServices
. Như bạn sẽ thấy, có một số lưu ý và chi phí khi làm như vậy.
Trong phương thức ConfigureServices
, chúng ta có thể tạo một thể hiện của trình cung cấp dịch vụ (service provider).
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<Dependency>();
var provider = services.BuildServiceProvider();
var dependency = provider.GetRequiredService<Dependency>();
Console.WriteLine(dependency.Hello);
}
Đoạn mã sau sẽ không thành công.
public void ConfigureServices(IServiceCollection services)
{
var provider = services.BuildServiceProvider();
// we haven't registered Dependency yet
var dependency = provider.GetRequiredService<Dependency>();
services.AddScoped<Dependency>();
Console.WriteLine(dependency.Hello);
}
Mã trên không thành công vì chúng ta chưa đăng ký sự phụ thuộc cho lớp Dependency
của mình, nhưng chúng ta lại đang cố gắng khởi tạo nó.
Khởi tạo service trong phương thức Configure
Nếu mọi người muốn chạy một số mã khi khởi động, tốt nhất nên làm điều đó trong phương thức Configure
, vì tại thời điểm này, chúng ta đã đăng ký tất cả các service của mình.
Chúng ta có thể thực hiện các tác vụ như gọi một service từ xa, thực thi chuyển đổi cơ sở dữ liệu hoặc ghi nhật ký.
Lưu ý rằng bất kỳ thao tác block thread nào cũng có thể làm chậm thời gian khởi động ứng dụng của bạn và bất kỳ lỗi nào cũng sẽ khiến ứng dụng của bạn không thể khởi động.
Phương pháp khởi tạo service tốt nhất
Một cách dễ dàng, cách tốt nhất là thêm phần phụ thuộc của chúng ta vào danh sách các tham số của phương thức Configure
. ASP.NET Core sẽ khởi tạo service của chúng ta từ bộ sưu tập dịch vụ (service collection).
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env,
// our dependency
Dependency dependency
)
{
Console.WriteLine(dependency.Hello);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); });
});
}
Ưu điểm của cách tiếp cận này là chúng ta thậm chí có thể giải quyết các phụ thuộc theo phạm vi (scope), như trong trường hợp này, phạm vi là phương thức Configure
.
Phương pháp khác nhưng nên hạn chế
Một cách tiếp cận khác, mà tôi không khuyến khích, là sử dụng thuộc tính ApplicationServices
của interface IApplicationBuilder
. Interface IApplicationBuilder
là tham sốmặc định của phương thức Configure
trong Startup
.
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(
IApplicationBuilder app,
IWebHostEnvironment env
)
{
// cannot call Scoped dependencies
var dependency = app
.ApplicationServices
.GetRequiredService<Dependency>();
Console.WriteLine(dependency.Hello);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context => { await context.Response.WriteAsync("Hello World!"); });
});
}
Cách tiếp cận này chỉ hoạt động cho các service được đăng ký là Singleton
và Transient
. Cố gắng khởi tạo các service Scoped
sẽ dẫn đến ngoại lệ sau. ASP.NET Core sẽ ném ngoại lệ này vào một nỗ lực để tránh các phụ thuộc bị nắm bắt.
System.InvalidOperationException: Cannot resolve scoped service 'WebApplication7.Dependency' from root provider.
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteValidator.ValidateResolution(Type serviceType, IServiceScope scope, IServiceScope rootScope)
at Microsoft.Extensions.DependencyInjection.ServiceProvider.Microsoft.Extensions.DependencyInjection.ServiceLookup.IServiceProviderEngineCallback.OnResolve(Type serviceType, IServiceScope scope)
...
Điều này nằm ngoài phạm vi của bài viết này, tuy nhiên chúng ta sẽ thảo luận nhanh về các phụ thuộc bị nắm bắt. Phụ thuộc bị nắm bắt là khi một service Singleton
giữ một service Scoped
. Phụ thuộc bị nắm bắt đặc biệt nguy hiểm vì thể hiện đầu tiên của một service Scoped
có thể có thông tin về người dùng đầu tiên yêu cầu ứng dụng của chúng ta.
Bạn bắt buộc không được sử dụng BẤT KỲ dịch vụ nào được khởi tạo trong phương thứcConfigure như một phần của mã đăng ký pipeline của bạn.
Phần kết luận
Chúng ta đã có một số cách để khởi tạo các service trong lớp Startup
của chúng ta. Có nhiều cách để giải quyết vấn đề này. Một số thẳng thắn hơn những người khác.
Chúng ta phải xem xét chi phí để khởi tạo các service trong lớp Startup
của mình và tác động của nó đối với hiệu suất khởi động. Giống như nhiều thứ, tất cả đều phụ thuộc.
Tôi hy vọng bạn tìm thấy bài viết này thú vị và vui lòng cho tôi biết nếu bạn có các cách khởi tạo service khác trong lớp Startup
.