EV.Result 2.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package EV.Result --version 2.0.0
                    
NuGet\Install-Package EV.Result -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EV.Result" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EV.Result" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="EV.Result" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EV.Result --version 2.0.0
                    
#r "nuget: EV.Result, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EV.Result@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EV.Result&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=EV.Result&version=2.0.0
                    
Install as a Cake Tool

EV.Result

REI Pattern (Result with Exception Intelligence) es un patrón que he tenido la oportunidad de desarrollar con el objetivo de contribuir a la comunidad de .NET, evolucionando el tradicional patrón Result mediante la integración del manejo de excepciones con el enfoque funcional clásico.

He diseñado esta propuesta con la humilde aspiración de unir ambos enfoques: la elegancia del Railway-Oriented Programming (ROP) junto con la inteligencia de un manejo avanzado de excepciones, compartiendo lo que he aprendido a lo largo de mi experiencia.

EV.Result es mi implementación concreta de REI - una solución que quiero poner a disposición de la comunidad para .NET, la cual he creado con el propósito de ofrecer una alternativa al manejo tradicional de excepciones, basándome en las lecciones que he ido recopilando durante mi trayectoria profesional.

El patrón que propongo retorna objetos Result que encapsulan tanto el éxito como el fallo, pero con un enfoque que busca ser útil: preservar las excepciones de forma inteligente, manteniendo toda su información hasta los niveles más profundos, mientras se conserva el control del flujo mediante ROP.

✨ Características

Type-safe: Manejo de errores en tiempo de compilación
🎨 API Fluida: Encadenamiento de operaciones con OnSuccess, OnFailure y Map
🔄 Railway-Oriented Programming (ROP): Patrón de bifurcación para flujos complejos 📋 JSON Estructurado: Convierte cadenas de excepciones complejas en JSON legible para logging, monitoreo y debugging
🎯 Result genérico: Result<T> para operaciones que retornan valores
🏷️ Conversiones implícitas: Código más limpio y expresivo
🎨 Tipos de errores: 9 tipos predefinidos para clasificación precisa
🔍 Captura detallada de excepciones: Serialización completa de inner exceptions
📊 Severidad automática: Clasificación inteligente de excepciones
🔌 Zero dependencias externas: Solo Newtonsoft.Json
🚀 .NET Standard 2.0+: Compatible con .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, Xamarin, Unity y más

🔥 El PLUS que marca la diferencia:

Normalmente cuando una aplicación falla, el primer mensaje de error rara vez dice la verdad completa. La causa raíz está enterrada en las inner exceptions, por tal razón esta librería captura AUTOMÁTICAMENTE toda la cadena:

✅ Exception principal + TODAS las inner exceptions (hasta 30 niveles)

✅ Stack trace completo de cada nivel.

✅ Tipo exacto de cada excepción.

✅ Source, TargetSite y Data de cada error.

✅ Severidad calculada automáticamente.

✅ Todo serializado en JSON estructurado.

📦 Instalación

Package Manager Console

Install-Package EV.Result

.NET CLI

dotnet add package EV.Result

PackageReference

<PackageReference Include="EV.Result" Version="2.0.0" />

🚀 Inicio Rápido

1. Operación Simple sin Valor de Retorno

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class UserService
{
    public Result ActivateUser(Guid userId)
    {
        var user = _repository.GetById(userId);
        
        if (user == null)
        {
            return Result.Failure(
                Error.NotFound("User.NotFound", "Usuario no encontrado")
            );
        }

        user.Activate();
        _repository.Update(user);

        return Result.Success();
    }
}

// Uso
var result = userService.ActivateUser(userId);

if (result.IsSuccess)
{
    Console.WriteLine("Usuario activado correctamente");
}
else
{
    Console.WriteLine($"Error [{result.Error.Type}]: {result.Error.Message}");
}

2. Operación con Valor de Retorno

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class ProductService
{
    public Result<Product> GetProductById(Guid productId)
    {
        var product = _repository.GetById(productId);

        if (product == null)
        {
            return Result.Failure<Product>(
                Error.NotFound("Product.NotFound", "Producto no encontrado")
            );
        }

        return Result.Success(product);
    }
}

// Uso
var result = productService.GetProductById(productId);

if (result.IsSuccess)
{
    var product = result.Value;
    Console.WriteLine($"Producto: {product.Name} - ${product.Price}");
}

📚 Conceptos Básicos

Clase Error

using EV.Result.Abstractions.Errors;
using System.Collections.Generic;

public class Error
{
    public static readonly Error None;
    public static readonly Error NullValue;
    
    public string Code { get; set; }
    public string Message { get; set; }
    public ErrorType Type { get; set; }
    public List<ExceptionDetail> ExceptionDetails { get; set; }
    public string ExceptionJson { get; set; }

    // Métodos factory para crear errores específicos
    public static Error Failure(string code, string message);
    public static Error Validation(string code, string message);
    public static Error NotFound(string code, string message);
    public static Error Conflict(string code, string message);
    public static Error Unauthorized(string code, string message);
    public static Error Forbidden(string code, string message);
    public static Error Timeout(string code, string message);
    public static Error DependencyFailure(string code, string message);
    public static Error Unknown(string code, string message);
    
    // Métodos para crear errores desde excepciones
    public static Error FromException(string code, Exception exception, ErrorType type = ErrorType.Failure, int maxLevel = 30);
    public static Error TimeoutFromException(string code, Exception exception, int maxLevel = 30);
    public static Error DependencyFailureFromException(string code, Exception exception, int maxLevel = 30);
    public static Error UnknownFromException(string code, Exception exception, int maxLevel = 30);
    public static Error FailureFromException(string code, Exception exception, int maxLevel = 30);
}

Enum ErrorType

using EV.Result.Abstractions.Errors;

public enum ErrorType
{
    None = 0,              // Sin error
    Failure = 1,           // Error genérico
    Validation = 2,        // Error de validación
    NotFound = 3,          // Recurso no encontrado
    Conflict = 4,          // Conflicto con estado actual
    Unauthorized = 5,      // No autenticado
    Forbidden = 6,         // No autorizado
    Timeout = 7,           // Timeout de operación
    DependencyFailure = 8, // Fallo en dependencia externa
    Unknown = 9            // Error desconocido
}

Clase Result

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;

public class Result
{
    public bool IsSuccess { get; }
    public bool IsFailure { get; }
    public Error Error { get; }

    // Métodos para crear resultados sin valor
    public static Result Success();
    public static Result Failure(Error error);
    
    // Métodos para crear resultados con valor
    public static Result<TValue> Success<TValue>(TValue value);
    public static Result<TValue> Failure<TValue>(Error error);
    
    // Métodos helper
    public static Result Create(bool condition, Error error);
    public static Result<TValue> Create<TValue>(TValue value, Error error) where TValue : class;
}

Clase Result<T>

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class Result<TValue> : Result
{
    public TValue Value { get; }
    
    // Métodos fluidos
    public Result<TValue> OnSuccess(Action<TValue> action);
    public Result<TValue> OnFailure(Action<Error> action);
    public Result<TNewValue> Map<TNewValue>(Func<TValue, TNewValue> mapper);
    public TValue GetValueOrDefault(TValue defaultValue);
    
    // Conversión implícita
    public static implicit operator Result<TValue>(TValue value);
}

🛡️ Manejo de Excepciones

Captura Detallada de Excepciones

EV.Result incluye soporte integrado para capturar y serializar excepciones con todos sus detalles, incluyendo inner exceptions completas.

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;
using System.Net.Http;

public class PaymentService
{
    public Result<Payment> ProcessPayment(PaymentRequest request)
    {
        try
        {
            var payment = _paymentGateway.Charge(request);
            return Result.Success(payment);
        }
        catch (TimeoutException ex)
        {
            // Captura automática de todos los detalles de la excepción
            return Result.Failure<Payment>(
                Error.TimeoutFromException("Payment.Timeout", ex)
            );
        }
        catch (HttpRequestException ex)
        {
            return Result.Failure<Payment>(
                Error.DependencyFailureFromException("Payment.GatewayError", ex)
            );
        }
        catch (Exception ex)
        {
            return Result.Failure<Payment>(
                Error.UnknownFromException("Payment.UnexpectedError", ex)
            );
        }
    }
}

Métodos FromException

// Métodos disponibles para crear errores desde excepciones:

// Genérico - permite especificar el tipo de error
Error.FromException("Code", exception, ErrorType.Validation, maxLevel: 30);

// Específicos - tipos de error predefinidos
Error.TimeoutFromException("Code", exception);
Error.DependencyFailureFromException("Code", exception);
Error.UnknownFromException("Code", exception);
Error.FailureFromException("Code", exception);

// maxLevel: Niveles de inner exceptions a capturar (por defecto 30)

Clase ExceptionDetail

using EV.Result.Abstractions.Exceptions;
using System.Collections;

public class ExceptionDetail
{
    public string ErrorLevel { get; set; }        // Nivel en la cadena (1, 2, 3...)
    public string ErrorMessage { get; set; }      // Mensaje de la excepción
    public string ErrorSource { get; set; }       // Fuente del error
    public string ErrorStackTrace { get; set; }   // Stack trace completo
    public string ErrorTargetSite { get; set; }   // Método donde ocurrió
    public IDictionary ErrorData { get; set; }    // Data adicional
    public string ExceptionType { get; set; }     // Tipo completo de la excepción
    public string Severity { get; set; }          // Warning, Error, Critical
}

Ejemplo de JSON Serializado

Cuando captura una excepción, EV.Result genera un JSON estructurado como este:

[
  {
    "ErrorLevel": "1",
    "ErrorMessage": "Error procesando el pago",
    "ErrorSource": "PaymentGateway",
    "ErrorStackTrace": "at PaymentGateway.Charge(...)",
    "ErrorTargetSite": "Charge",
    "ExceptionType": "PaymentException",
    "Severity": "Error"
  },
  {
    "ErrorLevel": "2",
    "ErrorMessage": "Connection timeout",
    "ErrorSource": "System.Net.Http",
    "ErrorStackTrace": "at HttpClient.SendAsync(...)",
    "ErrorTargetSite": "SendAsync",
    "ExceptionType": "System.TimeoutException",
    "Severity": "Warning"
  }
]

🔄 Railway-Oriented Programming (ROP)

El patrón ROP permite encadenar operaciones de forma fluida, donde cada operación puede fallar o tener éxito.

Ejemplo Básico

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public Result<string> ProcessOrder(Order order)
{
    return ValidateOrder(order)
        .Map(o => CalculateTotal(o))
        .OnSuccess(total => _logger.LogInformation($"Total calculado: {total}"))
        .Map(total => ApplyDiscount(total))
        .Map(total => FormatCurrency(total))
        .OnFailure(error => _logger.LogError($"Error: {error.Message}"));
}

Encadenamiento Complejo

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class OrderProcessor
{
    public Result<OrderConfirmation> ProcessOrder(CreateOrderRequest request)
    {
        return ValidateRequest(request)
            .OnSuccess(r => _logger.LogInformation("Request validado"))
            .Map(r => CreateOrder(r))
            .OnSuccess(order => _logger.LogInformation($"Orden creada: {order.Id}"))
            .Map(order => ReserveInventory(order))
            .Map(order => ProcessPayment(order))
            .Map(order => SendConfirmation(order))
            .OnSuccess(confirmation => _logger.LogInformation("Proceso completado"))
            .OnFailure(error => HandleOrderError(error));
    }

    private Result<CreateOrderRequest> ValidateRequest(CreateOrderRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.CustomerId))
        {
            return Result.Failure<CreateOrderRequest>(
                Error.Validation("Order.InvalidCustomer", "ID de cliente requerido")
            );
        }

        if (request.Items == null || request.Items.Count == 0)
        {
            return Result.Failure<CreateOrderRequest>(
                Error.Validation("Order.NoItems", "La orden debe tener al menos un item")
            );
        }

        return Result.Success(request);
    }
}

🎯 Conversiones Implícitas

EV.Result permite conversiones implícitas para hacer el código más limpio:

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class ProductService
{
    // Conversión implícita de valor a Result<T>
    public Result<Product> GetProduct(Guid id)
    {
        var product = _repository.GetById(id);
        
        // Conversión implícita: Product -> Result<Product>
        if (product != null)
            return product;  // Automáticamente se convierte a Result.Success(product)
            
        return Result.Failure<Product>(
            Error.NotFound("Product.NotFound", "Producto no encontrado")
        );
    }

    // Uso más limpio con operador ternario
    public Result<Product> GetProductSimple(Guid id)
    {
        var product = _repository.GetById(id);
        
        return product != null
            ? product  // Conversión implícita a Success
            : Result.Failure<Product>(Error.NotFound("Product.NotFound", "No encontrado"));
    }
}

🌐 Integración con ASP.NET Core

Extensión para Controladores

using Microsoft.AspNetCore.Mvc;
using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;

public static class ResultExtensions
{
    public static IActionResult ToActionResult<T>(this Result<T> result)
    {
        if (result.IsSuccess)
        {
            return new OkObjectResult(result.Value);
        }

        return result.Error.Type switch
        {
            ErrorType.Validation => new BadRequestObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message,
                type = result.Error.Type.ToString()
            }),
            ErrorType.NotFound => new NotFoundObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.Conflict => new ConflictObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.Unauthorized => new UnauthorizedObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.Forbidden => new ObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }) 
            { 
                StatusCode = 403 
            },
            ErrorType.Timeout => new ObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }) 
            { 
                StatusCode = 408 
            },
            _ => new ObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message,
                type = result.Error.Type.ToString()
            }) 
            { 
                StatusCode = 500 
            }
        };
    }

    public static IActionResult ToActionResult(this Result result)
    {
        if (result.IsSuccess)
        {
            return new OkResult();
        }

        return result.Error.Type switch
        {
            ErrorType.Validation => new BadRequestObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.NotFound => new NotFoundObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.Conflict => new ConflictObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.Unauthorized => new UnauthorizedObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }),
            ErrorType.Forbidden => new ObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }) 
            { 
                StatusCode = 403 
            },
            _ => new ObjectResult(new 
            { 
                error = result.Error.Code, 
                message = result.Error.Message 
            }) 
            { 
                StatusCode = 500 
            }
        };
    }
}

Uso en Controladores

using Microsoft.AspNetCore.Mvc;
using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;

    public ProductsController(IProductService productService)
    {
        _productService = productService;
    }

    [HttpGet("{id}")]
    public IActionResult GetProduct(Guid id)
    {
        var result = _productService.GetProductById(id);
        return result.ToActionResult();
    }

    [HttpPost]
    public IActionResult CreateProduct([FromBody] CreateProductRequest request)
    {
        var result = _productService.CreateProduct(request);
        return result.ToActionResult();
    }

    [HttpPut("{id}")]
    public IActionResult UpdateProduct(Guid id, [FromBody] UpdateProductRequest request)
    {
        var result = _productService.UpdateProduct(id, request);
        return result.ToActionResult();
    }

    [HttpDelete("{id}")]
    public IActionResult DeleteProduct(Guid id)
    {
        var result = _productService.DeleteProduct(id);
        return result.ToActionResult();
    }
}

📖 Ejemplos Completos

Ejemplo 1: Servicio de Autenticación

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class AuthService
{
    private readonly IUserRepository _userRepository;
    private readonly IPasswordHasher _passwordHasher;
    private readonly ITokenGenerator _tokenGenerator;
    private readonly ILogger<AuthService> _logger;

    public Result<AuthResponse> Login(LoginRequest request)
    {
        return ValidateCredentials(request)
            .Map(user => GenerateToken(user))
            .Map(token => new AuthResponse
            {
                Token = token,
                ExpiresIn = 3600
            })
            .OnSuccess(response => _logger.LogInformation("Login exitoso"))
            .OnFailure(error => _logger.LogWarning($"[{error.Type}] Login fallido: {error.Code}"));
    }

    private Result<User> ValidateCredentials(LoginRequest request)
    {
        if (string.IsNullOrWhiteSpace(request.Email))
        {
            return Result.Failure<User>(
                Error.Validation("Auth.EmailRequired", "El email es requerido")
            );
        }

        var user = _userRepository.GetByEmail(request.Email);

        if (user == null)
        {
            return Result.Failure<User>(
                Error.Unauthorized("Auth.InvalidCredentials", "Credenciales inválidas")
            );
        }

        if (!_passwordHasher.Verify(request.Password, user.PasswordHash))
        {
            return Result.Failure<User>(
                Error.Unauthorized("Auth.InvalidCredentials", "Credenciales inválidas")
            );
        }

        if (!user.IsActive)
        {
            return Result.Failure<User>(
                Error.Forbidden("Auth.UserInactive", "Usuario inactivo")
            );
        }

        return Result.Success(user);
    }

    private string GenerateToken(User user)
    {
        return _tokenGenerator.Generate(user.Id, user.Email, user.Roles);
    }
}

Ejemplo 2: Procesamiento de Pagos

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class PaymentService
{
    private readonly IOrderRepository _orderRepository;
    private readonly IPaymentGateway _paymentGateway;
    private readonly IEmailService _emailService;
    private readonly ILogger<PaymentService> _logger;

    public Result ProcessPayment(Guid orderId, PaymentMethod method)
    {
        return ValidateOrder(orderId)
            .OnSuccess(order => _logger.LogInformation($"Procesando pago para orden {order.Id}"))
            .Map(order => CreatePaymentRequest(order, method))
            .Map(request => ChargePayment(request))
            .OnSuccess(response => UpdateOrderStatus(orderId, "Paid"))
            .OnSuccess(_ => SendConfirmationEmail(orderId))
            .OnFailure(error => HandlePaymentError(orderId, error));
    }

    private Result<Order> ValidateOrder(Guid orderId)
    {
        var order = _orderRepository.GetById(orderId);

        if (order == null)
        {
            return Result.Failure<Order>(
                Error.NotFound("Order.NotFound", "Orden no encontrada")
            );
        }

        if (order.Status == "Paid")
        {
            return Result.Failure<Order>(
                Error.Conflict("Order.AlreadyPaid", "La orden ya fue pagada")
            );
        }

        if (order.TotalAmount <= 0)
        {
            return Result.Failure<Order>(
                Error.Validation("Order.InvalidAmount", "El monto de la orden es inválido")
            );
        }

        return Result.Success(order);
    }

    private PaymentRequest CreatePaymentRequest(Order order, PaymentMethod method)
    {
        return new PaymentRequest
        {
            OrderId = order.Id,
            Amount = order.TotalAmount,
            Currency = "USD",
            Method = method
        };
    }

    private PaymentResponse ChargePayment(PaymentRequest request)
    {
        // Lógica de cobro con gateway de pago
        return _paymentGateway.Charge(request);
    }

    private void UpdateOrderStatus(Guid orderId, string status)
    {
        var order = _orderRepository.GetById(orderId);
        order.Status = status;
        _orderRepository.Update(order);
    }

    private void SendConfirmationEmail(Guid orderId)
    {
        _emailService.SendPaymentConfirmation(orderId);
    }

    private void HandlePaymentError(Guid orderId, Error error)
    {
        _logger.LogError($"[{error.Type}] Pago fallido orden {orderId}: {error.Message}");
        _notificationService.SendAlert($"Pago fallido: {error.Code}");
    }
}

Ejemplo 3: CRUD Completo con ErrorTypes

using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using System;

public class ProductService
{
    private readonly IProductRepository _repository;
    private readonly ILogger<ProductService> _logger;

    // CREATE
    public Result<Product> CreateProduct(CreateProductRequest request)
    {
        // Validaciones
        if (string.IsNullOrWhiteSpace(request.Name))
        {
            return Result.Failure<Product>(
                Error.Validation("Product.InvalidName", "El nombre es requerido")
            );
        }

        if (request.Price <= 0)
        {
            return Result.Failure<Product>(
                Error.Validation("Product.InvalidPrice", "El precio debe ser mayor a cero")
            );
        }

        // Verificar duplicados
        if (_repository.ExistsByName(request.Name))
        {
            return Result.Failure<Product>(
                Error.Conflict("Product.NameExists", "Ya existe un producto con ese nombre")
            );
        }

        var product = new Product
        {
            Id = Guid.NewGuid(),
            Name = request.Name,
            Price = request.Price,
            CreatedAt = DateTime.UtcNow
        };

        _repository.Add(product);
        return Result.Success(product);
    }

    // READ
    public Result<Product> GetProductById(Guid id)
    {
        var product = _repository.GetById(id);

        return Result.Create(
            product,
            Error.NotFound("Product.NotFound", $"Producto con ID {id} no encontrado")
        );
    }

    // UPDATE
    public Result<Product> UpdateProduct(Guid id, UpdateProductRequest request)
    {
        return GetProductById(id)
            .Map(product => ApplyUpdates(product, request))
            .OnSuccess(product => _repository.Update(product));
    }

    private Product ApplyUpdates(Product product, UpdateProductRequest request)
    {
        if (!string.IsNullOrWhiteSpace(request.Name))
            product.Name = request.Name;

        if (request.Price.HasValue)
            product.Price = request.Price.Value;

        product.UpdatedAt = DateTime.UtcNow;
        return product;
    }

    // DELETE
    public Result DeleteProduct(Guid id)
    {
        return GetProductById(id)
            .OnSuccess(product => ValidateCanDelete(product))
            .OnSuccess(product => _repository.Delete(product.Id));
    }

    private Result ValidateCanDelete(Product product)
    {
        if (product.HasActiveOrders)
        {
            return Result.Failure(
                Error.Conflict("Product.HasOrders", "No se puede eliminar un producto con pedidos")
            );
        }

        return Result.Success();
    }
}

🔧 Compatibilidad

.NET Standard 2.0

Esta librería está construida sobre .NET Standard 2.0, lo que garantiza la máxima compatibilidad con:

  • .NET Framework 4.6.1+
  • .NET Core 2.0+
  • .NET 5, 6, 7, 8+
  • Mono 5.4+
  • Xamarin.iOS 10.14+
  • Xamarin.Android 8.0+
  • Unity 2018.1+

📝 Notas Importantes

Dependencias

La librería utiliza Newtonsoft.Json para la serialización de excepciones, ya que proporciona mejor compatibilidad con .NET Standard 2.0 que System.Text.Json.

<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />

Mejores Prácticas

  1. Siempre verifica IsSuccess antes de acceder a Value
if (result.IsSuccess)
{
    var value = result.Value;  // Seguro
}
  1. Usa conversiones implícitas para código más limpio
public Result<Product> GetProduct(Guid id)
{
    var product = _repository.GetById(id);
    return product != null ? product : Result.Failure<Product>(Error.NotFound(...));
}
  1. Aprovecha el encadenamiento fluido
return ValidateInput(input)
    .Map(x => Transform(x))
    .OnSuccess(x => LogSuccess(x))
    .OnFailure(e => LogError(e));
  1. Captura excepciones con contexto completo
catch (Exception ex)
{
    return Result.Failure<T>(
        Error.FailureFromException("Operation.Failed", ex, maxLevel: 30)
    );
}

📄 Licencia

Copyright © 2025 EV. Todos los derechos reservados.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.1 221 11/9/2025
2.0.0 144 11/1/2025

REI Pattern (Result with Exception Intelligence) es un patrón que he tenido la oportunidad de desarrollar con el objetivo de contribuir a la comunidad de .NET, evolucionando el tradicional patrón Result mediante la integración del manejo de excepciones con el enfoque funcional clásico.

He diseñado esta propuesta con la humilde aspiración de unir ambos enfoques: la elegancia del Railway-Oriented Programming (ROP) junto con la inteligencia de un manejo avanzado de excepciones, compartiendo lo que he aprendido a lo largo de mi experiencia.

EV.Result es mi implementación concreta de REI - una solución que quiero poner a disposición de la comunidad para .NET, la cual he creado con el propósito de ofrecer una alternativa al manejo tradicional de excepciones, basándome en las lecciones que he ido recopilando durante mi trayectoria profesional.

El patrón que propongo retorna objetos Result que encapsulan tanto el éxito como el fallo, pero con un enfoque que busca ser útil: preservar las excepciones de forma inteligente, manteniendo toda su información hasta los niveles más profundos, mientras se conserva el control del flujo mediante ROP.

✨ Características principales:

🎯 Patrón Result T - Manejo funcional de errores sin excepciones para flujos más limpios.
🔄 Railway-Oriented Programming - Encadenamiento fluido con OnSuccess, OnFailure y Map.
🏷️ 9 Tipos de Error Clasificados - Validation, NotFound, Conflict, Unauthorized, Forbidden, Timeout, DependencyFailure, Unknown y Failure con mapeo directo a HTTP status codes.
🔍 Captura Profunda de Excepciones - El diferenciador clave: serializa TODA la cadena exception + innerException hasta 30 niveles con stack traces completos, tipos, mensajes y metadata.
📊 Severidad Automática - Clasifica excepciones en Warning/Error/Critical automáticamente.
📋 JSON Estructurado - Convierte cadenas de excepciones complejas en JSON legible para logging, monitoreo y debugging.
⚡ Type-Safe - El compilador te obliga a manejar errores, eliminando olvidos.
🎨 API Fluida - Código más limpio y expresivo con métodos de transformación.
🔌 Zero Dependencias Externas - Máximo rendimiento.
🚀 .NET Standard 2.0+ - Compatible con .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5+, Xamarin, Unity y más.


🔥 El plus que marca la diferencia:

Normalmente cuando una aplicación falla, el primer mensaje de error rara vez dice la verdad completa. La causa raíz está enterrada en las inner exceptions, por tal razón esta librería captura AUTOMÁTICAMENTE toda la cadena:

✅ Exception principal + TODAS las inner exceptions (hasta 30 niveles)
✅ Stack trace completo de cada nivel
✅ Tipo exacto de cada excepción
✅ Source, TargetSite y Data de cada error
✅ Severidad calculada automáticamente
✅ Todo serializado en JSON estructurado