paint-brush
需要了解 ASP.NET Core 中 Autofac ContainerBuilder 的技巧经过@devleader
285 讀數

需要了解 ASP.NET Core 中 Autofac ContainerBuilder 的技巧

经过 Dev Leader12m2024/05/10
Read on Terminal Reader

太長; 讀書

了解如何在 ASP.NET Core 中使用 Autofac ContainerBuilder 连接依赖项注入。我将探讨使用此方法可以做什么和不能做什么!
featured image - 需要了解 ASP.NET Core 中 Autofac ContainerBuilder 的技巧
Dev Leader HackerNoon profile picture
0-item


在我们的应用程序内部管理依赖注入的方法有很多,我认为了解不同方法的优点和局限性很重要。在 ASP.NET Core 中使用 Autofac ContainerBuilder作为构建依赖项的主要方法,而不是使用建议的AutofacServiceProviderFactory ,这就是我们可以探索的一条路径!


在本文中,我将重点介绍如何在 ASP.NET Core 应用程序中使用 Autofac 的 ContainerBuilder 而不是 AutofacServiceProviderFactory。我们将看看这种方法与我们可以使用依赖注入的其他方法相比可以做什么和不能做什么。


这将是我探索在 ASP.NET Core 中使用 Autofac 进行依赖解析的系列文章的一部分。我一定会在发布问题时将以下系列文章包括在内:


在本系列结束时,您将能够更自信地探索 ASP.NET Core 和 Blazor 内部的插件架构,这将为您提供更多内容供您探索。


AutofacServiceProviderFactory 的问题

公平地说,本节标题*几乎*是点击诱饵。我确实认为使用AutofacServiceProviderFactory作为在 ASP.NET Core 应用程序中设置 Autofac 的建议方式对于大多数应用程序来说都很好。绝大多数想要使用 Autofac 作为首选依赖注入框架的开发人员不会遇到太多问题。


它确实使我们能够:

  • 访问WebApplicationBuilder (以及此时可用的任何内容)
  • 访问IConfiguration实例(也可从WebApplicationBuilder实例获得)
  • 能够将依赖项传递给最少的 API


但对我来说,最大的问题是:我们无法访问WebApplication实例。当我用 C# 构建插件架构时,尤其是构建 ASP.NET Core 应用程序时,我希望能够访问WebApplication实例以注册路由。这使我能够轻松地从插件中注册最少的 API,从技术上讲,只需要访问IEndpointRouteBuilder的实现即可获得方便的语法。

我可以不使用这个来注册非最小 API 吗?当然可以。还有其他方法可以提供类似的语法并且不需要WebApplication实例吗?很有可能。但是我不想尝试解决该问题,而是想看看是否可以访问我感兴趣的依赖项。


是时候改变如何设置我的依赖容器的计划了!


探索示例 ASP.NET Core 应用程序

让我们看一个示例应用程序,以便我们有一些共同点可以探索。如果你读过上一篇文章,它看起来会很相似——示例天气应用程序的一个变体:

 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);


这种方法的一个亮点是使用 Autofac ContainerBuilder类作为我们的主要依赖容器,使我们有机会将我们的入口点构建为:

  • 容器创建
  • 依赖项注册
  • 主依赖解析
  • … 调用单一方法启动应用程序!


在我看来,这是理想的应用程序启动代码。为什么?因为你永远不需要回到这里来触碰它。永远不需要。无论你添加多少东西!而这一切都是因为你可以扫描程序集来加载更多模块。

再说一遍,这是我的个人偏好,我并不是想声称这应该是每个人的目标。

ASP.NET Core 中 Autofac ContainerBuilder 的缺陷

当然,另一种方法并不十分可靠。那么让我们讨论一下 Autofac 的这种设置无法实现什么:

  • 通过最少的 API 传递的服务解析参数根本不起作用。构建的WebApplication未配置为使用 Autofac 作为依赖项注入框架!
  • 与上一篇文章类似,我们仍然无法获取依赖容器上的WebApplication实例……所以我们没有在访问它方面取得任何进展。


但基本上就是这样了!这不是一个糟糕的缺点列表,但 Autofac ContainerBuilder 方法对我们来说并不是一个万能的解决方案。那么,我们从中得到了什么?也将有助于解释以下内容:

ASP.NET Core 中 Autofac ContainerBuilder 的优势

我们所做的每件事都有利有弊!现在我们已经看到了 ASP.NET Core 中 Autofac ContainerBuilder 的问题,现在是时候看看我们从中获得了哪些进步:


  • 我们仍然可以访问WebApplicationBuilderIConfiguration实例,因此这与使用AutofacServiceProviderFactory方法具有相当的好处。
  • 我们可以获得一个非常精简的程序入口点,我真的很喜欢看到这一点。容器创建、注册、解析入口点方法,就这些!
  • 最小 API 可以工作,但不能处理依赖项。不过,我们可以预先解析最小 API 所需的依赖项,并在方法注册时传入这些依赖项。请参阅注释代码!

基于插件的 ASP.NET 路由有更多反转吗?

我们看到,我们可以在 Autofac 注册方法中注册最小 API,但不幸的是,我们无法直接从最小 API 调用本身解析来自容器的依赖项。我们可以构建一个专用类,如下所示,处理路由定义并自动解析依赖项:

 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; } }


如果此类及其依赖项都在同一个容器中,则会发生自动解析。然后只需调用此代码即可:

 var weatherForecastRoutes = ctx.Resolve<WeatherForecastRoutes>(); app.MapGet("/weatherforecast2", weatherForecastRoutes.Forecast);


从插件的角度来看,这仍然有点糟糕,因为我们需要手动解析所有路由类才能调用这样的注册代码 - 所有这些都源于这些东西无法解析它们自己对WebApplication实例的访问。


但是等等...如果我们反过来会怎么样?如果我们可以解析一些接口(如IRegisterRoutes并传入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); }


现在,我们甚至不需要关心WebApplication实例是否可以被我们的插件访问!也许第一个版本毕竟没有那么限制。也许我们在这里发现了一些东西……然而,下一篇文章应该会更详细地解释这一点。


在 ASP.NET Core 中完成 Autofac ContainerBuilder

在本文中,我探索了明确使用 Autofac ContainerBuilder而不是通常建议使用AutofacServiceProviderFactory 。我们看到了一些类似的优点和缺点,但也有一些不同的考虑因素。每种方式都有利有弊,具体取决于您在应用程序中的需求。


有趣的是,如果我们尝试使用插件,我们甚至可能根本不需要从插件访问WebApplication实例!如果我们关心最小 API,这可能仍然有限制……但除此之外,我们有一个有趣的思路!

如果您发现这很有用并且您正在寻找更多的学习机会,请考虑订阅我的免费每周软件工程通讯在 YouTube 上查看我的免费视频