Phoenix.Result 2.0.1

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

๐Ÿš€ Phoenix.Result

NuGet Version NuGet Downloads License: MIT .NET Supported ASP.NET Core


๐ŸŒ Read in Arabic / ุงู‚ุฑุฃ ุจุงู„ุนุฑุจูŠุฉ


Phoenix.Result is a lightweight and practical Result Pattern library for .NET 10 and ASP.NET Core 10 applications.

It provides a clean and explicit way to represent successful operations, business failures, validation errors, resource errors, and API responses without using exceptions for expected business logic.

Phoenix.Result also provides ASP.NET Core integration for:

  • Global exception handling
  • ProblemDetails responses
  • Model validation responses
  • HTTP status code mapping
  • Reusable API base controller
  • Standardized API error handling

The goal is simple:

Write your Result logic once, reuse it across every project, and avoid repeating the same API infrastructure.


โšก Key Features

  • ๐ŸŽฏ Explicit Results โ€” Handle expected business outcomes without throwing exceptions.
  • ๐Ÿ“ฆ Generic & Non-Generic Results โ€” Supports both Result and Result<TValue>.
  • ๐Ÿท๏ธ Strongly Typed Errors โ€” Categorize errors using the ErrorType enum.
  • ๐Ÿ”„ Implicit Conversions โ€” Return DTOs, Error, or List<Error> directly from service methods.
  • ๐Ÿ“‘ Built-in Pagination โ€” Native PaginatedResult<TEntity> support.
  • ๐Ÿšจ Global Exception Handling โ€” Centralized ASP.NET Core exception middleware.
  • ๐ŸŒ ProblemDetails Integration โ€” Standard HTTP error responses using ASP.NET Core ProblemDetails.
  • โœ… Automatic Model Validation Handling โ€” Centralized validation response configuration.
  • ๐Ÿ”ข HTTP Status Mapping โ€” Automatically maps ErrorType to the appropriate HTTP status code.
  • ๐ŸŽฎ Reusable API Base Controller โ€” Handle Result responses without repeating controller logic.
  • ๐Ÿงฉ ASP.NET Core Extensions โ€” Simple AddPhoenixResult() and UsePhoenixResult() registration.
  • ๐Ÿš€ .NET 10 โ€” Built specifically for modern .NET 10 applications.
  • ๐Ÿงน Less Boilerplate โ€” Removes repetitive result, validation, exception, and controller infrastructure from individual projects.

๐Ÿ“‘ Table of Contents


๐Ÿ’ก Overview

In traditional .NET applications, expected business failures are often represented using exceptions:

if (user is null)
    throw new Exception("User not found.");

This approach makes expected business outcomes depend on exception handling.

With Phoenix.Result, expected failures are represented explicitly:

if (user is null)
    return Error.NotFound("User.NotFound", "The requested user was not found.");

The caller can then inspect the result:

if (result.IsFailure)
    return HandleResult(result);

This makes service behavior explicit, predictable, and easier to maintain.

Exceptions should still be used for unexpected system failures. Phoenix.Result does not attempt to replace exceptions completely.


๐Ÿš€ Installation

Install Phoenix.Result from NuGet.

.NET CLI

dotnet add package Phoenix.Result

Package Manager Console

Install-Package Phoenix.Result

โšก Quick Start

After installing the package, register Phoenix.Result in Program.cs:

builder.Services.AddPhoenixResult();

var app = builder.Build();

app.UsePhoenixResult();

app.MapControllers();

app.Run();

That's all the infrastructure required.

You no longer need to create these classes inside every API project:

  • ExceptionHandlerMiddleware
  • ApiResponseFactory
  • ApiBaseController
  • Repeated exception-to-HTTP mapping
  • Repeated validation response configuration

๐Ÿงฉ Core Architecture

Phoenix.Result contains both the Result Pattern core and ASP.NET Core integration.

Phoenix.Result
โ”‚
โ”œโ”€โ”€ Common
โ”‚   โ””โ”€โ”€ Results
โ”‚       โ”œโ”€โ”€ Result.cs
โ”‚       โ”œโ”€โ”€ Result<TValue>.cs
โ”‚       โ”œโ”€โ”€ Error.cs
โ”‚       โ”œโ”€โ”€ PaginatedResult<TEntity>.cs
โ”‚       โ”‚
โ”‚       โ””โ”€โ”€ Enums
โ”‚           โ””โ”€โ”€ ErrorType.cs
โ”‚
โ”œโ”€โ”€ Exceptions
โ”‚   โ””โ”€โ”€ NotFoundException.cs
โ”‚
โ”œโ”€โ”€ Middleware
โ”‚   โ””โ”€โ”€ ExceptionHandlingMiddleware.cs
โ”‚
โ”œโ”€โ”€ Extensions
โ”‚   โ”œโ”€โ”€ PhoenixResultExtensions.cs
โ”‚   โ””โ”€โ”€ PhoenixResultServiceCollectionExtensions.cs
โ”‚
โ””โ”€โ”€ Controllers
    โ””โ”€โ”€ ApiBaseController.cs

๐Ÿ”‘ Core Concepts

Result

Result represents an operation that does not return a value.

Successful operation

return Result.Ok();

Failed operation

return Result.Fail(
    Error.NotFound(
        "Employee.NotFound",
        "The requested employee was not found."
    )
);

Checking the result

if (result.IsSuccess)
{
    // Operation succeeded
}

if (result.IsFailure)
{
    // Operation failed
}

Available properties

Property Type Description
IsSuccess bool Indicates that the operation succeeded.
IsFailure bool Indicates that the operation failed.
Errors IReadOnlyList<Error> Contains the operation errors.

๐Ÿ“ฆ Result<TValue>

Result<TValue> represents an operation that returns a value.

Success

Result<UserDto> result = Result<UserDto>.Ok(userDto);

Failure

Result<UserDto> result =
    Result<UserDto>.Fail(
        Error.NotFound(
            "User.NotFound",
            "The requested user was not found."
        )
    );

Accessing Data

if (result.IsSuccess)
{
    var user = result.Data;
}

โš ๏ธ Accessing Data when the result has failed throws an InvalidOperationException.

Always check IsSuccess before accessing Data.


๐Ÿท๏ธ Error

Error represents a structured business or application error.

Each error contains:

Code
Description
Type

Example:

var error = Error.NotFound(
    "Employee.NotFound",
    "The requested employee was not found."
);

Error Factory Methods

Phoenix.Result provides predefined factory methods for common error scenarios.

Method Default Code Default Description Type
Error.Failure() General.Failure A general error occurred. Failure
Error.Validation() General.Validation A validation error occurred. Validation
Error.NotFound() General.NotFound The requested resource was not found. NotFound
Error.Unauthorized() General.Unauthorized The user is not authorized to access this resource. Unauthorized
Error.Forbidden() General.Forbidden You do not have permission to access this resource. Forbidden
Error.Conflict() General.Conflict A conflict occurred with the current data. Conflict
Error.InvalidCredentials() General.InvalidCredentials The provided credentials are invalid. InvalidCredentials
Error.AlreadyExists() General.AlreadyExists The requested resource already exists. AlreadyExists
Error.NotAllowed() General.NotAllowed The requested operation is not allowed. NotAllowed
Error.InvalidState() General.InvalidState The current state does not allow this operation. InvalidState

Custom Error

You can provide your own code and description:

return Error.NotFound(
    "Employee.NotFound",
    "The requested employee does not exist."
);

Or use the default values:

return Error.NotFound();

๐Ÿ”ข ErrorType

ErrorType categorizes the error and allows Phoenix.Result to map it to the appropriate HTTP status code.

public enum ErrorType
{
    Failure = 0,
    Validation = 1,
    NotFound = 2,
    Unauthorized = 3,
    Forbidden = 4,
    Conflict = 5,
    InvalidCredentials = 6,
    AlreadyExists = 7,
    NotAllowed = 8,
    InvalidState = 9
}

Error Categories

ErrorType Purpose
Failure General unexpected failure
Validation Invalid input or business validation
NotFound Requested resource does not exist
Unauthorized Authentication is required or failed
Forbidden User is authenticated but not allowed
Conflict Resource/data conflict
InvalidCredentials Invalid login credentials
AlreadyExists Resource already exists
NotAllowed Operation is not allowed
InvalidState Current resource state does not allow the operation

๐Ÿ“‘ PaginatedResult<TEntity>

PaginatedResult<TEntity> provides a standard structure for paginated data.

var paginatedResult = new PaginatedResult<UserDto>(
    pageIndex: 1,
    pageSize: 10,
    totalCount: 45,
    data: users
);

It provides:

Property Type Description
PageIndex int Current page number
PageSize int Number of items per page
TotalCount int Total number of records
TotalPages int Automatically calculated total pages
HasNext bool Indicates whether another page exists
HasPrevious bool Indicates whether a previous page exists
Data IEnumerable<TEntity> Current page data

Pagination Example

public async Task<Result<PaginatedResult<UserDto>>> GetUsersPagedAsync(
    int pageIndex,
    int pageSize)
{
    if (pageIndex < 1 || pageSize < 1)
        return Error.Validation(
            "Pagination.InvalidParameters",
            "Page index and page size must be greater than zero."
        );

    var totalCount = await _context.Users.CountAsync();

    var users = await _context.Users
        .Skip((pageIndex - 1) * pageSize)
        .Take(pageSize)
        .Select(u => new UserDto
        {
            Id = u.Id,
            Name = u.Name
        })
        .ToListAsync();

    var result = new PaginatedResult<UserDto>(
        pageIndex,
        pageSize,
        totalCount,
        users
    );

    return result;
}

Because of implicit conversion, the PaginatedResult<TEntity> can be returned directly as a Result<PaginatedResult<TEntity>>.


๐Ÿ”„ Implicit Conversions

Result<TValue> supports implicit conversions to reduce unnecessary boilerplate.

Returning a DTO

public Result<UserDto> GetUser()
{
    var user = new UserDto
    {
        Id = 1,
        Name = "Nouri"
    };

    return user;
}

The DTO is automatically converted to:

Result<UserDto>.Ok(user);

Returning an Error

public Result<UserDto> GetUser(int id)
{
    var user = _users.FirstOrDefault(x => x.Id == id);

    if (user is null)
        return Error.NotFound(
            "User.NotFound",
            "The requested user was not found."
        );

    return user;
}

Returning Multiple Errors

return new List<Error>
{
    Error.Validation(
        "User.EmailRequired",
        "Email is required."
    ),
    Error.Validation(
        "User.PasswordRequired",
        "Password is required."
    )
};

๐Ÿšจ NotFoundException

Phoenix.Result provides a simple NotFoundException for cases where an exception is appropriate and should be automatically converted to HTTP 404.

public sealed class NotFoundException(string entityName, object id)
    : Exception($"The {entityName} with ID '{id}' was not found.") { }

Example:

throw new NotFoundException("Employee", id);

The Phoenix.Result middleware automatically converts this exception to a 404 Not Found response.

Use Error.NotFound() for expected business results and NotFoundException when an exception-based flow is appropriate.


๐ŸŒ ASP.NET Core Integration

Phoenix.Result includes ASP.NET Core integration so common API infrastructure does not need to be implemented repeatedly in every project.

Register the package:

builder.Services.AddPhoenixResult();

Enable the middleware:

app.UsePhoenixResult();

Complete example:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddPhoenixResult();

var app = builder.Build();

app.UsePhoenixResult();

app.MapControllers();

app.Run();

๐ŸŽฎ API Base Controller

Phoenix.Result provides a reusable ApiBaseController.

Instead of implementing Result handling in every project:

[ApiController]
public abstract class BaseApiController : ControllerBase
{
    // Repeated implementation...
}

Simply inherit from Phoenix.Result:

public class EmployeesController : ApiBaseController
{
}

Then use:

return HandleResult(result);

๐Ÿงฉ Controller Example

[ApiController]
[Route("api/[controller]")]
public class EmployeesController : ApiBaseController
{
    private readonly IEmployeeService _employeeService;

    public EmployeesController(IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    [HttpGet("{id:int}")]
    public IActionResult GetById(int id)
    {
        var result = _employeeService.GetById(id);

        return HandleResult(result);
    }
}

For generic results:

[HttpGet("{id:int}")]
public ActionResult<EmployeeDto> GetById(int id)
{
    var result = _employeeService.GetById(id);

    return HandleResult(result);
}

๐Ÿšฆ Result to HTTP Status Mapping

Phoenix.Result automatically maps ErrorType to HTTP status codes.

ErrorType HTTP Status
Validation 400 Bad Request
NotFound 404 Not Found
Unauthorized 401 Unauthorized
Forbidden 403 Forbidden
InvalidCredentials 401 Unauthorized
Conflict 409 Conflict
AlreadyExists 409 Conflict
NotAllowed 405 Method Not Allowed
InvalidState 409 Conflict
Failure 500 Internal Server Error

This means:

return Error.NotFound(
    "Employee.NotFound",
    "Employee was not found."
);

can automatically become an HTTP 404.


๐Ÿšจ Global Exception Handling

Phoenix.Result provides a global exception middleware:

app.UsePhoenixResult();

The middleware catches unhandled exceptions and returns standardized ProblemDetails.

For example:

throw new NotFoundException("Employee", id);

becomes:

404 Not Found

Unexpected exceptions are handled as:

500 Internal Server Error

The exception is logged internally while the API returns a safe generic error response.

This prevents sensitive exception details from being exposed to clients.


โœ… Model Validation

Phoenix.Result automatically configures ASP.NET Core model validation through:

builder.Services.AddPhoenixResult();

You do not need to create a separate:

ApiResponseFactory

inside your project.

For example:

public class CreateUserRequest
{
    [Required]
    public string Email { get; set; } = string.Empty;

    [Required]
    public string Password { get; set; } = string.Empty;
}

If the request is invalid, ASP.NET Core automatically returns a standardized validation response.

Example:

{
  "title": "Validation failed.",
  "detail": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Email": [
      "The Email field is required."
    ],
    "Password": [
      "The Password field is required."
    ]
  }
}

๐Ÿ›  Real-World Service Examples

๐Ÿ” Authentication Service

public async Task<Result<ResUserDto>> LoginAsync(LoginDto loginDto)
{
    var user = await _userManager.FindByEmailAsync(loginDto.Email);

    if (user is null)
        return Error.InvalidCredentials(
            "User.InvalidCredentials",
            "The provided credentials are invalid."
        );

    var isPasswordValid = await _userManager.CheckPasswordAsync(
        user,
        loginDto.Password
    );

    if (!isPasswordValid)
        return Error.InvalidCredentials(
            "User.InvalidCredentials",
            "The provided credentials are invalid."
        );

    var token = await CreateJwtToken(user);

    var result = new ResUserDto(
        user.Email!,
        user.UserName!,
        token
    );

    return result;
}

๐Ÿ‘ค Get Employee

public async Task<Result<EmployeeDto>> GetByIdAsync(int id)
{
    var employee = await _context.Employees
        .FirstOrDefaultAsync(x => x.Id == id);

    if (employee is null)
        return Error.NotFound(
            "Employee.NotFound",
            "The requested employee was not found."
        );

    return new EmployeeDto
    {
        Id = employee.Id,
        Name = employee.Name
    };
}

โœ๏ธ Update Entity

public async Task<Result<EmployeeDto>> UpdateAsync(
    int id,
    UpdateEmployeeDto request,
    CancellationToken cancellationToken)
{
    var employee = await _context.Employees
        .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);

    if (employee is null)
        return Error.NotFound(
            "Employee.NotFound",
            "The requested employee was not found."
        );

    employee.Name = request.Name;

    await _context.SaveChangesAsync(cancellationToken);

    return new EmployeeDto
    {
        Id = employee.Id,
        Name = employee.Name
    };
}

๐ŸŽฏ Complete API Flow

The recommended Phoenix.Result flow is:

HTTP Request
     โ”‚
     โ–ผ
Controller
     โ”‚
     โ–ผ
Service
     โ”‚
     โ”œโ”€โ”€ Success โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ Result<T>
     โ”‚
     โ””โ”€โ”€ Business Failure โ”€โ”€โ”€โ”€โ”€โ–บ Error
                                      โ”‚
                                      โ–ผ
                              ApiBaseController
                                      โ”‚
                                      โ–ผ
                              HTTP Response

For unexpected exceptions:

HTTP Request
     โ”‚
     โ–ผ
Controller
     โ”‚
     โ–ผ
Service
     โ”‚
     โ–ผ
Unexpected Exception
     โ”‚
     โ–ผ
Phoenix.Result Middleware
     โ”‚
     โ–ผ
ProblemDetails
     โ”‚
     โ–ผ
HTTP 500

๐Ÿ“ก API Response Examples

1. Successful Response

For:

return user;

the controller can return:

200 OK

with the result payload.

Example:

{
  "data": {
    "id": 1,
    "name": "Nouri",
    "email": "test@phoenix.ly"
  }
}

2. Not Found

Service:

return Error.NotFound(
    "Employee.NotFound",
    "The requested employee was not found."
);

Response:

404 Not Found

Example:

{
  "title": "Employee.NotFound",
  "detail": "The requested employee was not found.",
  "status": 404,
  "type": "NotFound"
}

3. Unauthorized

return Error.Unauthorized(
    "User.Unauthorized",
    "Authentication is required."
);

Response:

401 Unauthorized

4. Forbidden

return Error.Forbidden(
    "User.Forbidden",
    "You do not have permission to perform this operation."
);

Response:

403 Forbidden

5. Conflict

return Error.Conflict(
    "Employee.Conflict",
    "The employee data conflicts with the current state."
);

Response:

409 Conflict

6. Validation

return Error.Validation(
    "Employee.InvalidName",
    "Employee name is required."
);

Response:

400 Bad Request

Phoenix.Result is designed to work well with Clean Architecture and layered ASP.NET Core applications.

Example:

MyProject
โ”‚
โ”œโ”€โ”€ Domain
โ”‚   โ”œโ”€โ”€ Entities
โ”‚   โ””โ”€โ”€ Enums
โ”‚
โ”œโ”€โ”€ Application
โ”‚   โ”œโ”€โ”€ DTOs
โ”‚   โ”œโ”€โ”€ Interfaces
โ”‚   โ””โ”€โ”€ Services
โ”‚
โ”œโ”€โ”€ Infrastructure
โ”‚   โ”œโ”€โ”€ Data
โ”‚   โ””โ”€โ”€ Repositories
โ”‚
โ””โ”€โ”€ API
    โ”œโ”€โ”€ Controllers
    โ””โ”€โ”€ Program.cs

Install Phoenix.Result into the project that needs its API integration.

In a typical ASP.NET Core API:

builder.Services.AddPhoenixResult();

var app = builder.Build();

app.UsePhoenixResult();

๐Ÿงน What Phoenix.Result Removes From Your Project

Without Phoenix.Result, many projects end up repeating infrastructure such as:

ApiBaseController
ExceptionHandlerMiddleware
ApiResponseFactory
Error โ†’ HTTP mapping
Validation response configuration
ProblemDetails handling

With Phoenix.Result:

Phoenix.Result
      โ”‚
      โ”œโ”€โ”€ Result Pattern
      โ”œโ”€โ”€ Error Handling
      โ”œโ”€โ”€ Pagination
      โ”œโ”€โ”€ Exception Middleware
      โ”œโ”€โ”€ Validation Handling
      โ”œโ”€โ”€ ProblemDetails
      โ”œโ”€โ”€ HTTP Mapping
      โ””โ”€โ”€ API Base Controller

Your project only consumes the package.


๐Ÿง  Best Practices

1. Use Result for expected business failures

Good:

if (employee is null)
    return Error.NotFound("Employee.NotFound");

Avoid:

if (employee is null)
    throw new Exception("Employee not found.");

when the situation is an expected business outcome.


2. Use exceptions for unexpected failures

Exceptions are still appropriate for unexpected system failures such as:

  • Infrastructure failures
  • Database failures
  • Unexpected runtime errors
  • External service failures

Phoenix.Result's middleware provides centralized handling for those exceptions.


3. Use meaningful error codes

Prefer:

Error.NotFound(
    "Employee.NotFound",
    "The requested employee was not found."
);

instead of:

Error.NotFound(
    "Error",
    "Something went wrong."
);

A structured error code makes errors easier to identify, log, test, and consume.


4. Keep business logic inside services

Controllers should remain simple:

var result = await _employeeService.GetByIdAsync(id);

return HandleResult(result);

The service decides whether the operation succeeds or fails.


5. Do not expose sensitive exception details

Unexpected exceptions should be logged by the server.

Do not return database exception messages, stack traces, connection strings, or internal implementation details to API clients.


๐Ÿ“ฆ Package Metadata

Property Value
Package ID Phoenix.Result
Version 2.0.0
Target Framework .NET 10.0
ASP.NET Core 10.0
Author Nouri Aldrissi
Company Phoenix Tech Solutions
License MIT
Repository GitHub
NuGet NuGet
Official Website Phoenix Tech Solutions
Author Website Nouri Aldrissi

๐Ÿ“œ License

Distributed under the MIT License.

See LICENSE for more information.


<p align="center"> Developed with โค๏ธ by <b>Nouri Aldrissi</b> | <b>Phoenix Tech Solutions</b><br> <a href="https://phoenix.ly/">phoenix.ly</a> </p>

Product Compatible and additional computed target framework versions.
.NET 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.
  • net10.0

    • No dependencies.

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
2.0.1 95 9/8/2026
1.0.0 89 9/7/2026