Có nhiều cách để quản lý việc chèn phần phụ thuộc bên trong các ứng dụng của chúng tôi và tôi nghĩ điều quan trọng là phải hiểu được lợi ích cũng như hạn chế của các phương pháp tiếp cận khác nhau. Sử dụng Autofac ContainerBuilder
trong ASP.NET Core làm cách chính để cấu trúc các phần phụ thuộc của bạn thay vì sử dụng AutofacServiceProviderFactory
được đề xuất là một trong những con đường mà chúng ta có thể khám phá!
Trong bài viết này, tôi nêu bật cách sử dụng ContainerBuilder của Autofac trong ứng dụng ASP.NET Core của bạn thay vì AutofacServiceProviderFactory. Chúng tôi sẽ xem xét những gì bạn có thể và không thể làm với phương pháp này so với các phương pháp khác mà chúng tôi có thể áp dụng với tính năng chèn phụ thuộc.
Đây sẽ là một phần của loạt bài trong đó tôi khám phá độ phân giải phụ thuộc bằng Autofac bên trong ASP.NET Core. Tôi chắc chắn sẽ bao gồm loạt bài bên dưới khi các số báo được xuất bản:
Khi kết thúc loạt bài này, bạn sẽ có thể tự tin khám phá các kiến trúc plugin bên trong ASP.NET Core và Blazor hơn, điều này sẽ cung cấp nhiều nội dung hơn nữa để bạn khám phá.
Công bằng mà nói, tiêu đề của phần này *gần như* là mồi nhử nhấp chuột. Tôi thực sự nghĩ rằng việc sử dụng AutofacServiceProviderFactory
làm cách được đề xuất để thiết lập Autofac trong các ứng dụng ASP.NET Core của bạn là điều tuyệt vời cho hầu hết các ứng dụng. Phần lớn các nhà phát triển muốn sử dụng Autofac làm khung tiêm phụ thuộc mà họ lựa chọn sẽ không gặp phải nhiều vấn đề theo cách này.
Nó cho chúng ta khả năng:
WebApplicationBuilder
(và mọi thứ có sẵn tại thời điểm này)IConfiguration
(cũng có sẵn từ phiên bản WebApplicationBuilder
)
Nhưng vấn đề lớn đối với tôi: Chúng tôi không thể truy cập phiên bản WebApplication
. Khi tôi xây dựng kiến trúc plugin trong C#, đặc biệt là khi xây dựng các ứng dụng ASP.NET Core, tôi muốn có quyền truy cập vào phiên bản WebApplication
để đăng ký các tuyến đường. Điều này cho phép tôi đăng ký các API tối thiểu từ các plugin của mình một cách dễ dàng, về mặt kỹ thuật chỉ cần truy cập vào việc triển khai IEndpointRouteBuilder
để có được cú pháp tiện dụng.
Tôi có thể đăng ký API không tối thiểu mà không cần điều này không? Tuyệt đối. Có cách nào khác để cung cấp cú pháp tương tự và không yêu cầu phiên bản WebApplication
không? Rất có khả năng. Nhưng thay vì cố gắng giải quyết vấn đề ĐÓ, tôi muốn xem liệu tôi có thể truy cập vào phần phụ thuộc mà tôi quan tâm hay không.
Đã đến lúc thay đổi kế hoạch về cách thiết lập vùng chứa phụ thuộc của tôi!
Hãy xem xét một ứng dụng mẫu để chúng ta có một số điểm chung để khám phá. Nếu bạn đã đọc bài viết trước thì bài viết này sẽ trông tương tự — một biến thể của ứng dụng thời tiết mẫu:
using Autofac; using Microsoft.AspNetCore.Mvc; // personal opinion: // I absolutely love having the entry point of my // applications being essentially: // - make my dependencies // - give me the primary dependency // - use it // - ... nothing else :) ContainerBuilder containerBuilder = new(); containerBuilder.RegisterModule<MyModule>(); using var container = containerBuilder.Build(); using var scope = container.BeginLifetimeScope(); var app = scope.Resolve<WebApplication>(); app.Run(); internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) { public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); } internal sealed class MyModule : Module { protected override void Load(ContainerBuilder containerBuilder) { containerBuilder.RegisterType<DependencyA>().SingleInstance(); containerBuilder.RegisterType<DependencyB>().SingleInstance(); containerBuilder.RegisterType<DependencyC>().SingleInstance(); containerBuilder .Register(ctx => { var builder = WebApplication.CreateBuilder(Environment.GetCommandLineArgs()); return builder; }) .SingleInstance(); containerBuilder .Register(ctx => ctx.Resolve<WebApplicationBuilder>().Configuration) .As<IConfiguration>() .SingleInstance(); containerBuilder .Register(ctx => { var builder = ctx.Resolve<WebApplicationBuilder>(); var app = builder.Build(); app.UseHttpsRedirection(); // FIXME: the problem is that the Autofac ContainerBuilder // was used to put all of these pieces together, // but we never told the web stack to use Autofac as the // service provider. // this means that the minimal API will never be able to // find services off the container. we would need to resolve // them BEFORE the API is called, like in this registration // method itself, from the context that is passed in. //DependencyA dependencyA = ctx.Resolve<DependencyA>(); // FIXME: But... What happens if something wants to take a // dependency on the WebApplication instance itself? Once the // web application has been built, there's no more adding // dependencies to it! var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet( "/weatherforecast", ( [FromServices] DependencyA dependencyA , [FromServices] DependencyB dependencyB , [FromServices] DependencyC dependencyC ) => { var forecast = Enumerable .Range(1, 5) .Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }); return app; }) .SingleInstance(); } } internal sealed class DependencyA( WebApplicationBuilder _webApplicationBuilder); internal sealed class DependencyB( WebApplication _webApplication); internal sealed class DependencyC( IConfiguration _configuration);
Một chú thích của phương pháp này là việc sử dụng lớp Autofac ContainerBuilder
làm vùng chứa phụ thuộc chính sẽ mang lại cho chúng tôi cơ hội cấu trúc điểm vào của mình thành:
Theo tôi, đây là mã khởi động ứng dụng lý tưởng. Tại sao? Bởi vì bạn không bao giờ cần phải quay lại đây để chạm vào nó. Bao giờ. Cho dù bạn thêm bao nhiêu thứ vào! Và đó là tất cả vì bạn có thể quét các cụm để tải thêm mô-đun.
Một lần nữa, đây là sở thích cá nhân của tôi và tôi không cố khẳng định rằng đây phải là mục tiêu của mọi người.
Tất nhiên, có một cách tiếp cận khác không hoàn toàn chống đạn được. Vì vậy, hãy thảo luận về những gì chúng ta KHÔNG nhận được với thiết lập Autofac này:
WebApplication
đã được xây dựng không được định cấu hình để sử dụng Autofac làm khung chèn phụ thuộc!WebApplication
trên vùng chứa phụ thuộc… Vì vậy, chúng tôi không thực hiện bất kỳ tiến bộ nào cụ thể về việc truy cập vào phiên bản đó.
Nhưng chủ yếu là vậy thôi! Đây không phải là một danh sách hạn chế khủng khiếp, nhưng phương pháp Autofac ContainerBuilder không phải là giải pháp hiệu quả đối với chúng tôi. Vì vậy, chúng ta đã nhận được gì từ nó? cũng sẽ giúp giải thích những điều sau:
Ưu và nhược điểm đối với mọi việc chúng tôi làm! Bây giờ chúng ta đã thấy các vấn đề với Autofac ContainerBuilder trong ASP.NET Core, đã đến lúc xem xét những tiến bộ mà chúng ta đã đạt được từ vấn đề này:
WebApplicationBuilder
và IConfiguration
, vì vậy đó là lợi ích có thể so sánh được khi sử dụng phương pháp AutofacServiceProviderFactory
.Chúng tôi thấy rằng chúng tôi có thể đăng ký các API tối thiểu trong phương thức đăng ký Autofac, nhưng thật không may, chúng tôi không thể giải quyết trực tiếp các phần phụ thuộc từ vùng chứa trên chính lệnh gọi API tối thiểu. Chúng ta có thể xây dựng một lớp chuyên dụng như sau để xử lý các định nghĩa tuyến đường với các phần phụ thuộc được giải quyết tự động:
internal sealed class WeatherForecastRoutes( DependencyA _dependencyA // FIXME: still can't depend on this because // we can't get the WebApplication //, DependencyB _dependencyB , DependencyC _dependencyC) { private static readonly string[] _summaries = new[] { "Freezing", "Bracing", // ... }; public WeatherForecast[] Forecast() { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), _summaries[Random.Shared.Next(_summaries.Length)] )) .ToArray(); return forecast; } }
Độ phân giải tự động xảy ra nếu chúng ta có lớp này VÀ tất cả các phần phụ thuộc trên cùng một vùng chứa. Sau đó, vấn đề chỉ là gọi mã này:
var weatherForecastRoutes = ctx.Resolve<WeatherForecastRoutes>(); app.MapGet("/weatherforecast2", weatherForecastRoutes.Forecast);
Điều này vẫn hơi tệ nếu xét từ góc độ plugin vì chúng ta cần phải giải quyết thủ công tất cả các lớp định tuyến chỉ để gọi mã đăng ký như vậy - tất cả đều xuất phát từ thực tế là những thứ này không thể giải quyết quyền truy cập của chính chúng vào WebApplication
ví dụ.
Nhưng chờ đã… Điều gì sẽ xảy ra nếu chúng ta lật ngược tình thế? Điều gì sẽ xảy ra nếu chúng ta có thể giải quyết một số giao diện như IRegisterRoutes
và chuyển vào phiên bản WebApplication
?!
// NOTE: make sure to register WeatherForecastRouteRegistrar on the // autofac container as IRegisterRoutes! internal interface IRegisterRoutes { void RegisterRoutes(WebApplication app); } internal sealed class WeatherForecastRouteRegistrar( WeatherForecastRoutes _weatherForecastRoutes) : IRegisterRoutes { public void RegisterRoutes(WebApplication app) { app.MapGet("/weatherforecast2", _weatherForecastRoutes.Forecast); } } // TODO: add this to the autofac code where the // WebApplication instance is built: foreach (var registrar in ctx.Resolve<IEnumerable<IRegisterRoutes>>()) { registrar.RegisterRoutes(app); }
Bây giờ, chúng ta thậm chí không cần quan tâm liệu phiên bản WebApplication
có thể truy cập được vào các plugin của chúng ta hay không! Có lẽ phiên bản đầu tiên không quá hạn chế. Có lẽ chúng ta đang tìm hiểu điều gì đó ở đây… Tuy nhiên, bài viết tiếp theo sẽ giải thích điều này chi tiết hơn.
Trong bài viết này, tôi đã khám phá cách sử dụng Autofac ContainerBuilder
một cách rõ ràng thay vì sử dụng AutofacServiceProviderFactory
như thường được đề xuất. Chúng tôi đã thấy một số lợi ích và hạn chế tương tự, nhưng cũng có một số điều khác cần xem xét. Mỗi cách có thể đưa ra những ưu và nhược điểm tùy thuộc vào những gì bạn đang theo đuổi trong đơn đăng ký của mình.
Điều thú vị là nếu chúng ta đang cố gắng hướng tới các plugin, chúng ta thậm chí có thể không cần truy cập vào phiên bản WebApplication
từ các plugin của mình! Nếu chúng tôi quan tâm đến các API tối thiểu, điều này có thể vẫn còn hạn chế… nhưng mặt khác, chúng tôi đang có một suy nghĩ thú vị!
Nếu bạn thấy điều này hữu ích và bạn đang tìm kiếm thêm cơ hội học tập, hãy cân nhắc đăng ký nhận bản tin kỹ thuật phần mềm miễn phí hàng tuần của tôi và xem các video miễn phí của tôi trên YouTube !