The Result Pattern in .NET
Return success or failure results in your .net applications elegantly.
Start by defining the Error and Result models
// Error.cs
public record Error(string Code, string Message);
// Result.cs
public class Result<T> : Result
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public T Value { get; }
[JsonConstructor]
private Result(bool isSuccess, T value, Error error) : base(isSuccess, error)
{
Value = value;
}
public static Result<T> Success(T value) => new(true, value, null!);
public static Result<T> Failure(Error error, bool isSuccess = false) =>
new(isSuccess, default!, error);
}
public class Result
{
public bool IsSuccess { get; }
public bool IsFailure => !IsSuccess;
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Error Error { get; }
[JsonConstructor]
protected Result(bool isSuccess, Error error)
{
IsSuccess = isSuccess;
Error = error;
}
public static Result Success() => new(true, null!);
public static Result Failure(Error error) => new(false, error);
}
Note that the generic result type Result<T>
is only required if you want to return data
Define static Error
classes based on your application needs. For example:
public static class UserErrors
{
public static Error NotFound =>
new("NotFound", "The requested user was not found.");
public static Error ProfileUpdateFailed =>
new("ProfileUpdateFailed", "Failed to update user profile. Please try again later.");
}
Then use your newly found superpowers like so
if (user is null)
{
logger.LogWarning("User not found for changing password: {UserId}", userId);
return Result.Failure(UserErrors.NotFound);
}
// ...
logger.LogInformation("Password changed successfully for user ID: {UserId}", userId);
return Result.Success();
If you would like to return additional data, then…
return Result<UserProfileDto>.Success(user);
I'd take this over exceptions for flow-control any day. Exceptions have their place; this is not it.