C# Academy · Lesson

Pipeline Behaviors

Add cross-cutting logic with behaviors.

Lesson 3 of 413 steps

Pipeline Behaviors is a free C# Academy lesson on CoddyKit. This is 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, and your progress syncs across the web and the CoddyKit app. The C# Academy course includes 4 lessons in total.

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.

Free to start

Learn C# with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
93
Lessons
346

Frequently Asked Questions

Is the “Pipeline Behaviors” lesson free?

Yes — the full text of “Pipeline Behaviors” is free to read here on the web. 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. The C# Academy course includes 4 lessons in total.

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, so you can start here or from the beginning and move at your own pace. This is lesson 3 of 4.

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