**Lightweight result wrapper, validation and transformation toolkit for C# **
Brings clean Result<T> handling and extensible validation with custom attributes.
- β
Consistent
Result<T>response pattern - β
One-line
.Validate()extension for request models - β
.Transform()extension for clean and safe object/result projection - β Rich built-in and custom validation attributes
- β Works great with System.ComponentModel.Annotations
- β Validation for nested objects
dotnet add package Routya.ResultKit --version 2.1.0Upgrading from v1.x? See the Migration Guide below.
using Routya.ResultKit.Attributes;
using System.ComponentModel.DataAnnotations;
private enum UserRole { Admin, User, Guest }
private class TestModel
{
[Required]
public string Name { get; set; }
[EmailAddress]
public string Email { get; set; }
[Range(18, 120)]
public int Age { get; set; }
[StringEnum(typeof(UserRole))]
public string Role { get; set; }
[Required]
public string Password { get; set; }
[Compare("Password")]
public string ConfirmPassword { get; set; }
public decimal MinPurchase { get; set; }
[GreaterThan("MinPurchase")]
public decimal MaxPurchase { get; set; }
}using Routya.ResultKit;
using Routya.ResultKit.Validation;
app.MapPost("/users", (CreateUserRequest request) =>
{
var validationResult = request.Validate();
if (!validationResult.Success)
return Results.BadRequest(validationResult.Error); // Returns ProblemDetails
var user = new User { Id = 1, Name = request.Name, Email = request.Email };
return Results.Ok(user);
});{
"id": 1,
"name": "Henry",
"email": "henry@example.com"
}{
"type": "urn:problem-type:validation-error",
"title": "Validation Failed",
"status": 422,
"detail": "One or more validation errors occurred.",
"errors": {
"name": ["The Name field is required."],
"email": ["The Email field is not a valid e-mail address."],
"age": ["The field Age must be between 18 and 120."],
"role": ["Role must be one of: Admin, User, Guest"],
"confirmPassword": ["'ConfirmPassword' and 'Password' do not match."],
"maxPurchase": ["MaxPurchase must be greater than MinPurchase"]
}
}Note: v2.0 uses RFC 7807 compliant ProblemDetails for all errors.
Routya.ResultKit includes powerful validation attributes ready to use:
| Attribute | Purpose |
|---|---|
[GreaterThan("OtherProp")] |
Ensure a property is greater than another |
[LessThan("OtherProp")] |
Ensure a property is less than another |
[RequiredIf("OtherProp", "Value")] |
Conditionally require a property |
[RequiredIfEmpty("OtherProp")] |
Require a property if another is empty |
[StringEnum(typeof(EnumType))] |
Ensure a string matches an Enum name |
[MatchRegex("pattern")] |
Validate a string against a regex |
[MinItems(count)] |
Validate minimum items in a collection |
[MaxItems(count)] |
Validate maximum items in a collection |
[ValidStartEndDateRange("Start", "End")] |
Validate that StartDate is before EndDate (This is a class level attribute) |
[ValidDateTimeOffsetRange("End")] |
Validate DateTimeOffset ranges |
[ValidDateTimeRange("End")] |
Validate DateTime ranges |
Use .Transform(...) to reshape validated models or result data into domain entities or response objects β cleanly and safely.
var request = "Hello";
var greeting = request.Transform(str => new Greeting
{
Message = str,
Length = str.Length
});public class Greeting
{
public string Message { get; set; }
public int Length { get; set; }
}var result = request.Validate()
.Transform(req => new CreateUserCommand
{
Name = req.Name,
Email = req.Email,
Role = Enum.Parse<UserRole>(req.Role, ignoreCase: true)
});var result = Result.Ok(user)
.Transform(u => new UserResponse
{
Id = u.Id,
Name = u.Name
});| Benefit | Description |
|---|---|
| β Fluent | Clean chaining after .Validate() |
| β Safe | When using Result it only transforms data if result is successful |
| β Expressive | Encourages intentional mapping logic |
| β Lightweight | Zero dependencies, pure functional mapping |
TOut Transform<TIn, TOut>(this TIn input, Func<TIn, TOut> selector)
Result<TOut> Transform<TIn, TOut>(this Result<TIn> result, Func<TIn, TOut> selector)Result carries semantic HTTP intent with automatic status code handling:
// Success status codes
Result<User>.Ok(user); // 200 OK
Result<User>.Created(user); // 201 Created
Result<User>.Accepted(user); // 202 Accepted
Result<User>.NoContent(); // 204 No Content
// Redirect status codes
Result<string>.Redirect(location); // 302 Found (temporary)
Result<string>.RedirectPermanent(location); // 301 Moved Permanently
// Error status codes (automatic from factory methods)
Result<User>.NotFound("User not found"); // 404 Not Found
Result<User>.BadRequest("Invalid data"); // 400 Bad Request
Result<User>.Unauthorized("Not authenticated"); // 401 Unauthorized[HttpDelete("users/{id}")]
public IActionResult DeleteUser(int id)
{
var user = _repository.FindById(id);
if (user == null)
return Result<User>.NotFound($"User {id} not found").ToActionResult(HttpContext);
_repository.Delete(user);
return Result<User>.NoContent().ToActionResult(HttpContext); // Returns 204
}// Temporary redirect (302) - for moved resources
[HttpGet("docs")]
public IActionResult RedirectToDocs()
{
return Result<string>.Redirect("https://routya.github.io/").ToActionResult(HttpContext);
}
// Permanent redirect (301) - for permanently moved endpoints
[HttpGet("old-users")]
public IActionResult OldEndpoint()
{
var newLocation = $"{Request.Scheme}://{Request.Host}/api/users";
return Result<string>.RedirectPermanent(newLocation).ToActionResult(HttpContext);
}
// HEAD request with NoContent
[HttpHead("users/check-email")]
public IActionResult CheckEmailExists([FromQuery] string email)
{
var exists = _repository.EmailExists(email);
return exists
? Result<User>.NoContent().ToActionResult(HttpContext)
: Result<User>.NotFound("Email not found").ToActionResult(HttpContext);
}Note: When using the
Routya.ResultKit.AspNetCorepackage,ToActionResult()andToHttpResult()automatically use the appropriate status code from the Result. See ASP.NET Core Integration for details.
v2.0 introduces RFC 7807 ProblemDetails and a cleaner API. Here's what changed:
-
ProblemDetails replaces old error format
// β v1.x - Simple error dictionary Result.Fail("Validation Failed", 400, errors) // β v2.0 - RFC 7807 ProblemDetails Result.Fail(ProblemDetailsBuilder.ValidationError("Validation Failed") .WithErrors(errors) .Build())
-
Result factory methods renamed
// β v1.x Result.Success(data) Result.Failure("Error", 400) // β v2.0 Result.Ok(data) Result.Created(data) // New: Sets 201 status Result.Accepted(data) // New: Sets 202 status Result.NoContent() // New in v2.1: Sets 204 status Result.Redirect(location) // New in v2.1: Sets 302 status Result.RedirectPermanent(location) // New in v2.1: Sets 301 status Result.Fail(problemDetails)
-
Validation returns ProblemDetails
// β v1.x var result = request.ValidateObject(); if (!result.Success) return result; // Returned Result<T> // β v2.0 var result = request.Validate(); if (!result.Success) return result.Error; // Returns ProblemDetails
-
ASP.NET Core Integration (New Package)
dotnet add package Routya.ResultKit.AspNetCore
// Automatic conversion to IResult/IActionResult return result.ToActionResult(HttpContext); // Automatic exception handling builder.Services.AddResultKitProblemDetails(); app.UseResultKitExceptionHandler();
- Update package:
dotnet add package Routya.ResultKit --version 2.1.0 - Replace
Result.SuccessβResult.Ok - Replace
Result.FailureβResult.Fail(use ProblemDetailsBuilder) - Replace
ValidateObject()βValidate() - For ASP.NET Core, add
Routya.ResultKit.AspNetCorepackage
π Full Migration Guide - Complete migration documentation with examples
- Official Website - Routya project homepage
- Migration Guide to v2.0 - Complete guide for upgrading from v1.x
- ASP.NET Core Integration - ProblemDetails, middleware, IResult/IActionResult extensions
- Demo API - Comprehensive examples of all features