ModResults implements the Result pattern, providing a structured way to represent the outcome of operations as either success or failure. Instead of using exceptions or loosely-typed error codes, ModResults encapsulates operation results in strongly-typed objects to improve code readability, maintainability, and error handling. Uses nullability annotations for safer, more predictable code.
- Result Pattern: Encapsulates success or failure states with associated error messages and additional information.
- Easy-to-Use: Simplifies error handling and result management with a clear API.
- Serialization: System.Text.Json serialization support without need for special configuration. Microsoft.Orleans serialization support via
ModResults.Orleanspackage. - Ready-to-Use Implementations:
ResultandResult<TValue>with a defaultFailuretype.Result<TValue, TFailure>for custom failure states.
- 📦 Extension Packages for Various Scenarios:
ModResults.FluentValidationbridges FluentValidation with ModResults for unified validation error handling.ModResults.MinimalApisprovides extensions to convert Result and Result<TValue> instances to ASP.NET Core Minimal APIs' IResult responses with proper HTTP status codes and response formatting.ModResults.Orleansprovides necessary surrogate, converter and populator implementations required by the Orleans serializer.
- Success: Use
Result.Ok()orResult<TValue>.Ok(TValue value). - Failure: Use static factory methods like
Result.NotFound()orResult<TValue>.Invalid()to create failed results with a specific failure type. Also,FailureResultclass has same set of static factory methods to serve as a helper for returning failedResult<TValue>instances without the need to specifyTValuedirectly.FailureResultcan be converted to eitherResultorResult<TValue>via implicit operators or explicitAsResult(),AsResult<TValue>(),ToResult()andToResult<TValue>()methods.
For each failure type (like Error, Forbidden, Unauthorized, etc.), there are several overloads that creates a failed result with the given FailureType:
- No parameters: Creates a failed result with the given FailureType and no errors.
params IEnumerable<String>errorMessages: Converts each string to an Error and includes them in the result.params IEnumerable<Error>errors: Directly includes the provided Error objects in the result.params IEnumerable<Exception>exceptions: Converts each exception to an Error and includes them in the result.
These methods make it easy and consistent to create failed results with detailed error information, categorized by failure type, for both non-generic and generic result types.
public async Task<Result<GetBookByIdResponse>> GetBookById(GetBookByIdRequest req, CancellationToken ct)
{
var entity = await db.Books.FirstOrDefaultAsync(b => b.Id == req.Id, ct);
return entity is null ?
FailureResult.NotFound() :
Result.Ok(new GetBookByIdResponse(
Id: entity.Id,
Title: entity.Title,
Author: entity.Author,
Price: entity.Price));
}A result can be either in an Ok or Failed state.
- Ok State: If the
IsOkproperty is true (IsFailedis false), aResult<TValue>instance will have a non-nullValueproperty of typeTValue. - Failed State: If the
IsOkproperty is false (IsFailedis true), bothResultandResult<TValue>instances will have a non-nullFailureproperty.
Note: When using nullable contexts, the compiler will not generate "may be null" warnings in these cases, as the library leverages conditional attributes to assist null-state analysis.
Checking the state of a result to run some business logic is straightforward:
public async Task<Result<PerformGetBookByIdResponse>> PerformGetBookById(GetBookByIdRequest req, CancellationToken ct)
{
var result = await GetBookById(req, ct);
if (result.IsFailed)
{
// Handle failure case,
Console.WriteLine($"GetBookById has failed.");
// Return the failure information as a failed Result<PerformGetBookByIdResponse>
// FailureResult can be implicitly converted to Result<TAnyValue>, so we can return it directly.
return FailureResult.From(result);
}
// Handle success case, access value via result.Value
// ...
}Sometimes, you may want to convert a Result<TValue> to a Result (which doesn't hold a value object, just state and information related to it) while preserving the failure information. You can do this using the implicit operator or the AsResult()/ToResult() methods:
public async Task<Result> PerformGetBookById(GetBookByIdRequest req, CancellationToken ct)
{
var result = await GetBookById(req, ct);
if (result.IsOk)
{
Console.WriteLine($"GetBookById is successful. Book title is {result.Value.Title}");
}
else
{
Console.WriteLine($"GetBookById has failed.");
}
// Implicitly convert Result<GetBookByIdResponse> to Result, preserving state and any failure information.
return result
}Other times, you may want to convert a Result<TSourceValue> to a Result<TTargetValue> while preserving the failure information. You can do this using the AsResult<TTargetValue>()/ToResult<TTargetValue>() methods or any similar overloads with same name:
public async Task<Result<PerformGetBookByIdResponse>> PerformGetBookById(GetBookByIdRequest req, CancellationToken ct)
{
var result = await GetBookById(req, ct);
// Convert Result<GetBookByIdResponse> to Result<PerformGetBookByIdResponse>,
// preserving state and any failure information.
// Factory parameter will be called only if the source result is in Ok state,
// and it will receive the value of type GetBookByIdResponse (TSourceValue)
// to create a value of type PerformGetBookByIdResponse (TTargetValue).
return result.AsResult<PerformGetBookByIdResponse>(v =>
{
// Construct a PerformGetBookByIdResponse using the value of type GetBookByIdResponse (v) and return it.
// This factory will only be called if the result is in Ok state,
// otherwise the failure information will be preserved and returned as a failed Result<PerformGetBookByIdResponse>.
return new PerformGetBookById(v.Name);
});
}For further examples showcasing other functionalities, refer to the following:
- Implicit Operators and QoL Features
- Adding Information to Result Objects
- Create a Failed Result from Exception
- Converting a Result to Another Result
- Mapping Results into Other Objects
- Minimal API Integration: If you are using Minimal APIs and want to convert a
ResultorResult<TValue>into an API response, check out theWebResultEndpointin the ModEndpoints project. This project structures ASP.NET Core Minimal APIs as REPR format endpoints and integrates smoothly with the result pattern, providing automatic response mapping.