Поведения конвейера
Добавляйте сквозную логику с помощью поведений.
«Поведения конвейера» — бесплатный урок C# Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Сквозные задачи
Журналирование, проверка, измерение времени и транзакции применяются ко многим обработчикам. Копирование этого кода в каждый handler приводит к повторениям и ошибкам.
Поведения конвейера MediatR позволяют оборачивать каждый запрос общей логикой.
// One behavior runs around all handlersПоведение конвейера
Поведение реализует IPipelineBehavior<TRequest, TResponse>. Его метод Handle получает запрос и делегат next, который вызывает оставшуюся часть конвейера.
public class LoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
// before
var response = await next();
// after
return response;
}
}Следующий делегат
next() вызывает следующее поведение или фактический handler, если это последнее поведение. Код до вызова выполняется при прохождении запроса внутрь, а код после — при его выходе.
// [Behavior A in] -> [Behavior B in] -> [Handler]
// <- [Behavior B out] <- [A out]Поведение для журналирования
Конкретное поведение для журналирования записывает имя запроса при входе и затраченное время при выходе.
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
var name = typeof(TRequest).Name;
_logger.LogInformation("Handling {Name}", name);
var sw = Stopwatch.StartNew();
var response = await next();
_logger.LogInformation("{Name} took {Ms}ms", name, sw.ElapsedMilliseconds);
return response;
}Регистрация поведения
Зарегистрируйте открытое обобщённое поведение в DI. MediatR применит его к каждому запросу.
builder.Services.AddTransient(
typeof(IPipelineBehavior<,>),
typeof(LoggingBehavior<,>));Поведение для проверки
Популярный вариант использования — проверка запросов с помощью FluentValidation. Поведение запускает все проверяющие компоненты до выполнения handler и выдаёт исключение, если хотя бы один из них сообщает об ошибке.
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct)
{
var context = new ValidationContext<TRequest>(request);
var failures = _validators
.Select(v => v.Validate(context))
.SelectMany(r => r.Errors)
.Where(f => f is not null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
return await next();
}Досрочное завершение
Поведение может полностью пропустить handler, вернув результат без вызова next(). Это полезно для кэширования результатов запросов или отклонения недопустимых запросов.
if (_cache.TryGet(key, out TResponse cached))
return cached; // handler never runs
var response = await next();
_cache.Set(key, response);
return response;Порядок поведения
Поведения выполняются в порядке регистрации. Разместите журналирование снаружи, затем проверку, а затем транзакции, чтобы каждое поведение корректно оборачивало следующее.
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));Поведение транзакции
Оберните обработчики команд в транзакцию базы данных, чтобы вся операция атомарно фиксировалась или откатывалась.
await using var tx = await _db.Database.BeginTransactionAsync(ct);
try
{
var response = await next();
await tx.CommitAsync(ct);
return response;
}
catch
{
await tx.RollbackAsync(ct);
throw;
}Выбор определённых запросов
Используйте интерфейс-маркер и ограничение обобщённого типа, чтобы поведение применялось только к запросам, которые явно его подключают. Например, транзакции могут применяться только к командам.
public interface ITransactional { }
public record CreateOrderCommand(...) : IRequest<int>, ITransactional;
// where TRequest : ITransactionalПочему поведения лучше промежуточного ПО
В отличие от промежуточного ПО HTTP, поведения выполняются для каждого сообщения, получают типизированные запрос и ответ и работают даже тогда, когда запрос отправляется вне HTTP (например, из фоновой задачи).
// Strongly typed, transport-independent cross-cutting logicБыстрая проверка
Проверьте, понимаете ли Вы принцип работы поведений.
Итоги
Вы изучили поведения конвейера:
IPipelineBehavior<TRequest, TResponse>оборачивает каждый запрос общей логикой.- Делегат
nextпродвигает запрос по конвейеру; его пропуск досрочно завершает обработку handler. - Типичные применения: журналирование, проверка, транзакции и кэширование.
- Поведения выполняются в порядке регистрации и строго типизированы.
Далее: уведомления и события.
Часто задаваемые вопросы
Урок «Поведения конвейера» бесплатный?
Да — полный текст урока «Поведения конвейера» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Поведения конвейера»?
Добавляйте сквозную логику с помощью поведений. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Поведения конвейера»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Концепции CQRS
- Команды и обработчики с MediatR
- Поведения конвейера
- Уведомления и события