v2 focuses on three things: multi-target frameworks, pluggable strategies (status-code mapper + ProblemDetails factory), and Minimal-API parity. All existing v1 extension methods remain source-compatible; bump the new version can be done without code changes and adopt the new surfaces incrementally.
| v1.x | v2.x |
|---|---|
netstandard2.1 only |
netstandard2.1, net6.0, net7.0, net8.0, net9.0 |
The netstandard2.1, still depends on Microsoft.AspNetCore.Mvc 2.1.3 (unchanged). The net6.0+ TFMs use <FrameworkReference Include="Microsoft.AspNetCore.App" /> — do not add explicit Microsoft.AspNetCore.* package references on those target framerowks; remove them from your .csproj if present.
Already renamed in v1.2.0.8001, but call out for anyone jumping from earlier v1 releases.
- result.AsToProblemDetails(HttpStatusCode.BadRequest);
+ result.AsProblemDetails(HttpStatusCode.BadRequest);v1.x — hand-crafted in every call:
return result.IsSuccess
? result.AsActionResult(HttpStatusCode.OK)
: result.AsProblemDetails(HttpStatusCode.BadRequest);v2.x — register once, then the extension methods resolve the status automatically:
// Startup / Program.cs
services.AddWebResultMessageMapper(); // default mapper
// or
services.AddWebResultMessageMapper<MyStatusCodeMapper>(); // custom// Controller
return result.AsActionResult(); // status from mapper
return result.AsProblemDetails(); // status from mapper
return result.ToHttpResult(); // Minimal API (net6+)Per-call status codes still win if you pass them explicitly.
Backwards compatibility: the v1-style overloads (
AsActionResult(result, HttpStatusCode), etc.) are unchanged. Adoption is opt-in.
v1.x — override title/type/etc. per call via method arguments.
v2.x — register a factory once:
public sealed class MyProblemFactory : DefaultProblemDetailsResultFactory
{
protected override string ResolveType(ResultProblemDetailsContext ctx)
=> $"https://errors.my-api.example/{ctx.StatusCode:D}";
protected override void ApplyExtensions(
ResultMessageProblemDetails problem, ResultProblemDetailsContext ctx)
{
base.ApplyExtensions(problem, ctx); // keeps ResultMessages + traceId
problem.Extensions["service"] = "orders-api";
}
}services.AddProblemDetailsResultFactory<MyProblemFactory>();Per-call arguments to AsProblemDetails(...) / ToHttpResult(...) still override the factory's defaults.
v1.x — manual try/catch (or a bespoke filter) to translate failures into ProblemDetails.
v2.x — two opt-in surfaces ship out of the box:
// MVC-only filter
services.AddWebResultExceptionFilter();
// Whole-pipeline middleware (recommended; also catches middleware-level exceptions)
services.AddResultExceptionMiddleware(o =>
{
o.IncludeUnhandledExceptions = true; // catch non-WebResultException too
o.DefaultStatusCode = 500;
});
app.UseResultExceptionMiddleware(); // before UseRouting()Throw WebResultException(result, status) from any layer; the response body matches AsProblemDetails 1:1.
New in v2. Wire format identical to MVC.
app.MapGet("/orders/{id}", (int id, HttpContext http, IOrderService svc)
=> svc.Get(id).ToHttpResult(httpContext: http));
app.MapPost("/orders", (OrderDto dto, HttpContext http, IOrderService svc)
=> ResultMessageHttpResults.From(svc.Create(dto), httpContext: http));If you had already emitted IResult bodies manually via Results.Json(result, status) in v1, replace those sites with .ToHttpResult(http) so the ProblemDetails shape stays consistent.
New in v2, zero configuration.
- Filter path, middleware path, and Minimal-API path all forward the ambient
HttpContexttoDefaultProblemDetailsResultFactory, which setsproblem.Extensions["traceId"] = HttpContext.TraceIdentifier. - If your code passes a
traceIdviaadditionalInformation, that value is preserved (no overwrite). - To opt out, register a factory that overrides
ApplyExtensionsand skips thetraceIdinjection.
If you previously carried correlation on your own, either:
- Remove your bespoke injection and let the library handle it, or
- Pre-populate
additionalInformation["traceId"]yourself — the library will not overwrite it.
No breaking namespace or public-type renames. Newly added public types:
| Namespace | Type |
|---|---|
RzR.ResultMessage.Web.Abstractions |
IResultStatusCodeMapper |
RzR.ResultMessage.Web.Abstractions |
IProblemDetailsResultFactory |
RzR.ResultMessage.Web.Mappers |
DefaultResultStatusCodeMapper, ResultStatusCodeMapper (ambient) |
RzR.ResultMessage.Web.Factories |
DefaultProblemDetailsResultFactory, ProblemDetailsResultFactory (ambient) |
RzR.ResultMessage.Web.Filters |
WebResultExceptionFilter |
RzR.ResultMessage.Web.Middlewares |
WebResultExceptionMiddleware, WebResultExceptionMiddlewareOptions |
RzR.ResultMessage.Web.Models |
ResultProblemDetailsContext |
RzR.ResultMessage.Web.Extensions.MinimalApi |
ResultToHttpResult, ResultMessageHttpResults (net6.0+) |
RzR.ResultMessage.Web.WebDependencyInjection |
ServiceCollectionExtensions, ApplicationBuilderExtensions |
If your v1 project explicitly referenced individual Microsoft.AspNetCore.* packages to work around netstandard2.1, remove those references when targeting net6.0+; they are now covered by the shared framework.
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="..." />
- <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="..." />- Bump package to v2.
- Remove stray
Microsoft.AspNetCore.*package refs on net6+ TFMs. services.AddWebResultMessageMapper();— or register a custom mapper.- (Optional)
services.AddProblemDetailsResultFactory<MyProblemFactory>();for global ProblemDetails shape. - (Optional)
services.AddWebResultExceptionFilter();and/orservices.AddResultExceptionMiddleware(...); app.UseResultExceptionMiddleware();. - (Optional) Replace Minimal-API hand-rolled
Results.Json(...)with.ToHttpResult(http). - Verify
traceIdappears in ProblemDetails responses (or opt out in your factory).