На собесе по ASP.NET Core проверяют, понимаете ли вы путь запроса: конвейер middleware, DI, маршрутизацию, контроллеры или Minimal API, и где тут типичные ошибки.
Хост и Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(o => o.UseNpgsql(builder.Configuration.GetConnectionString("Db")));
builder.Services.AddScoped<IOrderService, OrderService>();
builder.Services.AddControllers();
var app = builder.Build();
app.UseExceptionHandler();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.MapGet("/health", () => Results.Ok());
app.Run();
До Build() регистрируем сервисы, после него собираем конвейер.
Middleware
Конвейер это цепочка делегатов: каждый получает HttpContext и next. Код до await next() выполняется на пути запроса, после него на пути ответа. Любой middleware может не вызывать next и сам завершить запрос (короткое замыкание).
app.Use(async (ctx, next) =>
{
var sw = Stopwatch.StartNew();
await next();
logger.LogInformation("{Path} за {Ms} мс", ctx.Request.Path, sw.ElapsedMilliseconds);
});
app.Run(handler) терминальный, app.Map("/path", ...) ветвит конвейер. Порядок критичен: обработчик исключений первым, аутентификация до авторизации, CORS до аутентификации.
Внедрение зависимостей
Время жизни:
- Singleton: один на приложение, должен быть потокобезопасным.
- Scoped: один на запрос (
DbContext). - Transient: новый при каждом разрешении.
Правило: сервис не зависит от сервиса с более коротким временем жизни. Scoped внутри Singleton это captive dependency, в Development её ловит ValidateScopes. В синглтонах и BackgroundService scoped-сервисы берут через IServiceScopeFactory.CreateScope(). В .NET 8 есть keyed services для нескольких реализаций одного интерфейса.
Контроллеры и Minimal API
Контроллеры с [ApiController]: выводят источники параметров (сложный тип из тела), автоматически возвращают 400 при невалидной модели, есть фильтры MVC.
Minimal API: app.MapGet/MapPost, параметры из маршрута, запроса, тела и DI, MapGroup для общих префиксов и политик, фильтры конечных точек, TypedResults. Подходят и для больших сервисов, это вопрос стиля.
Конфигурация и Options
Источники накладываются: appsettings.json, appsettings.{Environment}.json, переменные окружения, секреты пользователя, аргументы командной строки. Типизированные настройки:
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection("Smtp"));
IOptions<T> читает один раз, IOptionsSnapshot<T> на каждый запрос (scoped), IOptionsMonitor<T> отслеживает изменения и подходит для синглтонов.
HttpClient
Не создавайте new HttpClient() на каждый запрос: исчерпание сокетов. Используйте IHttpClientFactory (AddHttpClient<T>) или один долгоживущий клиент с PooledConnectionLifetime. Для повторов и таймаутов пакет Microsoft.Extensions.Http.Resilience (на основе Polly).
Фоновая работа
BackgroundService с ExecuteAsync(stoppingToken) для фоновой обработки. Очередь внутри процесса через Channel<T>, задания, которые должны пережить рестарт, через внешний брокер или outbox.
Безопасность
- Аутентификация JWT: проверять подпись,
exp,iss,aud, секрет хранить вне кода. - Авторизация:
[Authorize], политики, проверка владельца ресурса (IDOR). - Пароли только хешами (
PasswordHasher<T>). - Rate limiting (
AddRateLimiter, .NET 7) на вход и дорогие эндпоинты. - CORS со списком доменов, а не
AllowAnyOrigin. - Наружу DTO, а не сущности базы.
Обработка ошибок
AddProblemDetails и IExceptionHandler (.NET 8): исключения превращаются в ответ RFC 9457 без деталей стека.
Частые ошибки
- Неправильный порядок middleware.
- Scoped-зависимость в синглтоне.
new HttpClient()в каждом запросе.Task.Run«в фоне» из контроллера.- Синхронные вызовы базы в обработчиках.
Что спросят на собесе
- Как запрос проходит через конвейер middleware?
- Чем отличаются Singleton, Scoped и Transient и что такое captive dependency?
- Контроллеры или Minimal API?
- Как правильно использовать HttpClient?
- Как организовать фоновую обработку?