paint-brush
Необходимо знать советы по использованию Autofac ContainerBuilder в ASP.NET Coreк@devleader
764 чтения
764 чтения

Необходимо знать советы по использованию Autofac ContainerBuilder в ASP.NET Core

к Dev Leader12m2024/05/10
Read on Terminal Reader

Слишком долго; Читать

Узнайте, как использовать Autofac ContainerBuilder в ASP.NET Core для подключения внедрения зависимостей. Я исследую, что мы можем и чего не можем сделать с помощью этого подхода!
featured image - Необходимо знать советы по использованию Autofac ContainerBuilder в ASP.NET Core
Dev Leader HackerNoon profile picture
0-item


Существует множество способов управления внедрением зависимостей внутри наших приложений, и я думаю, что важно понимать преимущества и ограничения различных подходов. Использование Autofac ContainerBuilder в ASP.NET Core в качестве основного способа структурирования ваших зависимостей вместо использования предлагаемого AutofacServiceProviderFactory — один из таких путей, которые мы можем изучить!


В этой статье я расскажу, как использовать ContainerBuilder компании Autofac в приложении ASP.NET Core вместо AutofacServiceProviderFactory. Мы рассмотрим, что можно и что нельзя делать с помощью этого подхода по сравнению с другими подходами, к которым у нас есть доступ с помощью внедрения зависимостей.


Это будет частью серии статей, в которой я исследую разрешение зависимостей с помощью Autofac внутри ASP.NET Core. Я обязательно включу следующую серию по мере публикации выпусков:


В конце этой серии вы сможете более уверенно исследовать архитектуры плагинов внутри ASP.NET Core и Blazor, что предоставит вам еще больше контента для изучения.


Проблема с AutofacServiceProviderFactory

Честно говоря, название раздела — *почти* кликбейт. Я действительно считаю, что использование AutofacServiceProviderFactory в качестве рекомендуемого способа настройки Autofac в ваших приложениях ASP.NET Core отлично подходит для большинства приложений. Подавляющее большинство разработчиков, которые хотят использовать Autofac в качестве предпочтительной среды внедрения зависимостей, вообще не столкнутся при этом со многими проблемами.


Это дает нам возможность:

  • Получите доступ к WebApplicationBuilder (и всему, что доступно на данный момент)
  • Доступ к экземпляру IConfiguration (также доступен вне экземпляра WebApplicationBuilder )
  • Возможность передавать зависимости на минимальные API.


Но для меня большая проблема: мы не можем получить доступ к экземпляру WebApplication . Когда я создаю архитектуру плагинов на C#, особенно при создании приложений ASP.NET Core, мне нравится иметь доступ к экземпляру WebApplication для регистрации маршрутов. Это позволяет мне с легкостью регистрировать минимальные API из моих плагинов, которым технически нужен только доступ к реализации IEndpointRouteBuilder , чтобы получить удобный синтаксис.

Могу ли я без этого зарегистрировать неминимальные API? Абсолютно. Есть ли другой способ предоставить аналогичный синтаксис и не требовать экземпляра WebApplication ? Скорее всего. Но вместо того, чтобы пытаться обойти ЭТУ проблему, я хотел посмотреть, смогу ли я просто получить доступ к интересующей меня зависимости.


Пришло время изменить план настройки моего контейнера зависимостей!


Изучение примера основного приложения ASP.NET

Давайте рассмотрим пример приложения, чтобы у нас была общая основа для изучения. Если вы читали предыдущую статью , это будет выглядеть аналогично — вариант примера приложения погоды:

 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 ContainerBuilder в ASP.NET Core

Конечно, есть еще один подход, который не совсем надежен. Итак, давайте обсудим, чего мы НЕ получаем при такой настройке Autofac:

  • Параметры, разрешаемые службой, передаваемые через минимальные API, просто не работают. Созданное WebApplication не настроено на использование Autofac в качестве среды внедрения зависимостей!
  • Как и в предыдущей статье, мы по-прежнему не можем получить экземпляр WebApplication в контейнере зависимостей… Поэтому мы не внесли никаких улучшений специально в доступ к нему.


Но это в основном все! Это не такой уж и ужасный список недостатков, но подход Autofac ContainerBuilder не стал для нас панацеей. Итак, что же мы из этого получили? также поможет объяснить следующее:

Преимущества Autofac ContainerBuilder в ASP.NET Core

Плюсы и минусы всего, что мы делаем! Теперь, когда мы увидели проблемы с Autofac ContainerBuilder в ASP.NET Core, пришло время посмотреть, какие улучшения мы получили благодаря этому:


  • Мы по-прежнему можем получить доступ к экземплярам WebApplicationBuilder и IConfiguration , так что это сравнимое преимущество с использованием подхода AutofacServiceProviderFactory .
  • Мы можем получить очень упрощенную точку входа в нашу программу, и мне это очень нравится. Создание контейнера, регистрация, разрешение вашего метода точки входа и все!
  • Минимальные API работают, но не с зависимостями. Тем не менее, мы можем предварительно разрешить зависимости, которые нужны минимальным API, и передать их во время регистрации метода. Смотрите прокомментированный код!

Дополнительная инверсия для маршрутов ASP.NET на основе плагинов?

Мы увидели, что можем зарегистрировать минимальные 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 в ASP.NET Core

В этой статье я рассмотрел использование Autofac ContainerBuilder вместо использования AutofacServiceProviderFactory , как обычно предлагается. Мы увидели некоторые схожие преимущества и недостатки, но также и другой набор вещей, которые следует учитывать. Каждый из способов может иметь свои плюсы и минусы в зависимости от того, что вы ищете в своем приложении.


Что было интересно, так это то, что если мы пытаемся работать над плагинами, нам может вообще не понадобиться доступ к экземпляру WebApplication из наших плагинов! Если мы заботимся о минимальном количестве API, это все еще может быть ограничением… но в остальном мы находимся на интересном пути мышления!

Если вы нашли это полезным и ищете дополнительные возможности для обучения, рассмотрите возможность подписки на мой бесплатный еженедельный информационный бюллетень по разработке программного обеспечения и посмотрите мои бесплатные видео на YouTube !