CleanResult 1.3.4
dotnet add package CleanResult --version 1.3.4
NuGet\Install-Package CleanResult -Version 1.3.4
<PackageReference Include="CleanResult" Version="1.3.4" />
<PackageVersion Include="CleanResult" Version="1.3.4" />
<PackageReference Include="CleanResult" />
paket add CleanResult --version 1.3.4
#r "nuget: CleanResult, 1.3.4"
#:package CleanResult@1.3.4
#addin nuget:?package=CleanResult&version=1.3.4
#tool nuget:?package=CleanResult&version=1.3.4
CleanResult

<div align="center">
A clean, Rust-inspired Result type implementation for .NET 8.0, 9.0, and 10.0
Brings functional error handling to C# with built-in ASP.NET Core integration and RFC 9457 compliance
Features • Quick Start • Extensions • Examples • Documentation
</div>
✨ Features
Core Capabilities
- 🦀 Rust-inspired Design - Familiar
Result<T>pattern for functional error handling - 🔒 Type Safety - Compile-time guarantees for error handling paths
- 🔄 Automatic Type Conversions - Implicit conversions between
Result<T>andResultfor seamless error propagation - 🌐 ASP.NET Core Integration - Direct
IResultimplementation for seamless web API integration - 📜 RFC 9457 Compliant - Full Problem Details specification support
- 🎯 Smart Content-Type Detection - Automatically sets correct Content-Type based on return type
- 🪶 Zero Dependencies - Lightweight library with minimal overhead
- ⚡ High Performance - No reflection, optimized for speed
Content-Type Intelligence
CleanResult automatically selects the appropriate Content-Type based on your return value:
| Type | Content-Type | Example |
|---|---|---|
string |
text/plain; charset=utf-8 |
Plain text responses |
byte[] |
application/octet-stream |
Binary data, files |
Stream |
application/octet-stream |
File streams |
XDocument, XElement |
application/xml; charset=utf-8 |
XML documents |
| Objects, primitives | application/json; charset=utf-8 |
JSON responses |
📦 Extension Packages
CleanResult offers specialized extensions for popular frameworks:
| Package | Description | Documentation |
|---|---|---|
| CleanResult.FluentValidation | FluentValidation integration | Docs |
| CleanResult.WolverineFx | WolverineFx messaging integration | Docs |
| CleanResult.Swashbuckle | Swagger/OpenAPI integration | Docs |
| CleanResult.AspNet | Traditional IActionResult adapter | Docs |
🚀 Quick Start
Installation
dotnet add package CleanResult
Basic Usage
using CleanResult;
// ✅ Success without value
Result simpleSuccess = Result.Ok();
// ✅ Success with value
Result<User> userResult = Result.Ok(new User { Name = "John" });
// ❌ Error with message and HTTP status
Result error = Result.Error("User not found", 404);
ASP.NET Core Controller
[ApiController]
[Route("api/[controller]")]
public class UsersController : Controller
{
[HttpGet("{id}")]
public Result<User> GetUser(int id)
{
var user = _userService.FindById(id);
if (user == null)
return Result.Error("User not found", 404);
return Result.Ok(user); // Returns HTTP 200 with JSON
}
[HttpPost]
public Result CreateUser([FromBody] User user)
{
if (string.IsNullOrEmpty(user.Email))
return Result.Error("Email is required", 400);
_userService.Create(user);
return Result.Ok(); // Returns HTTP 204 No Content
}
}
Content-Type Examples
// 📄 Plain text
[HttpGet("message")]
public Result<string> GetMessage()
=> Result.Ok("Hello World"); // Content-Type: text/plain
// 📦 Binary data
[HttpGet("file")]
public Result<byte[]> GetFile()
=> Result.Ok(fileBytes); // Content-Type: application/octet-stream
// 🔤 XML document
[HttpGet("xml")]
public Result<XDocument> GetXml()
=> Result.Ok(xmlDoc); // Content-Type: application/xml
// 📊 JSON object (default)
[HttpGet("data")]
public Result<User> GetData()
=> Result.Ok(user); // Content-Type: application/json
📚 Examples
Service Layer Pattern
public class UserService
{
private readonly IUserRepository _repository;
public Result<User> GetById(int id)
{
var user = _repository.FindById(id);
if (user == null)
return Resul.Error("User not found", HttpStatusCode.NotFound);
return Result.Ok(user);
}
public Result<User> Create(CreateUserDto dto)
{
// Validation
if (string.IsNullOrEmpty(dto.Email))
return Result.Error("Email is required", HttpStatusCode.BadRequest);
if (_repository.ExistsByEmail(dto.Email))
return Result.Error("Email already exists", HttpStatusCode.Conflict);
// Create user
var user = new User
{
Name = dto.Name,
Email = dto.Email
};
_repository.Add(user);
return Result.Ok(user);
}
}
Error Chaining and Conversion
public class OrderService
{
public Result ProcessOrder(int userId, int productId, int quantity)
{
// Validate user
var userResult = _userService.GetById(userId);
if (userResult.IsError())
return userResult; // Implicit conversion from Result<User> to Result
// Validate product
var productResult = _productService.GetById(productId);
if (productResult.IsError())
return productResult; // Implicit conversion from Result<Product> to Result
// Process order with validated entities
var user = userResult.Value;
var product = productResult.Value;
if (product.Stock < quantity)
return Result.Error("Insufficient stock", HttpStatusCode.Conflict);
// Create order...
return Result.Ok();
}
}
Exception Wrapping
public class FileService
{
public Result<string> ReadFile(string path)
{
try
{
if (!File.Exists(path))
return Result.Error("File not found", 404);
var content = File.ReadAllText(path);
return Result.Ok(content);
}
catch (UnauthorizedAccessException)
{
return Result.Error("Access denied", 403);
}
catch (Exception ex)
{
return Result.Error(
$"Failed to read file: {ex.Message}",
HttpStatusCode.InternalServerError
);
}
}
}
📖 Documentation
API Reference
Result (Non-Generic)
// Factory Methods
Result.Ok() // Success without value
Result.Error(string title) // Error with title
Result.Error(string title, int status) // Error with status code
Result.Error(string title, HttpStatusCode status) // Error with HTTP status
Result.Error(Error error) // Error from Error object
// Conversion
Result.From<T>(Result<T> result) // Convert from generic result
// State Checking
bool IsOk() // Check if success
bool IsError() // Check if error
Error ErrorValue // Get error (throws if Ok)
Result<T> (Generic)
// Factory Methods
Result<T>.Ok(T value) // Success with value
Result<T>.Error(string title) // Error with title
Result<T>.Error(string title, int status) // Error with status code
Result<T>.Error(string title, HttpStatusCode status) // Error with HTTP status
Result<T>.Error(Error error) // Error from Error object
// Conversion
Result<T>.From<T1>(Result<T1> result) // Convert between types (errors only)
// State Checking
bool IsOk() // Check if success
bool IsError() // Check if error
T Value // Get value (throws if Error)
Error ErrorValue // Get error (throws if Ok)
Error Structure (RFC 9457 Compliant)
public struct Error
{
public string Type { get; set; } // URI identifying problem type
public string Title { get; set; } // Human-readable summary
public int Status { get; set; } // HTTP status code
public string? Detail { get; set; } // Specific explanation
public string? Instance { get; set; } // URI identifying occurrence
public IDictionary<string, string[]>? Errors { get; set; } // Validation errors
}
HTTP Response Behavior
| Result | HTTP Status | Content-Type | Body |
|---|---|---|---|
Result.Ok() |
204 No Content | application/json | Empty |
Result<string>.Ok("text") |
200 OK | text/plain | Raw string |
Result<User>.Ok(user) |
200 OK | application/json | JSON object |
Result<byte[]>.Ok(data) |
200 OK | application/octet-stream | Binary data |
Result.Error("msg", 404) |
404 Not Found | application/json | Problem Details |
Error Response (RFC 9457):
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.4",
"title": "User not found",
"status": 404,
"detail": "No user exists with ID 123",
"instance": "/api/users/123"
}
🎯 Best Practices
✅ Do's
// ✅ Use Result for expected error conditions
public Result<User> GetUser(int id)
{
var user = _repository.FindById(id);
return user == null
? Result.Error("Not found", 404)
: Result.Ok(user);
}
// ✅ Check results before accessing values
var result = GetUser(123);
if (result.IsOk())
{
Console.WriteLine(result.Value.Name);
}
// ✅ Use HTTP status codes for web APIs
return Result.Error("Unauthorized", HttpStatusCode.Unauthorized);
❌ Don'ts
// ❌ Don't use Result for unexpected exceptions
public Result<string> ReadFile(string path)
{
// Bad: Let exceptions propagate for unexpected errors
var content = File.ReadAllText(path); // Could throw unexpected IOException
return Result.Ok(content);
}
// ❌ Don't access Value without checking
var result = GetUser(123);
var name = result.Value.Name; // Throws if result is error!
// ❌ Don't expose internal error details to users
return Result.Error(ex.StackTrace, 500); // Bad: leaks implementation details
🔧 Advanced Usage
With extension packages, CleanResult can integrate with popular libraries and frameworks.
FluentValidation Integration
// Install: dotnet add package CleanResult.FluentValidation
public class CreateUserValidator : AbstractValidator<CreateUserDto>
{
public CreateUserValidator()
{
RuleFor(x => x.Email).EmailAddress();
RuleFor(x => x.Name).NotEmpty();
}
}
[HttpPost]
public async Task<Result<User>> CreateUser(
[FromBody] CreateUserDto dto,
[FromServices] IValidator<CreateUserDto> validator)
{
// Validate and return Result directly
var validationResult = await validator.ValidateToResultAsync(dto);
if (validationResult.IsError())
return Result<User>.From(validationResult);
// Or validate and transform in one step
return await validator.ValidateAndTransformAsync(
dto,
async validated => await _userService.CreateAsync(validated)
);
}
WolverineFx Messaging
// Install: dotnet add package CleanResult.WolverineFx
public class CreateUserHandler
{
// Load returns Result<T> - framework extracts value or short-circuits on error
public static async Task<Result<User>> LoadAsync(CreateUserCommand command)
{
var user = await _repository.FindByEmailAsync(command.Email);
if (user != null)
return Result<User>.Error("Email exists", 409);
return Result.Ok(new User { Email = command.Email });
}
// Handle receives extracted value only if Load succeeded
public static async Task<Result<UserCreatedEvent>> Handle(
CreateUserCommand command,
User user)
{
await _repository.SaveAsync(user);
return Result.Ok(new UserCreatedEvent(user.Id));
}
}
Swagger/OpenAPI Integration
// Install: dotnet add package CleanResult.Swashbuckle
builder.Services.AddSwaggerGen(options =>
{
// Unwraps Result<T> types to show actual return types in Swagger UI
options.AddCleanResultFilters();
});
🤝 Contributing
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
Inspired by Rust's Result<T, E> type and the functional programming community.
<div align="center">
</div>
<div align="center"> Gwynbleid85 © 2025 </div>
| Product | Versions 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. |
-
net10.0
- No dependencies.
-
net8.0
- No dependencies.
-
net9.0
- No dependencies.
NuGet packages (4)
Showing the top 4 NuGet packages that depend on CleanResult:
| Package | Downloads |
|---|---|
|
CleanResult.WolverineFx
Extension of CleanResult for WolverineFx. |
|
|
CleanResult.Swashbuckle
Extension of CleanResult to generate nicer OpenAPI schema. |
|
|
CleanResult.AspNet
Extension of CleanResult for creating IActionResult from Result. |
|
|
CleanResult.FluentValidation
Extension of CleanResult for FluentValidation integration. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.3.4 | 167 | 1/5/2026 |
| 1.3.3 | 279 | 11/25/2025 |
| 1.3.2 | 782 | 11/25/2025 |
| 1.3.1 | 266 | 11/25/2025 |
| 1.3.0 | 533 | 11/4/2025 |
| 1.2.9 | 249 | 10/9/2025 |
| 1.2.8 | 457 | 10/8/2025 |
| 1.2.7 | 655 | 8/25/2025 |
| 1.2.6 | 263 | 8/11/2025 |
| 1.2.5 | 944 | 7/28/2025 |
| 1.2.4 | 558 | 7/24/2025 |
| 1.2.3 | 626 | 7/23/2025 |
| 1.2.2 | 227 | 7/17/2025 |
| 1.2.1 | 227 | 7/10/2025 |
| 1.2.0 | 225 | 7/9/2025 |
| 1.1.0 | 246 | 7/9/2025 |
| 1.0.2 | 554 | 6/7/2025 |
| 1.0.1 | 184 | 6/7/2025 |
| 1.0.0 | 218 | 6/5/2025 |