一键导入
dck-error-handling
Use when mapping AHKFlowApp errors, Ardalis.Result outcomes, validation failures, exceptions, or ProblemDetails.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Use when mapping AHKFlowApp errors, Ardalis.Result outcomes, validation failures, exceptions, or ProblemDetails.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Use when creating, removing, or troubleshooting AHKFlowApp git worktrees across Claude Code, Codex, or Copilot.
Use when optimizing AHKFlowApp agent workflow, worktrees, planning, verification loops, context budget, or tool usage.
Use when verifying AHKFlowApp build, tests, formatting, diagnostics, security, or readiness before commit, push, or PR.
Compact the current conversation into a handoff document for another agent to pick up.
Use to scan .NET code for performance anti-patterns across async, memory, strings, collections, LINQ, regex, and I/O.
Use to evaluate assertion depth and diversity across a test suite and flag assertion-free, shallow, or tautological tests.
| name | dck-error-handling |
| description | Use when mapping AHKFlowApp errors, Ardalis.Result outcomes, validation failures, exceptions, or ProblemDetails. |
Result<T>. Never throw exceptions for not-found, validation, or conflict. These are expected outcomes.ValidatingUseCase<TRequest, TResult> runs before every handler. Handlers never receive invalid requests.result.ToActionResult(this) — From Ardalis.Result.AspNetCore. No manual if/else status code mapping.GlobalExceptionMiddleware handles unhandled exceptions.Use typed factory methods: Result.Success(value), Result.NotFound(), Result.Invalid(errors), Result.Conflict(), Result.Error().
// Application/Queries/GetHotstringHandler.cs
internal sealed class GetHotstringHandler(AppDbContext db)
: IUseCaseHandler<GetHotstringQuery, Result<HotstringDto>>
{
public async Task<Result<HotstringDto>> ExecuteAsync(
GetHotstringQuery request, CancellationToken ct)
{
var entity = await db.Hotstrings.FindAsync([request.Id], ct);
return entity is not null
? Result.Success(new HotstringDto(entity.Id, entity.Trigger, entity.Replacement))
: Result.NotFound();
}
}
// Application/Commands/CreateHotstringHandler.cs
internal sealed class CreateHotstringHandler(AppDbContext db)
: IUseCaseHandler<CreateHotstringCommand, Result<HotstringDto>>
{
public async Task<Result<HotstringDto>> ExecuteAsync(
CreateHotstringCommand request, CancellationToken ct)
{
var exists = await db.Hotstrings.AnyAsync(h => h.Trigger == request.Trigger, ct);
if (exists) return Result.Conflict();
var hotstring = new Hotstring { Trigger = request.Trigger, Replacement = request.Replacement };
db.Hotstrings.Add(hotstring);
await db.SaveChangesAsync(ct);
return Result.Success(new HotstringDto(hotstring.Id, hotstring.Trigger, hotstring.Replacement));
}
}
// API/Controllers/HotstringsController.cs
[ApiController]
[Route("api/v1/[controller]")]
public sealed class HotstringsController(
IUseCase<GetHotstringQuery, Result<HotstringDto>> getHotstring)
: ControllerBase
{
[HttpGet("{id:int}")]
[ProducesResponseType<HotstringDto>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetById(int id, CancellationToken ct)
{
var result = await getHotstring.ExecuteAsync(new GetHotstringQuery(id), ct);
return result.ToActionResult(this); // maps Result status to HTTP automatically
}
}
ToActionResult(this) maps:
Result.Success(value) → 200 OK (or 201 Created for POST)Result.NotFound() → 404Result.Invalid(errors) → 400 with validation errorsResult.Conflict() → 409Result.Error() → 500// Application/Behaviors/ValidatingUseCase.cs
internal sealed class ValidatingUseCase<TRequest, TResult>(
IEnumerable<IValidator<TRequest>> validators,
IUseCaseHandler<TRequest, TResult> inner)
: IUseCase<TRequest, TResult>
where TRequest : notnull
{
public async Task<TResult> ExecuteAsync(
TRequest request,
CancellationToken ct)
{
if (!validators.Any()) return await inner.ExecuteAsync(request, ct);
var context = new ValidationContext<TRequest>(request);
var failures = (await Task.WhenAll(
validators.Select(v => v.ValidateAsync(context, ct))))
.SelectMany(r => r.Errors)
.Where(f => f is not null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures);
return await inner.ExecuteAsync(request, ct);
}
}
Register in DI:
// Application DependencyInjection.cs
services.AddScoped(typeof(IUseCase<,>), typeof(ValidatingUseCase<,>));
services.AddUseCase<CreateHotstringCommand, Result<HotstringDto>, CreateHotstringCommandHandler>();
services.AddValidatorsFromAssembly(typeof(CreateHotstringValidator).Assembly);
// Application/Commands/CreateHotstringValidator.cs
public sealed class CreateHotstringValidator : AbstractValidator<CreateHotstringCommand>
{
public CreateHotstringValidator()
{
RuleFor(x => x.Trigger)
.NotEmpty().WithMessage("Trigger is required")
.MaximumLength(50).WithMessage("Trigger must be 50 characters or less");
RuleFor(x => x.Replacement)
.NotEmpty().WithMessage("Replacement is required")
.MaximumLength(500).WithMessage("Replacement must be 500 characters or less");
}
}
Catches unexpected exceptions and returns RFC 9457 ProblemDetails.
// API/Middleware/GlobalExceptionMiddleware.cs
public sealed class GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
{
public async Task InvokeAsync(HttpContext context)
{
try
{
await next(context);
}
catch (Exception ex)
{
logger.LogError(ex, "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"
};
context.Response.StatusCode = problem.Status!.Value;
context.Response.ContentType = "application/problem+json";
await context.Response.WriteAsJsonAsync(problem);
}
}
}
// Registration in Program.cs
app.UseMiddleware<GlobalExceptionMiddleware>();
// BAD — rolling your own Result type
public class Result<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public List<string> Errors { get; }
}
// GOOD — use Ardalis.Result package
// dotnet add package Ardalis.Result
// dotnet add package Ardalis.Result.AspNetCore
// BAD — ValidationFilter as endpoint filter (Minimal API pattern)
group.MapPost("/", CreateHotstring)
.AddEndpointFilter<ValidationFilter<CreateHotstringCommand>>();
// GOOD — FluentValidation through ValidatingUseCase<TRequest, TResult>
// ValidatingUseCase<TRequest, TResult> runs automatically for all registered commands/queries
// BAD — manual if/else status code mapping
var result = await createHotstring.ExecuteAsync(command, ct);
if (result.IsSuccess) return Ok(result.Value);
if (result.Status == ResultStatus.NotFound) return NotFound();
return BadRequest();
// GOOD — ToActionResult does it automatically
return result.ToActionResult(this);
// BAD — exception for expected outcome
var hotstring = await db.Hotstrings.FindAsync(id)
?? throw new NotFoundException($"Hotstring {id} not found");
// GOOD — Ardalis.Result
var entity = await db.Hotstrings.FindAsync([id], ct);
return entity is not null ? Result.Success(...) : Result.NotFound();
// BAD — inconsistent error format
return BadRequest("Something went wrong");
return BadRequest(new { error = "Invalid input" });
// GOOD — ProblemDetails via ToActionResult (automatic) or GlobalExceptionMiddleware
// BAD
try { await ProcessAsync(ct); }
catch (Exception) { /* ignore */ }
// GOOD — log and return Result.Error()
try { await ProcessAsync(ct); }
catch (ExternalApiException ex)
{
logger.LogWarning(ex, "External API failed");
return Result.Error("External service unavailable");
}
| Scenario | Approach |
|---|---|
| Entity not found | Result.NotFound() |
| Input invalid | Result.Invalid(errors) from handlers, or ValidationException through ValidatingUseCase |
| Duplicate / conflict | Result.Conflict() |
| External service failure | Catch specific exception, return Result.Error() |
| Unhandled crash | GlobalExceptionMiddleware → ProblemDetails 500 |
| Controller HTTP mapping | result.ToActionResult(this) — always |
| Validation runs where | ValidatingUseCase<TRequest, TResult> — before handler, never in handler |