OperationResultTools.AspNetCore 1.0.47

dotnet add package OperationResultTools.AspNetCore --version 1.0.47
                    
NuGet\Install-Package OperationResultTools.AspNetCore -Version 1.0.47
                    
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="OperationResultTools.AspNetCore" Version="1.0.47" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OperationResultTools.AspNetCore" Version="1.0.47" />
                    
Directory.Packages.props
<PackageReference Include="OperationResultTools.AspNetCore" />
                    
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 OperationResultTools.AspNetCore --version 1.0.47
                    
#r "nuget: OperationResultTools.AspNetCore, 1.0.47"
                    
#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 OperationResultTools.AspNetCore@1.0.47
                    
#: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=OperationResultTools.AspNetCore&version=1.0.47
                    
Install as a Cake Addin
#tool nuget:?package=OperationResultTools.AspNetCore&version=1.0.47
                    
Install as a Cake Tool

OperationResults

Lint Code Base CodeQL License: MIT

A set of lightweight libraries to totally decouple operation results and actual application responses.

Core library

NuGet Nuget

Installation

The library is available on NuGet. Just search for OperationResultTools in the Package Manager GUI or run the following command in the .NET CLI:

dotnet add package OperationResultTools

Usage example

The core library can be used in your business layer to return successful or failed results without referencing ASP.NET Core types.

public class PeopleService(ApplicationDbContext dbContext, IImageService imageService) : IPeopleService
{
    public async Task<Result<Person>> GetAsync(Guid id)
    {
        var dbPerson = await dbContext.People.AsNoTracking().FirstOrDefaultAsync(p => p.Id == id);
        if (dbPerson is null)
        {
            return Result.Fail(FailureReasons.ItemNotFound);
        }

        var person = new Person
        {
            Id = dbPerson.Id,
            FirstName = dbPerson.FirstName,
            LastName = dbPerson.LastName,
            Email = dbPerson.Email,
            City = dbPerson.City
        };

        return person;
    }

    public async Task<Result<Person>> SaveAsync(Person person)
    {
        var samePersonExists = await dbContext.People
            .AnyAsync(p => p.FirstName == person.FirstName && p.LastName == person.LastName
                && p.CreateDate.AddMinutes(1) > DateTime.UtcNow);

        if (samePersonExists)
        {
            var validationErrors = new List<ValidationError>
            {
                new("FirstName", "First name already in use"),
                new("LastName", "Last name already in use")
            };

            return Result.Fail(
                FailureReasons.ClientError,
                "Unable to create a person with same first name and last name within 1 minute",
                validationErrors);
        }

        return person;
    }
}

Result<T> can be returned directly from a value when the operation succeeds, and Result.Fail can be used when the operation needs to carry a failure reason, a message, and optional validation errors. This keeps application services focused on business outcomes instead of HTTP response types.

You can also inspect a result without throwing exceptions:

var result = await peopleService.GetAsync(id);

if (result.TryGetContent(out var person))
{
    Console.WriteLine($"Loaded {person.FirstName} {person.LastName}");
}
else if (result.HasError)
{
    Console.WriteLine(result.ErrorMessage);
}

The same pattern also works for file results or other payloads:

public class ImageService : IImageService
{
    public async Task<Result<ByteArrayFileContent>> GetImageAsync()
    {
        if (!File.Exists(@"D:\Taggia.jpg"))
        {
            return Result.Fail(FailureReasons.ItemNotFound);
        }

        var content = await File.ReadAllBytesAsync(@"D:\Taggia.jpg");
        return new ByteArrayFileContent(content, "image/jpg");
    }
}

ASP.NET Core integration library (Controller-based projects)

NuGet Nuget

Note: This is the library to use if you're working with Controller.

This library provides HttpContext extension methods to automatically map Operation Results (that may come, for sample, from a business layer) to HTTP responses, along with the appropriate status codes.

A full sample is available in the Sample folder. Search for the registration in the Program.cs file and the usage in Controllers folder.

Installation

The library is available on NuGet. Just search for OperationResultTools.AspNetCore in the Package Manager GUI or run the following command in the .NET CLI:

dotnet add package OperationResultTools.AspNetCore

Usage example

Once the package is registered, your controllers can delegate the HTTP response generation to HttpContext.CreateResponse.

[ApiController]
[Route("api/[controller]")]
[Produces(MediaTypeNames.Application.Json)]
public class PeopleController(IPeopleService peopleService) : ControllerBase
{
    [HttpGet("{id:guid}", Name = nameof(GetPerson))]
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Person))]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetPerson(Guid id)
    {
        var result = await peopleService.GetAsync(id);
        return HttpContext.CreateResponse(result);
    }

    [HttpPost]
    [ProducesResponseType<Person>(StatusCodes.Status201Created)]
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Save(Person person)
    {
        var result = await peopleService.SaveAsync(person);
        return HttpContext.CreateResponse(result, nameof(GetPerson), new { id = result.Content?.Id });
    }

    [HttpDelete("{id:guid}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Delete(Guid id)
    {
        var result = await peopleService.DeleteAsync(id);
        return HttpContext.CreateResponse(result);
    }
}

This approach also works for binary responses:

[ApiController]
[Route("api/[controller]")]
public class ImageController(IImageService imageService) : ControllerBase
{
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetImage()
        => HttpContext.CreateResponse(await imageService.GetImageAsync());
}

Configuration

Register the controller adapter once at startup. You can keep the default mapping between FailureReasons and HTTP status codes, add custom failure reasons, or decide to use failure reasons directly as HTTP status codes:

builder.Services.AddOperationResult(options =>
{
    options.ErrorResponseFormat = ErrorResponseFormat.Default;
    options.StatusCodesMapping.Add(CustomFailureReasons.NotAvailable, StatusCodes.Status501NotImplemented);

    // If your application uses HTTP status codes directly as failure reasons:
    // options.MapStatusCodes = false;

    // If a failure reason is not mapped, use a fallback status code.
    options.UnmappedFailureReasonBehavior = UnmappedFailureReasonBehavior.UseDefaultStatusCode;
    options.UnmappedFailureReasonStatusCode = StatusCodes.Status501NotImplemented;
});

Controller-based projects can also opt in to operation-result-style automatic model-state validation responses:

builder.Services.AddOperationResult(
    options => options.ErrorResponseFormat = ErrorResponseFormat.List,
    validationErrorDefaultMessage: "The submitted data is invalid");

When this option is used, invalid model-state responses are emitted as application/problem+json and reuse the configured validation error format.

ASP.NET Core integration library (Minimal API projects)

NuGet Nuget

Note: This is the library to use if you're working with Minimal APIs.

This library provides HttpContext extension methods to automatically map Operation Results (that may come, for sample, from a business layer) to HTTP responses, along with the appropriate status codes.

A full sample is available in the Samples folder. Search for the registration and the usage in Program.cs file.

Installation

The library is available on NuGet. Just search for OperationResultTools.AspNetCore.Http in the Package Manager GUI or run the following command in the .NET CLI:

dotnet add package OperationResultTools.AspNetCore.Http

Usage example

Register the library in your Minimal API application and customize the mapping between operation failures and HTTP status codes:

builder.Services.AddOperationResult(options =>
{
    options.ErrorResponseFormat = ErrorResponseFormat.Default;
    options.StatusCodesMapping.Add(CustomFailureReasons.NotAvailable, StatusCodes.Status501NotImplemented);

    // If you want to use failure reasons directly as HTTP status codes:
    // options.MapStatusCodes = false;

    // If a failure reason is not mapped, use a fallback status code.
    options.UnmappedFailureReasonBehavior = UnmappedFailureReasonBehavior.UseDefaultStatusCode;
    options.UnmappedFailureReasonStatusCode = StatusCodes.Status501NotImplemented;
});

Then convert results returned by your services into HTTP responses inside your endpoints:

var peopleApi = app.MapGroup("api/people");

peopleApi.MapGet("/", async (IPeopleService peopleService, HttpContext httpContext) =>
{
    var result = await peopleService.GetAsync();
    return httpContext.CreateResponse(result);
})
.Produces<IEnumerable<Person>>();

peopleApi.MapGet("{id:guid}", async (Guid id, IPeopleService peopleService, HttpContext httpContext) =>
{
    var result = await peopleService.GetAsync(id);
    return httpContext.CreateResponse(result);
})
.Produces<Person>()
.Produces(StatusCodes.Status404NotFound)
.WithName("GetPerson");

peopleApi.MapPost("/", async (Person person, IPeopleService peopleService, HttpContext httpContext) =>
{
    var result = await peopleService.SaveAsync(person);
    return httpContext.CreateResponse(result, "GetPerson", new { id = result.Content?.Id });
})
.Produces<Person>(StatusCodes.Status201Created)
.ProducesProblem(StatusCodes.Status400BadRequest, MediaTypeNames.Application.Json);

Minimal API endpoints can return non-generic results as well. Successful non-generic results are translated to 204 No Content by default, while failures are translated to problem details using the configured status-code mapping:

peopleApi.MapDelete("{id:guid}", async (Guid id, IPeopleService peopleService, HttpContext httpContext) =>
{
    var result = await peopleService.DeleteAsync(id);
    return httpContext.CreateResponse(result);
})
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status403Forbidden)
.Produces(StatusCodes.Status404NotFound);

If a service returns content together with a failure, the adapter returns that content with the mapped failure status code instead of creating a problem details response. This is useful for partial results or domain-specific error payloads.

The same mechanism can also return files:

app.MapGet("api/image", async (IImageService imageService, HttpContext httpContext)
    => httpContext.CreateResponse(await imageService.GetImageAsync())
)
.Produces<FileContentResult>(StatusCodes.Status200OK, contentType: MediaTypeNames.Application.Octet)
.Produces(StatusCodes.Status404NotFound);

In this way, the business layer remains completely decoupled from ASP.NET Core, while the web layer consistently translates Result and Result<T> instances into HTTP responses.

Contribute

The project is constantly evolving. Contributions are welcome. Feel free to file issues and pull requests on the repo and we'll address them as we can.

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 is compatible.  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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.47 29 6/18/2026
1.0.46 359 3/11/2026
1.0.44 396 11/12/2025
1.0.43 514 9/18/2025
1.0.41 1,036 1/15/2025
1.0.40 389 10/21/2024
1.0.38 261 9/23/2024
1.0.37 330 6/17/2024
1.0.35 572 2/23/2024
1.0.30 449 12/13/2023
1.0.27 839 4/13/2023
1.0.25 495 3/6/2023
1.0.24 596 1/9/2023
1.0.22 613 12/20/2022
1.0.20 573 12/2/2022
1.0.17 542 11/16/2022
Loading failed