ResultFluent 1.0.9
dotnet add package ResultFluent --version 1.0.9
NuGet\Install-Package ResultFluent -Version 1.0.9
<PackageReference Include="ResultFluent" Version="1.0.9" />
paket add ResultFluent --version 1.0.9
#r "nuget: ResultFluent, 1.0.9"
// 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
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 | Versions 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. |
-
.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.