ResultFluent 1.0.9

dotnet add package ResultFluent --version 1.0.9                
NuGet\Install-Package ResultFluent -Version 1.0.9                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ResultFluent" Version="1.0.9" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ResultFluent --version 1.0.9                
#r "nuget: ResultFluent, 1.0.9"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install ResultFluent as a Cake Addin
#addin nuget:?package=ResultFluent&version=1.0.9

// Install ResultFluent as a Cake Tool
#tool nuget:?package=ResultFluent&version=1.0.9                

FluentResult

Nuget downloads Nuget build build mvc spell check codeql analyze codecov License: MIT

This is a lightweight .NET library, that can be used for returning and validating Result without relaying on exceptions.

You can install ResultFluent with NuGet:

dotnet add package ResultFluent

Models

class Result<TModel>
{
    TModel Data { get; }
    ResultComplete Status { get; }
    ICollection<string> Messages { get; }

    bool IsSuccessfulStatus();
}

class ResultOfItems<TItem> : Result<IEnumerable<TItem>>
{
     ResultMetadata Metadata { get; }
}

Creating a Result

A Result can store Data, Messages and Status code.

Model model = new Model();

// create a result which indicates success
Result<Model> successResult = new Result<Model>(model, ResultComplete.Success, messages);
// or
Result<Model> successResult = new Result<Model>(model); // Status = Success, Messages = null
// or just
Result<Model> successResult = Result.Create(model);

// We can also add success messages
Result<Model> successResult = Result.Create(model, "OK");

// create a result which indicates error
Result<Model> errorResult = new Result<Model>(model, ResultComplete.InvalidArgument, new [] { "Model identifier must be a positive number" });
// or
Result<Model> errorResult = Result.CreateResultWithError<Model>(ResultComplete.NotFound, "Model not found");

Map and MapAsync

// simple map
Result<string> result =
    Result
        .Create(5)
        .Map(value => value * 2)
        .Map(value => $"value is {value}");
// result.Data == "value is 10"

// async map
Result<UserModel> result =
    await Result
        .Create(requestedUserId)
        .MapAsync(async userId => await _userRepository.GetByIdAsync(userId))
        .MapAsync(user => ConvertUserToUserModel(user));

Validating

Static validation

int userId = -1;
string userName = string.Empty;
Result<bool> validateResult =
    Result
        .Validate(userId > 0, ResultComplete.InvalidArgument, "User identifier must be positive number")
        .Validate(userName.Length > 0, ResultComplete.InvalidArgument, "User name is required");

// validateResult:
//  Data = false
//  Status = InvalidArgument
//  Messages = string[] { "User identifier must be positive number", "User name is required" }

string userName = "someLongUserName";
Result<bool> validateResult =
    Result
        .Create(userName)
        .Validate(
            x => x.Length < 5,
            ResultComplete.InvalidArgument,
            x => $"User name must be less than 5 symbols, the provided value was {x.Length} symbols.")

// validateResult:
//  Data = "someLongUserName"
//  Status = InvalidArgument
//  Messages = string[] { "User name must be less than 5 symbols, the provided value was 12 symbols." }

Validate can skip rules on fail

Result<bool> validateResult =
    Result
        .Create(userModel)
        .Validate(user => user != null, ResultComplete.InvalidArgument, "User model is null")
        .Validate(user => user.UserId > 0, ResultComplete.InvalidArgument, "User identifier is required", skipOnInvalidResult: true);

Validate nullable

Result<bool> validateResult =
    Result
        .Create<string?>(name)
        .ValidateNotNull(ResultComplete.InvalidArgument, "Name is null")
        .Validate(name => /** name is not null */, ResultComplete.InvalidArgument, "");

Chain validation and mapping

Task<Result<User>> UpdateUserAsync(int userId, string userName)
{
    var userUpdateResult = Result
        .Validate(
            userId > 0,
            ResultComplete.InvalidArgument,
            "User identifier must be positive number")
        .Validate(
            userName.Length > 0,
            ResultComplete.InvalidArgument,
            "User name is required")
        .MapAsync(
            isValid => _userRepository.GetByIdAsync(userId))
        .ValidateAsync(
            user => user != null,
            ResultComplete.NotFound,
            "User doesn't exists")
        .MapAsync(
            async user =>
            {
                user.UserName = userName;
                var updatedUser = await _userRepository.UpdateUserAsync(user);
                return updatedUser;
            })
        .ValidateAsync(
            updatedUser => updatedUser != null,
            ResultComplete.OperationFailed,
            "User was not updated");

    return userUpdateResult;
}

await UpdateUserAsync(10, "");
// Status=InvalidArgument, Messages=["User name is required"], Data=null

await UpdateUserAsync(100, "Rosen");
// One of the following:
//
// Status=NotFound, Messages=["User doesn't exists"], Data=null
// Status=OperationFailed, Messages=["User was not updated"], Data=null
// Status=Success, Messages=null, Data={ UserId=100, UserName="Rosen" }

Switch Mapping

You may need to use multiple method returning Result. For that reason there is Switch method.

Result<int> Multiply(int a, int b) =>
    Result.Create(a + b);

Result<int> AddAndDouble(int a, int b) =>
    Result
        .Create(a + b)
        .Switch(value => Multiply(value, 2))
        .Map(value => $"The result is {value}");

AddAndDouble(2, 3) // Data = "The result is 10"

Ensure successful result and return data

// return user or throw exception
User validatedUser =
    Result
        .Create(user)
		.ValidateNotNull(ResultComplete.InvalidArgument, string.Empty)
		.AsValidData();

// When invalid we throw [ResultValidationException](src/FluentResult/ResultValidationException.cs).
Result
  .Validate(false, ResultComplete.OperationFailed, "Invalid result")
  .AsValidData();

// We can also use `AsValidDataAsync` for `Task<Result<TSource>>` and `Task<ResultOfItems<TSource>>`.
Result.Create(5)
  .MapAsync(Task.FromResult)
  .AsValidDataAsync();

Catch Exception

We can catch async exception by using the Catch extensions.

Result<User> result =
    Result
        .Create(requestedUserId)
        .MapAsync(userId => getUserAsync(userId))
        .CatchAsync(ex => Result.CreateResultWithError<User>(ResultComplete.OperationFailed, ex.Message));

ResultOfItems

The result of items is a result that contain metadata for a collection of items.

ResultOfItems<int> result = new ResultOfItems<int>(
    items: new [] { 4, 5 },
    status: ResultComplete.Success,
    messages: null,
    totalCount: 5,
    pageSize: 3,
    pageIndex: 1,
    count: 2
);

// is the same as
ResultOfItems<int> result = Result.CreateResultOfItems(
    items: new [] { 4, 5 },
    totalCount: 5,
    pageSize: 3,
    pageIndex: 1);

// or may be
ResultOfItems<Item> GetItemsByPage(int pageIndex, int pageSize) =>
    Result
        .Validate(pageIndex >= 0, ResultComplete.InvalidArgument, "Page index is invalid")
        .Validate(pageSize > 0, ResultComplete.InvalidArgument, "Page size is invalid")
        .MapAsync(
            isValid => _itemsRepository.GetByPageAsync(pageIndex, pageSize))
        .ToResultOfItemsAsync(
            data => Result.CreateResultOfItems(data.Items, data.TotalCount, pageSize, pageIndex));

// or simply
ResultOfItems<Item> GetItemsByPage(int pageIndex, int pageSize) =>
    Result
        .Validate(pageIndex >= 0, ResultComplete.InvalidArgument, "Page index is invalid")
        .Validate(pageSize > 0, ResultComplete.InvalidArgument, "Page size is invalid")
        .MapAsync(_ => _itemsRepository.GetByPageAsync(pageIndex, pageSize))
        .ToResultOfItemsAsync();

Combine and CombineAsync

// We can combine from 1 to 5 results.
var helloWorld = Result.Create("Hello").Combine(
	number => Result.Create("World"),
	(a, b) => a + b);

// Sum is 9
var sum = Result.Create(2).Combine(
	number => (
		Result.Create(3),
		Result.Create(4)),
	(a, b, c) => a + b + c);

// async example
Task<Result<Classroom>> UpdateClassroomAsync(UpdateClassroomRequest request) =>
    Result
		.Validate(request != null, ResultComplete.InvalidArgument, "The request must not be null")
	    .MapAsync(_ => _classroomRepository.GetByIdAsync(request.Id))
		.CombineAsync(
		    classroom => (
			    _schoolRepository.GetByIdAsync(request.SchoolId),
				_userRepository.GetByIdAsync(request.TeacherId)),
			async (classroom, school, teacher) =>
			{
			    classroom.School = school;
				classroom.Teacher = teacher;
				await _classroomRepository.UpdateAsync(classroom);
			});

Async helper class

The Async is a helper structure to reduce code definitions.

Task<Result<IReadOnlyList<string>>> GetNamesAsync();
// Becomes
Async<IReadOnlyList<string>> GetNamesAsync();

We can use .AsAsync() extension method.

Async task = Result.Create(5).MapAsync(Task.FromResult).ToAsync();

Deserialize the as Result<TResult>

In order to deserialize it we need to add JsonConstructorAttribute, because all properties are with private set. For this to happen we need to use System.Test.Json or Newtonsoft.Json. This library do not include it, because we do not want to depend on specific serialization. It can be achieved by inhering the class like:

// my /Result{TResult}.cs
using Newtonsoft.Json;
// ...

public class Result<TResult> : FluentResult.Result<TResult>
{
    [JsonConstructor]
    public Result(TResult data, FluentResult.ResultComplete status, ICollection<string> messages)
        : base(data, status, messages)
    {
    }
}

or

using System.Text.Json.Serialization;
// ...

public class Result<TResult> : FluentResult.Result<TResult>
{
    [JsonConstructor]
    public Result(TResult data, FluentResult.ResultComplete status, ICollection<string> messages)
        : base(data, status, messages)
    {
    }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ResultFluent:

Package Downloads
ResultFluent.Mvc

A service result library that can map fluent Result to ActionResult

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.9 19,078 7/11/2023
1.0.8 1,791 4/25/2023
1.0.7 4,232 1/9/2023
1.0.6 8,608 12/10/2022
1.0.5 18,169 9/8/2021
1.0.4 337 9/3/2021
1.0.3 296 9/2/2021
1.0.2 361 9/1/2021
1.0.1 460 5/24/2021