Pandatech.ResponseCrafter
7.1.0
dotnet add package Pandatech.ResponseCrafter --version 7.1.0
NuGet\Install-Package Pandatech.ResponseCrafter -Version 7.1.0
<PackageReference Include="Pandatech.ResponseCrafter" Version="7.1.0" />
<PackageVersion Include="Pandatech.ResponseCrafter" Version="7.1.0" />
<PackageReference Include="Pandatech.ResponseCrafter" />
paket add Pandatech.ResponseCrafter --version 7.1.0
#r "nuget: Pandatech.ResponseCrafter, 7.1.0"
#:package Pandatech.ResponseCrafter@7.1.0
#addin nuget:?package=Pandatech.ResponseCrafter&version=7.1.0
#tool nuget:?package=Pandatech.ResponseCrafter&version=7.1.0
Pandatech.ResponseCrafter
Exception-based error handling for ASP.NET Core 8+ with RFC 9457-compliant ProblemDetails, structured logging, and SignalR support.
Note: If you prefer the Result pattern over exception-based error handling, consider **ResultCrafter ** by Haik Asatryan. ResultCrafter provides a more functional approach with explicit error handling through Result types. ResponseCrafter takes a different path—using typed exceptions with automatic ProblemDetails mapping—suitable for teams that prefer traditional exception flows.
Installation
dotnet add package Pandatech.ResponseCrafter
Setup
using ResponseCrafter.Enums;
using ResponseCrafter.Extensions;
builder.AddResponseCrafter(NamingConvention.ToSnakeCase);
// Optional: SignalR support
builder.Services.AddSignalR(o => o.AddFilter<SignalRExceptionFilter>());
var app = builder.Build();
app.UseResponseCrafter();
Quick Start
Throw Typed Exceptions
using ResponseCrafter.HttpExceptions;
app.MapPost("/users", (CreateUserRequest req) =>
{
// Validation
BadRequestException.ThrowIfNullOrWhiteSpace(req.Email, "email_required");
// Not found
var user = db.Users.Find(req.Id);
NotFoundException.ThrowIfNull(user, "user_not_found");
// Conflict
if (db.Users.Any(u => u.Email == req.Email))
throw new ConflictException("email_already_exists");
// With field errors
throw new BadRequestException("invalid_payload", new Dictionary<string, string>
{
["email"] = "invalid_email_format",
["age"] = "must_be_18_or_older"
});
});
Response Format
All errors return RFC 9457 ProblemDetails:
{
"requestId": "0HN8K2MJ7F4QP:00000001",
"traceId": "00-abc123...",
"instance": "/api/users",
"statusCode": 400,
"type": "BadRequestException",
"message": "invalid_payload",
"errors": {
"email": "invalid_email_format",
"age": "must_be_18_or_older"
}
}
Available Exceptions
| Exception | Status | Static Helpers |
|---|---|---|
BadRequestException |
400 | ✅ ThrowIf*, ThrowIfNull*, etc. |
UnauthorizedException |
401 | ✅ |
PaymentRequiredException |
402 | ❌ |
ForbiddenException |
403 | ✅ |
NotFoundException |
404 | ✅ |
ConflictException |
409 | ✅ |
TooManyRequestsException |
429 | ❌ |
ForceToChangePasswordException |
469 | ❌ |
InternalServerErrorException |
500 | ✅ |
ServiceUnavailableException |
503 | ✅ |
GatewayTimeoutException |
504 | ✅ |
Static Helper Methods
All exceptions with static helpers provide:
// Null checks
NotFoundException.ThrowIfNull(user);
NotFoundException.ThrowIfNull(user, "custom_message");
// String checks
BadRequestException.ThrowIfNullOrWhiteSpace(input, "input_required");
// Collection checks
NotFoundException.ThrowIfNullOrEmpty(list, "no_results_found");
// Conditional
ForbiddenException.ThrowIf(condition, "access_denied");
// Numeric checks
BadRequestException.ThrowIfNullOrNegative(amount, "invalid_amount");
Naming Conventions
ResponseCrafter automatically converts error messages and field names to your preferred case:
builder.AddResponseCrafter(NamingConvention.ToSnakeCase);
Available conventions:
Default- No transformationToSnakeCase-user_not_foundToCamelCase-userNotFoundToPascalCase-UserNotFoundToKebabCase-user-not-foundToTitleCase-User Not FoundToHumanCase-User not foundToUpperSnakeCase-USER_NOT_FOUND
SignalR Support
Handle exceptions in SignalR hubs with automatic error broadcasting:
using ResponseCrafter.ExceptionHandlers.SignalR;
// Hub method
public class ChatHub : Hub<IChatClient>
{
public Task SendMessage(Message request)
{
BadRequestException.ThrowIfNullOrWhiteSpace(request.Content, "message_empty");
// Process message...
}
}
// Request must implement IHubArgument
public class Message : IHubArgument
{
public required string Content { get; set; }
public required string InvocationId { get; set; }
}
Client receives error via ReceiveError event:
{
"traceId": "00-abc123...",
"invocationId": "unique-call-id",
"instance": "SendMessage",
"statusCode": 400,
"message": "message_empty",
"errors": null
}
Automatic Exception Mapping
ResponseCrafter automatically handles:
- DbUpdateConcurrencyException → 409 Conflict
- FluentImporter exceptions → 400 Bad Request (every
ImportException, including subclasses added by future importer releases;MessageDetailsis appended to the message, andMissingColumnsException/InvalidCellValueExceptionalso populateerrors). The one exception is InvalidPropertyNameException → 500, because it means an import rule references a property the model does not have, which is a bug in your rule and not something the caller's file can cause. - Gridify exceptions → 400 Bad Request
- BadHttpRequestException (malformed JSON) → 400 with parse details
- Unhandled exceptions → 500 Internal Server Error
Error Visibility
Control error detail exposure via configuration:
{
"ResponseCrafterVisibility": "Public"
}
- Public (default): 5xx errors show generic message, 4xx show actual errors
- Private: All errors show full details (dev/staging only)
OpenAPI Integration
app.MapPost("/users", (CreateUserRequest req) =>
{
// ... handler logic
})
.ProducesBadRequest()
.ProducesConflict()
.ProducesNotFound();
Available extension methods:
ProducesBadRequest()- 400ProducesUnauthorized()- 401ProducesForbidden()- 403ProducesNotFound()- 404ProducesConflict()- 409ProducesTooManyRequests()- 429ProducesServiceUnavailable()- 503ProducesPaymentRequired()- 402ProducesErrorResponse(statusCode)- Custom statusProducesErrorResponse(params int[])- Multiple statuses
Advanced Usage
Custom Error Dictionaries
var errors = new Dictionary<string, string>
{
["field1"] = "error_message_1",
["field2"] = "error_message_2"
};
throw new BadRequestException("validation_failed", errors);
// or
throw new BadRequestException(errors); // Uses default message
Conditional Throwing
BadRequestException.ThrowIf(age < 18, "must_be_adult");
ForbiddenException.ThrowIf(!user.IsAdmin, "admin_only");
Comparison with ResultCrafter
| Feature | ResponseCrafter | ResultCrafter |
|---|---|---|
| Approach | Exception-based | Result-based (functional) |
| Control flow | try/catch | Explicit Result<T> |
| Performance | Exception overhead | No exceptions |
| API clarity | Implicit errors | Explicit in signatures |
| Best for | Traditional .NET teams | Teams preferring functional style |
Use ResponseCrafter if:
- Your team is comfortable with exceptions
- You have existing exception-based code
- You prefer implicit error handling
Use ResultCrafter if:
- You want explicit error handling in method signatures
- You prefer functional programming patterns
- Performance is critical (no exception overhead)
Configuration Reference
appsettings.json:
{
"ResponseCrafterVisibility": "Public"
}
Program.cs:
builder.AddResponseCrafter(NamingConvention.ToSnakeCase);
app.UseResponseCrafter();
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Humanizer (>= 3.0.10)
- PandaTech.FluentImporter (>= 6.0.0)
- Pandatech.GridifyExtensions (>= 4.1.3)
-
net8.0
- Humanizer (>= 3.0.10)
- PandaTech.FluentImporter (>= 6.0.0)
- Pandatech.GridifyExtensions (>= 4.1.3)
-
net9.0
- Humanizer (>= 3.0.10)
- PandaTech.FluentImporter (>= 6.0.0)
- Pandatech.GridifyExtensions (>= 4.1.3)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Pandatech.ResponseCrafter:
| Package | Downloads |
|---|---|
|
Pandatech.SharedKernel
Opinionated ASP.NET Core 10 infrastructure kernel: OpenAPI (Swagger + Scalar), Serilog, MediatR, FluentValidation, CORS, SignalR, OpenTelemetry, health checks, maintenance mode, resilience pipelines, and shared utilities. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 7.1.0 | 122 | 7/26/2026 |
| 7.0.1 | 133 | 7/6/2026 |
| 7.0.0 | 235 | 2/28/2026 |
| 6.0.2 | 145 | 2/26/2026 |
| 6.0.1 | 173 | 1/26/2026 |
| 6.0.0 | 158 | 12/28/2025 |
| 5.3.1 | 224 | 12/23/2025 |
| 5.3.0 | 303 | 10/30/2025 |
| 5.2.2 | 310 | 9/8/2025 |
| 5.2.1 | 247 | 9/8/2025 |
| 5.2.0 | 235 | 8/15/2025 |
| 5.1.12 | 333 | 8/7/2025 |
| 5.1.11 | 221 | 7/30/2025 |
| 5.1.10 | 355 | 6/1/2025 |
| 5.1.9 | 331 | 4/9/2025 |
| 5.1.8 | 302 | 2/18/2025 |
| 5.1.7 | 252 | 2/17/2025 |
| 5.1.6 | 256 | 2/5/2025 |
| 5.1.5 | 264 | 1/10/2025 |
| 5.1.4 | 221 | 1/9/2025 |
FluentImporter mapping is now driven off the ImportException base type, so MissingColumnsException, DuplicateColumnException and SheetNotFoundException (added in FluentImporter 6.0.0) return 400 instead of falling through to 500. MessageDetails is no longer dropped, and MissingColumnsException/InvalidCellValueException now populate the errors dictionary. Behaviour change: InvalidPropertyNameException is now a 500 — it means a rule names a property the model does not have, which is a developer error rather than bad input. FluentImporter dependency bumped to 6.0.0.