SkyWebFramework.Results
1.0.1
dotnet add package SkyWebFramework.Results --version 1.0.1
NuGet\Install-Package SkyWebFramework.Results -Version 1.0.1
<PackageReference Include="SkyWebFramework.Results" Version="1.0.1" />
<PackageVersion Include="SkyWebFramework.Results" Version="1.0.1" />
<PackageReference Include="SkyWebFramework.Results" />
paket add SkyWebFramework.Results --version 1.0.1
#r "nuget: SkyWebFramework.Results, 1.0.1"
#:package SkyWebFramework.Results@1.0.1
#addin nuget:?package=SkyWebFramework.Results&version=1.0.1
#tool nuget:?package=SkyWebFramework.Results&version=1.0.1
🚀 EasyResult - Advanced Result Pattern for ASP.NET Core
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 | Versions 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. |
-
net8.0
- Microsoft.AspNetCore.Http.Abstractions (>= 2.3.0)
- Microsoft.AspNetCore.Mvc.Abstractions (>= 2.3.0)
- Microsoft.AspNetCore.Mvc.Core (>= 2.3.0)
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 |