FluentResults

repository·master·Indexed 25 days ago

https://github.com/altmann/fluentresults

A lightweight .NET library implementing the Result pattern to return success or failure objects instead of using exceptions for flow control. It provides tools for creating Result and Result<T> objects, chaining error and success messages, wrapping exceptions with Result.Try(), and merging multiple results. The library supports custom Error and Success objects, metadata attachment, and integration with Domain-Driven Design, MediatR, and ASP.NET WebApi.

Tokens
2.8K
Snippets
9
Records
18
Agent score
33%

What's inside FluentResults

  1. Use FluentResult with MediatR

    master

    To integrate FluentResult with MediatR request handlers:

    • Return Results, Not Exceptions: Return business validation errors via a Result object from the MediatR request handler instead of throwing exceptions.
    • Validation Pipelines: Use MediatR IPipelineBehavior to inject command and query validation. The behavior should return a Result object containing validation errors back to the consumer.
  2. Implement Domain-Driven Design with FluentResult

    master

    When using FluentResult within a Domain-Driven Design (DDD) context, follow these best practices:

    • Protect Invariants: Use factory methods that return a Result object to ensure domain invariants are maintained.
    • Custom Errors: Create unique error types by inheriting from the Error class or implementing the IError interface.
    • Selective Usage: Only use Result as a return type if the method has a legitimate failure scenario. If a method cannot fail, return the type directly.
    • Error Aggregation: You can merge multiple failed results or return the first failed result immediately to stop processing.
  3. Serialize Results in ASP.NET WebApi or Hangfire

    master

    When working with system boundaries like ASP.NET WebApi or Hangfire, do not serialize FluentResult objects directly.

    Instead, implement a custom ResultDto class for your public API to:

    • Control exactly which data is submitted and serialized.
    • Decouple your public API from third-party libraries like FluentResults.
    • Maintain a stable public API contract even if the underlying library changes.
  4. Configure global factories for Success and Error types

    master

    Use Result.Setup() to override the default factories for ISuccess, IError, and IExceptionalError. This allows you to inject custom logic (like adding timestamps via metadata) whenever a result is created via shorthand methods like Result.Ok() or Result.Fail().

    Result.Setup(cfg =>
    {
        cfg.SuccessFactory = successMessage => new Success(successMessage).WithMetadata("Timestamp", DateTime.Now);
        cfg.ErrorFactory = errorMessage => new Error(errorMessage).WithMetadata("Timestamp", DateTime.Now);
        cfg.ExceptionalErrorFactory = (errorMessage, exception) => new ExceptionalError(errorMessage ?? exception.Message, exception)
            .WithMetadata("Timestamp", DateTime.Now);
    });
  5. Chain multiple error and success messages

    master

    You can append multiple error or success messages to a single Result object using WithError() and WithSuccess().

    var result = Result.Fail("error message 1")
                       .WithError("error message 2")
                       .WithError("error message 3")
                       .WithSuccess("success message 1");
  6. Add metadata to Errors and Successes

    master

    Attach metadata to Error or Success objects using WithMetadata(key, value). This can be done during result creation or within custom error class constructors.

    // During creation
    var result1 = Result.Fail(new Error("Error 1").WithMetadata("metadata name", "metadata value"));
    
    // In a custom error class
    public class DomainError : Error
    {
        public DomainError(string message)
            : base(message)
        {
            WithMetadata("ErrorCode", "12");
        }
    }
  7. Check for specific errors, successes, or exceptions

    master
    Inspect results using HasError<T>(), HasSuccess<T>(), and HasException<T>(). These methods support predicates for fine-grained checking and provide an optional out parameter to retrieve the found object.
  8. Create results based on conditions

    master
    Use FailIf(), OkIf(), and FailIfNotEmpty() to create results based on boolean conditions or error collections. You can also use lazy initialization with Func<string> or Func<IError> to avoid expensive error object creation if the condition is not met.
  9. Execute actions safely with Result.Try()

    master

    Use Result.Try() to wrap code that might throw exceptions. The exception is caught and transformed into a Result object. You can provide a custom catchHandler via the Try method or configure a global DefaultTryCatchHandler via Result.Setup().

    // Basic usage
    var result = Result.Try(() => DoSomethingCritical());
    
    // Custom catch handler per call
    var result2 = Result.Try(() => DoSomethingCritical(), ex => new MyCustomExceptionError(ex));
    
    // Global configuration
    Result.Setup(cfg =>
    {
        cfg.DefaultTryCatchHandler = exception =>
        {
            if (exception is SqlTypeException sqlException)
                return new ExceptionalError("Sql Fehler", sqlException);
            return new Error(exception.Message);
        };
    });
  10. Create a Result object

    master

    Use the Result class for methods that do not return a value (void equivalents). Use Result<T> for methods that return a specific type. You can create success results using Result.Ok() and failure results using Result.Fail() with strings, custom error objects, or lists of errors.

    // For void-like methods
    Result successResult1 = Result.Ok();
    Result errorResult1 = Result.Fail("My error message");
    Result errorResult3 = Result.Fail(new StartDateIsAfterEndDateError(startDate, endDate));
    
    // For methods returning a value
    Result<int> successResult1 = Result.Ok(42);
    Result<int> errorResult = Result.Fail<int>("My error message");