SimpleResults.FluentValidation
2.2.2
See the version list below for details.
dotnet add package SimpleResults.FluentValidation --version 2.2.2
NuGet\Install-Package SimpleResults.FluentValidation -Version 2.2.2
<PackageReference Include="SimpleResults.FluentValidation" Version="2.2.2" />
paket add SimpleResults.FluentValidation --version 2.2.2
#r "nuget: SimpleResults.FluentValidation, 2.2.2"
// Install SimpleResults.FluentValidation as a Cake Addin #addin nuget:?package=SimpleResults.FluentValidation&version=2.2.2 // Install SimpleResults.FluentValidation as a Cake Tool #tool nuget:?package=SimpleResults.FluentValidation&version=2.2.2
SimpleResults
A simple library to implement the Result pattern for returning from services. It also provides a mechanism for translating the Result object to an ActionResult.
This library was inspired by Arcadis.Result.
See the API documentation for more information on this project.
Index
- Operation Result Pattern
- Why did I make this library?
- Why don't I use exceptions?
- Installation
- Overview
- Samples
- Language settings
- Contribution
Operation Result Pattern
The purpose of the Result design pattern is to give an operation (a method) the possibility to return a complex result (an object), allowing the consumer to:
- Access the result of an operation; in case there is one.
- Access the success indicator of an operation.
- Access the failure indicator of an operation.
- Access the value (data) of the result if it exists.
- Access the cause of the failure in case the operation was not successful.
- Access an error or success message.
- Access to a collection of error messages.
Why did I make this library?
- I designed this library for use in the DentallApp project because no library like Arcadis.Result followed this response format:
{
"success": true,
"data": { "id": 1 },
"message": "..",
"errors": ["..", ".."]
}
I couldn't change this format because the front-end used it, so I didn't want to make a breaking change.
- I do not want to throw exceptions for all cases.
Why don't I use exceptions?
I usually throw exceptions when developing open source libraries to alert the developer immediately that an error has occurred and must be corrected. In this case, it makes sense to me to throw an exception because the developer can know exactly where the error originated (by looking at the stack trace).
However, when I develop applications I very rarely find a case for using exceptions.
For example, I could throw an exception when a normal user enters empty fields but this does not make sense to me, because it is an error caused by the end user (he/she manages the system from the user interface). So in this case throwing an exception is useless because:
Stack trace included in the exception object is of no use to anyone, neither the end user nor the developer. This is not a bug that a developer should be concerned about.
Nobody cares where the error originated, whether it was in method X or Y, it doesn't matter.
And there are many more examples of errors caused by the end user: the email is duplicated or a password that does not comply with security policies, among others.
I only throw exceptions when the exception object is useful to someone (like a developer); otherwise, I use a Result object to handle errors. I use return statements in my methods to create the error.
This is just my opinion, it is not an absolute truth either. My point of view is more philosophical, so the purpose of my paragraphs is not to indicate the disadvantages of using exceptions, but to explain why for me it does not make sense in some cases to throw exceptions.
Anecdote
At work I had to implement a module to generate a report that performs a monthly comparison of income and expenses for a company, so it was necessary to create a function that is responsible for calculating the percentage of a balance per month:
Percentage.Calculate(double amount, double total);
The
total
parameter if it is zero, will cause a division by zero (undefined operation), however, this value was not provided by an end user, but by the income and expense reporting module, but since I did not implement this module correctly, I created a bug, so the algorithm was passing a zero value for a strange reason (I call this a logic error, caused by the developer).
Since I didn't throw an exception in the
Percentage.Calculate
function, it took me a couple of minutes to find out where the error originated (I didn't know that the problem was a division by zero).
Dividing a floating-point value by zero doesn't throw an exception; it result is not a number (NaN). This was a surprise to me! I didn't know! I was expecting an exception but it was not the case.
If I had thrown an exception, I would have found the error very quickly, just by looking at the stack trace, oh yeah. In this case, it is very useful the exception object, for me and other developers.
Interesting resource about exceptions
Installation
You can run any of these commands from the terminal:
dotnet add package SimpleResults
dotnet add package SimpleResults.AspNetCore
dotnet add package SimpleResults.FluentValidation
SimpleResults package is the main library (the core). The other two packages complement the main library (they are like add-ons).
Overview
You must import the namespace types at the beginning of your class file:
using SimpleResults;
This library provides four main types:
Result
Result<TValue>
ListedResult<TValue>
PagedResult<TValue>
andPagedInfo
With any of these types you can handle errors and at the same time generate errors with the return
statement.
This approach provides a new way to generate an error using return statements without the need to throw exceptions.
See the API documentation for more information on these types.
Using the Result
type
You can use the Result
class when you do not want to return any value.
Example:
public class UserService
{
private readonly List<User> _users;
public UserService(List<User> users) => _users = users;
public Result Update(string id, string name)
{
if (string.IsNullOrWhiteSpace(id))
return Result.Invalid("ID is required");
if (string.IsNullOrWhiteSpace(name))
return Result.Invalid("Name is required");
var user = _users.Find(u => u.Id == id);
if (user is null)
return Result.NotFound();
user.Name = name;
return Result.UpdatedResource();
}
}
You can use the Result<TValue>
class when you want to return a value (such as a User
object).
Example:
public class UserService
{
private readonly List<User> _users;
public UserService(List<User> users) => _users = users;
public Result<User> GetById(string id)
{
if(string.IsNullOrWhiteSpace(id))
return Result.Invalid("ID is required");
var user = _users.Find(u => u.Id == id);
if(user is null)
return Result.NotFound();
return Result.Success(user, "User found");
}
}
Using the ListedResult
type
You can use the ListedResult<TValue>
class when you want to return a set of values (such as a collection of objects of type User
).
Example:
public class UserService
{
private readonly List<User> _users;
public UserService(List<User> users) => _users = users;
public ListedResult<User> GetAll()
{
if(_users.Count == 0)
return Result.Failure("No user found");
return Result.ObtainedResources(_users);
}
}
Using the PagedResult
type
You can use the PagedResult<TValue>
class when you want to include paged information and a data collection in the result.
Example:
public class UserService
{
private readonly List<User> _users;
public UserService(List<User> users) => _users = users;
public PagedResult<User> GetPagedList(int pageNumber, int pageSize)
{
if(pageNumber <= 0)
return Result.Invalid("PageNumber must be greater than zero");
int itemsToSkip = (pageNumber - 1) * pageSize;
var data = _users
.Skip(itemsToSkip)
.Take(pageSize);
if (data.Any())
{
var pagedInfo = new PagedInfo(pageNumber, pageSize, _users.Count);
return Result.Success(data, pagedInfo);
}
return Result.Failure("No results found");
}
}
Creating a resource with Result<T>
type
You can tell the method to return a successfully created resource as a result by using the Result.CreatedResource
method.
In addition, you can use the CreatedGuid
class to specify the ID assigned to the created resource.
Example:
public class UserService
{
private readonly List<User> _users;
public UserService(List<User> users) => _users = users;
public Result<CreatedGuid> Create(string name)
{
if(string.IsNullOrWhiteSpace(name))
return Result.Invalid("Name is required");
var guid = Guid.NewGuid();
_users.Add(new User { Id = guid.ToString(), Name = name });
return Result.CreatedResource(guid);
}
}
You can also use the CreatedId
class when using an integer as identifier.
An example using Entity Framework Core:
public class UserModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class UserService
{
private readonly DbContext _db;
public UserService(DbContext db) => _db = db;
public Result<CreatedId> Create(string name)
{
if(string.IsNullOrWhiteSpace(name))
return Result.Invalid("Name is required");
var user = new UserModel { Name = name };
_db.Add(user);
_db.SaveChanges();
return Result.CreatedResource(user.Id);
}
}
Integration with ASP.NET Core
You can convert the Result
object to an ActionResult using the ToActionResult
extension method.
You need to install the SimpleResults.AspNetCore package to have access to the extension method.
Example:
public class UserRequest
{
public string Name { get; init; }
}
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController(UserService userService) => _userService = userService;
[HttpPost]
public ActionResult<Result<CreatedGuid>> Create([FromBody]UserRequest request)
=> _userService.Create(request.Name).ToActionResult();
[HttpPut("{id}")]
public ActionResult<Result> Update(string id, [FromBody]UserRequest request)
=> _userService.Update(id, request.Name).ToActionResult();
[HttpGet("{id}")]
public ActionResult<Result<User>> Get(string id)
=> _userService.GetById(id).ToActionResult();
[HttpGet("paged")]
public ActionResult<PagedResult<User>> GetPagedList([FromQuery]PagedRequest request)
=> _userService
.GetPagedList(request.PageNumber, request.PageSize)
.ToActionResult();
[HttpGet]
public ActionResult<ListedResult<User>> Get()
=> _userService.GetAll().ToActionResult();
}
See the API documentation for a list of available extension methods.
Using TranslateResultToActionResult as an action filter
You can also use the TranslateResultToActionResult
filter to translate the Result object to ActionResult.
TranslateResultToActionResult
class will internally call the ToActionResult
method and perform the translation.
Example:
[TranslateResultToActionResult]
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
private readonly UserService _userService;
public UserController(UserService userService) => _userService = userService;
[HttpGet("{id}")]
public Result<User> Get(string id) => _userService.GetById(id);
}
The return value of Get
action is a Result<User>
. After the action is executed, the filter (i.e. TranslateResultToActionResult
) will run and translate the Result<User>
to ActionResult.
See the source code, it is very simple.
Add action filter as global
If you do not want to use the filter on each controller, you can add it globally for all controllers (see sample).
builder.Services.AddControllers(options =>
{
// Add filter for all controllers.
options.Filters.Add<TranslateResultToActionResultAttribute>();
});
This way you no longer need to add the TranslateResultToActionResult
attribute on each controller or individual action.
Translate Result object to HTTP status code
SimpleResults.AspNetCore package is responsible for translating the status of a Result object into an HTTP status code.
The following table is used as a reference to know which type of result corresponds to an HTTP status code:
Result type | HTTP status code |
---|---|
Result.Success | 200 - Ok |
Result.CreatedResource | 201 - Created |
Result.UpdatedResource | 200 - Ok |
Result.DeletedResource | 200 - Ok |
Result.ObtainedResource | 200 - Ok |
Result.Invalid | 400 - Bad Request |
Result.NotFound | 404 - Not Found |
Result.Unauthorized | 401 - Unauthorized |
Result.Conflict | 409 - Conflict |
Result.Failure | 422 - Unprocessable Entity |
Result.CriticalError | 500 - Internal Server Error |
Result.Forbidden | 403 - Forbidden |
Integration with Fluent Validation
You need to install the SimpleResults.FluentValidation package to have access to the extension methods.
Example:
public class UserService
{
public Result Create(CreateUserRequest request)
{
ValidationResult result = new CreateUserValidator().Validate(request);
if(result.IsFailed())
return Result.Invalid(result.AsErrors());
// Some code..
}
}
See the API documentation for a list of available extension methods.
Samples
You can find a complete and functional example in these projects:
- SimpleResults.Example
- SimpleResults.Example.AspNetCore
- SimpleResults.Example.Web.Tests
- SimpleResults.Example.FluentValidation
Language settings
SimpleResults
uses default messages in English. You can change the language in this way:
// Allows to load the resource in Spanish.
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("es");
In ASP.NET Core applications, the UseRequestLocalization extension method is used:
app.UseRequestLocalization("es");
At the moment, only two languages are available:
- English
- Spanish
Feel free to contribute 😄
Contribution
Any contribution is welcome! Remember that you can contribute not only in the code, but also in the documentation or even improve the tests.
Follow the steps below:
- Fork it
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Added some feature')
- Push to the branch (git push origin my-new-feature)
- Create new Pull Request
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
- FluentValidation (>= 10.0.4)
- SimpleResults (>= 2.2.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.