Wiaoj.Results
0.3.0-alpha.1
dotnet add package Wiaoj.Results --version 0.3.0-alpha.1
NuGet\Install-Package Wiaoj.Results -Version 0.3.0-alpha.1
<PackageReference Include="Wiaoj.Results" Version="0.3.0-alpha.1" />
<PackageVersion Include="Wiaoj.Results" Version="0.3.0-alpha.1" />
<PackageReference Include="Wiaoj.Results" />
paket add Wiaoj.Results --version 0.3.0-alpha.1
#r "nuget: Wiaoj.Results, 0.3.0-alpha.1"
#:package Wiaoj.Results@0.3.0-alpha.1
#addin nuget:?package=Wiaoj.Results&version=0.3.0-alpha.1&prerelease
#tool nuget:?package=Wiaoj.Results&version=0.3.0-alpha.1&prerelease
Wiaoj.Results
Wiaoj.Results is a high-performance, zero-dependency library that implements the Result Pattern for .NET. It allows you to write robust, expressive, and type-safe code by replacing exception-based control flow with a functional approach known as Railway Oriented Programming (ROP).
Designed with Domain-Driven Design (DDD) and Clean Architecture in mind, it helps you manage application flow elegantly while minimizing performance overhead.
Features
- Zero Dependencies. Built with pure C#, no external packages required.
- High Performance. Uses lightweight
readonly record structtypes (Error,ErrorType) to minimize heap allocations and garbage collection pressure. - Rich Error Model. Structured, immutable errors featuring
Code,Description,Type, and extensibleMetadata. - Advanced Async Support. First-class extension methods for both
TaskandValueTaskto keep asynchronous pipelines allocation-conscious. - Elegant Control Flow. Eliminate nested
ifchecks using fluent combinators like.Then(),.Map(),.Ensure(),.Do(), and.Match(). - Collection Power. Combine, partition, or filter
IEnumerable<Result<T>>andIAsyncEnumerable<Result<T>>collections in a single pass. - Safe Resource Management. Handle
IDisposableandIAsyncDisposablepayloads using.Consume()and.ConsumeAsync(). - Exception Safety. Wrap throwing code and third-party libraries using
Result.TryandResult.TryAsync, with automatic mapping of common exception types toErrorType.
Installation
Install via the .NET CLI:
dotnet add package Wiaoj.Results
Or via the Package Manager Console:
Install-Package Wiaoj.Results
Core Concepts
1. Returning Results (Success or Failure)
Instead of throwing exceptions or returning null, return a Result<T>. The library supports implicit conversions from both raw values and Error objects, keeping your code clean.
using Wiaoj.Results;
public class UserService
{
public Result<User> GetUser(int id)
{
if (id <= 0)
return Error.Validation("User.InvalidId", "ID must be positive.");
var user = _repository.Find(id);
if (user is null)
return Error.NotFound("User.NotFound", $"User with id {id} was not found.");
// Implicitly converts the User object to a successful Result<User>
return user;
}
}
2. Void Operations (Result<Success>)
For operations that do not return a specific value, use Result<Success> (or simply return Result.Success()). Success is a zero-allocation, 1-byte struct optimized for this case.
public Result<Success> DeleteUser(int id)
{
if (!_repo.Exists(id))
return Error.NotFound();
_repo.Delete(id);
return Result.Success();
}
3. Handling Results (Pattern Matching)
Extract values or handle errors gracefully at the edges of your application (UI, API controllers) using .Match() or .Switch().
var result = service.GetUser(1);
// Match: returns a value based on the outcome
string response = result.Match(
user => $"Welcome back, {user.Name}!",
errors => $"Failed: {errors[0].Description}"
);
// Switch: executes an action based on the outcome (returns void)
result.Switch(
user => Console.WriteLine($"Success: {user.Email}"),
errors => Console.WriteLine($"Error Code: {errors[0].Code}")
);
Railway Oriented Programming (Chaining)
Instead of writing nested if (!result.IsSuccess) checks, chain your operations. If any step fails, the pipeline short-circuits and bypasses subsequent steps, propagating the error down the chain.
public async Task<Result<Guid>> RegisterUserAsync(UserDto dto)
{
return await ValidateDtoAsync(dto) // 1. Returns Result<UserDto>
.EnsureAsync(IsEmailUniqueAsync, Error.Conflict("Email.InUse")) // 2. Fails if email exists
.ThenAsync(validDto => CreateUserInDbAsync(validDto)) // 3. Executes next step returning Result<User>
.DoAsync(user => SendWelcomeEmailAsync(user)) // 4. Side-effect: runs only on success
.MapAsync(user => user.Id); // 5. Transforms Result<User> to Result<Guid>
}
Rich Error Model
The library uses an extensible value-type approach (ErrorType) instead of a plain enum to categorize errors, making it straightforward to map them to HTTP status codes or log severity levels. You can declare your own domain-specific ErrorType values alongside the built-in ones — see Custom Errors & Metadata below.
Built-in Error Types
| Factory Method | Suggested HTTP Code | Use Case |
|---|---|---|
Error.Failure() |
500 | General failures or default unhandled states. |
Error.Unexpected() |
500 | Unexpected system errors. |
Error.Validation() |
400 | Invalid input formats or schema violations. |
Error.NotFound() |
404 | The requested resource does not exist. |
Error.Conflict() |
409 | Duplicate resource or business logic conflict. |
Error.Unauthorized() |
401 | Authentication is required but missing or invalid. |
Error.Forbidden() |
403 | Authenticated, but lacks required permissions. |
Error.UnprocessableEntity() |
422 | Syntactically valid request, but a semantic rule was violated. |
Error.RateLimitExceeded() |
429 | Caller has sent too many requests. |
Error.ServiceUnavailable() |
503 | A downstream dependency is temporarily unreachable. |
Error.Timeout() |
408 / 504 | The operation did not complete within the allowed time. |
Error.Gone() |
410 | The resource has been permanently removed. |
Custom Errors & Metadata
Define domain-specific error types and attach contextual metadata to your errors.
public static class AppErrorTypes
{
public static readonly ErrorType Maintenance = new("Maintenance");
}
// Emitting a custom error with metadata
return Error.Custom(AppErrorTypes.Maintenance, "System.Offline", "System is under maintenance.")
.WithMetadata("RetryAfter", DateTime.UtcNow.AddHours(1))
.WithMetadata("TicketId", 12345);
Multiple Errors
Useful for returning aggregated validation errors instead of failing at the first bad input. A List<Error> implicitly converts to a failed Result<T> carrying every error in the list.
List<Error> errors = [
Error.Validation("Name.Required", "Name cannot be empty."),
Error.Validation("Age.Min", "Age must be at least 18.")
];
return errors; // Implicitly converts to a failed Result<Success> containing all errors
Advanced Features
Safely Wrapping Exceptions (Try / TryAsync)
Convert exceptions from third-party libraries (or the BCL) into Result objects. Error.FromException maps common exception types (TimeoutException, UnauthorizedAccessException, ArgumentException) to the matching ErrorType automatically; anything else becomes ErrorType.Unexpected.
// Automatically maps TimeoutException, UnauthorizedAccessException, etc. to the correct ErrorTypes
Result<string> content = await Result.TryAsync(ct => File.ReadAllTextAsync("data.txt", ct));
// Or provide a custom exception mapper:
Result<int> parsed = Result.Try(
() => int.Parse("bad_input"),
ex => Error.Validation("ParseError", ex.Message)
);
Nullability Bridges (ToResult / EnsureNotNull)
Convert null reference returns from existing APIs (like Entity Framework) into strict Result types.
var user = await dbContext.Users.FindAsync(id);
// If user is null, returns the provided NotFound error.
return user.ToResult(Error.NotFound("User.NotFound", "User not found."));
// Or chaining on an existing Result<T?>:
Result<User> strictResult = nullableResult.EnsureNotNull(Error.NotFound());
Collection Combinators (Combine, Partition)
Process a batch of results in a single pass.
IEnumerable<Result<User>> userResults = userIds.Select(id => GetUser(id));
// Returns Result<IReadOnlyList<User>>.
// If any GetUser call failed, `combined` becomes a failure holding all collected errors.
Result<IReadOnlyList<User>> combined = userResults.Combine();
// Or split successes and failures without short-circuiting:
var (users, errors) = userResults.Partition();
The same operations are available for Task<Result<T>> collections (CombineAsync, PartitionAsync, evaluated in parallel via Task.WhenAll) and for IAsyncEnumerable<Result<T>> streams.
Resource Management (Consume / DisposeValue)
When your Result<T> holds an IDisposable or IAsyncDisposable resource (e.g., a Stream or HttpResponseMessage), .Consume() / .ConsumeAsync() ensure it gets disposed right after execution.
await Result.TryAsync(ct => httpClient.GetAsync(url, ct))
.ConsumeAsync(async (response, ct) =>
{
var data = await response.Content.ReadAsStringAsync(ct);
Console.WriteLine(data);
// The response is automatically disposed at the end of this block.
});
ValueTask Optimization
For high-performance scenarios (like cache lookups that are usually synchronous), the library provides dedicated .ThenAsync, .MapAsync, .EnsureAsync, .DoAsync/.TapAsync, and .MatchAsync overloads for ValueTask<Result<T>> to avoid the heap allocation a Task-based pipeline would otherwise incur on the synchronous-completion path.
Web API / Minimal API Integration
Mapping a Result<T> to an HTTP response is straightforward using .Match(). Here is a common pattern for ASP.NET Core Minimal APIs or controllers:
[HttpGet("{id}")]
public async Task<IResult> GetUser(int id)
{
var result = await _userService.GetUserAsync(id);
return result.Match(
user => Results.Ok(user),
errors => Results.Problem(
statusCode: GetStatusCode(errors[0].Type),
title: errors[0].Description,
extensions: errors[0].Metadata?.ToDictionary(k => k.Key, v => v.Value)
)
);
}
// Helper to map your ErrorTypes to standard HTTP status codes
private static int GetStatusCode(ErrorType type) => type.Name switch
{
nameof(ErrorType.Validation) => StatusCodes.Status400BadRequest,
nameof(ErrorType.NotFound) => StatusCodes.Status404NotFound,
nameof(ErrorType.Conflict) => StatusCodes.Status409Conflict,
nameof(ErrorType.Unauthorized) => StatusCodes.Status401Unauthorized,
nameof(ErrorType.Forbidden) => StatusCodes.Status403Forbidden,
nameof(ErrorType.RateLimit) => StatusCodes.Status429TooManyRequests,
nameof(ErrorType.Unavailable) => StatusCodes.Status503ServiceUnavailable,
_ => StatusCodes.Status500InternalServerError
};
Contributing
Contributions, bug reports, and feature requests are welcome. Feel free to open an issue or submit a pull request on the GitHub repository.
License
This project is licensed under the MIT License.
| 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 (1)
Showing the top 1 NuGet packages that depend on Wiaoj.Results:
| Package | Downloads |
|---|---|
|
Wiaoj.Results.AspNetCore
A high-performance, zero-dependency implementation of the Result Pattern for .NET. Replaces exceptions with a type-safe, functional approach (Railway Oriented Programming) using lightweight structs. Perfect for Domain-Driven Design (DDD) and Clean Architecture. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0-alpha.1 | 43 | 10/5/2026 |
| 0.2.0-alpha.3 | 59 | 9/24/2026 |
| 0.2.0-alpha.2 | 53 | 9/24/2026 |
| 0.2.0-alpha.1 | 63 | 9/24/2026 |
| 0.1.0-alpha.9 | 168 | 9/21/2026 |
| 0.1.0-alpha.8 | 63 | 9/21/2026 |
| 0.1.0-alpha.7 | 65 | 9/18/2026 |
| 0.1.0-alpha.6 | 63 | 9/16/2026 |
| 0.1.0-alpha.5 | 62 | 9/16/2026 |
| 0.1.0-alpha.4 | 63 | 9/16/2026 |
| 0.1.0-alpha.3 | 58 | 9/15/2026 |
| 0.1.0-alpha.2 | 95 | 9/15/2026 |
| 0.1.0-alpha.1 | 105 | 9/14/2026 |
| 0.0.1-alpha.112-preview | 62 | 9/13/2026 |
| 0.0.1-alpha.111-preview | 70 | 9/13/2026 |
| 0.0.1-alpha.110-preview | 63 | 9/12/2026 |
| 0.0.1-alpha.109-preview | 86 | 9/11/2026 |
| 0.0.1-alpha.108-preview | 76 | 9/8/2026 |
| 0.0.1-alpha.107-preview | 72 | 9/8/2026 |
| 0.0.1-alpha.106-preview | 69 | 9/8/2026 |