Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
F

FluentResults

> 编程语言
Open source

A generalised Result object implementation for .NET/C#

2.6K stars0 likes0 views
WebsiteGitHub

About

A generalised Result object implementation for .NET/C#

FluentResults

**FluentResults is a lightweight .NET library developed to solve a common problem. It returns an object indicating success or failure of an operation instead of throwing/using exceptions.**

You can install FluentResults with NuGet:

Install-Package FluentResults

:heart: The most needed community feature is pushed to nuget: FluentResults.Extensions.AspNetCore Read documentation. Try it, test it, give feedback.

Key Features

  • Generalised container which works in all contexts (ASP.NET MVC/WebApi, WPF, DDD Domain Model, etc)
  • Store multiple errors in one Result object
  • Store powerful and elaborative Error and Success objects instead of only error messages in string format
  • Designing Errors/Success in an object-oriented way
  • Store the root cause with chain of errors in a hierarchical way
  • Provide
    • .NET Standard, .NET Core, .NET 5+ and .NET Full Framework support (details see .NET Targeting)
    • SourceLink support
    • powerful code samples which show the integration with famous or common frameworks/libraries
  • NEW Enhanced FluentAssertions Extension to assert FluentResult objects in an elegant way
  • IN PREVIEW Returning Result Objects from ASP.NET Controller

Why Results instead of exceptions

To be honest, the pattern - returning a Result object indicating success or failure - is not at all a new idea. This pattern comes from functional programming languages. With FluentResults this pattern can also be applied in .NET/C#.

The article Exceptions for Flow Control by Vladimir Khorikov describes very well in which scenarios the Result pattern makes sense or not. See the list of Best Practices and the list of resources to learn more about the Result Pattern.

Creating a Result

A Result can store multiple Error and Success messages.

csharp
// create a result which indicates success
Result successResult1 = Result.Ok();

// create a result which indicates failure
Result errorResult1 = Result.Fail("My error message");
Result errorResult2 = Result.Fail(new Error("My error message"));
Result errorResult3 = Result.Fail(new StartDateIsAfterEndDateError(startDate, endDate));
Result errorResult4 = Result.Fail(new List<string> { "Error 1", "Error 2" });
Result errorResult5 = Result.Fail(new List<IError> { new Error("Error 1"), new Error("Error 2") });

The class Result is typically used by void methods which have no return value.

csharp
public Result DoTask()
{
    if (this.State == TaskState.Done)
        return Result.Fail("Task is in the wrong state.");

    // rest of the logic

    return Result.Ok();
}

Additionally a value from a specific type can also be stored if necessary.

csharp
// create a result which indicates success
Result<int> successResult1 = Result.Ok(42);
Result<MyCustomObject> successResult2 = Result.Ok(new MyCustomObject());

// create a result which indicates failure
Result<int> errorResult = Result.Fail<int>("My error message");

The class Result<T> is typically used by methods with a return type.

csharp
public Result<Task> GetTask()
{
    if (this.State == TaskState.Deleted)
        return Result.Fail<Task>("Deleted Tasks can not be displayed.");

    // rest of the logic

    return Result.Ok(task);
}

Processing a Result

After you get a Result object from a method you have to process it. This means, you have to check if the operation was completed successfully or not. The properties IsSuccess and IsFailed in the Result object indicate success or failure. The value of a Result<T> can be accessed via the properties Value and ValueOrDefault.

…

Designing errors and success messages

There are many Result Libraries which store only simple string messages. FluentResults instead stores powerful object-oriented Error and Success objects. The advantage is all the relevant information of an error or success is encapsulated within one class.

The entire public api of this library uses the interfaces IReason, IError and ISuccess for representing a reason, error or success. IError and ISuccess inherit from IReason. If at least one IError object exists in the Reasons property then the result indicates a failure and the property IsSuccess is false.

You can create your own Success or Error classes when you inherit from ISuccess or IError or if you inherit from Success or Error.

csharp
public class StartDateIsAfterEndDateError : Error
{
    public StartDateIsAfterEndDateError(DateTime startDate, DateTime endDate)
        : base($"The start date {startDate} is after the end date {endDate}")
    { 
        Metadata.Add("ErrorCode", "12");
    }
}

With this mechanism you can also create a class Warning. You can choose if a Warning in your system indicates a success or a failure by inheriting from Success or Error classes.

Further features

Chaining error and success messages

In some cases it is necessary to chain multiple error and success messages in one result object.

csharp
var result = Result.Fail("error message 1")
                   .WithError("error message 2")
                   .WithError("error message 3")
                   .WithSuccess("success message 1");

Create a result depending on success/failure condition

Very often you have to create a fail or success result depending on a condition. Usually you can write it in this way:

csharp
var result = string.IsNullOrEmpty(firstName) ? Result.Fail("First Name is empty") : Result.Ok();

With the methods FailIf() and OkIf() you can also write in a more readable way. You can also supply a collection of errors:

csharp
var result = Result.FailIf(string.IsNullOrEmpty(firstName), "First Name is empty");

bool isValid = false; // Some check
var errors = new List<Error>{ new Error("Error 1"), new Error("Error 2") };

var result2 = Result.FailIf(isValid, errors);

If your success check is based on whether or not there are errors, you can use FailIfNotEmpty()

csharp
var errors = PerformSomeValidation();

var result = Result.FailIfNotEmpty(errors);

If an error instance should be lazily initialized, overloads accepting Func<string> or Func<IError> can be used to that effect:

csharp
var list = Enumerable.Range(1, 9).ToList();

var result = Result.FailIf(
    list.Any(IsDivisibleByTen),
    () => new Error($"Item {list.First(IsDivisibleByTen)} should not be on the list"));

bool IsDivisibleByTen(int i) => i % 10 == 0;

var errors = PerformSomeValidation();

var result = Result.FailIfNotEmpty(
    errors,
    (err) => new Error("Custom error message based on err"));

// rest of the code

Try

In some scenarios you want to execute an action. If this action throws an exception then the exception should be caught and transformed to a result object.

csharp
var result = Result.Try(() => DoSomethingCritical());

You can also return your own Result object

csharp
var result = Result.Try(() => {
    if(IsInvalid()) 
    {
        return Result.Fail("Some error");
    }

    int id = DoSomethingCritical();

    return Result.Ok(id);
});

In the above example the default catchHandler is used. The behavior of the default catchHandler can be overwritten via the global Result settings (see next example). You can control how the Error object looks.

csharp
Result.Setup(cfg =>
{
    cfg.DefaultTryCatchHandler = exception =>
    {
        if (exception is SqlTypeException sqlException)
            return new ExceptionalError("Sql Fehler", sqlException);

        if (exception is DomainException domainException)
            return new Error("Domain Fehler")
                .CausedBy(new ExceptionError(domainException.Message, domainException));

        return new Error(exception.Message);
    };
});

var result = Result.Try(() => DoSomethingCritical());

It is also possible to pass a custom catchHandler via the Try(..) method.

csharp
var result = Result.Try(() => DoSomethingCritical(), ex => new MyCustomExceptionError(ex));

Root cause of the error

You can also store the root cause of the error in the error object. With the method CausedBy(...) the root cause can be passed as Error, list of Errors, string, list of strings or as exception. The root cause is stored in the Reasons property of the error object.

Example 1 - root cause is an exception

csharp
try
{
    //export csv file
}
catch(CsvExportException ex)
{
    return Result.Fail(new Error("CSV Export not executed successfully").CausedBy(ex));
}

Example 2 - root cause is an error

csharp
Error rootCauseError = new Error("This is the root cause of the error");
Result result = Result.Fail(new Error("Do something failed", rootCauseError));

Example 3 - reading root cause from errors

csharp
Result result = ....;
if (result.IsSuccess)
   return;

foreach(IError error in result.Errors)
{
    foreach(ExceptionalError causedByExceptionalError in error.Reasons.OfType<ExceptionalError>())
    {
        Console.WriteLine(causedByExceptionalError.Exception);
    }
}

Metadata

It is possible to add metadata to Error or Success objects.

One way of doing that is to call the method WithMetadata(...) directly where result object is being created.

csharp
var result1 = Result.Fail(new Error("Error 1").WithMetadata("metadata name", "metadata value"));

var result2 = Result.Ok()
                    .WithSuccess(new Success("Success 1")
                                 .WithMetadata("metadata name", "metadata value"));

Another way is to call WithMetadata(...) in constructor of the Error or Success class.

csharp
public class DomainError : Error
{
    public DomainError(string message)
        : base(message)
    { 
        WithMetadata("ErrorCode", "12");
    }
}

Merging

Multiple results can be merged with the static method Merge().

csharp
var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var mergedResult = Result.Merge(result1, result2, result3);

A list of results can be merged to one result with the extension method Merge().

csharp
var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var results = new List<Result> { result1, result2, result3 };

var mergedResult = results.Merge();

You can also merge results containing a collection of elements into a flattened collection with MergeFlat(). The value type and Enumerable type must be specified as generic parameters

csharp
var result1 = Result.Ok(new string[] { "A", "B" });
var result2 = Result.Ok(new string[] { "C", "D" });
var result3 = Result.Ok(new string[] { "E", "F" });

// Will contain ["A", "B", "C", "D", "E", "F"]
var mergedResult = Result.MergeFlat<string, string[]>(result1, result2, result3);

Converting and Transformation

A result object can be converted to another result object with methods ToResult() and ToResult<TValue>().

…

A value of a result object to another value can be transformed via method ``Map(..)`

cshar

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C#dotnetdotnet-coredotnet-standarderror-handling

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言