0Pricing
C# Academy · Lesson

Pipeline Behaviors

Add cross-cutting logic with behaviors.

Pipeline Behaviors is a free C# Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Cross-Cutting Concerns

Logging, validation, timing and transactions apply to many handlers. Copying that code into each handler is repetitive and error-prone.

MediatR's pipeline behaviors let you wrap every request with shared logic.

// One behavior runs around all handlers

IPipelineBehavior

A behavior implements IPipelineBehavior<TRequest, TResponse>. Its Handle method receives the request and a next delegate that invokes the rest of the pipeline.

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;
    }
}

The next Delegate

next() calls the next behavior, or the actual handler if this is the last one. Code before the call runs on the way in; code after runs on the way out.

// [Behavior A in] -> [Behavior B in] -> [Handler]
//               <- [Behavior B out] <- [A out]

A Logging Behavior

A concrete logging behavior records the request name on the way in and the elapsed time on the way 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;
}

Registering a Behavior

Register the open generic behavior in DI. MediatR applies it to every request.

builder.Services.AddTransient(
    typeof(IPipelineBehavior<,>),
    typeof(LoggingBehavior<,>));

A Validation Behavior

A popular use is request validation with FluentValidation. The behavior runs all validators before the handler executes, throwing if any fail.

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();
}

Short-Circuiting

A behavior can skip the handler entirely by returning without calling next() - useful for caching query results or rejecting invalid requests.

if (_cache.TryGet(key, out TResponse cached))
    return cached;          // handler never runs
var response = await next();
_cache.Set(key, response);
return response;

Ordering Behaviors

Behaviors execute in the order they are registered. Put logging outermost, then validation, then transactions, so each wraps the next correctly.

services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));

A Transaction Behavior

Wrap command handlers in a database transaction so the whole operation commits or rolls back atomically.

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;
}

Targeting Specific Requests

Use a marker interface and a generic constraint so a behavior only applies to requests that opt in - for example, only commands get transactions.

public interface ITransactional { }
public record CreateOrderCommand(...) : IRequest<int>, ITransactional;

// where TRequest : ITransactional

Why Behaviors Beat Middleware

Unlike HTTP middleware, behaviors run per message, have the typed request and response, and work even when a request is dispatched outside of HTTP (e.g. from a background job).

// Strongly typed, transport-independent cross-cutting logic

Quick Check

Confirm how behaviors work.

Recap

You learned pipeline behaviors:

  • IPipelineBehavior<TRequest, TResponse> wraps every request with shared logic.
  • The next delegate advances the pipeline; skipping it short-circuits the handler.
  • Common uses: logging, validation, transactions, caching.
  • Behaviors run in registration order and are strongly typed.

Next: notifications and events.

Frequently asked questions

Is the “Pipeline Behaviors” lesson free?

Yes — the full text of “Pipeline Behaviors” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Pipeline Behaviors”?

Add cross-cutting logic with behaviors. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Pipeline Behaviors” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. CQRS Concepts
  2. Commands and Handlers with MediatR
  3. Pipeline Behaviors
  4. Notifications and Events
← Back to C# Academy