EV.Result 2.0.1

dotnet add package EV.Result --version 2.0.1
                    
NuGet\Install-Package EV.Result -Version 2.0.1
                    
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.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EV.Result" Version="2.0.1" />
                    
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.1
                    
#r "nuget: EV.Result, 2.0.1"
                    
#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.1
                    
#: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.1
                    
Install as a Cake Addin
#tool nuget:?package=EV.Result&version=2.0.1
                    
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
🌐 Integración ASP.NET Core: Extensión ToApiResponse para convertir automáticamente Result<T> en respuestas HTTP estandarizadas
📋 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.1" />

🚀 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}");
}

3. Integración con ASP.NET Core WebAPI

using EV.Result.Abstractions.Extensions;
using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ProjectName.WebApi.Models;

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

    public ProductsController(IProductService productService, ILogger<ProductsController> logger)
    {
        _productService = productService;
        _logger = logger;
    }

    [HttpGet("{id}")]
    public ActionResult<ApiResponse<Product>> GetProduct(Guid id)
    {
        var result = _productService.GetProductById(id);
        return result.ToApiResponse(this, _logger);
    }

    [HttpPost]
    public ActionResult<ApiResponse<Product>> CreateProduct([FromBody] CreateProductRequest request)
    {
        if (!ModelState.IsValid)
        {
            return ResultToApiResponseExtension.FromModelState<Product>(ModelState, this, _logger);
        }

        var result = _productService.CreateProduct(request);
        return result.ToApiResponse(this, _logger);
    }

    [HttpPut("{id}")]
    public ActionResult<ApiResponse<Product>> UpdateProduct(Guid id, [FromBody] UpdateProductRequest request)
    {
        var result = _productService.UpdateProduct(id, request);
        return result.ToApiResponse(this, _logger);
    }

    [HttpDelete("{id}")]
    public ActionResult<ApiResponse<bool>> DeleteProduct(Guid id)
    {
        var result = _productService.DeleteProduct(id);
        return result.ToApiResponse(this, _logger);
    }
}

Respuestas generadas automáticamente:

// Éxito (200 OK)
{
    "success": true,
    "statusCode": 200,
    "message": "Request successful",
    "data": {
        "id": "123e4567-e89b-12d3-a456-426614174000",
        "name": "Laptop",
        "price": 999.99
    },
    "timestamp": "2025-11-09T10:30:00Z"
}

// Error de validación (400 Bad Request)
{
    "success": false,
    "statusCode": 400,
    "message": "El precio debe ser mayor a cero",
    "data": null,
    "timestamp": "2025-11-09T10:30:00Z"
}

// No encontrado (404 Not Found)
{
    "success": false,
    "statusCode": 404,
    "message": "Producto no encontrado",
    "data": null,
    "timestamp": "2025-11-09T10:30:00Z"
}

📚 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);
}

🌐 Integración ASP.NET Core

Clase ApiResponse<T>

Modelo de respuesta estandarizado para APIs REST:

using System;

namespace ProjectName.WebApi.Models
{
    public class ApiResponse<T>
    {
        public bool Success { get; set; }
        public int StatusCode { get; set; }
        public string Message { get; set; }
        public T Data { get; set; }
        public DateTime Timestamp { get; set; }

        // Métodos factory
        public static ApiResponse<T> Ok(T data, string message = null);
        public static ApiResponse<T> Created(T data, string message = null);
        public static ApiResponse<T> NotFound(string message);
        public static ApiResponse<T> BadRequest(string message);
        public static ApiResponse<T> Conflict(string message);
        public static ApiResponse<T> Error(string message, int statusCode = 500);
    }
}

Extensión ToApiResponse

La extensión ToApiResponse convierte automáticamente objetos Result<T> en respuestas HTTP apropiadas con el formato ApiResponse<T>:

using EV.Result.Abstractions.Extensions;
using EV.Result.Abstractions.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

public static class ResultToApiResponseExtension
{
    // Convierte Result<T> en ActionResult<ApiResponse<T>>
    public static ActionResult<ApiResponse<T>> ToApiResponse<T>(
        this Result<T> result, 
        ControllerBase controller, 
        ILogger logger = null);

    // Convierte ModelStateDictionary en ActionResult<ApiResponse<T>> 
    public static ActionResult<ApiResponse<T>> FromModelState<T>(
        ModelStateDictionary modelState, 
        ControllerBase controller, 
        ILogger logger = null);
}

Mapeo Automático de ErrorType a HTTP Status

La extensión mapea automáticamente los tipos de error a códigos de estado HTTP:

ErrorType HTTP Status Descripción
None 200 OK Sin error
Validation 400 Bad Request Error de validación
NotFound 404 Not Found Recurso no encontrado
Conflict 409 Conflict Conflicto con estado actual
Unauthorized 401 Unauthorized No autenticado
Forbidden 403 Forbidden No autorizado
Timeout 408 Request Timeout Timeout de operación
DependencyFailure 503 Service Unavailable Fallo en dependencia externa
Failure 500 Internal Server Error Error genérico
Unknown 500 Internal Server Error Error desconocido

Ejemplo Completo de Controller

using EV.Result.Abstractions.Extensions;
using EV.Result.Abstractions.Results;
using EV.Result.Abstractions.Errors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ProjectName.WebApi.Models;
using System;
using System.Threading.Tasks;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IUserService _userService;
    private readonly ILogger<UsersController> _logger;

    public UsersController(IUserService userService, ILogger<UsersController> logger)
    {
        _userService = userService;
        _logger = logger;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<ApiResponse<UserDto>>> GetUser(Guid id)
    {
        var result = await _userService.GetUserByIdAsync(id);
        return result.ToApiResponse(this, _logger);
    }

    [HttpPost]
    public async Task<ActionResult<ApiResponse<UserDto>>> CreateUser([FromBody] CreateUserRequest request)
    {
        if (!ModelState.IsValid)
        {
            return ResultToApiResponseExtension.FromModelState<UserDto>(ModelState, this, _logger);
        }

        var result = await _userService.CreateUserAsync(request);
        return result.ToApiResponse(this, _logger);
    }

    [HttpPut("{id}")]
    public async Task<ActionResult<ApiResponse<UserDto>>> UpdateUser(Guid id, [FromBody] UpdateUserRequest request)
    {
        var result = await _userService.UpdateUserAsync(id, request);
        return result.ToApiResponse(this, _logger);
    }

    [HttpDelete("{id}")]
    public async Task<ActionResult<ApiResponse<bool>>> DeleteUser(Guid id)
    {
        var result = await _userService.DeleteUserAsync(id);
        return result.ToApiResponse(this, _logger);
    }

    [HttpPost("{id}/activate")]
    public async Task<ActionResult<ApiResponse<bool>>> ActivateUser(Guid id)
    {
        var result = await _userService.ActivateUserAsync(id);
        return result.ToApiResponse(this, _logger);
    }
}

Beneficios de la Integración

Respuestas consistentes: Todas las APIs devuelven el mismo formato estandarizado

Mapeo automático: Los tipos de error se convierten automáticamente en códigos HTTP apropiados

Logging integrado: Registra automáticamente información relevante en cada operación

Manejo de ModelState: Convierte errores de validación de ASP.NET Core en respuestas consistentes

Type-safe: Mantiene los tipos genéricos en toda la cadena de respuesta

Menos boilerplate: Elimina código repetitivo en los controllers

🛡️ 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 = _gateway.Charge(request);
            return Result.Success(payment);
        }
        catch (HttpRequestException ex)
        {
            return Result.Failure<Payment>(
                Error.DependencyFailureFromException("Payment.GatewayError", ex, maxLevel: 30)
            );
        }
        catch (TimeoutException ex)
        {
            return Result.Failure<Payment>(
                Error.TimeoutFromException("Payment.Timeout", ex, maxLevel: 30)
            );
        }
        catch (Exception ex)
        {
            return Result.Failure<Payment>(
                Error.FailureFromException("Payment.UnexpectedError", ex, maxLevel: 30)
            );
        }
    }
}

JSON de Excepciones

Cuando una excepción es capturada, EV.Result genera automáticamente un JSON estructurado:

{
  "code": "Payment.GatewayError",
  "message": "Error al procesar el pago",
  "type": "DependencyFailure",
  "exceptionJson": "[{\"Level\":0,\"Type\":\"HttpRequestException\",\"Message\":\"Connection refused\",\"Source\":\"System.Net.Http\",\"StackTrace\":\"...\",\"Severity\":\"High\",\"InnerException\":{\"Level\":1,\"Type\":\"SocketException\",\"Message\":\"No connection could be made\",\"Severity\":\"Critical\"}}]"
}

🎯 Casos de Uso Avanzados

Ejemplo 1: Autenticación JWT

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

public class AuthenticationService
{
    private readonly IUserRepository _userRepository;
    private readonly IPasswordHasher _passwordHasher;
    private readonly ITokenGenerator _tokenGenerator;

    public Result<AuthToken> Login(string email, string password)
    {
        return ValidateCredentials(email, password)
            .Map(user => GenerateToken(user));
    }

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

        var user = _userRepository.GetByEmail(email);

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

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

        if (!user.IsActive)
        {
            return Result.Failure<User>(
                Error.Forbidden("Auth.AccountDisabled", "La cuenta está deshabilitada")
            );
        }

        return Result.Success(user);
    }

    private AuthToken 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)
    {
        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)
    );
}
  1. Usa ToApiResponse en tus controllers para respuestas consistentes
[HttpGet("{id}")]
public ActionResult<ApiResponse<Product>> GetProduct(Guid id)
{
    var result = _productService.GetProductById(id);
    return result.ToApiResponse(this, _logger);
}
  1. Valida ModelState con FromModelState
if (!ModelState.IsValid)
{
    return ResultToApiResponseExtension.FromModelState<Product>(ModelState, this, _logger);
}

📈 Novedades en v2.0.1

✨ Nueva Extensión ToApiResponse

Se ha agregado la extensión ToApiResponse que permite una integración perfecta con ASP.NET Core WebAPI:

  • Conversión automática: Result<T> se convierte automáticamente en ActionResult<ApiResponse<T>>
  • Mapeo inteligente: Los ErrorType se mapean automáticamente a códigos HTTP apropiados
  • Logging integrado: Registra automáticamente información relevante según el tipo de resultado
  • Manejo de ModelState: Método FromModelState para convertir errores de validación ASP.NET Core
  • Respuestas estandarizadas: Todas las APIs devuelven el mismo formato con ApiResponse<T>

Beneficios de v2.0.1

✅ Elimina código boilerplate en controllers

✅ Garantiza consistencia en todas las respuestas de la API

✅ Simplifica el manejo de errores en endpoints

✅ Mejora la experiencia de desarrollo con menos código repetitivo

✅ Facilita la integración con Clean Architecture

📄 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