Phoenix.Result
2.0.1
dotnet add package Phoenix.Result --version 2.0.1
NuGet\Install-Package Phoenix.Result -Version 2.0.1
<PackageReference Include="Phoenix.Result" Version="2.0.1" />
<PackageVersion Include="Phoenix.Result" Version="2.0.1" />
<PackageReference Include="Phoenix.Result" />
paket add Phoenix.Result --version 2.0.1
#r "nuget: Phoenix.Result, 2.0.1"
#:package Phoenix.Result@2.0.1
#addin nuget:?package=Phoenix.Result&version=2.0.1
#tool nuget:?package=Phoenix.Result&version=2.0.1
๐ Phoenix.Result
๐ 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
ProblemDetailsresponses- 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
ResultandResult<TValue>. - ๐ท๏ธ Strongly Typed Errors โ Categorize errors using the
ErrorTypeenum. - ๐ Implicit Conversions โ Return DTOs,
Error, orList<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
ErrorTypeto the appropriate HTTP status code. - ๐ฎ Reusable API Base Controller โ Handle Result responses without repeating controller logic.
- ๐งฉ ASP.NET Core Extensions โ Simple
AddPhoenixResult()andUsePhoenixResult()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
- Installation
- Quick Start
- Core Architecture
- Result
- Result<TValue>
- Error
- ErrorType
- PaginatedResult<TEntity>
- Implicit Conversions
- NotFoundException
- ASP.NET Core Integration
- API Base Controller
- Global Exception Handling
- Model Validation
- HTTP Status Code Mapping
- Service Examples
- Controller Examples
- API Response Examples
- Recommended Project Structure
- Best Practices
- Package Metadata
- License
๐ก 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:
ExceptionHandlerMiddlewareApiResponseFactoryApiBaseController- 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
Datawhen the result has failed throws anInvalidOperationException.
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 andNotFoundExceptionwhen 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
๐ Recommended Project Structure
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 | Versions 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. |
-
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.