SkyWebFramework.Results 1.0.1

dotnet add package SkyWebFramework.Results --version 1.0.1
                    
NuGet\Install-Package SkyWebFramework.Results -Version 1.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="SkyWebFramework.Results" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SkyWebFramework.Results" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="SkyWebFramework.Results" />
                    
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 SkyWebFramework.Results --version 1.0.1
                    
#r "nuget: SkyWebFramework.Results, 1.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 SkyWebFramework.Results@1.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=SkyWebFramework.Results&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=SkyWebFramework.Results&version=1.0.1
                    
Install as a Cake Tool

🚀 EasyResult - Advanced Result Pattern for ASP.NET Core

.NET License NuGet

EasyResult is a powerful, feature-rich result pattern implementation for ASP.NET Core that goes beyond traditional success/failure handling. It provides 7 unique features not commonly found in other result management packages.

✨ Why EasyResult?

Unlike other result pattern libraries, EasyResult offers:

  • Warning System - Success with non-critical warnings
  • 🔗 Result Composition - Intelligent aggregation of multiple operations
  • ⏱️ Performance Tracking - Built-in execution time monitoring
  • 🔁 Smart Retry with Compensation - Automatic retry with rollback logic
  • 🔄 Execution Pipeline - Fluent conditional operation chaining
  • 📋 Business Rule Validation - Domain-specific validation framework
  • 🔍 Contextual Metadata - Automatic request context capture

📦 Installation

# Coming soon to NuGet
dotnet add package SkyWebFramework.Results

Or copy the EasyResult.cs file directly into your project under the SkyWebFramework.Results namespace.

🎯 Quick Start

Basic Usage

using SkyWebFramework.Results;

// Success result
var result = EasyResult<User>.Ok(user, "User retrieved successfully");

// Failure result
var result = EasyResult<User>.NotFound("User not found");

// Validation error
var result = EasyResult<User>.ValidationError("Invalid email format", "Email");

// Multiple errors
var result = EasyResult<User>.Fail(new[] {
    new ErrorDetail("INVALID_EMAIL", "Email is required", "Email"),
    new ErrorDetail("INVALID_NAME", "Name is required", "Name")
});

ASP.NET Core Integration

[HttpGet("{id}")]
public IActionResult GetEmployee(int id)
{
    var employee = _repository.GetById(id);
    
    if (employee == null)
        return EasyResult<Employee>.NotFound($"Employee {id} not found")
            .ToActionResult();
    
    return EasyResult<Employee>.Ok(employee).ToActionResult();
}

🌟 Core Features

1. Standard Result Operations

// Generic result with data
EasyResult<Employee>.Ok(employee);
EasyResult<Employee>.Fail("ERROR_CODE", "Error message");
EasyResult<Employee>.NotFound();
EasyResult<Employee>.Unauthorized();
EasyResult<Employee>.Forbidden();
EasyResult<Employee>.ValidationError("Invalid input", "FieldName");
EasyResult<Employee>.Conflict("Resource already exists");

// Non-generic result (no data)
EasyResult.Ok("Operation completed");
EasyResult.Fail("ERROR_CODE", "Error message");

// Functional programming support
result.Map(user => user.Name);
result.Bind(user => GetUserDetails(user.Id));
result.Match(
    onSuccess: user => Console.WriteLine(user.Name),
    onFailure: errors => Console.WriteLine("Failed")
);

2. Pagination Support

[HttpGet]
public IActionResult GetEmployees(int page = 1, int pageSize = 10)
{
    var totalCount = _repository.Count();
    var items = _repository.GetPage(page, pageSize);
    
    var result = EasyPagedResult<Employee>.Ok(items, page, pageSize, totalCount);
    return result.ToActionResult();
}

// Response includes pagination metadata
{
  "success": true,
  "data": {
    "items": [...],
    "pageInfo": {
      "page": 1,
      "pageSize": 10,
      "totalCount": 50,
      "totalPages": 5,
      "hasNextPage": true,
      "hasPreviousPage": false
    }
  }
}

🎨 Unique Advanced Features

1. ⚠️ Warning System

Success with non-critical warnings - unique to EasyResult!

[HttpPost]
public IActionResult CreateEmployee(CreateEmployeeRequest request)
{
    var warnings = new List<Warning>();
    
    // Check for deprecated field usage
    if (!string.IsNullOrEmpty(request.LegacyCode))
    {
        warnings.Add(new Warning(
            "DEPRECATED_FIELD",
            "LegacyCode will be removed in v2.0",
            "LegacyCode",
            WarningSeverity.Medium
        ));
    }
    
    // Check for potential issues
    if (request.Salary < 40000)
    {
        warnings.Add(new Warning(
            "LOW_SALARY",
            "Salary below market average",
            "Salary",
            WarningSeverity.Low
        ));
    }
    
    var employee = CreateEmployee(request);
    
    var result = warnings.Any()
        ? EasyResultWithWarnings<Employee>.OkWithWarnings(employee, warnings)
        : EasyResultWithWarnings<Employee>.Ok(employee);
    
    return new ObjectResult(result) { StatusCode = 201 };
}

Response:

{
  "success": true,
  "data": { "id": 1, "name": "John Doe", ... },
  "warnings": [
    {
      "code": "DEPRECATED_FIELD",
      "message": "LegacyCode will be removed in v2.0",
      "field": "LegacyCode",
      "severity": "Medium"
    },
    {
      "code": "LOW_SALARY",
      "message": "Salary below market average",
      "field": "Salary",
      "severity": "Low"
    }
  ],
  "hasWarnings": true
}

2. 🔗 Result Composition

Intelligently combine multiple operations:

// All must succeed (AND logic)
var results = new[] {
    ValidateEmployee(emp1),
    ValidateEmployee(emp2),
    ValidateEmployee(emp3)
};
var allResult = ResultComposer.All(results);

// At least one must succeed (OR logic)
var anyResult = ResultComposer.Any(
    TryPrimaryDatabase(),
    TrySecondaryDatabase(),
    TryCache()
);

// Collect all results
var composite = ResultComposer.Collect(results);
Console.WriteLine($"Success: {composite.SuccessCount}, Failed: {composite.FailureCount}");

// Merge multiple results into one
var employeeResult = GetEmployee(id);
var departmentResult = GetDepartment(deptId);
var salaryResult = GetSalary(id);

var merged = ResultComposer.Merge(
    employeeResult,
    departmentResult,
    (emp, dept) => new { Employee = emp, Department = dept }
);

Bulk operations example:

[HttpPost("bulk-create")]
public IActionResult BulkCreate(List<CreateEmployeeRequest> requests)
{
    var results = requests.Select(r => CreateEmployee(r)).ToArray();
    var composite = ResultComposer.Collect(results);
    
    return Ok(new {
        AllSucceeded = composite.AllSucceeded,
        SuccessCount = composite.SuccessCount,
        FailureCount = composite.FailureCount,
        Successes = composite.Successes,
        Failures = composite.Failures
    });
}

3. ⏱️ Performance Tracking

Automatic execution time measurement:

[HttpGet("search")]
public async Task<IActionResult> Search(string query)
{
    var result = await PerformanceTracker.TrackAsync(async () =>
    {
        // Your expensive operation
        return await _repository.SearchAsync(query);
    }, "SearchEmployees");
    
    return new ObjectResult(result) { StatusCode = 200 };
}

Response includes metrics:

{
  "success": true,
  "data": [...],
  "metrics": {
    "duration": "00:00:00.1234567",
    "startTime": "2025-11-01T10:30:00Z",
    "endTime": "2025-11-01T10:30:00.123Z",
    "operationName": "SearchEmployees",
    "customMetrics": {
      "recordsScanned": 1000
    }
  }
}

Synchronous tracking:

var result = PerformanceTracker.Track(
    () => ExpensiveCalculation(),
    "CalculateSalary"
);

4. 🔁 Smart Retry with Compensation

Automatic retry with rollback on permanent failure:

[HttpPost("{id}/activate")]
public async Task<IActionResult> ActivateEmployee(int id)
{
    var policy = new RetryPolicy(
        MaxAttempts: 3,
        InitialDelay: TimeSpan.FromMilliseconds(100),
        BackoffMultiplier: 2.0,
        ShouldRetry: ex => ex is not UnauthorizedException
    );
    
    var result = await RetryExecutor.ExecuteWithRetryAsync(
        operation: async () => await _externalService.ActivateEmployeeAsync(id),
        policy: policy,
        onFailureCompensation: async () =>
        {
            // Rollback actions on permanent failure
            await _logger.LogErrorAsync($"Failed to activate {id}");
            await _notificationService.SendFailureAlert(id);
        }
    );
    
    return result.ToActionResult();
}

Custom retry conditions:

var policy = new RetryPolicy(
    MaxAttempts: 5,
    InitialDelay: TimeSpan.FromSeconds(1),
    BackoffMultiplier: 2.0,
    ShouldRetry: ex => ex is HttpRequestException || ex is TimeoutException
);

5. 🔄 Execution Pipeline

Fluent conditional operation chaining:

[HttpPut("{id}")]
public async Task<IActionResult> UpdateEmployee(int id, UpdateRequest request)
{
    var result = await ResultPipeline<Employee>.Start(new Employee { Id = id })
        .Then(emp => FetchEmployee(emp.Id))
        .Then(emp => ValidatePermissions(emp))
        .Then(emp => ApplyUpdates(emp, request))
        .Tap(emp => _logger.LogInfo($"Updated employee {emp.Id}"))
        .ThenAsync(emp => SaveEmployeeAsync(emp))
        .Then(emp => InvalidateCache(emp.Id))
        .OnError(errors => {
            _logger.LogError($"Update failed: {string.Join(", ", errors)}");
            return EasyResult<Employee>.Fail(errors);
        })
        .Finally(result => _metrics.RecordUpdate(result.Success))
        .Build();
    
    return result.ToActionResult();
}

Transform data types in pipeline:

var result = ResultPipeline<Employee>.Start(employee)
    .Transform(emp => emp.Department)
    .Then(dept => EnrichDepartmentData(dept))
    .Build();

6. 📋 Business Rule Validation

Domain-specific validation framework:

[HttpPost("validate")]
public IActionResult ValidateEmployee(CreateEmployeeRequest request)
{
    var employee = MapToEmployee(request);
    
    var validator = new BusinessRuleValidator<Employee>()
        .AddRule("ValidSalaryRange",
            e => e.Salary >= 30000 && e.Salary <= 200000,
            "INVALID_SALARY",
            "Salary must be between $30,000 and $200,000")
        .AddRule("ValidEmail",
            e => IsValidEmail(e.Email),
            "INVALID_EMAIL",
            "Email must be a valid email address")
        .AddRule("UniqueName",
            e => !_repository.NameExists(e.Name),
            "DUPLICATE_NAME",
            "Employee with this name already exists")
        .AddRule("ApprovedDepartment",
            e => _approvedDepartments.Contains(e.Department),
            "INVALID_DEPARTMENT",
            "Department not in approved list");
    
    var result = validator.Validate(employee);
    return result.ToActionResult();
}

Custom rule implementation:

public class MinimumAgeRule : IBusinessRule<Employee>
{
    public string RuleName => "MinimumAge";
    
    public bool IsSatisfied(Employee data) => 
        CalculateAge(data.DateOfBirth) >= 18;
    
    public ErrorDetail GetError() => 
        new("UNDERAGE", "Employee must be at least 18 years old", "DateOfBirth",
            new Dictionary<string, object> { ["RuleName"] = RuleName });
}

validator.AddRule(new MinimumAgeRule());

Validation response:

{
  "success": false,
  "errors": [
    {
      "code": "INVALID_SALARY",
      "message": "Salary must be between $30,000 and $200,000",
      "field": null,
      "metadata": {
        "ruleName": "ValidSalaryRange"
      }
    },
    {
      "code": "DUPLICATE_NAME",
      "message": "Employee with this name already exists",
      "metadata": {
        "ruleName": "UniqueName"
      }
    }
  ]
}

7. 🔍 Contextual Metadata

Automatic request context capture for debugging:

[HttpGet("{id}/with-context")]
public IActionResult GetEmployeeWithContext(int id)
{
    var employee = _repository.GetById(id);
    
    var baseResult = employee != null
        ? EasyResult<Employee>.Ok(employee)
        : EasyResult<Employee>.NotFound($"Employee {id} not found");
    
    // Automatically captures user, tenant, trace ID, headers, etc.
    var contextResult = EasyResultWithContext<Employee>.FromHttpContext(
        baseResult, 
        HttpContext
    );
    
    return new ObjectResult(contextResult) { StatusCode = employee != null ? 200 : 404 };
}

Response with full context:

{
  "success": true,
  "data": { "id": 1, "name": "John Doe", ... },
  "context": {
    "userId": "john.doe@company.com",
    "tenantId": "tenant-123",
    "correlationId": "abc-xyz-789",
    "requestPath": "/api/employee/1/with-context",
    "headers": {
      "User-Agent": "Mozilla/5.0...",
      "X-Tenant-Id": "tenant-123",
      "Authorization": "Bearer ..."
    },
    "requestTime": "2025-11-01T10:30:00Z"
  },
  "traceId": "0HN1234567890",
  "timestamp": "2025-11-01T10:30:00.123Z"
}

🛠️ Extension Methods

Error Handling Extensions

// Convert string to ErrorDetail
var error = "Something went wrong".ToError("ERROR_CODE", "FieldName");

// Convert exception to ErrorDetail
try {
    // operation
} catch (Exception ex) {
    var error = ex.ToError("EXCEPTION_CODE");
}

// Convert validation results
var validationResults = Validate(model);
var errors = validationResults.ToErrorDetails();
var result = EasyResult<Model>.Fail(errors);

Result Extensions

// Wrap value in success result
var result = employee.AsSuccess("Employee created");

// Create failure from errors
var result = errors.AsFailure<Employee>();

// Add trace ID from HttpContext
var result = EasyResult<Employee>.Ok(employee)
    .WithTraceId(HttpContext);

// Ensure data exists
var result = await GetEmployeeAsync(id);
result = result.EnsureData("Employee data is required");

📊 HTTP Status Code Mapping

EasyResult automatically maps error codes to appropriate HTTP status codes:

Error Code HTTP Status Status Code
NOT_FOUND Not Found 404
UNAUTHORIZED Unauthorized 401
FORBIDDEN Forbidden 403
VALIDATION_ERROR Unprocessable Entity 422
CONFLICT Conflict 409
Any other Bad Request 400
Success OK 200

Custom status codes:

// Use custom success status code
return result.ToActionResult(successStatusCode: StatusCodes.Status201Created);

🎯 Best Practices

1. Use Specific Error Codes

// ❌ Bad
EasyResult<User>.Fail("ERROR", "Something went wrong");

// ✅ Good
EasyResult<User>.Fail("USER_NOT_FOUND", "User with ID 123 not found");
EasyResult<User>.Fail("INVALID_EMAIL_FORMAT", "Email must be in valid format", "Email");

2. Leverage Field Parameter for Validation Errors

EasyResult<User>.ValidationError("Email is required", "Email");
EasyResult<User>.ValidationError("Password must be at least 8 characters", "Password");

3. Use Metadata for Rich Error Context

var error = new ErrorDetail(
    "RATE_LIMIT_EXCEEDED",
    "Too many requests",
    null,
    new Dictionary<string, object> {
        ["RetryAfter"] = 60,
        ["CurrentCount"] = 150,
        ["Limit"] = 100
    }
);

4. Combine Features for Complex Scenarios

// Performance tracking + Pipeline + Warnings
var result = await PerformanceTracker.TrackAsync(async () =>
{
    var warnings = new List<Warning>();
    
    var pipeline = await ResultPipeline<Order>.Start(order)
        .Then(o => ValidateOrder(o))
        .ThenAsync(o => ProcessPayment(o))
        .Then(o => CreateInvoice(o))
        .Tap(o => warnings.AddRange(CheckInventory(o)))
        .Build();
    
    return warnings.Any()
        ? EasyResultWithWarnings<Order>.OkWithWarnings(pipeline.Data!, warnings)
        : EasyResultWithWarnings<Order>.Ok(pipeline.Data!);
        
}, "ProcessOrder");

📝 Complete Example

using Microsoft.AspNetCore.Mvc;
using SkyWebFramework.Results;

[ApiController]
[Route("api/[controller]")]
public class EmployeeController : ControllerBase
{
    private readonly IEmployeeRepository _repository;
    private readonly ILogger<EmployeeController> _logger;
    
    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var employee = _repository.GetById(id);
        
        if (employee == null)
            return EasyResult<Employee>.NotFound($"Employee {id} not found")
                .ToActionResult();
        
        return EasyResult<Employee>.Ok(employee).ToActionResult();
    }
    
    [HttpPost]
    public async Task<IActionResult> Create(CreateEmployeeRequest request)
    {
        // Validate with business rules
        var validator = new BusinessRuleValidator<CreateEmployeeRequest>()
            .AddRule("ValidEmail", r => IsValidEmail(r.Email), 
                "INVALID_EMAIL", "Invalid email format")
            .AddRule("UniqueName", r => !_repository.NameExists(r.Name),
                "DUPLICATE_NAME", "Name already exists");
        
        var validationResult = validator.Validate(request);
        if (!validationResult.Success)
            return validationResult.ToActionResult();
        
        // Create with pipeline
        var result = await ResultPipeline<Employee>.Start(MapToEmployee(request))
            .ThenAsync(emp => SaveEmployeeAsync(emp))
            .Tap(emp => _logger.LogInformation($"Created employee {emp.Id}"))
            .ThenAsync(emp => SendWelcomeEmailAsync(emp))
            .OnError(errors => {
                _logger.LogError("Failed to create employee");
                return EasyResult<Employee>.Fail(errors);
            })
            .Build();
        
        return result.ToActionResult(StatusCodes.Status201Created);
    }
    
    [HttpGet]
    public IActionResult GetAll(int page = 1, int pageSize = 10)
    {
        var totalCount = _repository.Count();
        var items = _repository.GetPage(page, pageSize);
        
        var result = EasyPagedResult<Employee>.Ok(items, page, pageSize, totalCount);
        return result.ToActionResult();
    }
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

Built with ❤️ for the ASP.NET Core community.

📞 Support

  • 📧 Email: Surya29012001@gmail.com
  • +91 9519723413

Made with ❤️ by SkyWebFramework Team

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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. 
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
1.0.1 157 11/1/2025