Purview.Results.AspNetCore
1.0.0-prerelease.2
dotnet add package Purview.Results.AspNetCore --version 1.0.0-prerelease.2
NuGet\Install-Package Purview.Results.AspNetCore -Version 1.0.0-prerelease.2
<PackageReference Include="Purview.Results.AspNetCore" Version="1.0.0-prerelease.2" />
<PackageVersion Include="Purview.Results.AspNetCore" Version="1.0.0-prerelease.2" />
<PackageReference Include="Purview.Results.AspNetCore" />
paket add Purview.Results.AspNetCore --version 1.0.0-prerelease.2
#r "nuget: Purview.Results.AspNetCore, 1.0.0-prerelease.2"
#:package Purview.Results.AspNetCore@1.0.0-prerelease.2
#addin nuget:?package=Purview.Results.AspNetCore&version=1.0.0-prerelease.2&prerelease
#tool nuget:?package=Purview.Results.AspNetCore&version=1.0.0-prerelease.2&prerelease
Purview.Results.AspNetCore
Maps Purview.Results values onto ASP.NET Core responses, so
an endpoint can return Result<TValue, 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.
Failure — the error is resolved to its case value (the active case of a union error, or the error itself for a non-union error), then mapped in this order:
- a mapping registered for the case type —
Map<TenantNotFound>(...) - 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 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/examples/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, and shows the response an endpoint that returns default
receives.
dotnet run --project src/examples/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 | net11.0 is compatible. |
-
net11.0
- Purview.Results (>= 1.0.0-prerelease.2)
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-prerelease.2 | 48 | 9/30/2026 |
| 1.0.0-prerelease.1 | 41 | 9/30/2026 |