Purview.Results.AspNetCore
1.0.0.1
dotnet add package Purview.Results.AspNetCore --version 1.0.0.1
NuGet\Install-Package Purview.Results.AspNetCore -Version 1.0.0.1
<PackageReference Include="Purview.Results.AspNetCore" Version="1.0.0.1" />
<PackageVersion Include="Purview.Results.AspNetCore" Version="1.0.0.1" />
<PackageReference Include="Purview.Results.AspNetCore" />
paket add Purview.Results.AspNetCore --version 1.0.0.1
#r "nuget: Purview.Results.AspNetCore, 1.0.0.1"
#:package Purview.Results.AspNetCore@1.0.0.1
#addin nuget:?package=Purview.Results.AspNetCore&version=1.0.0.1
#tool nuget:?package=Purview.Results.AspNetCore&version=1.0.0.1
Purview.Results.AspNetCore
Maps Purview.Results values onto ASP.NET Core responses, so
an endpoint can return Result<TValue, TError> or a value-less Result<TError> and let the host decide what
each error case looks like on the wire.
Installation
dotnet add package Purview.Results.AspNetCore
Quick start
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResultsHttp(options => options
.Map<TenantNotFound>(error => TypedResults.NotFound())
.Map<TenantDisabled>(error => TypedResults.Problem(statusCode: StatusCodes.Status403Forbidden))
.Map<TenantError>(error => TypedResults.Problem(statusCode: StatusCodes.Status409Conflict)) // every unmapped case
.AddFallback((error, context) => error is null ? null : TypedResults.Problem())
);
var app = builder.Build();
app.MapGet("/tenants/{id:int}", (int id) => GetTenant(id)).WithResultsHttp();
The handler returns a Result<Tenant, TenantError>; the filter converts it into the configured response.
AddResultsHttp also registers problem-details services (AddProblemDetails()), so the unmapped-failure and
uninitialized-result paths work without further host setup.
How a result becomes a response
Success — the value is serialized with SuccessStatusCode (200 OK by default). A result whose successful
value is itself an IResult is passed through untouched, and SuccessMapper overrides both when set. A
successful unit Result<TError> carries no payload, so it answers 204 No Content unless SuccessMapper is
set.
Failure — the error is resolved to its case value (the innermost active case of a union error, so a nested union resolves to its leaf; or the error itself for a non-union error), then mapped in this order:
- a mapping registered for the most specific case type —
Map<TenantNotFound>(...) - a mapping registered for each enclosing union type, from the inside out —
Map<BillingError>(...)handles a whole nested union when its leaf has no mapping of its own - a mapping registered for the error type —
Map<TenantError>(...), which handles every case without its own mapping - the fallback stage, in registration order: the
AddFallback(...)delegates and theAddFailureMapper<TMapper>()mappers share one list, and whatever is registered first is consulted first; a fallback or mapper receives the leaf case and returnsnullto defer to the next entry - a
ProblemDetailsresponse usingUnmappedStatusCode(500),UnmappedTitle, and anerrorTypeextension naming the unmapped case — or anInvalidOperationExceptionwhenThrowOnUnmappedFailureis set
An uninitialized result (default) is logged and answered with the unmapped-failure response, because an
endpoint returning default is a host bug rather than a domain outcome.
Options
| Option | Default | Purpose |
|---|---|---|
SuccessStatusCode |
200 |
Status code for a serialized successful value |
SuccessMapper |
null |
Replaces the default success handling entirely |
UnmappedStatusCode |
500 |
Status code for a failure with no mapping |
UnmappedTitle |
"The operation failed with an error that is not mapped to an HTTP response." | Title of the unmapped ProblemDetails |
ThrowOnUnmappedFailure |
false |
Throw instead of producing a problem response; useful during development |
IncludeTraceId |
true |
Whether problem responses this package writes itself carry the request trace identifier |
Map<TCase>(Func<TCase, IResult>) |
— | Maps a case (or the error itself) to a response |
Map<TCase>(Func<TCase, HttpContext, IResult>) |
— | Same, with access to the request |
AddFallback(Func<object?, HttpContext, IResult?>) |
— | Consulted in order for unmapped failures, with the case value |
AddFailureMapper<TMapper>() |
— | Same stage, for a mapper class resolved from dependency injection |
Registering the same type twice replaces the earlier mapping.
Choosing an extension point
| The rule needs… | Use |
|---|---|
| One answer per error or case type | Map<TCase>(...) |
| The value the failure carries — a validation code, a category, a field | IResultsFailureMapper via AddFailureMapper<TMapper>() |
| A quick inline rule, with no dependencies | AddFallback((error, context) => ...) |
| To replace the whole pipeline | Your own IResultsHttpMapper (see Extensibility) |
A failure mapper is a shape rule, not a catch-all:
public sealed class BlankIdentifierMapper : IResultsFailureMapper
{
public IResult? Map(ResultsFailureContext context) =>
context.Case is ITenantFailure { TenantId.Value: var id } && string.IsNullOrWhiteSpace(id)
? TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "An identifier is required.")
: null; // defer: the case mappings and the other fallbacks still apply
}
builder.Services.AddSingleton<BlankIdentifierMapper>();
builder.Services.AddResultsHttp(options => options
.Map<TenantNotFound>(_ => TypedResults.NotFound())
.AddFailureMapper<BlankIdentifierMapper>());
The mapper is resolved from the request's services the first time it is needed, so it may take its own
dependencies in its constructor. Register it (AddSingleton<MyMapper>(), or against IResultsFailureMapper and
name that interface as TMapper) before the first request; a failure that reaches an unregistered mapper throws
an InvalidOperationException naming the registration that is missing.
Converting a result by hand
When a filter is not appropriate, convert explicitly:
app.MapGet("/tenants/{id:int}", (int id, HttpContext context) =>
GetTenant(id).ToHttpResult(context));
ToHttpResult(HttpContext) resolves IResultsHttpMapper from the request services; the
ToHttpResult(IResultsHttpMapper, HttpContext) overload takes one directly.
Extensibility
IResultsHttpMapper is registered with AddResultsHttp as
DefaultResultsHttpMapper via TryAddSingleton, so a host can register its own implementation first to replace
the defaults entirely. A host that replaces it also bypasses ResultsHttpOptions — including the failure mappers
— so prefer the extension points above unless the pipeline itself has to change.
Examples
src/src/Examples.AspNetCore
is a runnable minimal-API example that maps the TenantNotFound case to 404, the TenantDisabled case to
403 and the TenantError error type to 409. Its billing endpoint returns a union that nests TenantError
and BillingError, so it also shows a nested union resolving to its leaf (the billing cases map to 503 and
402) while the TenantError mapping still covers a nested tenant failure. Its DELETE endpoint returns a
unit Result<TenantError>, so a success answers 204 while a missing tenant still maps to 404. It shows the
response an endpoint that returns default receives too.
dotnet run --project src/src/Examples.AspNetCore --urls http://localhost:5215
The repository README lists the Basic, ZodSharp and ASP.NET Core + Zod examples too.
Related packages
Validation failures that carry ZodSharp errors are rendered as HttpValidationProblemDetails by
Purview.Results.ZodSharp.AspNetCore.
This package deliberately knows nothing about ZodSharp.
Agent skills
This package ships the purview-results-http-mapping agent skill under .agents/. Repositories that import
Purview.BuildSdk get it mirrored into their own .agents/ folder on the next restore or build, so AI agents
working there receive the guidance automatically.
License
MIT — see LICENSE.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. net11.0 is compatible. |
-
net10.0
- Purview.Results (>= 1.0.0.1)
-
net11.0
- Purview.Results (>= 1.0.0.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Purview.Results.AspNetCore:
| Package | Downloads |
|---|---|
|
Purview.Results.ZodSharp.AspNetCore
Maps Purview result failures that carry ZodSharp validation errors onto ASP.NET Core HttpValidationProblemDetails responses. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0.1 | 42 | 10/8/2026 |
| 1.0.0 | 47 | 10/7/2026 |
| 1.0.0-prerelease.2 | 77 | 9/30/2026 |
| 1.0.0-prerelease.1 | 60 | 9/30/2026 |