Skip to content

Result object implementation to contain values or failures and used as service method returns, either in-process or over network.

License

Notifications You must be signed in to change notification settings

modabas/ModResults

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ModResults

Nuget downloads Nuget License: MIT

Result Pattern that provides a structured way to represent success or failure with optional details, enhancing readability and maintainability in codebases and designed to be used either in-process or over the network.

It is robust, leveraging nullability annotations, immutability (init properties), and factory methods for clarity.

Contains Result and Result<TValue> implementations with a default Failure implementation which are ready to be used out of the box.

Also contains a Result<TValue, TFailure> implementation, but this requires further development for a custom failure class at least.

ModResults.FluentValidations

Default implementations to convert failed FluentValidations.Results.ValidationResult instances to invalid Result and Result<TValue> objects.

ModResults.MinimalApis

Default implementations to convert Result and Result<TValue> instances in either Ok or any FailureType state to Microsoft.AspNetCore.Http.IResult.

ModResults.Orleans

Surrogate and converter implementations for Result and Result<TValue> objects to be used in Microsoft.Orleans projects.

Usage

Creating a Result or Result<TValue> instance

Creating an Ok Result instance is done by calling Result.Ok() or Result<TValue>.Ok(TValue value) static methods. Result<TValue> also has an implicit operator to convert an instance of type TValue to a Result<TValue> in Ok state.

Creating a Failed Result instance is done by calling corresponding static method for each FailureType (i.e. Result.NotFound() or Result<TValue>.NotFound() for creating a Result object in Failed state with FailureType NotFound).

public async Task<Result<GetBookByIdResponse>> GetBookById(GetBookByIdRequest req, CancellationToken ct)
{
    var entity = await db.Books.FirstOrDefaultAsync(b => b.Id == req.Id, ct);

    return entity is null ?
      Result<GetBookByIdResponse>.NotFound() :
      Result.Ok(new GetBookByIdResponse(
        Id: entity.Id,
        Title: entity.Title,
        Author: entity.Author,
        Price: entity.Price));
}

Checking state of a Result or Result<TValue> instance

State of a result instance is either Ok or Failed. State can be checked from result.IsOk and result.IsFailed boolean properties.

If state is ok, Result<TValue> instance contains a not null Value property of type TValue.

If state is failed, Result and Result<TValue> instances contain a not null Failure property of type Failure.

public async Task<Result> PerformGetBookById(GetBookByIdRequest req, CancellationToken ct)
{
    var result = await GetBookById(req, ct);
    if (result.IsOk)
    {
        Console.WriteLine($"GetBookById is successful. Book title is {result.Value.Title}");
    }
    else
    {
        Console.WriteLine($"GetBookById has failed.");
    }
    return result
}

Adding information to Result instances

All types of Result implementations contain a Statement property which encapsulates collections of Fact and Warning classes.

See various WithFact and WithWarning extension methods to add fact and warning information to result instances.

Default Failure implementation used in Result and Result<TValue> objects a collection of Error class.

See various static methods of Result objects to create a Failed Result containing Error information. Errors can only be attached to a Failed Result instance during Result instance creation and cannot be mutated afterwards.

public async Task<Result<GetBookByIdResponse>> GetBookById(GetBookByIdRequest req, CancellationToken ct)
{
    var entity = await db.Books.FirstOrDefaultAsync(b => b.Id == req.Id, ct);

    return entity is null ?
      Result<GetBookByIdResponse>.NotFound($"Book with id: {0} not found.", req.Id)
        .WithFact($"dbContextId: {db.ContextId}") :
      Result.Ok(new GetBookByIdResponse(
        Id: entity.Id,
        Title: entity.Title,
        Author: entity.Author,
        Price: entity.Price))
      .WithFact($"dbContextId: {db.ContextId}");
}

Creating a Failed Result or Result<TValue> instance from an exception

Creating a Failed Result instance from an exception is done by calling corresponding static method with an exception input parameter for each FailureType, or can be left to implicit operator which creates a Failed Result with FailureType CriticalError by default.

Exception object is converted to an Error object and added to Error collection of Failure.

public async Task<Result<GetBookByIdResponse>> GetBookById(GetBookByIdRequest req, CancellationToken ct)
{
    try
    {
        var entity = await db.Books.FirstOrDefaultAsync(b => b.Id == req.Id, ct);

        return entity is null ?
          Result<GetBookByIdResponse>.NotFound() :
          Result.Ok(new GetBookByIdResponse(
            Id: entity.Id,
            Title: entity.Title,
            Author: entity.Author,
            Price: entity.Price));
    }
    catch (Exception ex)
    {
        return ex;
    }
}

Converting Result<TValue> to Result

Converting a Result<TValue> object to Result is straightforward, and can be achieved by calling parameterless ToResult() method of Result<TValue> instance or can be left to implicit operator.

Any Failure information, Errors, Facts and Warnings are automatically copied to output Result.

Converting a Result instance to Result<TValue> or a Result<TSourceValue> instance to Result<TValue>

These types of conversions require a TValue object creation for Ok state of output Result<TValue> object. Any Failure information, Errors, Facts and Warnings are automatically copied to output Result.

There are various overloads of ToResult() and ToResultAsync() extension methods that accepts TValue object factory functions and additional parameters for such conversions.

public record Request(string Name);

public record Response(string Reply);

public Result<Response> GetResponse(Request req, CancellationToken ct)
{
    Result<string> result = await GetMeAResultOfString(req.Name, ct);
    return result.ToResult(x => new Response(x));
}

private async Task<Result<string>> GetMeAResultOfString(string name, CancellationToken ct)
{
    //some async stuff
    await Task.CompletedTask;

    return $"Hello {name}";
}

Mapping Result and Result<TValue> instances to another object type (usually not a Result)

These types of conversions require two seperate output object factory functions for Ok state and Failed state of input Result object.

There are various overloads of Map() and MapAsync() extension methods that accepts object factory functions and additional parameters for such conversions.

ToResult extension methods described previously, also use these Map methods underneath.

  public static Result<TTargetValue> ToResult<TValue, TState, TTargetValue>(
    this Result<TValue> result,
    Func<TValue, TState, TTargetValue> valueFuncOnOk,
    TState state)
  {
    return result.Map<TValue, TState, Result<TTargetValue>>(
      (okResult, state) => Result<TTargetValue>.Ok(
        valueFuncOnOk(
          okResult.Value!,
          state))
        .WithStatementsFrom(okResult),
      (failResult, _) => Result<TTargetValue>.Fail(failResult),
      state);
  }
}

Converting Result or Result<TValue> object to Minimal Apis Microsoft.AspNetCore.Http.IResult

ModResults.MinimalApis project contains ToResponse() method implementations to convert Result and Result<TValue> instances in either Ok or Failed state to Microsoft.AspNetCore.Http.IResult.

public record GetBookByIdRequest(Guid Id);

public record GetBookByIdResponse(Guid Id, string Title, string Author, decimal Price);

app.MapPost("GetBookById/{Id}",
    async Task<IResult> (
    [AsParameters] GetBookByIdRequest req,
    [FromServices] IBookService svc,
    CancellationToken cancellationToken) =>
{
    Result<GetBookByIdResponse> result = await svc.GetBookById(req.Id, cancellationToken);
    return result.ToResponse();
}).Produces<GetBookByIdResponse>();

About

Result object implementation to contain values or failures and used as service method returns, either in-process or over network.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages