ASP.NET Core и EF Core
Конвейер middleware, внедрение зависимостей и времена жизни сервисов, привязка модели, change tracker EF Core, проблема N+1 и миграции без простоя.
9 вопросов
JuniorТеорияОчень частоЧто такое middleware-компонент в ASP.NET Core и что делает вызов next?
Что такое middleware-компонент в ASP.NET Core и что делает вызов next?
Middleware-компонент — это делегат, принимающий HttpContext и делегат next. Он может осмотреть запрос, вызвать next, передав управление следующему компоненту, а затем поработать с ответом на обратном ходе. Если next не вызвать, конвейер обрывается — ничего после него не выполнится.
Типичные ошибки
- ✗Думать, что компонент не может тронуть ответ после вызова
next - ✗Ждать, что остаток конвейера отработает, хотя
nextтак и не вызвали - ✗Писать в ответ после
next, когда заголовки уже отправлены
Уточняющие вопросы
- →Почему нельзя менять заголовки после того, как
nextзаписал тело ответа? - →Чем терминальный компонент отличается от того, который вызывает
next?
JuniorТеорияЧастоВ хосте по умолчанию какое значение победит для одного ключа — appsettings.json или переменная окружения?
В хосте по умолчанию какое значение победит для одного ключа — appsettings.json или переменная окружения?
Победит переменная окружения. Хост по умолчанию складывает провайдеры по порядку — appsettings.json, appsettings.{Environment}.json, user secrets, переменные окружения, аргументы командной строки — и последний провайдер, давший ключ, перекрывает все предыдущие.
Типичные ошибки
- ✗Считать, что JSON-файл всегда побеждает, раз он лежит в репозитории
- ✗Писать двоеточие вместо двойного подчёркивания для вложенности в имени переменной окружения
- ✗Забывать, что аргументы командной строки перекрывают переменные окружения
Уточняющие вопросы
- →Как записать вложенный ключ
ConnectionStrings:Defaultв виде имени переменной окружения? - →Где в порядке провайдеров стоят user secrets и в каком окружении они подгружаются?
JuniorТеорияЧастоКак зарегистрировать IOrderService во встроенном контейнере внедрения зависимостей, чтобы контроллер его получил?
Как зарегистрировать IOrderService во встроенном контейнере внедрения зависимостей, чтобы контроллер его получил?
Зарегистрируйте отображение на builder.Services — например, AddScoped<IOrderService, OrderService>(). Затем объявите IOrderService параметром конструктора: фреймворк создаёт контроллер через контейнер и подставляет реализацию. Тип, который не был зарегистрирован, бросает исключение при разрешении.
Типичные ошибки
- ✗Ждать, что контейнер сам найдёт реализацию без всякой регистрации
- ✗Зарегистрировать только конкретный класс, а просить интерфейс
- ✗Тянуться к
RequestServicesкак к service locator вместо внедрения через конструктор
Уточняющие вопросы
- →Что произойдёт при разрешении, если нужный сервис так и не зарегистрировали?
- →Когда регистрируют делегат-фабрику вместо отображения одного типа на другой?
JuniorТеорияЧастоЧто делает UseExceptionHandler, когда компонент ниже по конвейеру бросает исключение?
Что делает UseExceptionHandler, когда компонент ниже по конвейеру бросает исключение?
Он оборачивает всё зарегистрированное после него в try/catch. Необработанное исключение раскручивается вверх до него, а не доходит до клиента, и он повторно выполняет запрос по маршруту ошибки или пишет тело ProblemDetails, возвращая 500. То, что бросает выше него, он не видит.
Типичные ошибки
- ✗Регистрировать его поздно в конвейере, так что он не видит бросающих выше
- ✗Ожидать, что он продолжит упавший компонент, а не раскрутит запрос
- ✗Путать его со страницей разработчика, которая нужна только для локальной диагностики
Уточняющие вопросы
- →Чем
UseExceptionHandlerотличается от страницы исключений для разработчика? - →Где его регистрируют относительно
UseHttpsRedirectionиUseRouting?
MiddleТеорияЧастоПочему порядок регистрации middleware-конвейера меняет поведение запроса?
Почему порядок регистрации middleware-конвейера меняет поведение запроса?
Порядок регистрации — это порядок выполнения: компонент оборачивает всё, что после него. Обработчик исключений идёт первым, чтобы ловить происходящее ниже; UseRouting стоит до UseAuthentication и UseAuthorization, а те — до эндпоинта, иначе эндпоинт отработает без principal. Ранний обрыв скрывает остальное.
Типичные ошибки
- ✗Регистрировать
UseAuthorizationдоUseRouting, когда метаданные эндпоинта ещё неизвестны - ✗Ставить обработчик исключений после компонентов, чьи исключения он должен ловить
- ✗Считать, что фреймворк сам переставит конвейер в правильном порядке
Уточняющие вопросы
- →Почему
UseRoutingдолжен идти доUseAuthorization, чтобы применились метаданные[Authorize]? - →Что станет с ответом, если компонент запишет в него и всё же вызовет
next?
JuniorТеорияИногдаЗачем контейнеризованный сервис на ASP.NET Core выставляет health-check эндпоинт?
Зачем контейнеризованный сервис на ASP.NET Core выставляет health-check эндпоинт?
Оркестратор не видит, что происходит внутри процесса. MapHealthChecks выставляет эндпоинт, который он опрашивает: провалившийся liveness-пробник перезапускает контейнер, а провалившийся readiness-пробник убирает инстанс из балансировщика, пока его зависимости — база, кэш — снова не ответят.
Типичные ошибки
- ✗Считать liveness и readiness одной проверкой, из-за чего медленная зависимость перезапускает здоровый процесс
- ✗Проверять базу в liveness-пробнике, превращая временный сбой в цикл перезапусков
- ✗Думать, что оркестратор выводит здоровье из CPU и памяти, а не вызывает эндпоинт
Уточняющие вопросы
- →Какая зависимость уместна в readiness-проверке, но не в liveness-проверке?
- →Как не дать authentication middleware заблокировать health-эндпоинт?
MiddleТеорияИногдаКогда хост ASP.NET Core строит service provider и из какого scope разрешаются сервисы запроса?
Когда хост ASP.NET Core строит service provider и из какого scope разрешаются сервисы запроса?
builder.Services собирает дескрипторы; builder.Build() строит корневой IServiceProvider и замораживает их, поэтому поздняя регистрация бросит исключение. Каждый запрос получает свой IServiceScope — эндпоинт и его зависимости разрешаются из него, и он уничтожается вместе со своими scoped IDisposable-сервисами по завершении ответа.
Типичные ошибки
- ✗Добавлять регистрации в
builder.ServicesпослеBuild()и ждать, что они подействуют - ✗Разрешать scoped-сервис прямо из корневого провайдера в фоновой задаче
- ✗Считать, что освобождение — дело сборщика мусора, а не scope
Уточняющие вопросы
- →Как
BackgroundServiceправильно разрешает scoped-сервис? - →Что контейнер делает с
IDisposable-синглтоном при остановке приложения?
MiddleТеорияИногдаЧем различаются Transient, Scoped и Singleton и что такое captive dependency?
Чем различаются Transient, Scoped и Singleton и что такое captive dependency?
Transient — новый экземпляр на разрешение, Scoped — один на scope запроса, Singleton — один на приложение. Captive dependency — это долгоживущий сервис, захвативший короткоживущий: Singleton, держащий scoped DbContext, оставляет его живым навсегда и делит между параллельными запросами и потоками. Внедряйте IServiceScopeFactory.
Типичные ошибки
- ✗Внедрять scoped
DbContextвSingleton, получая общее состояние между запросами и гонки - ✗Понимать captive dependency наоборот — будто короткоживущий сервис захватывает долгоживущий
- ✗Считать, что контейнер заново разрешает захваченную зависимость при каждом вызове
Уточняющие вопросы
- →Какая стартовая проверка ловит captive dependency и в каком окружении она включена по умолчанию?
- →Как
BackgroundServiceполучаетDbContext, не захватывая его?
SeniorТеорияИногдаПроведите запрос от веб-сервера Kestrel до записанного ответа — что делает каждый этап?
Проведите запрос от веб-сервера Kestrel до записанного ответа — что делает каждый этап?
Kestrel разбирает запрос в HttpContext, а хост открывает под него scope контейнера. Контекст идёт через конвейер middleware — обработчик исключений, routing (выбор эндпоинта), authentication, authorization — пока endpoint middleware не разрешит сервисы эндпоинта, не привяжет и не провалидирует модель, не выполнит обработчик и его результат. Затем ответ разматывается обратно через конвейер, и scope уничтожается.
Типичные ошибки
- ✗Ставить routing после authorization, из-за чего политике не на какие метаданные эндпоинта смотреть
- ✗Думать, что результат выполняется вне конвейера, где middleware не видит ответа
- ✗Считать, что scope контейнера живёт всё соединение, а не один запрос
Уточняющие вопросы
- →Где именно заполняется
HttpContext.Userи что к этому моменту уже отработало? - →Что ещё можно изменить в ответе после того, как первый байт уже отправлен?