Error Handling

codewithmukesh/dotnet-claude-kit/skills/error-handling

by codewithmukesh23300897f4d1No license754 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 2 months ago

Error handling strategy for .NET 10 applications. Covers the Result pattern, ProblemDetails (RFC 9457), global exception handling, FluentValidation, and structured error responses. Load this skill when implementing error handling, validation, or designing API error contracts, or when the user mentions "error handling", "Result pattern", "ProblemDetails", "exception", "validation", "FluentValidation", "error response", "global exception handler", or "RFC 9457".

Instructions onlySoftware Development
AI-generated overview

Guidance for implementing error handling, validation and API error contracts in .NET 10 applications.

What it does
This skill documents an error-handling strategy for .NET 10 applications, covering the Result pattern, ProblemDetails per RFC 9457, a global exception handler, FluentValidation with endpoint filters, and typed error results. It supplies C# code samples for result types, mapping results and errors to HTTP responses, validation filters, and a decision guide. It also lists anti-patterns such as throwing exceptions for flow control, returning raw error strings, and swallowing exceptions.
When to use it
Use it when implementing error handling or validation in a .NET API, or when designing API error contracts. It also applies when the user mentions error handling, the Result pattern, ProblemDetails, exceptions, validation, FluentValidation, error responses, a global exception handler, or RFC 9457.
Requirements
No scripts or assets ship with the skill; it is instructions and code samples only. Applying the samples assumes a .NET 10 project, ASP.NET Core minimal APIs, and the FluentValidation package.

Error Handling

Core Principles

  1. Use the Result pattern for expected failures — Don't throw exceptions for things like "order not found" or "validation failed". These are expected outcomes, not exceptional conditions. See ADR-002.
  2. Reserve exceptions for unexpected failures — Database connection lost, null reference bugs, network timeouts — these are truly exceptional and should propagate to the global handler.
  3. Every API error returns ProblemDetails — RFC 9457 is the standard. Every error response has type, title, status, detail, and optionally errors.
  4. Validate at the boundary — Validate incoming requests at the API layer, not deep inside business logic.

Patterns

Result Pattern

A simple, generic result type that carries either a value or errors.

csharp
public class Result{    public bool IsSuccess { get; }    public bool IsFailure => !IsSuccess;    public List<string> Errors { get; }
    protected Result(bool isSuccess, List<string>? errors = null)    {        IsSuccess = isSuccess;        Errors = errors ?? [];    }
    public static Result Success() => new(true);    public static Result Failure(params string[] errors) => new(false, [..errors]);    public static Result<T> Success<T>(T value) => new(value);    public static Result<T> Failure<T>(params string[] errors) => new(errors);}
public class Result<T> : Result{    public T Value { get; }
    internal Result(T value) : base(true) => Value = value;    internal Result(IEnumerable<string> errors) : base(false, [..errors]) => Value = default!;}

Result to ProblemDetails Mapping

csharp
public static class ResultExtensions{    public static IResult ToProblemDetails(this Result result, int statusCode = 400)    {        return TypedResults.Problem(            title: "One or more errors occurred",            statusCode: statusCode,            extensions: new Dictionary<string, object?>            {                ["errors"] = result.Errors            });    }}
// Usage in endpointgroup.MapPost("/", async (CreateOrder.Command command, ISender sender, CancellationToken ct) =>{    var result = await sender.Send(command, ct);    return result.IsSuccess        ? TypedResults.Created($"/api/orders/{result.Value.Id}", result.Value)        : result.ToProblemDetails();});

Global Exception Handler

Catches unexpected exceptions and converts them to ProblemDetails. For the modern IExceptionHandler approach (preferred), see knowledge/common-infrastructure.md. The inline lambda below works for simple cases:

csharp
// Program.csapp.UseExceptionHandler(errorApp =>{    errorApp.Run(async context =>    {        var exception = context.Features.Get<IExceptionHandlerFeature>()?.Error;        var logger = context.RequestServices.GetRequiredService<ILogger<Program>>();
        logger.LogError(exception, "Unhandled exception for {Method} {Path}",            context.Request.Method, context.Request.Path);
        var problem = new ProblemDetails        {            Title = "An unexpected error occurred",            Status = StatusCodes.Status500InternalServerError,            Type = "https://tools.ietf.org/html/rfc9110#section-15.6.1"        };
        // Don't leak details in production        if (context.RequestServices.GetRequiredService<IHostEnvironment>().IsDevelopment())        {            problem.Detail = exception?.Message;        }
        context.Response.StatusCode = problem.Status.Value;        await context.Response.WriteAsJsonAsync(problem);    });});

FluentValidation with Endpoint Filters

csharp
// Validatorpublic class CreateOrderValidator : AbstractValidator<CreateOrderRequest>{    public CreateOrderValidator()    {        RuleFor(x => x.CustomerId)            .NotEmpty().WithMessage("Customer ID is required");
        RuleFor(x => x.Items)            .NotEmpty().WithMessage("At least one item is required");
        RuleForEach(x => x.Items).ChildRules(item =>        {            item.RuleFor(x => x.ProductId).NotEmpty();            item.RuleFor(x => x.Quantity).GreaterThan(0);        });    }}
// Generic validation filterpublic class ValidationFilter<TRequest> : IEndpointFilter{    public async ValueTask<object?> InvokeAsync(        EndpointFilterInvocationContext context,        EndpointFilterDelegate next)    {        var validator = context.HttpContext.RequestServices.GetService<IValidator<TRequest>>();        if (validator is null)            return await next(context);
        var request = context.Arguments.OfType<TRequest>().FirstOrDefault();        if (request is null)            return await next(context);
        var result = await validator.ValidateAsync(request);        if (!result.IsValid)        {            return TypedResults.ValidationProblem(result.ToDictionary());        }
        return await next(context);    }}
// Registrationgroup.MapPost("/", CreateOrder)    .AddEndpointFilter<ValidationFilter<CreateOrderRequest>>();

Typed Error Results

For richer error handling, use typed error enums or error objects.

csharp
public abstract record Error(string Code, string Message);public record NotFoundError(string Entity, object Id)    : Error("not_found", $"{Entity} with ID {Id} was not found");public record ValidationError(string Field, string Message)    : Error("validation", Message);public record ConflictError(string Message)    : Error("conflict", Message);
// Map to HTTP status codespublic static IResult ToHttpResult(this Error error) => error switch{    NotFoundError => TypedResults.Problem(title: error.Message, statusCode: 404),    ValidationError => TypedResults.Problem(title: error.Message, statusCode: 400),    ConflictError => TypedResults.Problem(title: error.Message, statusCode: 409),    _ => TypedResults.Problem(title: error.Message, statusCode: 500)};

Anti-patterns

Don't Throw Exceptions for Flow Control

csharp
// BAD — exceptions for expected outcomespublic Order GetOrder(Guid id){    var order = db.Orders.Find(id)        ?? throw new NotFoundException($"Order {id} not found");    return order;}
// GOOD — Result patternpublic Result<Order> GetOrder(Guid id){    var order = db.Orders.Find(id);    return order is not null        ? Result.Success(order)        : Result.Failure<Order>($"Order {id} not found");}

Don't Return Raw Error Strings from APIs

csharp
// BAD — inconsistent error formatreturn Results.BadRequest("Something went wrong");return Results.BadRequest(new { error = "Invalid input" });
// GOOD — always ProblemDetailsreturn TypedResults.Problem(title: "Invalid input", statusCode: 400);return TypedResults.ValidationProblem(validationResult.ToDictionary());

Don't Catch and Swallow Exceptions

csharp
// BAD — silently swallowingtry { await ProcessOrder(order); }catch (Exception) { /* ignore */ }
// GOOD — log and handle appropriatelytry { await ProcessOrder(order); }catch (PaymentException ex){    logger.LogWarning(ex, "Payment failed for order {OrderId}", order.Id);    return Result.Failure<Order>("Payment processing failed");}

Decision Guide

ScenarioRecommendation
Expected business failureResult pattern
Input validationFluentValidation with endpoint filter
Unexpected crashGlobal exception handler → ProblemDetails
API error formatRFC 9457 ProblemDetails — always
Validation in handlerReturn Result.Failure, don't throw
External service failureCatch specific exception, return Result.Failure
Logging errorsStructured logging with correlation ID

Source and attribution

Source:codewithmukesh/dotnet-claude-kitinskills/error-handlingat commit2330089

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal

More from codewithmukesh/dotnet-claude-kit

Wrap Up

codewithmukesh

Captures end-of-session work, pending tasks and learnings into a handoff file, and reloads it at session start.

Productivity & Workflow754updated 2 months ago

Workflow Mastery

codewithmukesh

Claude Code workflow mastery for .NET developers. Covers parallel execution with git worktrees, plan mode strategy, verification loops, auto-formatting hooks, permission setup for dotnet CLI, prompting techniques, subagent patterns, and context discipline — token budget management, MCP-first navigation, lazy loading, and subagent isolation — all adapted for the .NET ecosystem. Load this skill when setting up Claude Code for a .NET project, optimizing workflows, running parallel sessions, when context is running low or sessions feel sluggish, when exploring a large codebase efficiently, or when the user mentions "productivity", "workflow", "parallel", "worktree", "plan mode", "permissions", "hooks", "10x", "setup Claude Code", "speed up development", "context", "tokens", "budget", "running out of context", "too many files", or "large codebase". Inspired by tips from Boris Cherny (creator of Claude Code) and the Anthropic team.

Awaiting classification754updated 2 months ago

Vertical Slice

codewithmukesh

Guides .NET developers in structuring applications with Vertical Slice Architecture, covering feature folders, endpoint grouping and handler patterns.

Software Development754updated 2 months ago

Testing

codewithmukesh

Testing strategy for .NET 10 applications. Covers xUnit v3, WebApplicationFactory for integration tests, Testcontainers for real database testing, Verify for snapshot testing, and the AAA pattern. Load this skill when writing tests, setting up test infrastructure, reviewing test coverage, or when the user mentions "test", "xUnit", "WebApplicationFactory", "Testcontainers", "integration test", "unit test", "bUnit", "snapshot test", "Verify", "test coverage", "AAA pattern", "WireMock", or "FakeTimeProvider".

Awaiting classification754updated 2 months ago

Tdd

codewithmukesh

Guided test-driven development workflow for .NET 10 using xUnit v3, WebApplicationFactory, Testcontainers, and Verify snapshots. Follows the strict red-green-refactor cycle. Use when: "TDD", "test-driven", "let's TDD this", "red green refactor", "write the test first", or when building a feature with clear acceptance criteria.

Awaiting classification754updated 2 months ago

Spec

codewithmukesh

Turns a vague feature idea into an agreed, persisted specification file through structured questioning rounds.

Productivity & Workflow754updated 2 months ago