• Stars
    star
    819
  • Rank 55,659 (Top 2 %)
  • Language
    C#
  • License
    MIT License
  • Created about 5 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A result abstraction that can be mapped to HTTP response codes if needed.

Ardalis.Result - NuGet NuGet Build Status

Ardails.Result.AspNetCore - NuGet NuGet Β  Ardails.Result.FluentValidation - NuGet NuGet

Follow @ardalis Β  Follow @nimblepros

Result

A result abstraction that can be mapped to HTTP response codes if needed.

Learn More

What Problem Does This Address?

Many methods on service need to return some kind of value. For instance, they may be looking up some data and returning a set of results or a single object. They might be creating something, persisting it, and then returning it. Typically, such methods are implemented like this:

public Customer GetCustomer(int customerId)
{
  // more logic
  return customer;
}

public Customer CreateCustomer(string firstName, string lastName)
{
  // more logic
  return customer;
}

This works great as long as we're only concerned with the happy path. But what happens if there are multiple failure modes, not all of which make sense to be handled by exceptions?

  • What happens if customerId is not found?
  • What happens if required lastName is not provided?
  • What happens if the current user doesn't have permission to create new customers?

The standard way to address these concerns is with exceptions. Maybe you throw a different exception for each different failure mode, and the calling code is then required to have multiple catch blocks designed for each type of failure. This makes life painful for the consumer, and results in a lot of exceptions for things that aren't necessarily exceptional. Like this:

[HttpGet]
public async Task<ActionResult<CustomerDTO>> GetCustomer(int customerId)
{
  try
  {
    var customer = _repository.GetById(customerId);
    
    var customerDTO = CustomerDTO.MapFrom(customer);
    
    return Ok(customerDTO);
  }
  catch (NullReferenceException ex)
  {
    return NotFound();
  }
  catch (Exception ex)
  {
    return new StatusCodeResult(StatusCodes.Status500InternalServerError);
  }
}

Another approach is to return a Tuple of the expected result along with other things, like a status code and additional failure mode metadata. While tuples can be great for individual, flexible responses, they're not as good for having a single, standard, reusable approach to a problem.

The Result pattern provides a standard, reusable way to return both success as well as multiple kinds of non-success responses from .NET services in a way that can easily be mapped to API response types. Although the Ardalis.Result package has no dependencies on ASP.NET Core and can be used from any .NET Core application, the Ardalis.Result.AspNetCore companion package includes resources to enhance the use of this pattern within ASP.NET Core web API applications.

Sample Usage

Creating a Result

The sample folder includes some examples of how to use the project. Here are a couple of simple uses.

Imagine the snippet below is defined in a domain service that retrieves WeatherForecasts. When compared to the approach described above, this approach uses a result to handle common failure scenarios like missing data denoted as NotFound and or input validation errors denoted as Invalid. If execution is successful, the result will contain the random data generated by the final return statement.

public Result<IEnumerable<WeatherForecast>> GetForecast(ForecastRequestDto model)
{
    if (model.PostalCode == "NotFound") return Result<IEnumerable<WeatherForecast>>.NotFound();

    // validate model
    if (model.PostalCode.Length > 10)
    {
        return Result<IEnumerable<WeatherForecast>>.Invalid(new List<ValidationError> {
            new ValidationError
            {
                Identifier = nameof(model.PostalCode),
                ErrorMessage = "PostalCode cannot exceed 10 characters." 
            }
        });
    }

    var rng = new Random();
    return new Result<IEnumerable<WeatherForecast>>(Enumerable.Range(1, 5)
        .Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
    .ToArray());
}

Translating Results to ActionResults

Continuing with the domain service example from the previous section, it's important to show that the domain service doesn't know about ActionResult or other MVC/etc types. But since it is using a Result<T> abstraction, it can return results that are easily mapped to HTTP status codes. Note that the method above returns a Result<IEnumerable<WeatherForecast> but in some cases, it might need to return an Invalid result, or a NotFound result. Otherwise, it returns a Success result with the actual returned value (just like an API would return an HTTP 200 and the actual result of the API call).

You can apply the [TranslateResultToActionResult] attribute to an API Endpoint (or controller action if you still use those things) and it will automatically translate the Result<T> return type of the method to an ActionResult<T> appropriately based on the Result type.

[TranslateResultToActionResult]
[HttpPost("Create")]
public Result<IEnumerable<WeatherForecast>> CreateForecast([FromBody]ForecastRequestDto model)
{
    return _weatherService.GetForecast(model);
}

Alternatively, you can use the ToActionResult helper method within an endpoint to achieve the same thing:

[HttpPost("/Forecast/New")]
public override ActionResult<IEnumerable<WeatherForecast>> Handle(ForecastRequestDto request)
{
    return this.ToActionResult(_weatherService.GetForecast(request));

    // alternately
    // return _weatherService.GetForecast(request).ToActionResult(this);
}

Translating Results to Minimal API Results

Similar to how the ToActionResult extension method translates Ardalis.Results to ActionResults, the ToMinimalApiResult translates results to the new Microsoft.AspNetCore.Http.Results IResult types in .NET 6+. The following code snippet demonstrates how one might use the domain service that returns a Result<IEnumerable<WeatherForecast>> and convert to a Microsoft.AspNetCore.Http.Results instance.

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

The full Minimal API sample can be found in the sample folder.

Mapping Results From One Type to Another

A common use case is to map between domain entities to API response types usually represented as DTOs. You can map a result containing a domain entity to a Result containing a DTO by using the Map method. The following example calls the method _weatherService.GetSingleForecast which returns a Result<WeatherForecast> which is then converted to a Result<WeatherForecastSummaryDto> by the call to Map. Then, the result is converted to an ActionResult<WeatherForecastSummaryDto> using the ToActionResult helper method.

[HttpPost("Summary")]
public ActionResult<WeatherForecastSummaryDto> CreateSummaryForecast([FromBody] ForecastRequestDto model)
{
    return _weatherService.GetSingleForecast(model)
        .Map(wf => new WeatherForecastSummaryDto(wf.Date, wf.Summary))
        .ToActionResult(this);
}

ASP.NET API Metadata

By default, Asp Net Core and API Explorer know nothing about [TranslateResultToActionResult] and what it is doing. To reflect [TranslateResultToActionResult] behavior in metadata generated by API Explorer (which is then used by tools like Swashbuckle, NSwag etc.), you can use ResultConvention:

services.AddControllers(mvcOptions => mvcOptions.AddDefaultResultConvention());

This will add [ProducesResponseType] for every known ResultStatus to every endpoint marked with [TranslateResultToActionResult]. To customize ResultConvention behavior, one may use the AddResultConvention method:

services.AddControllers(mvcOptions => mvcOptions
    .AddResultConvention(resultStatusMap => resultStatusMap
        .AddDefaultMap()
     ));

This code is functionally equivalent to the previous example.

From here you can modify the ResultStatus to HttpStatusCode mapping

services.AddControllers(mvcOptions => mvcOptions
    .AddResultConvention(resultStatusMap => resultStatusMap
        .AddDefaultMap()
        .For(ResultStatus.Ok, HttpStatusCode.OK, resultStatusOptions => resultStatusOptions
            .For("POST", HttpStatusCode.Created)
            .For("DELETE", HttpStatusCode.NoContent))
        .For(ResultStatus.Error, HttpStatusCode.InternalServerError)
    ));

ResultConvention will add [ProducesResponseType] for every result status configured in ResultStatusMap. AddDefaultMap() maps every known ResultType, so if you want to exclude certain ResultType from being listed (e.g. your app doesn't have authentication and authorization, and you don't want 401 and 403 to be listed as SupportedResponseType) you can do this:

services.AddControllers(mvcOptions => mvcOptions
    .AddResultConvention(resultStatusMap => resultStatusMap
        .AddDefaultMap()
        .For(ResultStatus.Ok, HttpStatusCode.OK, resultStatusOptions => resultStatusOptions
            .For("POST", HttpStatusCode.Created)
            .For("DELETE", HttpStatusCode.NoContent))
        .Remove(ResultStatus.Forbidden)
        .Remove(ResultStatus.Unauthorized)
    ));

Alternatively, you can specify which (failure) result statuses are expected from a certain endpoint:

[TranslateResultToActionResult()]
[ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)]
[HttpDelete("Remove/{id}")]
public Result RemovePerson(int id)
{
    // Method logic
}

!!! If you list a certain Result status in ExpectedFailures, it must be configured in ResultConvention on startup.

Another configurable feature is what part of the Result object is returned in case of specific failure:

services.AddControllers(mvcOptions => mvcOptions
    .AddResultConvention(resultStatusMap => resultStatusMap
        .AddDefaultMap()
        .For(ResultStatus.Error, HttpStatusCode.BadRequest, resultStatusOptions => resultStatusOptions
            .With((ctrlr, result) => string.Join("\r\n", result.ValidationErrors)))
    ));

Using Results with FluentValidation

We can use Ardalis.Result.FluentValidation on a service with FluentValidation like that:

public async Task<Result<BlogCategory>> UpdateAsync(BlogCategory blogCategory)
{
    if (Guid.Empty == blogCategory.BlogCategoryId) return Result<BlogCategory>.NotFound();

    var validator = new BlogCategoryValidator();
    var validation = await validator.ValidateAsync(blogCategory);
    if (!validation.IsValid)
    {
        return Result<BlogCategory>.Invalid(validation.AsErrors());
    }

    var itemToUpdate = (await GetByIdAsync(blogCategory.BlogCategoryId)).Value;
    if (itemToUpdate == null)
    {
        return Result<BlogCategory>.NotFound();
    }

    itemToUpdate.Update(blogCategory.Name, blogCategory.ParentId);

    return Result<BlogCategory>.Success(await _blogCategoryRepository.UpdateAsync(itemToUpdate));
}

Getting Started

If you're building an ASP.NET Core Web API you can simply install the 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.

More Repositories

1

CleanArchitecture

Clean Architecture Solution Template: A starting point for Clean Architecture with ASP.NET Core
C#
16,284
star
2

GuardClauses

A simple package with guard clause extensions.
C#
3,063
star
3

ApiEndpoints

A project for supporting API Endpoints in ASP.NET Core web applications.
C#
2,934
star
4

SmartEnum

A base class for quickly and easily creating strongly typed enum replacements in C#.
C#
2,178
star
5

Specification

Base class with tests for adding specifications to a DDD model
C#
1,922
star
6

pluralsight-ddd-fundamentals

Sample code for the Pluralsight DDD Fundamentals course by Julie Lerman and Steve "ardalis" Smith
CSS
841
star
7

CleanArchitecture.WorkerService

A solution template using Clean Architecture for building a .NET Core Worker Service.
C#
720
star
8

ddd-guestbook

A DDD guestbook example written for ASP.NET Core
C#
691
star
9

kata-catalog

My list of code katas
C#
670
star
10

DesignPatternsInCSharp

Samples associated with Pluralsight design patterns in c# courses.
C#
518
star
11

DDD-NoDuplicates

Some design approaches to enforcing a business rule requiring no duplicates. Domain driven design.
C#
512
star
12

SolidSample

C#
474
star
13

new-software-project-checklist

The ultimate new software project decision/question checklist
443
star
14

ddd-vet-sample

A sample meant to demonstrate domain driven design using a veterinary hospital management system.
C#
305
star
15

OrganizingAspNetCore

Offers several different ways to organize content in ASP.NET Core MVC and Razor Pages projects.
C#
207
star
16

Ardalis.Extensions

Some random C# extension methods I've found useful. Published as Ardalis.Extensions on Nuget.
C#
147
star
17

WebApiBestPractices

Resources related to my Pluralsight course on this topic.
C#
144
star
18

CachedRepository

A sample demonstrating the CachedRepository pattern
C#
104
star
19

HttpClientTestExtensions

Extensions for testing HTTP endpoints and deserializing the results. Currently works with XUnit.
C#
92
star
20

CertExpirationCheck

A simple xUnit C# test showing how to verify a certificate for a domain has at least 30 days before it expires.
C#
79
star
21

DotNetDataAccessTour

A tour of different data access approaches in .NET 8+.
C#
73
star
22

AspNetCoreStartupServices

A simple demo listing all services available to an app at startup
C#
69
star
23

GettingStartedWithFilters

Filters samples associated with MSDN article
C#
67
star
24

AspNetCoreRouteDebugger

An ASP.NET Core Route Debugger implemented as a Razor Page
C#
67
star
25

Ardalis.SharedKernel

Some useful base classes, mainly used with the CleanArchitecture template. Also, a template to make your own SharedKernel nuget package.
C#
56
star
26

MediatRAspNetCore

Sample showing MediatR with ASP.NET Core
C#
50
star
27

DevIQ-gatsby

JavaScript
48
star
28

DomainEventsConsole

A console app showing domain events in action using .NET 5
C#
41
star
29

BuilderTestSample

Show how to use a builder with unit tests.
C#
40
star
30

EFCore.Extensions

Extension methods to make working with EF Core easier.
C#
38
star
31

CSharpGenerics

Some samples using C# generics for a Pluralsight course.
C#
38
star
32

Ardalis.ApiClient

Some classes to make working with APIs easier.
C#
33
star
33

TestPatterns

Examples of approaches to unit testing different kinds of code in C#.
C#
33
star
34

StatePattern

An example of the State design pattern in C#
C#
30
star
35

MinimalWebApi

A minimal Web API for ASP.NET Core
C#
29
star
36

BlazorAuth

A sample showing how to add ASP.NET Core Identity auth options to the basic Blazor app.
C#
27
star
37

EditorConfig

A sample editorconfig file for use with .NET / C# applications
27
star
38

DevResources

A list of links to resources.
26
star
39

NotFoundMiddlewareSample

Middleware for detecting and correcting website 404 errors.
C#
25
star
40

DomainModeling

Some examples showing domain modeling tips and traps
C#
21
star
41

AggregateEvents

A sample showing how to use domain events within aggregates
C#
19
star
42

Specification.EFCore

Some utilities for EF Core to use Specifications
C#
16
star
43

LazyLoading

C#
16
star
44

TestSecureApiSample

A sample showing how to test a secure API endpoint using xunit, identityserver4, and environment variables
JavaScript
15
star
45

EulerCSharpStarter

A starting point for solving Project Euler problems using C#.
C#
14
star
46

EnumAlternative

A sample demonstrating strongly typed C# alternatives to enums.
C#
13
star
47

RouteAndBodyModelBinding

A model binder for ASP.NET Core that supports pulling in values from the route and body of a request.
C#
13
star
48

DoubleDispatchSamples

Some examples of double dispatch in C# and how to use in domain models.
C#
13
star
49

ardalis-com-gatsby

Back end content for ardalis.com running with Netlify and Gatsby.
JavaScript
12
star
50

GildedRoseStarter

A starting point for the Gilded Rose kata using dotnet core, C#, and xunit.
C#
11
star
51

ValueObjectsDemo

C#
10
star
52

MongoDbDotNetHelloWorld

Demonstrating how to get started with MongoDB as quickly as possible in dotnet
C#
10
star
53

TestingLogging

A sample showing how to test logging is working as expected in ASP.NET Core apps.
C#
10
star
54

GuardClauses.Analyzers

A project for holding Roslyn analyzers that leverage Ardalis.GuardClauses
C#
10
star
55

ValidateModel

A very small library/package that includes a model validation attribute for ASP.NET Core projects.
C#
9
star
56

RegExLib.com

Source code for the regexlib.com regular expression library site.
C#
9
star
57

SoftwareQualityWorkshop2020

Public resources supporting private software quality workshops I'm delivering in 2020.
C#
9
star
58

RepoMultiImplementation

C#
8
star
59

MediatREndpointsSample

A sample using MediatR to map endpoints using minimal apis
C#
8
star
60

DomainEventsDemo

A sample project showing how to wire up Domain Events
C#
8
star
61

MessagingDemo

C#
8
star
62

RefactoringKataAttempts2024

C#
8
star
63

AspNetCoreNewIn21

Samples showing what's new in ASP.NET Core 2.1
JavaScript
7
star
64

CSharpPropertiesExamples

Some examples of when and how C# properties work in different application scenarios.
C#
6
star
65

EulerNodeStarter

A starting point for solving Project Euler problems using node.js
JavaScript
6
star
66

RefactoringSamples

C#
6
star
67

UnitTestPerf

Some projects used to demonstrate the performance of different unit testing frameworks for .NET Core
C#
6
star
68

AccessTokenSample

A small sample showing how to create, manage, use access tokens.
C#
6
star
69

IntegrationEventSample

Sample showing Domain Events and Integration Events with Web API 2
C#
6
star
70

azure-cloud-native-book

5
star
71

ExtensionMethodSample

Some simple extension method samples
C#
5
star
72

Cachify

A .NET library to optimize data and reflection-based operations using caching.
PowerShell
5
star
73

Refactoring-To-SOLID-Pluralsight

Resources for my Pluralsight course on Refactoring to SOLID C# Code
C#
5
star
74

CodinGameFall2022

C#
5
star
75

EFCoreOwnedEntityTests

A unit test project that tests owned entities in EF Core
C#
5
star
76

EFCoreStringInterpolationDemo

Showing new EF Core 2.0 feature for raw SQL queries
C#
5
star
77

Diagrams

Text and images from UML and similar diagrams
5
star
78

RedisDotNetHelloWorld

Getting started with Redis in dotnet
C#
5
star
79

WhenAllTest

A simple app showing perf gains of using WhenAll for parallel task execution.
C#
5
star
80

AdventOfCode2022

Working on this: https://adventofcode.com for 2022
C#
4
star
81

ardalis

4
star
82

StructureMapLoggingSample

C#
4
star
83

ConsoleLoggingSample

A sample using NLog to create custom loggers that include app-specific data in the output.
C#
4
star
84

ValidationRules

A collection of validation rules for VS Web Tests
C#
3
star
85

CraftsmanshipCalendarIdeas

Ideas for next year's software craftsmanship calendar
3
star
86

CodinGameSpring2021

C#
3
star
87

Logging

Some logging utilities for .NET Core
C#
3
star
88

MigrateDotNetWithIIS

JavaScript
3
star
89

CastleWindsorSample

A console app showing the basic setup of Castle Windsor for IOC
C#
3
star
90

GeekDinnerSample

An incomplete sample showing clean code and DI in ASP.NET Core
C#
3
star
91

RegExLib

Regexlib.com source
C#
3
star
92

MontyHallMvc

An ASP.NET MVC application that lets users try out the Monty Hall problem and shows their stats.
C#
3
star
93

yarp-passthrough

The simplest YARP ASP.NET Core app that just passes everything through to another domain.
C#
3
star
94

AggregatePatterns

C#
2
star
95

EulerNode

A node.js implementation of Project Euler problems.
JavaScript
2
star
96

TennisGameKatas

C#
2
star
97

AspNetCoreLabs

2
star
98

EFCoreFeatureTests

Tests demonstrating EF Core features across versions.
C#
2
star
99

TestRepo

Just a repo for testing some things
2
star
100

FizzBuzzVS2015

A sample FizzBuzz kata implementation using VS2015
Smalltalk
2
star