Существует множество способов управления внедрением зависимостей внутри наших приложений, и я думаю, что важно понимать преимущества и ограничения различных подходов. Использование Autofac ContainerBuilder
в ASP.NET Core в качестве основного способа структурирования ваших зависимостей вместо использования предлагаемого AutofacServiceProviderFactory
— один из таких путей, которые мы можем изучить!
В этой статье я расскажу, как использовать ContainerBuilder компании Autofac в приложении ASP.NET Core вместо AutofacServiceProviderFactory. Мы рассмотрим, что можно и что нельзя делать с помощью этого подхода по сравнению с другими подходами, к которым у нас есть доступ с помощью внедрения зависимостей.
Это будет частью серии статей, в которой я исследую разрешение зависимостей с помощью Autofac внутри ASP.NET Core. Я обязательно включу следующую серию по мере публикации выпусков:
В конце этой серии вы сможете более уверенно исследовать архитектуры плагинов внутри ASP.NET Core и Blazor, что предоставит вам еще больше контента для изучения.
Честно говоря, название раздела — *почти* кликбейт. Я действительно считаю, что использование AutofacServiceProviderFactory
в качестве рекомендуемого способа настройки Autofac в ваших приложениях ASP.NET Core отлично подходит для большинства приложений. Подавляющее большинство разработчиков, которые хотят использовать Autofac в качестве предпочтительной среды внедрения зависимостей, вообще не столкнутся при этом со многими проблемами.
Это дает нам возможность:
WebApplicationBuilder
(и всему, что доступно на данный момент)IConfiguration
(также доступен вне экземпляра WebApplicationBuilder
)
Но для меня большая проблема: мы не можем получить доступ к экземпляру WebApplication
. Когда я создаю архитектуру плагинов на C#, особенно при создании приложений ASP.NET Core, мне нравится иметь доступ к экземпляру WebApplication
для регистрации маршрутов. Это позволяет мне с легкостью регистрировать минимальные API из моих плагинов, которым технически нужен только доступ к реализации IEndpointRouteBuilder
, чтобы получить удобный синтаксис.
Могу ли я без этого зарегистрировать неминимальные API? Абсолютно. Есть ли другой способ предоставить аналогичный синтаксис и не требовать экземпляра WebApplication
? Скорее всего. Но вместо того, чтобы пытаться обойти ЭТУ проблему, я хотел посмотреть, смогу ли я просто получить доступ к интересующей меня зависимости.
Пришло время изменить план настройки моего контейнера зависимостей!
Давайте рассмотрим пример приложения, чтобы у нас была общая основа для изучения. Если вы читали предыдущую статью , это будет выглядеть аналогично — вариант примера приложения погоды:
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
в качестве основного контейнера зависимостей дает нам возможность структурировать нашу точку входа следующим образом:
На мой взгляд, это идеальный код запуска приложения. Почему? Потому что вам никогда не придется возвращаться сюда, чтобы прикоснуться к нему. Всегда. Неважно, сколько вещей вы добавите! И это все потому, что вы можете сканировать сборки, чтобы загрузить больше модулей.
Опять же, это мое личное предпочтение, и я не пытаюсь утверждать, что это должно быть целью каждого.
Конечно, есть еще один подход, который не совсем надежен. Итак, давайте обсудим, чего мы НЕ получаем при такой настройке Autofac:
WebApplication
не настроено на использование Autofac в качестве среды внедрения зависимостей!WebApplication
в контейнере зависимостей… Поэтому мы не внесли никаких улучшений специально в доступ к нему.
Но это в основном все! Это не такой уж и ужасный список недостатков, но подход Autofac ContainerBuilder не стал для нас панацеей. Итак, что же мы из этого получили? также поможет объяснить следующее:
Плюсы и минусы всего, что мы делаем! Теперь, когда мы увидели проблемы с Autofac ContainerBuilder в ASP.NET Core, пришло время посмотреть, какие улучшения мы получили благодаря этому:
WebApplicationBuilder
и IConfiguration
, так что это сравнимое преимущество с использованием подхода AutofacServiceProviderFactory
.Мы увидели, что можем зарегистрировать минимальные API в методе регистрации Autofac, но, к сожалению, мы не можем разрешить зависимости от контейнера непосредственно на самом вызове минимального 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
нашим плагинам! Возможно, первая версия все-таки не была такой уж ограничивающей. Возможно, мы здесь что-то напутали… Впрочем, в следующей статье это должно быть объяснено более подробно.
В этой статье я рассмотрел использование Autofac ContainerBuilder
вместо использования AutofacServiceProviderFactory
, как обычно предлагается. Мы увидели некоторые схожие преимущества и недостатки, но также и другой набор вещей, которые следует учитывать. Каждый из способов может иметь свои плюсы и минусы в зависимости от того, что вы ищете в своем приложении.
Что было интересно, так это то, что если мы пытаемся работать над плагинами, нам может вообще не понадобиться доступ к экземпляру WebApplication
из наших плагинов! Если мы заботимся о минимальном количестве API, это все еще может быть ограничением… но в остальном мы находимся на интересном пути мышления!
Если вы нашли это полезным и ищете дополнительные возможности для обучения, рассмотрите возможность подписки на мой бесплатный еженедельный информационный бюллетень по разработке программного обеспечения и посмотрите мои бесплатные видео на YouTube !