NTResult 1.2.0

dotnet add package NTResult --version 1.2.0
                    
NuGet\Install-Package NTResult -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NTResult" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NTResult" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="NTResult" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NTResult --version 1.2.0
                    
#r "nuget: NTResult, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NTResult@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NTResult&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=NTResult&version=1.2.0
                    
Install as a Cake Tool

<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:

  • IsSuccessful and HasFailed for branching.
  • TryGetValue, GetValueOrDefault, and ValueOr for non-throwing access.
  • ValueOrThrow and ThrowOnFailure when an exception boundary is appropriate.
  • OnSuccess, OnFailure, and Finally for small continuations.
  • Async equivalents for Task<INTResult> and Task<INTResult<T>> in NTResult.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:

  1. ProblemDetails.Detail
  2. ProblemDetails.Title
  3. Response body text
  4. HTTP reason phrase
  5. 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 client INTResult<T> containing the deserialized value.
  • A string error body becomes the client result's ErrorMessage.
  • RFC Problem Details responses preserve their specific detail, with title as 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • 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.

Version Downloads Last Updated
1.2.0 0 7/28/2026
1.1.3 267 7/13/2026
1.1.2 128 7/13/2026
1.1.1 146 7/8/2026
1.1.0 887 6/8/2026
1.0.0 142 5/29/2026