ResponseResultHandler 12.1.33
dotnet add package ResponseResultHandler --version 12.1.33
NuGet\Install-Package ResponseResultHandler -Version 12.1.33
<PackageReference Include="ResponseResultHandler" Version="12.1.33" />
<PackageVersion Include="ResponseResultHandler" Version="12.1.33" />
<PackageReference Include="ResponseResultHandler" />
paket add ResponseResultHandler --version 12.1.33
#r "nuget: ResponseResultHandler, 12.1.33"
#:package ResponseResultHandler@12.1.33
#addin nuget:?package=ResponseResultHandler&version=12.1.33
#tool nuget:?package=ResponseResultHandler&version=12.1.33
ResponseResultHandler
A Result-Pattern library for .NET (net8.0–net10.0). Wraps the outcome of an operation — success or failure, an HTTP-mappable status, a title/detail, and optional data — in an immutable object instead of throwing exceptions or returning bare booleans.
This repository is two NuGet packages sharing this README:
| Package | Project | Purpose |
|---|---|---|
ResponseResultHandler |
src/ResultHandler |
Core library — framework-agnostic |
ResponseResultHandler.AspNetCore |
src/ResultHandler.AspNetCore |
Converts results to IActionResult / RFC 9457 Problem Details |
dotnet add package ResponseResultHandler
dotnet add package ResponseResultHandler.AspNetCore # optional: IActionResult / RFC 9457 adapter
See CHANGELOG.md for what changed between versions.
Everything below is demonstrated through one running example: a ProductService that looks up a
product by id, and a ProductsController that exposes it over HTTP.
Contents: 1. Core contract · 2. ResultStatus ·
3. Building results directly · 4. Result facade ·
5. ASP.NET Core (IActionResult) ·
6. Minimal APIs · 7. Functional composition ·
8. Result.Combine · 9. Async composition ·
10. Generic short-circuiting ·
11. Per-field validation errors ·
12. Serialization · 13. Equality & debugging ·
14. Migrating to v12
1. The core contract
Every result implements IOperationResult (ResultHandler.Core.Abstractions):
| Member | Meaning |
|---|---|
bool IsSuccessful |
did the operation succeed |
ResultStatus Status |
outcome status — an enum, not HttpStatusCode, see §2 |
string Title |
short summary, e.g. "Not Found" |
string? Detail |
optional extra context, e.g. "Product 42 does not exist." |
IReadOnlyList<string> Errors |
optional list of individual error messages (validation, etc.) |
IOperationResult<T> : IOperationResult adds T Data — guaranteed non-null when IsSuccessful is true
(the compiler enforces this via [MemberNotNullWhen], so result.Data is safe to use right after
an if (result.IsSuccessful) check without a null-forgiving operator).
2. ResultStatus
An enum covering every standard 1xx–5xx HTTP status (ResultHandler.Core.Enums), kept independent
of System.Net.HttpStatusCode so the library has no hard dependency on ASP.NET Core. Convert either
direction with the extension methods in ResultHandler.Mapping:
using ResultHandler.Core.Enums;
using ResultHandler.Mapping;
HttpStatusCode code = ResultStatus.NotFound.ToHttpStatusCode(); // 404
ResultStatus status = HttpStatusCode.Conflict.ToResultStatus(); // Conflict
3. Building results directly
SuccessResult / SuccessDataResult<T> and ErrorResult / ErrorDataResult<T>
(ResultHandler.Implementations.Success / .Error) pin IsSuccessful for you:
using ResultHandler.Core.Enums;
using ResultHandler.Implementations.Error;
using ResultHandler.Implementations.Success;
public IOperationResult<ProductDto> GetById(int id)
{
var product = _products.Find(id);
if (product is null)
{
return new ErrorDataResult<ProductDto>(
"Not found.", ResultStatus.NotFound, $"Product {id} does not exist.");
}
return new SuccessDataResult<ProductDto>(ToDto(product), "Product found.", ResultStatus.Ok);
}
Validation errors use the IReadOnlyList<string> errors overload instead of detail:
if (request.Name is { Length: 0 })
{
return new ErrorResult("Validation failed.", ResultStatus.UnprocessableContent,
new[] { "Name is required.", "Price must be greater than zero." });
}
OperationResult / OperationDataResult<T> (ResultHandler.Core.Base) are the base classes both of
the above inherit from — construct them directly only for a custom result shape that isn't a plain
success/error; SuccessResult/ErrorResult cover the normal cases.
4. The Result facade — the recommended way
ResultHandler.Facade.Result is a static class with one factory pair per ResultStatus
(non-generic and <T>), named after the status, with sensible default titles baked in. It's what
the example above looks like using it instead:
using ResultHandler.Facade; // Result
public IOperationResult<ProductDto> GetById(int id)
{
var product = _products.Find(id);
return product is null
? Result.NotFound<ProductDto>($"Product {id} does not exist.")
: Result.Success(ToDto(product), "Product found.");
}
public ErrorResult? ValidateCreate(CreateProductRequest request)
{
var errors = new List<string>();
if (string.IsNullOrEmpty(request.Name)) errors.Add("Name is required.");
if (request.Price <= 0) errors.Add("Price must be greater than zero.");
return errors.Count > 0 ? Result.Invalid(errors.ToArray()) : null;
}
public SuccessResult MoveResource(int id, string newLocation)
=> Result.MovedPermanently(newLocation); // 3xx redirects — location gets interpolated into the title
// Escape hatch for anything not covered by a named factory:
public ErrorResult CustomFailure()
=> Result.Failure("Payment declined.", "The card was rejected by the issuer.", ResultStatus.PaymentRequired);
5. ResultHandler.AspNetCore — converting to IActionResult
Add the ResponseResultHandler.AspNetCore package and call one extension method
(ResultHandler.AspNetCore.Extensions) at the end of a controller action:
using ResultHandler.AspNetCore.Extensions;
[ApiController]
[Route("api/products")]
public class ProductsController : ControllerBase
{
private readonly ProductService _products;
[HttpGet("{id}")]
public IActionResult GetById(int id)
=> _products.GetById(id).ToActionResult(HttpContext);
[HttpPost]
public IActionResult Create(CreateProductRequest request)
{
var validation = _products.ValidateCreate(request);
if (validation is not null)
{
return validation.ToActionResult(); // 422 Problem Details with the two validation errors
}
return Result.Created(_products.Create(request)).ToActionResult();
}
}
ToActionResult()— for non-genericIOperationResult(commands with no data to return). Success is always bodyless — a bare status code matching the result's actualStatus(201forCreated,204forNoContent, etc.), never a payload; failure returns an RFC 9457ProblemDetailsbody.ToActionResult<T>()— forIOperationResult<T>. Success returns the rawTpayload with the result's actual status code (201 { ... }forCreated,202 { ... }forAccepted, etc. — not hardcoded to200), or a bodyless status forNoContent/1xx/3xx; failure is the sameProblemDetailsbody.ToEnvelopedActionResult()— success returns the whole result object (status/title/data) as the body instead of just the payload, with the same status-code-preserving behavior — useful when clients want metadata alongside the data.ToProblemDetails(HttpContext? httpContext = null)— builds theProblemDetailsyourself; passHttpContextandInstancegets set to the current request path (RFC 9457 §3.1.4).- Every 4xx/5xx
ResultStatusmapsProblemDetails.Typeto the actual RFC section that defines it (RFC 9110, RFC 6585, RFC 4918, RFC 7725, RFC 8470); 1xx/2xx/3xx useabout:blankper RFC 9457 §4.2.1, since those aren't "problems". - These
IActionResult-returning methods are for MVC controllers. In a Minimal API endpoint delegate, use theIResult-returning siblings from §6 instead — returningIActionResultfrom a delegate triggers analyzer warningASP0004and hides the response shape from OpenAPI/Swagger generation at compile time.
A failed GetById(999) call above produces:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
"title": "Not Found",
"status": 404,
"detail": "Product 999 does not exist.",
"instance": "/api/products/999"
}
6. Minimal APIs
Minimal API endpoint delegates can return IActionResult (via an MVC compatibility shim), but
don't — it triggers analyzer warning ASP0004, and it's opaque to the endpoint metadata pipeline that
Swashbuckle/Microsoft.AspNetCore.OpenApi use to infer response types, so your OpenAPI document ends
up missing or wrong for those endpoints.
Use the IResult-returning siblings instead — same shapes, same status-code-preserving behavior, but
native to Minimal APIs and fully visible to OpenAPI generation:
ToResult(HttpContext? httpContext = null)—IOperationResult→IResult. Success returns a bodyless status (204forNoContent,304forNotModified, plain status code for 1xx/3xx/Ok); failure returns an RFC 9457ProblemDetailsJSON body viaToProblemResult.ToResult<T>(HttpContext? httpContext = null)—IOperationResult<T>→IResult. Success returns the raw payload as JSON with the result's actual status code (201forCreated,202forAccepted, etc.); failure is the sameProblemDetailsJSON body.ToEnvelopedResult(HttpContext? httpContext = null)— success returns the whole result object (status/title/data) as the JSON body instead of just the payload.ToProblemResult(HttpContext? httpContext = null)— maps a failed result straight to anIResultcarrying RFC 9457ProblemDetailsJSON; the same building blockToResult/ToResult<T>/ToEnvelopedResultuse internally for their failure branch.
using ResultHandler.AspNetCore.Extensions;
using ResultHandler.Facade; // Result
var app = WebApplication.Create(args);
app.Services.GetRequiredService<IServiceCollection>(); // ...DI setup omitted
app.MapGet("/api/products/{id:int}", (int id, ProductService products, HttpContext httpContext)
=> products.GetById(id).ToResult(httpContext));
app.MapPost("/api/products", (CreateProductRequest request, ProductService products) =>
{
var validation = products.ValidateCreate(request);
return validation is not null
? validation.ToProblemResult() // 422 Problem Details with the two validation errors
: Result.Created(products.Create(request)).ToResult();
});
app.Run();
7. Functional composition
ResultHandler.Functional adds chaining helpers so callers don't have to repeat
if (!result.IsSuccessful) return ... at every step:
using ResultHandler.Functional;
string message = _products.GetById(id)
.Map(p => p.Name.ToUpperInvariant())
.Match(
onSuccess: name => $"Found: {name}",
onFailure: failure => $"Error: {failure.Title}");
_products.GetById(id)
.OnSuccess(product => _logger.LogInformation("Fetched {Name}", product.Name)) // typed: product is ProductDto
.OnFailure(failure => _logger.LogWarning("Lookup failed: {Title}", failure.Title));
IOperationResult<OrderDto> order = _products.GetById(id)
.Bind(product => _orders.CreateDraftOrder(product)); // chains into another IOperationResult<T>-returning call
IOperationResult<ProductDto> validated = _products.GetById(id)
.Ensure(product => product.Stock > 0, "Product is out of stock."); // guard clause: 422 if the predicate fails
Map/Bind short-circuit automatically: if the source result failed, the mapper/binder never runs
and the failure (title/status/detail/errors) is carried over into the new result type. Ensure turns
a still-successful result into a failure when a business-rule predicate rejects the data — the
shortcut overload above defaults to "Validation Failed" / 422 Unprocessable Content (same shape as
Result.Invalid); pass your own (title, detail, status) when a different outcome fits better:
_products.GetById(id)
.Ensure(product => product.OwnerId == currentUserId, "Forbidden.", "You do not own this product.", ResultStatus.Forbidden);
8. Combining independent checks — Result.Combine
Ensure (and Bind/Map) stop at the first failure — the rest of the chain never runs. That's the
right behavior for a pipeline of dependent steps, but wrong for independent checks where you want to
report every problem at once (e.g. every invalid field on a form). Result.Combine(...) (ResultHandler.Facade)
covers that case instead — it runs every result to completion and merges their outcomes:
using ResultHandler.Facade; // Result
ErrorResult? ValidateCreate(CreateProductRequest request)
{
var nameCheck = string.IsNullOrEmpty(request.Name)
? Result.Invalid("Name is required.")
: Result.Success();
var priceCheck = request.Price <= 0
? Result.Invalid("Price must be greater than zero.")
: Result.Success();
var combined = Result.Combine(nameCheck, priceCheck);
return combined.IsSuccessful ? null : (ErrorResult)combined;
}
If both checks fail, combined.Errors contains both messages — ["Name is required.", "Price must be greater than zero."]
— not just the first one. Every failed result's Errors (or Detail/Title when it carries none) are
concatenated in order; if every result succeeds, Combine returns Result.Success().
When the combined results carry FieldErrors (§11), Combine merges them by key instead of dropping
them — combining a "Name" failure and a "Price" failure gives you both keys back in one result.
When the independent checks each carry data you actually need afterward, the 2–4 arity generic overloads combine both the outcome and the payloads into a named tuple:
IOperationResult<(CustomerDto Customer, ShippingAddressDto Address)> ValidateOrder(int customerId, int addressId)
=> Result.Combine(_customers.GetById(customerId), _addresses.GetById(addressId));
If both succeed, Data is (CustomerDto Customer, ShippingAddressDto Address); if either (or both) fail,
you get the same aggregated Result.Invalid(...) as above, re-projected into IOperationResult<(...)>.
9. Async composition
Real handlers usually chain calls that are themselves async — a repository hit, an email send, a
downstream API call. ResultHandler.Functional mirrors every method from §7 with an ...Async
counterpart so those chains read the same way, without an await breaking up the fluent chain at
every step:
using ResultHandler.Functional;
public Task<IOperationResult<ProductDto>> ActivateAsync(int id)
=> _products.GetByIdAsync(id) // Task<IOperationResult<Product>>
.BindAsync(product => ValidateCanActivateAsync(product)) // async business rule
.MapAsync(product => ToDto(product)) // sync mapper
.OnSuccessAsync(dto => _email.SendActivationEmailAsync(dto.OwnerEmail)); // async side effect
If GetByIdAsync returns NotFound, or ValidateCanActivateAsync fails, the chain short-circuits
immediately — MapAsync and OnSuccessAsync never run, and the original failure (title/status/detail)
is what the caller gets back, exactly like the sync Map/Bind in §7.
Each method comes in three shapes so you can start the chain from either a Task<IOperationResult<T>>
or a plain IOperationResult<T>, and pass either a sync or an async delegate — mix and match freely
in the same chain:
| Shape | Example |
|---|---|
Task<IOperationResult<T>> source, sync delegate |
.MapAsync(p => p.Name) |
Task<IOperationResult<T>> source, async delegate |
.BindAsync(p => _orders.CreateDraftOrderAsync(p)) |
IOperationResult<T> source, async delegate |
existingResult.OnSuccessAsync(p => _email.SendAsync(p)) |
MatchAsync, OnSuccessAsync, OnFailureAsync, MapAsync, BindAsync, and EnsureAsync all follow
this pattern, for both IOperationResult and IOperationResult<T> (EnsureAsync only exists for
IOperationResult<T> — same reasoning as Ensure in §7, there's no data to check on the non-generic
form). The controller/endpoint at the edge (§5/§6)
doesn't change — just await the final result and call .ToActionResult() / .ToResult() as usual:
[HttpPost("{id}/activate")]
public async Task<IActionResult> Activate(int id)
=> (await _products.ActivateAsync(id)).ToActionResult();
10. Generic short-circuiting — IResultFailureFactory<TSelf> / ResultFailureFactory
Everything above assumes the calling code knows the concrete result type. Generic infrastructure often
doesn't — a MediatR IPipelineBehavior<TRequest, TResponse>, a gRPC interceptor, any short-circuiting
middleware only has TResponse as a type parameter, and you can't new TResponse(...) without knowing
what it actually is. Without this, the usual fix is throwing an exception just to unwind the pipeline.
IResultFailureFactory<TSelf> (ResultHandler.Core.Abstractions) solves this with a C# 11 static-abstract-interface
CRTP: it lets TSelf build its own failure instance. OperationResult and OperationDataResult<T> already
implement it, so any result type built on top of this library (concrete or still generic) gets it for free:
public interface IResultFailureFactory<TSelf> where TSelf : IOperationResult
{
static abstract TSelf Failure(IReadOnlyList<string> errors); // validation message list
static abstract TSelf Failure(string title, string detail, ResultStatus status); // everything else
}
ResultFailureFactory (ResultHandler.Functional) layers the same named, per-status vocabulary as Result on top
of these two primitives — BadRequest, NotFound, Unauthorized, Forbidden, and every other 4xx/5xx —
generically, for any TSelf. It delegates to the matching Result.XXX(detail) method internally, so titles
and default messages have exactly one source of truth (Result); nothing is duplicated.
A MediatR pipeline behavior that stops throwing and starts returning:
using ResultHandler.Core.Abstractions;
using ResultHandler.Functional;
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : IOperationResult, IResultFailureFactory<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
var errors = validators
.Select(v => v.Validate(request))
.SelectMany(r => r.Errors)
.Select(f => f.ErrorMessage)
.ToArray();
if (errors.Length > 0)
{
return TResponse.Failure(errors); // short-circuits the pipeline — no throw, no exception cost
}
return await next(ct);
}
}
public class AuthorizationBehavior<TRequest, TResponse>(ICurrentUser user)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>, IRequireRole
where TResponse : IOperationResult, IResultFailureFactory<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
if (!user.IsAuthenticated)
{
return ResultFailureFactory.Unauthorized<TResponse>();
}
if (!user.IsInRole(request.RequiredRole))
{
return ResultFailureFactory.Forbidden<TResponse>();
}
return await next(ct);
}
}
For this to type-check, the command/query itself declares its response as an OperationDataResult<T> (or
any other IResultFailureFactory implementer) instead of a bare DTO:
public record CreateProductCommand(string Name, decimal Price) : IRequest<OperationDataResult<ProductDto>>;
public class CreateProductHandler(IProductRepository repository)
: IRequestHandler<CreateProductCommand, OperationDataResult<ProductDto>>
{
public async Task<OperationDataResult<ProductDto>> Handle(CreateProductCommand request, CancellationToken ct)
{
if (await repository.ExistsByName(request.Name))
{
// built by a business-rule check that only knows IOperationResult, not the final DTO shape:
var duplicate = ResultFailureFactory.Conflict<OperationResult>($"A product named '{request.Name}' already exists.");
return duplicate.ToErrorDataResult<ProductDto>(); // re-projects into the handler's actual return type
}
var product = await repository.Add(request.Name, request.Price);
return Result.Success(ToDto(product));
}
}
ToErrorDataResult<T>() (ResultHandler.Functional) is the companion piece: it re-projects an existing
failed IOperationResult (title/status/detail/errors already decided elsewhere, e.g. in a business-rule
method) into the ErrorDataResult<T> shape a handler must return — a conversion, not a new factory call,
which is why it reads as failure.ToErrorDataResult<T>() rather than Result.ToErrorDataResult<T>(failure).
The controller/endpoint at the edge doesn't change at all — result.ToActionResult() still works, because
OperationDataResult<T> still implements IOperationResult<T>:
[HttpPost]
public async Task<IActionResult> Create(CreateProductCommand command)
=> (await mediator.Send(command)).ToActionResult();
11. Per-field validation errors — IHasFieldErrors / IResultFailureFactory<TSelf>
§3's plain IReadOnlyList<string> Errors is a flat list — fine for "here are the problems" messages,
but a form UI usually needs to know which input each message belongs to. OperationResult/
OperationDataResult<T> (and therefore ErrorResult/ErrorDataResult<T>) also implement
IHasFieldErrors (ResultHandler.Core.Abstractions):
public interface IHasFieldErrors
{
IReadOnlyDictionary<string, IReadOnlyList<string>> FieldErrors { get; }
}
Build one with OperationResult.Failure(fieldErrors) / OperationDataResult<T>.Failure(fieldErrors),
or the generic IResultFailureFactory<TSelf>.Failure(fieldErrors) (implemented by the same types, for
the same CRTP reason as §10) — keyed by property name, valued by that property's messages:
using ResultHandler.Core.Base;
var fieldErrors = new Dictionary<string, IReadOnlyList<string>>
{
["Name"] = ["Name is required."],
["Price"] = ["Price must be greater than zero."],
};
OperationResult validation = OperationResult.Failure(fieldErrors);
FieldErrors and the flattened Errors list are both populated from the same input — pick whichever
shape a given caller needs; Title/Status default to "Validation Failed" /
422 Unprocessable Content, same as the flat-list Failure(errors) overload.
A generic pipeline behavior (mirrors §10's ValidationBehavior, keyed per field instead of a flat list):
public class FieldValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
where TResponse : IOperationResult, IResultFailureFactory<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
var fieldErrors = validators
.Select(v => v.Validate(request))
.SelectMany(r => r.Errors)
.GroupBy(f => f.PropertyName)
.ToDictionary(g => g.Key, IReadOnlyList<string> (g) => [.. g.Select(f => f.ErrorMessage)]);
return fieldErrors.Count > 0 ? TResponse.Failure(fieldErrors) : await next(ct);
}
}
ToErrorDataResult<T>(), Map, and Bind (§7/§10) carry FieldErrors through when re-projecting a
failure into a different result type, same as they carry Title/Status/Detail/Errors;
Result.Combine (§8) merges FieldErrors from every combined failure instead.
ResultHandler.AspNetCore's ToProblemDetails() (§5/§6) adds a failed result's FieldErrors to the
Problem Details body as a "fieldErrors" extension when non-empty:
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.21",
"title": "Validation Failed",
"status": 422,
"errors": ["Name is required.", "Price must be greater than zero."],
"fieldErrors": {
"Name": ["Name is required."],
"Price": ["Price must be greater than zero."]
}
}
12. Serialization
System.Text.Json output uses fixed property names regardless of your JsonSerializerOptions
naming policy, and ResultStatus always serializes as its numeric HTTP code:
JsonSerializer.Serialize(Result.NotFound<ProductDto>("Product 42 does not exist."));
{
"resultData": null,
"isSuccessful": false,
"statusCode": 404,
"statusMessage": "Not Found",
"detail": "Product 42 does not exist.",
"errors": [],
"fieldErrors": {}
}
Detail is omitted entirely when null; errors and fieldErrors are always present, empty
([]/{}) when the failure doesn't carry any (see §11).
13. Equality & debugging
OperationResult/OperationDataResult<T> override Equals/GetHashCode (structural, by value) and ToString():
Result.NotFound("x") == Result.NotFound("x"); // false (reference types) — use .Equals()
Result.NotFound("x").Equals(Result.NotFound("x")); // true
Result.NotFound("x").ToString(); // "NotFound (404): Not Found"
14. Migrating to v12
v12 removes every member that v11 marked [Obsolete] — they no longer compile, there's no
forwarding shim. Replace them directly:
| Removed in v12 | Use instead |
|---|---|
StatusMessage |
Title |
StatusCode: HttpStatusCode |
Status: ResultStatus |
ResultData |
Data |
new ErrorResult(string statusMessage, HttpStatusCode statusCode) and the equivalent HttpStatusCode-based constructors on OperationResult, OperationDataResult<T>, SuccessResult, SuccessDataResult<T>, ErrorDataResult<T> |
The ResultStatus-based constructor, e.g. new ErrorResult(title, status). If you only have an HttpStatusCode on hand, convert it first: new ErrorResult(title, httpStatusCode.ToResultStatus()) (HttpStatusCodeExtensions.ToResultStatus). |
// Before (v11, obsolete):
var legacy = new ErrorResult("Not found.", HttpStatusCode.NotFound);
Console.WriteLine(legacy.StatusMessage);
// After (v12):
var current = new ErrorResult("Not found.", HttpStatusCode.NotFound.ToResultStatus());
Console.WriteLine(current.Title);
ResultHandler.AspNetCore behavior fix (carried over from v11): ToActionResult<T>() and ToEnvelopedActionResult()
used to hardcode a 200 status code for any successful result that carried a body, silently
discarding the result's actual Status (so Result.Created(...) came back as 200, not 201).
Both now honor Status correctly. If you were relying on the old (incorrect) 200-always behavior,
check call sites that use non-Ok success statuses with ToActionResult<T>/ToEnvelopedActionResult.
| 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
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (3)
Showing the top 3 NuGet packages that depend on ResponseResultHandler:
| Package | Downloads |
|---|---|
|
ResponseResultHandler.AspNetCore
ASP.NET Core integration for ResponseResultHandler: converts IOperationResult/IOperationResult<T> into IActionResult and RFC 9457 Problem Details responses. |
|
|
Core.CrossCuttingConcernLayer
Package Description |
|
|
Core.ApplicationLayer
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated | |
|---|---|---|---|
| 12.1.33 | 33 | 9/13/2026 | |
| 12.1.33-beta.36 | 44 | 9/13/2026 | |
| 12.1.32 | 50 | 9/13/2026 | |
| 12.1.31-beta.31 | 48 | 9/13/2026 | |
| 12.1.30-beta.30 | 49 | 9/13/2026 | |
| 12.1.29-beta.29 | 52 | 9/13/2026 | |
| 12.1.27-beta.27 | 85 | 8/23/2026 | |
| 12.1.0 | 68 | 9/13/2026 | |
| 12.1.0-beta.34 | 46 | 9/13/2026 | |
| 12.0.0 | 299 | 8/7/2026 | |
| 12.0.0-beta.26 | 216 | 8/23/2026 | |
| 12.0.0-beta.22 | 73 | 8/7/2026 | |
| 11.0.19 | 129 | 8/7/2026 | |
| 11.0.18-beta.18 | 92 | 8/7/2026 | |
| 11.0.17 | 288 | 8/1/2026 | |
| 11.0.16-beta.16 | 85 | 8/1/2026 | |
| 11.0.15-beta.15 | 88 | 8/1/2026 | |
| 11.0.12-beta.12 | 112 | 7/31/2026 | |
| 11.0.11-beta.11 | 82 | 7/31/2026 | |
| 11.0.10-beta.10 | 83 | 7/27/2026 |
v12.1 - Per-field validation errors, plus FieldErrors bug fixes:
Added:
- Per-field validation errors: IHasFieldErrors, IResultFailureFactory<TSelf>.Failure(fieldErrors), OperationResult.Failure(fieldErrors) (see README "Per-field validation errors"). Breaking for any external IResultFailureFactory<TSelf> implementer - see CHANGELOG.md.
Fixed:
- Map/Bind/ToErrorDataResult() dropped FieldErrors when re-projecting a failure into a different result type - now preserved, along with the original Errors list.
- Result.Combine(...) dropped FieldErrors when merging failures - field errors from every combined failure are now merged by key.
- Failure(fieldErrors) threw if the dictionary had a null value - that field's messages are skipped instead.
- GetHashCode() was order-sensitive for FieldErrors while Equals() wasn't, so equal-content dictionaries with a different key order could hash differently.
- OperationResult/OperationDataResult<T>'s errors+fieldErrors constructor is now public, so Native AOT source-generated JSON deserializers can call it.
Removed (breaking):
- net7.0 support (EOL) - net8.0/net9.0/net10.0 only; upgrade your target framework before taking this version.
See CHANGELOG.md for full details.
v12 (breaking): Removed all members previously marked [Obsolete] — StatusMessage, StatusCode, ResultData, and every HttpStatusCode-based constructor across OperationResult/OperationDataResult/SuccessResult/SuccessDataResult/ErrorResult/ErrorDataResult. Use Title/Status/Data and the ResultStatus-based constructors instead (see README "Migrating to v12"). Also includes the previously-unreleased v11.1 additions: Result.Combine, Ensure/EnsureAsync guard clauses, and Task-aware async composition helpers (MatchAsync/OnSuccessAsync/OnFailureAsync/MapAsync/BindAsync). See CHANGELOG.md for details.