Skip to content

Getting Started with Pragmatic.Result

This guide will get you up and running with Pragmatic.Result in 5 minutes.

Terminal window
dotnet add package Pragmatic.Result
dotnet add package Pragmatic.Result.AspNetCore # For ASP.NET Core integration
using Pragmatic.Result;
using Pragmatic.Result.Http;
public class UserService
{
public Result<User, NotFoundError> GetUser(int id)
{
var user = _db.Users.Find(id);
if (user is null)
return NotFoundError.Create("User", id);
return user; // Implicit conversion to Success
}
}
var result = userService.GetUser(42);
// Option 1: Pattern matching with Match
var response = result.Match(
user => $"Hello, {user.Name}!",
error => $"Error: {error.Code}"
);
// Option 2: TryGet pattern
if (result.TryGetValue(out var user))
{
Console.WriteLine($"Found: {user.Name}");
}
// Option 3: Check IsSuccess/IsFailure
if (result.IsSuccess)
{
var user = result.Value;
}
var result = GetUser(id)
.Ensure(u => u.IsActive, u => new InactiveUserError(u.Id))
.Map(u => u.ToDto())
.Tap(dto => _logger.LogInformation("User retrieved: {Id}", dto.Id))
.OnFailure(e => _logger.LogWarning("Failed: {Code}", e.Code));
Program.cs
builder.Services.AddPragmaticResult();
// Controller
[HttpGet("{id}")]
public async Task<Result<UserDto, NotFoundError>> GetUser(int id)
{
return await _userService.GetUserAsync(id);
}
// Returns 200 OK with user, or 404 ProblemDetails automatically
TypeUse Case
Result<TValue, TError>Operations that return a value or fail
VoidResult<TError>Operations that succeed or fail (no value)
Maybe<T>Optional values (Some or None)
ErrorHTTP StatusUse Case
NotFoundError404Entity not found
ForbiddenError403Insufficient permissions
UnauthorizedError401Authentication required
ConflictError409Duplicate key, concurrency
BadRequestError400Invalid request
InternalServerError500Unexpected failure
// Transformation
result.Map(x => Transform(x)) // Transform success value
result.MapError(e => NewError(e)) // Transform error
result.Bind(x => GetOther(x)) // Chain operations
// Side effects (don't change result)
result.Tap(x => Log(x)) // Execute on success
result.OnSuccess(x => Notify(x)) // Alias for Tap
result.OnFailure(e => LogError(e)) // Execute on failure
// Validation
result.Ensure(x => x.IsValid, _ => new ValidationError())
// Recovery
result.OrElse(e => FallbackResult()) // Try alternative
result.Recover(e => DefaultValue()) // Recover with default
result.GetValueOrDefault(fallback) // Get value or fallback

For operations that can fail in different ways:

public Result<User, NotFoundError, ForbiddenError> GetUser(int id, ClaimsPrincipal user)
{
var entity = _db.Users.Find(id);
if (entity is null)
return NotFoundError.Create("User", id);
if (!user.CanView(entity))
return new ForbiddenError();
return entity;
}
// Handle with Match
result.Match(
user => Ok(user),
notFound => NotFound(),
forbidden => Forbid()
);
var result = await GetUserAsync(id)
.MapAsync(u => EnrichUserAsync(u))
.BindAsync(u => ValidateAsync(u))
.TapAsync(u => SendNotificationAsync(u));
  1. Use specific error types - NotFoundError over generic Error
  2. Prefer pipelines - Chain operations with Map/Bind/Ensure
  3. Side effects in Tap - Logging, metrics, notifications
  4. Validate early - Use Ensure to add validation steps
  5. Handle all cases - Use Match for exhaustive handling