1. Архитектура приложения: Dependency Injection, Middleware и настройка окружения
Архитектура приложения: Dependency Injection, Middleware и настройка окружения
Добро пожаловать в курс Backend-разработка на ASP.NET Core: Современный стек. Мы начинаем погружение в одну из самых производительных и гибких платформ для создания веб-приложений. Современный ASP.NET Core — это не просто обновление старых технологий, это полностью переписанный, кроссплатформенный и модульный фреймворк.
В этой первой статье мы разберем фундамент, на котором строится любое приложение: внедрение зависимостей (Dependency Injection), конвейер обработки запросов (Middleware) и конфигурацию окружения. Понимание этих концепций критически важно, так как они определяют, как ваше приложение масштабируется, тестируется и обрабатывает данные.
Точка входа: Program.cs
В современных версиях .NET (начиная с .NET 6 и выше) шаблон приложения был значительно упрощен. Файлы Startup.cs и Program.cs были объединены в один файл Program.cs, использующий стиль top-level statements (инструкции верхнего уровня). Это делает код лаконичным и убирает лишний «шум».
Глобально файл Program.cs делится на две фазы:
Dependency Injection (DI): Сердце приложения
ASP.NET Core был спроектирован с учетом Dependency Injection с самого начала. Это не надстройка, а базовая часть архитектуры. DI позволяет нам реализовывать принцип Inversion of Control (IoC), делая классы слабо связанными.
Вместо того чтобы создавать экземпляры зависимостей внутри класса (например, new DatabaseContext()), мы запрашиваем их через конструктор. Фреймворк сам позаботится о создании и уничтожении этих объектов.
Жизненный цикл сервисов (Service Lifetimes)
При регистрации сервиса в контейнере (builder.Services) мы обязаны указать его жизненный цикл. Это, пожалуй, самый важный аспект DI, ошибка в котором может привести к утечкам памяти или некорректной работе с данными.
Существует три основных времени жизни:
AddTransient<IMyService, MyService>()DbContext), сервисы, работающие с данными текущего пользователя.
Метод:* AddScoped<IMyService, MyService>()AddSingleton<IMyService, MyService>()!Визуализация различий между Transient, Scoped и Singleton жизненными циклами.
Опасности захвата зависимостей (Captive Dependencies)
Важно помнить правило: Сервис с более долгим временем жизни не должен зависеть от сервиса с более коротким временем жизни.
Если вы внедрите Scoped сервис внутрь Singleton сервиса, то Scoped сервис станет де-факто синглтоном (так как синглтон создается один раз и держит ссылку на зависимость). Это частая ошибка, приводящая к тому, что DbContext не закрывается и накапливает данные из разных запросов.
Middleware: Конвейер обработки запросов
Когда HTTP-запрос приходит на сервер, он проходит через цепочку компонентов, называемых Middleware (промежуточное ПО). Каждый компонент может:
!Схема прохождения HTTP-запроса через конвейер Middleware.
Порядок имеет значение
В Program.cs порядок вызова методов app.Use... критически важен. Middleware выполняются ровно в том порядке, в котором они добавлены.
Пример классического порядка:
UseExceptionHandler (Ловит ошибки от всего, что ниже).UseHsts (Безопасность).UseHttpsRedirection (Перенаправление на HTTPS).UseStaticFiles (Отдача картинок, CSS, JS. Если файл найден, цепочка прерывается здесь).UseRouting (Определение, какой контроллер нужен).UseCors (Настройка кросс-доменных запросов).UseAuthentication (Кто это?).UseAuthorization (Можно ли ему это?).MapControllers (Финальная точка — выполнение бизнес-логики).Если вы поставите UseAuthorization перед UseAuthentication, приложение попытается проверить права доступа у пользователя, которого еще не идентифицировали. Это приведет к ошибке логики.
Написание собственного Middleware
Создание своего middleware — отличный способ реализовать сквозную функциональность, например, логирование всех запросов или глобальную обработку исключений.
Настройка окружения (Configuration)
Современное приложение не должно хранить настройки (строки подключения, API ключи) в коде. ASP.NET Core предоставляет мощную систему конфигурации, основанную на парах «ключ-значение».
Источники конфигурации считываются в определенном порядке (последний перезаписывает предыдущие):
appsettings.json.appsettings.{Environment}.json (например, appsettings.Development.json).IConfiguration
Доступ к настройкам осуществляется через интерфейс IConfiguration. Он регистрируется в DI контейнере автоматически.
Пример appsettings.json:
Чтение настроек:
Паттерн Options
Для более чистого кода рекомендуется использовать Options Pattern. Вместо того чтобы внедрять IConfiguration в каждый сервис, вы создаете класс, описывающий настройки, и мапите его на секцию конфигурации.
Это обеспечивает строгую типизацию настроек и защищает от опечаток в строковых ключах.
Заключение
Мы рассмотрели три кита архитектуры ASP.NET Core:
* Dependency Injection позволяет строить модульные и тестируемые приложения, управляя жизненным циклом объектов. * Middleware дает полный контроль над потоком HTTP-запроса, позволяя гибко настраивать безопасность, логирование и маршрутизацию. * Configuration обеспечивает разделение кода и настроек, позволяя безопасно деплоить приложение в разные среды (Dev, Test, Prod).
В следующей статье мы перейдем к работе с данными и подключим Entity Framework Core для взаимодействия с базой данных.