Giter VIP home page Giter VIP logo

fluentresults's Introduction

FluentResults

FluentResults

Nuget downloads Nuget Build status License: MIT

FluentResults is a lightweight .NET library developed to solve a common problem. It returns an object indicating success or failure of an operation instead of throwing/using exceptions.

You can install FluentResults with NuGet:

Install-Package FluentResults

❤️ The most needed community feature is pushed to nuget: FluentResults.Extensions.AspNetCore Read documentation. Try it, test it, give feedback.

Key Features

  • Generalised container which works in all contexts (ASP.NET MVC/WebApi, WPF, DDD Domain Model, etc)
  • Store multiple errors in one Result object
  • Store powerful and elaborative Error and Success objects instead of only error messages in string format
  • Designing Errors/Success in an object-oriented way
  • Store the root cause with chain of errors in a hierarchical way
  • Provide
    • .NET Standard, .NET Core, .NET 5+ and .NET Full Framework support (details see .NET Targeting)
    • SourceLink support
    • powerful code samples which show the integration with famous or common frameworks/libraries
  • NEW Enhanced FluentAssertions Extension to assert FluentResult objects in an elegant way
  • IN PREVIEW Returning Result Objects from ASP.NET Controller

Why Results instead of exceptions

To be honest, the pattern - returning a Result object indicating success or failure - is not at all a new idea. This pattern comes from functional programming languages. With FluentResults this pattern can also be applied in .NET/C#.

The article Exceptions for Flow Control by Vladimir Khorikov describes very good in which scenarios the Result pattern makes sense and in which not. See the list of Best Practices and the list of resources to learn more about the Result Pattern.

Creating a Result

A Result can store multiple Error and Success messages.

// create a result which indicates success
Result successResult1 = Result.Ok();

// create a result which indicates failure
Result errorResult1 = Result.Fail("My error message");
Result errorResult2 = Result.Fail(new Error("My error message"));
Result errorResult3 = Result.Fail(new StartDateIsAfterEndDateError(startDate, endDate));
Result errorResult4 = Result.Fail(new List<string> { "Error 1", "Error 2" });
Result errorResult5 = Result.Fail(new List<IError> { new Error("Error 1"), new Error("Error 2") });

The class Result is typically used by void methods which have no return value.

public Result DoTask()
{
    if (this.State == TaskState.Done)
        return Result.Fail("Task is in the wrong state.");

    // rest of the logic

    return Result.Ok();
}

Additionally a value from a specific type can also be stored if necessary.

// create a result which indicates success
Result<int> successResult1 = Result.Ok(42);
Result<MyCustomObject> successResult2 = Result.Ok(new MyCustomObject());

// create a result which indicates failure
Result<int> errorResult = Result.Fail<int>("My error message");

The class Result<T> is typically used by methods with a return type.

public Result<Task> GetTask()
{
    if (this.State == TaskState.Deleted)
        return Result.Fail<Task>("Deleted Tasks can not be displayed.");

    // rest of the logic

    return Result.Ok(task);
}

Processing a Result

After you get a Result object from a method you have to process it. This means, you have to check if the operation was completed successfully or not. The properties IsSuccess and IsFailed in the Result object indicate success or failure. The value of a Result<T> can be accessed via the properties Value and ValueOrDefault.

Result<int> result = DoSomething();
     
// get all reasons why result object indicates success or failure. 
// contains Error and Success messages
IEnumerable<IReason> reasons = result.Reasons;

// get all Error messages
IEnumerable<IError> errors = result.Errors;

// get all Success messages
IEnumerable<ISuccess> successes = result.Successes;

if (result.IsFailed)
{
    // handle error case
    var value1 = result.Value; // throws exception because result is in failed state
    var value2 = result.ValueOrDefault; // return default value (=0) because result is in failed state
    return;
}

// handle success case
var value3 = result.Value; // return value and doesn't throw exception because result is in success state
var value4 = result.ValueOrDefault; // return value because result is in success state

Designing errors and success messages

There are many Result Libraries which store only simple string messages. FluentResults instead stores powerful object-oriented Error and Success objects. The advantage is all the relevant information of an error or success is encapsulated within one class.

The entire public api of this library uses the interfaces IReason, IError and ISuccess for representing a reason, error or success. IError and ISuccess inherit from IReason. If at least one IError object exists in the Reasons property then the result indicates a failure and the property IsSuccess is false.

You can create your own Success or Error classes when you inherit from ISuccess or IError or if you inherit from Success or Error.

public class StartDateIsAfterEndDateError : Error
{
    public StartDateIsAfterEndDateError(DateTime startDate, DateTime endDate)
        : base($"The start date {startDate} is after the end date {endDate}")
    { 
        Metadata.Add("ErrorCode", "12");
    }
}

With this mechanism you can also create a class Warning. You can choose if a Warning in your system indicates a success or a failure by inheriting from Success or Error classes.

Further features

Chaining error and success messages

In some cases it is necessary to chain multiple error and success messages in one result object.

var result = Result.Fail("error message 1")
                   .WithError("error message 2")
                   .WithError("error message 3")
                   .WithSuccess("success message 1");

Create a result depending on success/failure condition

Very often you have to create a fail or success result depending on a condition. Usually you can write it in this way:

var result = string.IsNullOrEmpty(firstName) ? Result.Fail("First Name is empty") : Result.Ok();

With the methods FailIf() and OkIf() you can also write in a more readable way:

var result = Result.FailIf(string.IsNullOrEmpty(firstName), "First Name is empty");

If an error instance should be lazily initialized, overloads accepting Func<string> or Func<IError> can be used to that effect:

var list = Enumerable.Range(1, 9).ToList();

var result = Result.FailIf(
    list.Any(IsDivisibleByTen),
    () => new Error($"Item {list.First(IsDivisibleByTen)} should not be on the list"));

bool IsDivisibleByTen(int i) => i % 10 == 0;

// rest of the code

Try

In some scenarios you want to execute an action. If this action throws an exception then the exception should be catched and transformed to a result object.

var result = Result.Try(() => DoSomethingCritical());

In the above example the default catchHandler is used. The behavior of the default catchHandler can be overwritten via the global Result settings (see next example). You can control how the Error object looks.

Result.Setup(cfg =>
{
    cfg.DefaultTryCatchHandler = exception =>
    {
        if (exception is SqlTypeException sqlException)
            return new ExceptionalError("Sql Fehler", sqlException);

        if (exception is DomainException domainException)
            return new Error("Domain Fehler")
                .CausedBy(new ExceptionError(domainException.Message, domainException));

        return new Error(exception.Message);
    };
});

var result = Result.Try(() => DoSomethingCritical());

It is also possible to pass a custom catchHandler via the Try(..) method.

var result = Result.Try(() => DoSomethingCritical(), ex => new MyCustomExceptionError(ex));

Root cause of the error

You can also store the root cause of the error in the error object. With the method CausedBy(...) the root cause can be passed as Error, list of Errors, string, list of strings or as exception. The root cause is stored in the Reasons property of the error object.

Example 1 - root cause is an exception

try
{
    //export csv file
}
catch(CsvExportException ex)
{
    return Result.Fail(new Error("CSV Export not executed successfully").CausedBy(ex));
}

Example 2 - root cause is an error

Error rootCauseError = new Error("This is the root cause of the error");
Result result = Result.Fail(new Error("Do something failed", rootCauseError));

Example 3 - reading root cause from errors

Result result = ....;
if (result.IsSuccess)
   return;

foreach(IError error in result.Errors)
{
    foreach(ExceptionalError causedByExceptionalError in error.Reasons.OfType<ExceptionalError>())
    {
        Console.WriteLine(causedByExceptionalError.Exception);
    }
}

Metadata

It is possible to add metadata to Error or Success objects.

One way of doing that is to call the method WithMetadata(...) directly where result object is being created.

var result1 = Result.Fail(new Error("Error 1").WithMetadata("metadata name", "metadata value"));

var result2 = Result.Ok()
                    .WithSuccess(new Success("Success 1")
                                 .WithMetadata("metadata name", "metadata value"));

Another way is to call WithMetadata(...) in constructor of the Error or Success class.

public class DomainError : Error
{
    public DomainError(string message)
        : base(message)
    { 
        WithMetadata("ErrorCode", "12");
    }
}

Merging

Multiple results can be merged with the static method Merge().

var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var mergedResult = Result.Merge(result1, result2, result3);

A list of results can be merged to one result with the extension method Merge().

var result1 = Result.Ok();
var result2 = Result.Fail("first error");
var result3 = Result.Ok<int>();

var results = new List<Result> { result1, result2, result3 };

var mergedResult = results.Merge();

Converting and Transformation

A result object can be converted to another result object with methods ToResult() and ToResult<TValue>().

// converting a result to a result from type Result<int> with default value of int
Result.Ok().ToResult<int>();

// converting a result to a result from type Result<int> with a custom value
Result.Ok().ToResult<int>(5);

// converting a failed result to a result from type Result<int> without passing a custom value
// because result is in failed state and therefore no value is needed
Result.Fail("Failed").ToResult<int>();

// converting a result to a result from type Result<float>
Result.Ok<int>(5).ToResult<float>(v => v);

// converting a result from type Result<int> to result from type Result<float> without passing the converting
// logic because result is in failed state and therefore no converting logic needed
Result.Fail<int>("Failed").ToResult<float>();

// converting a result to a result from type Result
Result.Ok<int>().ToResult();

A value of a result object to another value can be transformed via method ``Map(..)`

// converting a result to a result from type Result<float>
Result.Ok<int>(5).Map(v => new Dto(5));

Implicit conversion from T to success result Result<T>

string myString = "hello world";
Result<T> result = myString;

Implicit conversion from Error to fail result Result or Result<T>

from a single error

error myError = new Error("error msg");
Result result = myError;

or from a list of errors

List<Error> myErrors = new List<Error>() 
    { 
        new Error("error 1"), 
        new Error("error 2") 
    };
    
Result result = myErrors;

Bind the result to another result

Binding is a transformation that returns a Result | Result<T>. It only evaluates the transformation if the original result is successful. The reasons of both Result will be merged into a new flattened Result.

// converting a result to a result which may fail
Result<string> r = Result.Ok(8)
    .Bind(v => v == 5 ? "five" : Result.Fail<string>("It is not five"));

// converting a failed result to a result, which can also fail, 
// returns a result with the errors of the first result only,
// the transformation is not evaluated because the value of the first result is not available
Result<string> r = Result.Fail<int>("Not available")
    .Bind(v => v == 5 ? "five" : Result.Fail<string>("It is not five"));

// converting a result with value to a Result via a transformation which may fail
Result.Ok(5).Bind(x => Result.OkIf(x == 6, "Number is not 6"));

// converting a result without value into a Result 
Result.Ok().Bind(() => Result.Ok(5));

// just running an action if the original result is sucessful. 
Result r = Result.Ok().Bind(() => Result.Ok());

The Bind has asynchronous overloads.

var result = await Result.Ok(5)
    .Bind(int n => Task.FromResult(Result.Ok(n + 1).WithSuccess("Added one")))
    .Bind(int n => /* next continuation */);

Set global factories for ISuccess/IError/IExceptionalError

Within the FluentResults library in some scenarios an ISuccess, IError or IExceptionalError object is created. For example if the method Result.Fail("My Error") is called then internally an IError object is created. If you need to overwrite this behavior and create in this scenario a custom error class then you can set the error factory via the settings. The same extension points are also available for ISuccess and IExceptionalError.

Result.Setup(cfg =>
{
    cfg.SuccessFactory = successMessage => new Success(successMessage).WithMetadata("Timestamp", DateTime.Now);
    
    cfg.ErrorFactory = errorMessage => new Error(errorMessage).WithMetadata("Timestamp", DateTime.Now);
    
    cfg.ExceptionalErrorFactory = (errorMessage, exception) => new ExceptionalError(errorMessage ?? exception.Message, exception)
    .WithMetadata("Timestamp", DateTime.Now);
});

Mapping errors and successes

If you want to add some information to all successes in a result you can use MapSuccesses(...) on a result object.

var result = Result.Ok().WithSuccess("Success 1");
var result2 = result.MapSuccesses(e => new Success("Prefix: " + e.Message));

If you want to add some information to all errors in a result you can use MapErrors(...) on a result object. This method only iterate through the first level of errors, the root cause errors (in error.Reasons) are not changed.

var result = Result.Fail("Error 1");
var result2 = result.MapErrors(e => new Error("Prefix: " + e.Message));

Handling/catching errors

Similar to the catch block for exceptions, the checking and handling of errors within Result object is also supported using some methods:

// check if the Result object contains an error from a specific type
result.HasError<MyCustomError>();

// check if the Result object contains an error from a specific type and with a specific condition
result.HasError<MyCustomError>(myCustomError => myCustomError.MyField == 2);

// check if the Result object contains an error with a specific metadata key
result.HasError(error => error.HasMetadataKey("MyKey"));

// check if the Result object contains an error with a specific metadata
result.HasError(error => error.HasMetadata("MyKey", metadataValue => (string)metadataValue == "MyValue")); 

All HasError() methods have an optional out parameter result to access the found errorors.

Handling successes

Checking if a result object contains a specific success object can be done with the method HasSuccess()

// check if the Result object contains a success from a specific type
result.HasSuccess<MyCustomSuccess>();

// check if the Result object contains a success from a specific type and with a specific condition
result.HasSuccess<MyCustomSuccess>(success => success.MyField == 3);

All HasSuccess() methods have an optional out parameter result to access the found successes.

Handling/catching exceptions

Checking if a result object contains an error with an specific exception type can be done with the method HasException()

// check if the Result object contains an exception from a specific type
result.HasException<MyCustomException>();

// check if the Result object contains an exception from a specific type and with a specific condition
result.HasException<MyCustomException>(MyCustomException => MyCustomException.MyField == 1);

All HasException() methods have an optional out parameter result to access the found error.

Pattern Matching

var result = Result.Fail<int>("Error 1");

var outcome = result switch
{
     { IsFailed: true } => $"Errored because {result.Errors}",
     { IsSuccess: true } => $"Value is {result.Value}",
     _ => null
};

Deconstruct Operators

var (isSuccess, isFailed, value, errors) = Result.Fail<bool>("Failure 1");

var (isSuccess, isFailed, errors) = Result.Fail("Failure 1");

Logging

Sometimes it is necessary to log results. First create a logger:

public class MyConsoleLogger : IResultLogger
{
    public void Log(string context, string content, ResultBase result, LogLevel logLevel)
    {
        Console.WriteLine("Result: {0} {1} <{2}>", result.Reasons.Select(reason => reason.Message), content, context);
    }

    public void Log<TContext>(string content, ResultBase result, LogLevel logLevel)
    {
        Console.WriteLine("Result: {0} {1} <{2}>", result.Reasons.Select(reason => reason.Message), content, typeof(TContext).FullName);
    }
}

Then you must register your logger in the Result settings:

var myLogger = new MyConsoleLogger();
Result.Setup(cfg => {
    cfg.Logger = myLogger;
});

Finally the logger can be used on any result:

var result = Result.Fail("Operation failed")
    .Log();

Additionally, a context can be passed in form of a string or of a generic type parameter. A custom message that provide more information can also be passed as content.

var result = Result.Fail("Operation failed")
    .Log("logger context", "More info about the result");

var result2 = Result.Fail("Operation failed")
    .Log<MyLoggerContext>("More info about the result");

It's also possible to specify the desired log level:

var result = Result.Ok().Log(LogLevel.Debug);
var result = Result.Fail().Log<MyContext>("Additional context", LogLevel.Error);

You can also log results only on successes or failures:

Result<int> result = DoSomething();

// log with default log level 'Information'
result.LogIfSuccess();

// log with default log level 'Error'
result.LogIfFailed();

Asserting FluentResult objects

Try it with the power of FluentAssertions and FluentResults.Extensions.FluentAssertions. Since v2.0 the assertion package is out of the experimental phase and its really a great enhancement to assert result objects in a fluent way.

.NET Targeting

FluentResults 3.x and above supports .NET Standard 2.0 and .NET Standard 2.1. If you need support for .NET Standard 1.1, .NET 4.6.1 or .NET 4.5 use FluentResults 2.x.

Samples/Best Practices

Here are some samples and best practices to be followed while using FluentResult or the Result pattern in general with some famous or commonly used frameworks and libraries.

Powerful domain model inspired by Domain Driven Design

  • Domain model with a command handler
  • Protecting domain invariants by using for example factory methods returning a Result object
  • Make each error unique by making your own custom Error classes inheriting from IError interface or Error class
  • If the method doesn't have a failure scenario then don't use the Result class as return type
  • Be aware that you can merge multiple failed results or return the first failed result asap

Serializing Result objects (ASP.NET WebApi, Hangfire)

  • Asp.net WebController
  • Hangfire Job
  • Don't serialize FluentResult result objects.
  • Make your own custom ResultDto class for your public api in your system boundaries
    • So you can control which data is submitted and which data is serialized
    • Your public api is independent of third party libraries like FluentResults
    • You can keep your public api stable

MediatR request handlers returning Result objects

Interesting Resources about Result Pattern

Donate

I love this project but implementing features, answering issues or maintaining ci/release pipelines takes time - this is my freetime. If you like FluentResult and you find it useful, consider making a donation. Click on the sponsor button on the top right side.

Contributors

Thanks to all the contributers and to all the people who gave feedback!

Copyright

Copyright (c) Michael Altmann. See LICENSE for details.

fluentresults's People

Contributors

altmann avatar azaferany avatar deepankkartikey avatar edwok avatar eltoncezar avatar gimmedakitty avatar jasonlandbridge avatar karql avatar kulikovekb avatar maltmann avatar marcinjahn avatar michaelmcneilnet avatar mtyski avatar pilouk avatar rhaughton avatar ricksanchez avatar spencerr avatar stbychkov avatar theiam79 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fluentresults's Issues

'Result' does not contain a definition of 'Ok'

This library looks like it would be very helpful for a project I manage, however I can't get it working.

The project is a C# WebAPI (REST service) project being developed in Visual Studio 2017 (Enterprise Edition) and targets the 4.7.2 version of the .NET Framework. I installed version 1.6.0 of FluentResults using the NuGet Package Manager built into VS.

In order to just test it quickly I looked at some of the unit tests in the GitHub repository (specifically, the ResultWithValueTests.cs file) and tried adding the following line in a method:

c# var okResult = FluentResults.Result.Ok(5);

This was taken from line 35 of the ResultWithValueTests.cs file. But the above line causes an error which states:

'Result' does not contain a definition for 'Ok'

and the code of the error is CS0117.

I tried clone the FluentResults repository directly from GitHub through VS 2017 and it works fine. The line does have any problem, but when I try it in my own project.

If anyone has any idea or suggestions on why I a getting this error or what a possible solution is, I would greatly appreciate it. This library seems like it would help streamline a lot of things in my project, but I can't even get a single line of code to work.

Thank you in advance for any assistance.

IEnumerable<Result> with extension method Merge()

IEnumerable<Result> results = new List<Result>();
Result mergedResult = results.Merge();

IEnumerable<Result<int>> results = new List<Result<int>>();
Result<IEnumerable<int>> mergedResult = results.Merge();

Add "WithErrors" methods that accept error object/error message collections?

I've just started with the whole concept of Result objects, and currently trying to use them/learn how to use them in the old pet project of mine.
One thing that I found lacking is the method like .WithErrors, that would accept IEnumerable of either Error objects or error messages. Because there are few places where I already have error collection of some sort, and it would be convenient to attach it to the Result.
Currently I had to write an extension method:

    public static class ResultExtensions
    {
        public static Result WithErrors(this Result result, IEnumerable<string> errorMessages)
        {
            foreach (var errorMessage in errorMessages)
            {
                result.WithError(errorMessage);
            }

            return result;
        }
    }

What are your thoughts on adding something similar to the library?
Or is there something I've missed?

Great FluentAssertions Support

Hi there,

Is there a clean way to use the Result Errors in a Fluent Assertions type of way.

As an example:
createResult.IsFailed.Should().BeFalse(createResult.Errors.ToString());

This outputs something like:
Expected createResult.IsFailed to be false because System.Collections.Generic.List`1[FluentResults.Error], but found True.

My suggestion would be something like:
createResult.IsFailed.Should().BeFalse(createResult.ErrorString);

Which outputs something like:
Expected createResult.IsFailed to be false because due to "'Height' must be greater than '0', 'Width' must be greater than '0'" and "'Size' must be greater than '0'", but found True.

I understand the need to not create a dependency on the FluentAssertion package but otherwise an extension method would make it even more easy to use.

Such as:
createResult.ShouldBeValid();

Which throws an exception outputting the errors.

Thanks in advance!

Generic Result

How do I achieve something like this in the new version?

return Results<GetPaymentLogsQueryResult>.Ok()
                    .WithSuccess($"Retrieved Payment logs successfully")
                    .With(res =>
                    {
                        res.AuditLogs = logs.ToArray();
                    });
    public class GetPaymentLogsQueryResult : ResultBase<GetPaymentLogsQueryResult>
    {
        public Domain.Model.PaymentAuditLog[] AuditLogs { get; internal set; }

        public GetPaymentLogsQueryResult()
        {
        }
    }

How do we get back the exception added with CausedBy()?

When I finish all my operations and get the results, I want to display some logs. I wonder, how am I able to log information about the exception that is attached to a given Error? I see that the only property that I am able to access is Message.

Port FluentResults to TypeScript

@JasonLandbridge wrote originally here

Also, I'm probably the biggest fan of FluentResults ever because I'm making a Typescript version of FluentResults as we speak, with the same syntax so it should work 1 on 1. It's called FluentTypeResults and I have just finished converting everything to Typescript, and now I will test it out in my own project.

The idea behind is to have my .NET Core WebApi always return a Fluent Result. And then in my Vue.js/Typescript front-end cast the API response to a Typescript Result and from there check if there are any errors which I would like to show to the user :D

Let me know is this is good or bad practice because I couldn't find any articles or resources on this.

Using FluentResults inside a MediatR validation pipeline

Hi there,

First off, this is a great library and I'm a big fan of it!

I initially followed this article but instead of reinventing the wheel I started using FluentResults in my Fluent Validation pipeline. Basically all responses coming from my CQRS Queries are wrapped in the Result object, this avoids having to work with exceptions as a method of error handeling.

However, I can't get my pipeline to play nice:

    public class ValidationPipeline<TRequest, TResponse>
        : IPipelineBehavior<TRequest, TResponse>
        where TResponse : class
        where TRequest : IRequest<TResponse>
    {
        private readonly IValidator<TRequest> _compositeValidator;

        public ValidationPipeline(IValidator<TRequest> compositeValidator)
        {
            _compositeValidator = compositeValidator;
        }

        public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate<TResponse> next)
        {
            var result = await _compositeValidator.ValidateAsync(request, cancellationToken);

            if (!result.IsValid)
            {
                Error error = new Error();
                var responseType = typeof(TResponse);

                foreach (var validationFailure in result.Errors)
                {
                    Log.Warning($"{responseType} - {validationFailure.ErrorMessage}");
                    error.Reasons.Add(new Error(validationFailure.ErrorMessage));
                }
                // This always returns null instead of a Result with errors in it. 
                var f = Result.Fail(error) as TResponse;
                return f;

                // This causes an exception saying it cannot find the constructor.
                var invalidResponse =
                        Activator.CreateInstance(invalidResponseType, null) as TResponse;

            }

            return await next();
        }
    }

I also have to somehow convert the Result object back to TResponse, where TResponse is always a Result

Any suggestions are greatly appreciated!

Edit: Could the Result constructor be made public instead of internal?

License?

Hello!
Can you please add license information to this project. Is it MIT?

Thanks!

[Question] Paging with Result

How can I use a PagedList with your library?

public class PagedList<T> : List<T>
{
    public int CurrentPage { get; private set; }
    public int TotalPages { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }

    public bool HasPrevious => CurrentPage > 1;
    public bool HasNext => CurrentPage < TotalPages;

    public PagedList(List<T> items, int count, int pageNumber, int pageSize)
    {
        TotalCount = count;
        PageSize = pageSize;
        CurrentPage = pageNumber;
        TotalPages = (int)Math.Ceiling(count / (double)pageSize);

        AddRange(items);
    }

    public static PagedList<T> ToPagedList(IQueryable<T> source, int pageNumber, int pageSize)
    {
        var count = source.Count();
        var items = source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();

        return new PagedList<T>(items, count, pageNumber, pageSize);
    }
}

Is this Ok?

var obj = myList.AsQueryable().ToPagedList(4, 1);
Result<PagedList<MyCustomObject>> x = Result.Ok(obj); // HERE

Additional OkIf and FailIf features

Just started using the library to refactor some existing code I have. I have a common scenario that has I have come across that I keep having to do something like:

public Result<Foo> GetTheFoos()
{
    Result<Bar> barsResult = GetTheBars();
 
    if (barsResult.IsFailed)
    {
        return barsResult.ToResult<Foo>();
    }
 
    return ConvertBarsToFoos(barsResult.Value);
}

Basically, I’m trying to come up with a way of shortcutting this, similar to the OkIf and FailIf methods, something like:

public Result<Foo> GetTheFoos()
{
    Result<Bar> barsResult = GetTheBars();
 
    return Result.FailIf<Foo>(barsResult, () => ConvertBarsToFoos(barsResult.Value));
}

Would these be suitable extensions to add? Do have you any better ideas?

Overloaded method for Fail to accept multiple errors

First of all thank you for this wonderful library. It really save so much of time & effort

I am having a requirement like below. I have two nested methods. Both returning Result object

Result<bool> Method1()
{
      var m2Result = Method2();
      if (m2Result.IsSuccess)
      {
           //Do some other operations
           return Results.Ok(true);
      }
      else
      {
           return Results.Fail<bool>(m2Result.Errors);       //Here I need to pass the list of errors for tracking purpose. However, at present Fail accepts only one Error object
      }
}

Result<int> Method2()
{
}

This is the problem I am currently facing. Hope my understanding about this library is correct. Or, can we achieve the same by someother means? If not, would it be possible to include this functionality?

Thanks in advance

Regards
Athi

Extension methods for HttpResponsesCodes

I am looking to use this library as a way to handle various different fail states that may occur in an Asp.Net API I am working on.

It's very rough at present, but essentially, I want to expose extension methods like:

public static Result Forbid(this Result result, string message = "Forbidden!")
{
   return result.WithError(new HttpError<string>(HttpResponseCode.Forbidden, message));
 }

Would you be interested in this here, or not at all?

Handling Errors

Hi,

first, thank you for your great work. We're using your library and we'd like to know what is the best way to handle Error?

For example at one point we return a failure:

return Results.Fail(new MoResourceNotFoundError(entityIdentifier.Id));

Now, handling (catching) it is a bit "unfriendly":

if (moSetupResponse.Errors.Any(x => x is MoResourceNotFoundError)

Maybe I'm missing some helper function?

Alternative is throwing/catching exceptions, but we'd like to avoid that here.

Remove Custom from Code

Custom Results (not using Value property, but a custom named property) seems like a good idea to make the Value property more descriptive. In reality it was not used. Best practice is to name the function descriptive which returns a result object and also think about naming the result object correctly.

So the idea is to remove Custom Results and make the code base smaller to be more flexible for other features.

Proposal: Refactoring Reasons to c#9 records

I'm currently working on a new project in which we use the FluentResults library a lot.
One of the shortcomings I see is the amount of boilerplate one has to write when working with typed error objects. Immediately I thought, this would be a perfect usecase for records.

Instead of having to write something like

public class InvalidIdError : Error {

    public InvalidIdError(int id)
    {
        Id = id;;
    }

    public int Id{ get; }
}

this would reduce to

public record InvalidIdError(int Id) : Error { }

So this proposal is probably more of a question, if you have already thought about that, and why it would be a good idea or not.

This would obviously lead to a major breaking change in the API, as existing derivations would have to be rewritten to records, but I think from a QoL perspective it could be an interesting way to go.

Question: Is it possible to get/set value, even if result is failed

First of all, I want to say, that it is a cool and simple library, which can help to up code readability.

But I faced with undesirable behavior (at least in some situations). Sometimes, throwing an exception when accessing Value is not desirable behavior when the Result is failed.

Let me describe my case:
I have method when I enumerate some lists and do some actions with items. As method output, I want to get a list of successfully processed items in Result.Value, and Failures in Failures list. I don't stop the flow if I have some failures, just log it in at the end.

I agree, that IsFailed/IsSuccess flags should depend on the existence of Failures, but what the reason to force block get/set Value?

Reference all ressources/documentation about the result pattern in the readme

Suggestion: Move ILogger to other namespace or rename

Unfortunately when working with Asp.Net and Microsoft.Extensions.Logging constantly need set "using ILogger = Microsoft.Extensions.Logging.ILogger" because there is a match by name.

Would you consider this suggestion and rename or move ILogger?

JsonSerialize Exception because Result.Value is a throw

Hi, congratulations with lib, it's great. But a have a problem when try serialize my Result object.

My Scene:
Anotação 2020-07-23 143619

I hope my exit is successful!

But, because "public bool IsFailed => Reasons.OfType().Any();", my 'Result.Value' is a throw, and Exception when a try serialize...

[Enhancement] Add the ability to add a generic object (or non generic) to the Fail(reason) method

It would be nice to add an abstraction to the 'Fail' to where developers can add error codes and more details around the error.

See Facebook error codes for example:
https://developers.facebook.com/docs/graph-api/using-graph-api/error-handling

Personally, I use https://github.com/ardalis/SmartEnum Smart Enums for this usecase.

I would be able to pass this:

Result.Fail(EnumIsInsertedHere, optionalStringMessage)

Right now if I want to accomplish this I must create a new class inheriting from Result and modify the library.

I hope others will find this useful too.

The goal imo is to not throw exceptions, yet maintain a unified result model for methods. This allows us to work with API's and error messages easily.

Thanks!

Create some typical examples

  • Serializing [DONE]
  • DDD inspired Domain Model [DONE]
  • Hangfire [DONE]

Only push and publishing a new version is now open - OkIf(...) FailIf(...) added.

Allow ToResult to accept a value

Would it be suitable to add a ToResult<T>(T value) option? Currently, when using the ToResult it is just using the default value, or the converter is supplied. For a scenario I have, I would like something other than the default. e.g.,

public Result<bool> DoStuff()
{
    if (something)
    {
        Result fooResult = Foo();

        return fooResult.ToResult(true);
    }

    return Result.Ok(false);
}

I have currently worked round this by doing fooResult.ToResult<bool>().WithValue(true). The implementation would be something like:

public Result<TNewValue> ToResult<TNewValue>(TNewValue newValue)
{
    return new Result<TNewValue>()
        .WithReasons(Reasons)
        .WithValue(newValue);
}

Add metadata to Reasons

@stefandevo mentioned in PR #1 that he want to be able to add the name of the field to errors.

I want to solve this in a more generic way: There is a property MetaInfo property at the Reason class which can store some meta info, e.g. the field name.

[BUG] Result is in status failed. Value is not set.

Hello all,

I am encountering what appears to be a bug.

GOAL:

In some methods, I want to return a Result such as "User", so I query the User, and the User is wrapped by a Results object.

I see in the demo code, that you can wrap an Object like this:
image


In my code, when I do this, I get the error:

Result is in status failed. Value is not set. and Result.Value appears to throw an excpetion.

Here is my code:
image

Error:
image

I get the same error when I do any of the following combinations (these are not in the demo code, I was just problem solving and trying to find a working @scenerio)

Result.Fail<AeonicProxyDto>("This is the error message explaining details of why it failed...").ToResult();

Result.Fail<AeonicProxyDto>("This is the error message explaining details of why it failed...").ToResult<AeonicProxyDto>()

Result.Fail("This is the error message explaining details of why it failed...").ToResult<AeonicProxyDto>();

From what I can see, my code is the same as the examples. If I remove the from the Results<Object> making it like Result instead, the method throws an error, and I must also remove the from the method. This prevents me from returning the User Object.


326 stars on this repository, it looks great! I can't imagine this is a bug with that many stars, but with that said I do not know what I am doing wrong.

Can someone please advise?

@altmann

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.