Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/bundler/docs/rexml-3.2.8
Browse files Browse the repository at this point in the history
  • Loading branch information
ardalis authored May 22, 2024
2 parents efef344 + c5ccf3d commit c86eda2
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 4 deletions.
64 changes: 63 additions & 1 deletion docs/getting-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,66 @@ has_children: true

# Getting Started

Coming soon...
## Adding the Package(s)

To get started, you need to add the appropriate package(s) to your project from NuGet.

[![Ardalis.Result - NuGet](https://img.shields.io/nuget/v/Ardalis.Result.svg?label=Ardalis.Result%20-%20nuget)](https://www.nuget.org/packages/Ardalis.Result) [![NuGet](https://img.shields.io/nuget/dt/Ardalis.Result.svg)](https://www.nuget.org/packages/Ardalis.Result) [![Build Status](https://github.com/ardalis/Result/workflows/.NET%20Core/badge.svg)](https://github.com/ardalis/Result/actions?query=workflow%3A%22.NET+Core%22)

[![Ardails.Result.AspNetCore - NuGet](https://img.shields.io/nuget/v/Ardalis.Result.AspNetCore.svg?label=Ardalis.Result.AspNetCore%20-%20nuget)](https://www.nuget.org/packages/Ardalis.Result.AspNetCore) [![NuGet](https://img.shields.io/nuget/dt/Ardalis.Result.AspNetCore.svg)](https://www.nuget.org/packages/Ardalis.Result.AspNetCore)

[![Ardails.Result.FluentValidation - NuGet](https://img.shields.io/nuget/v/Ardalis.Result.FluentValidation.svg?label=Ardalis.Result.FluentValidation%20-%20nuget)](https://www.nuget.org/packages/Ardalis.Result.FluentValidation) [![NuGet](https://img.shields.io/nuget/dt/Ardalis.Result.FluentValidation.svg)](https://www.nuget.org/packages/Ardalis.Result.FluentValidation)

```bash
dotnet add package Ardalis.Result
```

```bash
dotnet add package Ardalis.Result.AspNetCore
```

```bash
dotnet add package Ardalis.Result.FluentValidation
```

The base `Ardalis.Result` package includes all of the functionality and types needed for use in your domain model or business services. It has no dependency on any third party libraries or UI frameworks.

The `Ardalis.Result.AspNetCore` package includes helpers that can be used to map from `Ardalis.Result` types to `ASP.NET Core` `ActionResult` and `IResult` types used by Controller-based APIs and Minimal APIs.

The `Ardalis.Result.FluentValidation` package is used to allow for easy integration with the [`FluentValidation`](https://www.nuget.org/packages/FluentValidation) package and its validation error types.

## Returning a Result

Imagine you have a method that removes a record from your system:

```csharp
public void Remove(int id)
{
if (!Exists(id))
{
throw new InvalidOperationException($"Record with id {id} Not Found");
}

// Remove the record
}
```

It's almost certain that the id sent to this method comes from user input. While it's possible to validate that the input is properly formed and in a valid range, it's usually not possible to use standard validation techniques to ensure the id actually corresponds to a record in the data store. Hence, a call must be made by the service to confirm the record exists, and if it doesn't, an exception may be thrown to indicate the unexpected behavior. But is this behavior truly *exceptional*, given that it's a user-generated input? We know that in our HTTP APIs a missing record is represented by a 404 Not Found, which while not successful, is certainly better than a 500 Server Error response. It's also recommended to [avoid using exceptions for control flow expecially in web APIs](https://ardalis.com/avoid-using-exceptions-determine-api-status/), which we would need to do in this case if we wanted to return a 404, not a 500, from an API that was calling this method as part of its implementation.

How would this changed if we used a Result abstraction?

```csharp
public Result Remove(int id)
{
if (!Exists(id))
{
return Result.NotFound($"Record with id {id} Not Found");
}

// Remove the record
return Result.Success();
}
```

With this change, exceptions are no longer being used as a secondary means of communicating results back to the calling code. The calling code can use normal conditionals to determine what the result of the operation was, and craft its own response or additional behavior accordingly. Furthermore, the code is more intention-revealing, as it is quite clear what the result of the operation is at each return point.
68 changes: 67 additions & 1 deletion docs/getting-started/quick-start-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,70 @@ nav_order: 1

# Getting Started

If you're building an ASP.NET Core Web API you can simply install the [Ardalis.Result.AspNetCore](https://www.nuget.org/packages/Ardalis.Result.AspNetCore/) package to get started. Then, apply the `[TranslateResultToActionResult]` attribute to any actions or controllers that you want to automatically translate from Result types to ActionResult types.
If you're building an ASP.NET Core Web API you can simply install the [Ardalis.Result.AspNetCore](https://www.nuget.org/packages/Ardalis.Result.AspNetCore/) package to get started. Then, apply the `[TranslateResultToActionResult]` attribute to any actions or controllers that you want to automatically translate from Result types to ActionResult types.

## Minimal API Example

Add the `.ToMinimalApiResult()` extension method to the return statement of minimal APIs:

```csharp
app.MapPost("/Forecast/New", (ForecastRequestDto request, WeatherService weatherService) =>
{
return weatherService.GetForecast(request).ToMinimalApiResult(); // 👈
});
```

## Controller-based APIs

Add the `[[TranslateResultToActionResult]` attribute to action methods to perform automatic translation. Be sure to change the return type of the method to a `Result` or `Result<T>` type.

```csharp
/// <summary>
/// This uses a filter to convert an Ardalis.Result return type to an ActionResult.
/// This filter could be used per controller or globally!
/// </summary>
/// <returns></returns>
[TranslateResultToActionResult] // 👈
[ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)]
[HttpDelete("Remove/{id}")]
public Result RemovePerson(int id)
{
return _personService.Remove(id);
}

/// <summary>
/// This uses a filter to convert an Ardalis.Result return type to an ActionResult.
/// This filter could be used per controller or globally!
/// </summary>
/// <returns></returns>
[TranslateResultToActionResult] // 👈
[ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)]
[HttpPost("New/")]
public Result<Person> CreatePerson(CreatePersonRequestDto request)
{
return _personService.Create(request.FirstName, request.LastName);
}
```

Alternately, you can use an extension method within the action method to translate a result to an `ActionResult<T>`:

```csharp
/// <summary>
/// This uses an extension method to convert to an ActionResult
/// </summary>
/// <returns></returns>
[HttpPost("/Person/Create/")]
public override ActionResult<Person> Handle(CreatePersonRequestDto request)
{
if (DateTime.Now.Second % 2 == 0) // just so we can show both versions
{
// Extension method on ControllerBase
return this.ToActionResult(_personService.Create(request.FirstName, request.LastName)); // 👈
}

Result<Person> result = _personService.Create(request.FirstName, request.LastName);

// Extension method on a Result instance (passing in ControllerBase instance)
return result.ToActionResult(this); // 👈
}
```
4 changes: 2 additions & 2 deletions sample/Ardalis.Result.Sample.Core/Services/PersonService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public Result<Person> Create(string firstName, string lastName)

if (person.Forename == _existPerson.Forename && person.Surname == _existPerson.Surname)
{
return Result.Conflict($"Person ({person.Forename} {person.Surname}) is exist");
return Result.Conflict($"Person ({person.Forename} {person.Surname}) already exists in the system");
}

return Result.Success(person);
Expand All @@ -38,7 +38,7 @@ public Result Remove(int id)
{
if (!_knownIds.Any(knownId => knownId == id))
{
return Result.NotFound("Person Not Found");
return Result.NotFound($"Person with id {id} Not Found");
}

//Pretend removing person
Expand Down

0 comments on commit c86eda2

Please sign in to comment.