NTResult 1.2.0
dotnet add package NTResult --version 1.2.0
NuGet\Install-Package NTResult -Version 1.2.0
<PackageReference Include="NTResult" Version="1.2.0" />
<PackageVersion Include="NTResult" Version="1.2.0" />
<PackageReference Include="NTResult" />
paket add NTResult --version 1.2.0
#r "nuget: NTResult, 1.2.0"
#:package NTResult@1.2.0
#addin nuget:?package=NTResult&version=1.2.0
#tool nuget:?package=NTResult&version=1.2.0
<p align="center"> <img src="Logo.png" alt="NTResult logo" width="128" height="128" /> </p>
NTResult
NTResult is a small set of .NET libraries for representing an operation as either a successful value or an error. It keeps expected failures explicit without requiring exceptions for normal control flow, then carries that result cleanly through ASP.NET Core endpoints and Refit clients.
The typical flow is:
application/service -> INTResult<T> -> HTTP response -> Refit IApiResponse<T> -> INTResult<T>
Packages
| Package | Use it for | Targets |
|---|---|---|
NTResult |
Core INTResult, INTResult<T>, Expected, Optional, and NTFileDownload types |
.NET 8 |
NTResult.AspNetCore.Http |
Minimal API and controller integration through HttpNTResult, ToIResult, and NTResultControllerBase |
.NET 8 and later, including .NET 11 Preview |
NTResult.Refit |
Converting Refit IApiResponse values into client-side INTResult values |
.NET 8 and later, including .NET 11 Preview |
Install only the packages needed by each project. The integration packages reference the core package transitively.
# Domain, application, or shared project
dotnet add package NTResult
# ASP.NET Core API project
dotnet add package NTResult.AspNetCore.Http
# Refit client project
dotnet add package NTResult.Refit
Core package: NTResult
Create successful or failed results with the NTResult factory. A typed result exposes its value only on success and retains the original exception on failure.
using NTResult;
using NTResult.Exceptions;
public sealed record Order(int Id, string Description);
public static INTResult<Order> FindOrder(int id)
{
Order? order = id == 42 ? new Order(42, "Replacement keyboard") : null;
return order is null
? NTResult.Failure<Order>(new NotFoundException(typeof(Order), id))
: NTResult.Success(order);
}
INTResult<Order> result = FindOrder(42);
if (result.TryGetValue(out var order))
{
Console.WriteLine(order.Description);
}
else
{
Console.WriteLine(result.ErrorMessage);
}
Useful result members include:
IsSuccessfulandHasFailedfor branching.TryGetValue,GetValueOrDefault, andValueOrfor non-throwing access.ValueOrThrowandThrowOnFailurewhen an exception boundary is appropriate.OnSuccess,OnFailure, andFinallyfor small continuations.- Async equivalents for
Task<INTResult>andTask<INTResult<T>>inNTResult.Ext.
The core package also includes Expected<TValue, TError>, Optional<T>, and NTFileDownload for code that needs those more specialized representations.
Server package: NTResult.AspNetCore.Http
HttpNTResult combines an INTResult with an ASP.NET Core IResult. It can be returned directly from a minimal API and supports common HTTP outcomes such as OK, Created, Accepted, Bad Request, Not Found, Unauthorized, Forbidden, redirects, file downloads, and Internal Server Error.
using Microsoft.AspNetCore.Mvc;
using NTResult.AspNetCore.Http;
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/orders/{id:int}", (int id) =>
{
var order = id == 42 ? new Order(42, "Replacement keyboard") : null;
return order is null
? HttpNTResult<Order>.NotFound($"Order {id} was not found")
: HttpNTResult<Order>.Ok(order);
});
app.MapPost("/orders", (Order order) =>
HttpNTResult<Order>.Created($"/orders/{order.Id}", order));
app.MapGet("/health/dependency", () =>
HttpNTResult.InternalServerError(new ProblemDetails
{
Title = "Dependency unavailable",
Detail = "The inventory service could not be reached."
}));
app.Run();
If application services already return core INTResult values, convert them at the HTTP boundary:
using NTResult.AspNetCore.Http.Ext;
app.MapGet("/orders/{id:int}", async (int id, OrderService service) =>
(await service.FindOrderAsync(id)).ToIResult());
ToIResult maps NotFoundException to 404, UnauthorizedAccessException to 401, ForbiddenException to 403, and other failures to 400. Cancellation exceptions propagate to the ASP.NET Core host instead of being assigned an HTTP status without request context. Use HttpNTResult.RequestTimeout() for an incomplete request (408), HttpNTResult.GatewayTimeout() for an upstream timeout (504), or CustomError with an explicit IResult when the endpoint needs another specific response.
NTResultControllerBase provides the same result factories as protected helpers for controller-based APIs.
Client package: NTResult.Refit
Keep Refit methods typed as IApiResponse<T>, then convert the response into the same result abstraction used by the rest of the application.
using NTResult;
using NTResult.Refit.Ext;
using Refit;
public interface IOrdersApi
{
[Get("/orders/{id}")]
Task<IApiResponse<Order>> GetOrderAsync(int id);
}
IOrdersApi api = RestService.For<IOrdersApi>("https://api.example.com");
INTResult<Order> result = await api.GetOrderAsync(42).ToNTResultAsync();
if (result.IsSuccessful)
{
Console.WriteLine(result.Value.Description);
}
else
{
Console.WriteLine(result.ErrorMessage);
}
After applying the special 408, 499, and 504 mappings described below, the converter selects the most useful available error in this order:
ProblemDetails.DetailProblemDetails.Title- Response body text
- HTTP reason phrase
- A status-code fallback message
HTTP 408 and 504 responses become failures containing a TimeoutException. Nonstandard HTTP 499 responses are tolerated as failures containing an OperationCanceledException representing the server's compatibility signal, not cancellation of the caller's token. Created and redirect responses can return their Location header when the requested result type is string or Uri.
File downloads
A Refit stream response converts to INTResult<NTFileDownload>. Dispose the returned download after consuming it; that releases both the stream and its owning HTTP response.
public interface IReportsApi
{
[Get("/reports/{id}")]
Task<IApiResponse<Stream>> DownloadAsync(int id);
}
INTResult<NTFileDownload> result = await reportsApi.DownloadAsync(7).ToNTResultAsync();
using NTFileDownload download = result.ValueOrThrow();
await download.Contents.Stream!.CopyToAsync(destinationStream);
For other disposable Refit content types, keep and dispose the original IApiResponse<T> after the returned content is no longer needed. Non-disposable response content is disposed automatically during conversion.
End-to-end example
Using NTResult.AspNetCore.Http on the server and NTResult.Refit on the client preserves both sides of the operation:
- A successful
HttpNTResult<T>becomes a successful clientINTResult<T>containing the deserialized value. - A string error body becomes the client result's
ErrorMessage. - RFC Problem Details responses preserve their specific
detail, withtitleas a fallback. - The Refit response is disposed after conversion unless its disposable content must remain alive.
This lets callers work with one INTResult<T> contract instead of separately handling status codes, response bodies, deserialization errors, and thrown HTTP exceptions.
Legacy TnTResult names
Obsolete TnTResult, HttpTnTResult, and related extension aliases remain available for source compatibility. New code should use the NTResult names shown above.
Build and test
Run these commands from the repository root:
dotnet restore NTResult.slnx
dotnet build NTResult.slnx --configuration Release
dotnet test --project .\NTResult.Tests\NTResult.Tests.csproj --configuration Release
License
NTResult is licensed under the MIT License.
| 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 was computed. 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 was computed. 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. |
-
net8.0
- No dependencies.
NuGet packages (2)
Showing the top 2 NuGet packages that depend on NTResult:
| Package | Downloads |
|---|---|
|
NTResult.Refit
Refit integration for NTResult, enabling functional result types in Refit-based HTTP clients for .NET. |
|
|
NTResult.AspNetCore.Http
ASP.NET Core integration for NTResult, providing seamless result handling and controller extensions for web APIs. |
GitHub repositories
This package is not used by any popular GitHub repositories.