Ardalis.Result

repository·main·Indexed 21 days ago

https://github.com/ardalis/result

A .NET library providing a standardized result abstraction to handle success and failure states without relying on exceptions for control flow. It includes core functionality for domain services, integration with FluentValidation via Ardalis.Result.FluentValidation, and mapping to ASP.NET Core ActionResults or Minimal API IResult types via Ardalis.Result.AspNetCore.

Tokens
4.6K
Snippets
16
Records
18
Agent score
76%

What's inside Ardalis.Result

  1. What is Ardalis.Result?

    main

    Ardalis.Result is a result abstraction designed to provide a standard, reusable way to return both success and various non-success responses (like NotFound or Invalid) from .NET services.

    Instead of using exceptions for flow control (which can be expensive and difficult to manage) or Tuples (which lack a standard structure), the Result pattern allows services to return a Result<T> or Result object. This object can then be easily mapped to HTTP response codes in ASP.NET Core applications.

    Key packages:

    • Ardalis.Result: The core abstraction (no dependencies on ASP.NET Core).
    • Ardalis.Result.AspNetCore: Companion package for mapping results to ActionResult or Minimal API IResult types.
    • Ardalis.Result.FluentValidation: Integration for using results with FluentValidation.
  2. Quickstart: Setup Ardalis.Result in ASP.NET Core

    main
    To use Ardalis.Result in an ASP.NET Core Web API, install the Ardalis.Result.AspNetCore NuGet package. Once installed, apply the [TranslateResultToActionResult] attribute to your controller actions or controllers to enable automatic translation from Result types to ActionResult types.
  3. Use results in Controller-based APIs via extension methods

    main

    If you prefer not to use attributes, you can manually translate a result to an ActionResult<T> within your controller action using extension methods. There are two ways to do this:

    1. On the ControllerBase: Call this.ToActionResult(result).
    2. On the Result instance: Call result.ToActionResult(this), passing the current controller instance as an argument.
    [HttpPost("/Person/Create/")]
    public override ActionResult<Person> Handle(CreatePersonRequestDto request)
    {
        // Option 1: Extension method on ControllerBase
        return this.ToActionResult(_personService.Create(request.FirstName, request.LastName)); // 👈
    
        // Option 2: Extension method on a Result instance
        Result<Person> result = _personService.Create(request.FirstName, request.LastName);
        return result.ToActionResult(this); // 👈
    }
  4. Translate Results to Minimal API Results

    main

    For .NET 6+ Minimal APIs, use the ToMinimalApiResult() extension method. This converts an Ardalis.Result into a Microsoft.AspNetCore.Http.Results.IResult type, which is the standard return type for Minimal API endpoints.

    app.MapPost("/Forecast/New", (ForecastRequestDto request, WeatherService weatherService) =>
    {
        return weatherService.GetForecast(request).ToMinimalApiResult();
    })
    .WithName("NewWeatherForecast");
  5. Configure ASP.NET API Metadata for Result Translation

    main

    By default, ASP.NET Core and API Explorer do not recognize the [TranslateResultToActionResult] attribute. To ensure tools like Swashbuckle or NSwag correctly reflect the possible response types in your API documentation, you must configure ResultConvention during service registration.

    Use AddDefaultResultConvention() to automatically add [ProducesResponseType] for every known ResultStatus to endpoints marked with [TranslateResultToActionResult].

    services.AddControllers(mvcOptions => mvcOptions.AddDefaultResultConvention());
  6. Set up Ardalis.Result for ASP.NET Core Web APIs

    main

    To use automatic result translation in an ASP.NET Core Web API, install the Ardalis.Result.AspNetCore NuGet package. This package provides the necessary attributes and extension methods to convert Result and Result<T> types into appropriate HTTP responses (like ActionResult or Minimal API results).

    dotnet add package Ardalis.Result.AspNetCore
  7. Use the Result pattern to replace exceptions for control flow

    main

    Instead of throwing exceptions for expected business logic outcomes (like a record not being found), use the Result abstraction. This avoids using exceptions for control flow, which is a best practice in web APIs to prevent unnecessary 500 Server Error responses and to clearly communicate status (like a 404 Not Found) to the caller.

    By returning a Result, your method signature becomes intention-revealing, and the calling code can use standard conditionals to handle different outcomes.

    public Result Remove(int id)
    {
        if (!Exists(id))
        {
            return Result.NotFound($"Record with id {id} Not Found");
        }
    
        // Remove the record
    
        return Result.Success();
    }
  8. Install the Ardalis.Result packages

    main

    Depending on your needs, you can install the core library or its specialized integration packages via NuGet:

    • Ardalis.Result: The base package containing all core functionality and types for domain models or business services. It has no third-party dependencies.
    • Ardalis.Result.AspNetCore: Provides helpers to map Ardalis.Result types to ActionResult and IResult for ASP.NET Core Controller-based APIs and Minimal APIs.
    • Ardalis.Result.FluentValidation: Enables easy integration with the FluentValidation library and its error types.
    dotnet add package Ardalis.Result
    dotnet add package Ardalis.Result.AspNetCore
    dotnet add package Ardalis.Result.FluentValidation
  9. Use results in Controller-based APIs via attributes

    main

    For standard ASP.NET Core Controllers, you can use the [TranslateResultToActionResult] attribute on action methods or entire controllers. This applies a filter that automatically converts Result or Result<T> return types into ActionResult types.

    When using this attribute, ensure the method return type is changed to Result or Result<T>. You can also use [ExpectedFailures] to specify which ResultStatus values should be treated as specific failures (e.g., mapping NotFound to a 404).

    [TranslateResultToActionResult] // 👈
    [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)]
    [HttpDelete("Remove/{id}")]
    public Result RemovePerson(int id)
    {
        return _personService.Remove(id);
    }
    
    [TranslateResultToActionResult] // 👈
    [ExpectedFailures(ResultStatus.NotFound, ResultStatus.Invalid)]
    [HttpPost("New/")]
    public Result<Person> CreatePerson(CreatePersonRequestDto request)
    {
        return _personService.Create(request.FirstName, request.LastName);
    }
  10. Translate Results to ActionResults in ASP.NET Core

    main

    To bridge the gap between your domain services (which return Result<T>) and your web API (which returns ActionResult<T>), you can use the Ardalis.Result.AspNetCore package.

    There are two primary ways to perform this translation:

    1. Using the [TranslateResultToActionResult] attribute: Apply this attribute to a controller action or an API Endpoint. It automatically converts the Result<T> return type into the appropriate ActionResult<T> based on the result's state.
    2. Using the ToActionResult helper method: Call this extension method within your endpoint logic to manually convert a result.
    // Option 1: Using the attribute
    [TranslateResultToActionResult]
    [HttpPost("Create")]
    public Result<IEnumerable<WeatherForecast>> CreateForecast([FromBody]ForecastRequestDto model)
    {
        return _weatherService.GetForecast(model);
    }
    
    // Option 2: Using the ToActionResult helper
    [HttpPost("/Forecast/New")]
    public override ActionResult<IEnumerable<WeatherForecast>> Handle(ForecastRequestDto request)
    {
        return this.ToActionResult(_weatherService.GetForecast(request));
        // Alternatively: return _weatherService.GetForecast(request).ToActionResult(this);
    }
  11. Use results in Minimal APIs

    main

    In Minimal APIs, you can automatically translate a Result or Result<T> into a Minimal API response by calling the .ToMinimalApiResult() extension method on the result object in your return statement.

    app.MapPost("/Forecast/New", (ForecastRequestDto request, WeatherService weatherService) =>
    {
        return weatherService.GetForecast(request).ToMinimalApiResult(); // 👈
    });
  12. Customize ResultConvention mapping

    main

    You can use AddResultConvention to customize how ResultStatus maps to HttpStatusCode. This allows you to define specific status codes for different HTTP methods (e.g., returning 201 Created for POST instead of the default 200 OK) or to remove specific statuses from the API metadata (e.g., removing Unauthorized if your app doesn't use authentication).

    To ensure your API documentation is accurate, any ResultStatus you list in the [ExpectedFailures] attribute on a controller action must be configured in your ResultConvention during startup.

    // Customizing mapping and removing specific statuses
    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)
        ));