{"id":21000938,"url":"https://github.com/managedcode/communication","last_synced_at":"2026-02-07T16:05:17.173Z","repository":{"id":45725675,"uuid":"514178542","full_name":"managedcode/Communication","owner":"managedcode","description":"Communication library is a convenient wrapper for handling the results of functions that do not throw exceptions. Instead of throwing exceptions, these functions return an object that contains the result of the operation.","archived":false,"fork":false,"pushed_at":"2024-12-10T20:11:00.000Z","size":175,"stargazers_count":34,"open_issues_count":5,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-29T20:09:38.097Z","etag":null,"topics":["exeption-handling","requests","result","results"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/managedcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-15T07:37:32.000Z","updated_at":"2025-02-17T20:49:16.000Z","dependencies_parsed_at":"2024-04-25T20:25:52.914Z","dependency_job_id":"42bef8a5-eeff-4d01-ac83-5abd6fe2ef3b","html_url":"https://github.com/managedcode/Communication","commit_stats":{"total_commits":60,"total_committers":5,"mean_commits":12.0,"dds":"0.33333333333333337","last_synced_commit":"39f31190ee54b28c886547acdbb2241baa773c98"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FCommunication","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FCommunication/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FCommunication/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/managedcode%2FCommunication/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/managedcode","download_url":"https://codeload.github.com/managedcode/Communication/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247399885,"owners_count":20932880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exeption-handling","requests","result","results"],"created_at":"2024-11-19T08:13:02.421Z","updated_at":"2026-02-07T16:05:17.164Z","avatar_url":"https://github.com/managedcode.png","language":"C#","readme":"# ManagedCode.Communication\n\nResult pattern for .NET that replaces exceptions with type-safe return values. Features railway-oriented programming, ASP.NET Core integration, RFC 7807 Problem Details, and built-in pagination. Designed for production systems requiring explicit error handling without the overhead of throwing exceptions.\n\n[![NuGet](https://img.shields.io/nuget/v/ManagedCode.Communication.svg)](https://www.nuget.org/packages/ManagedCode.Communication/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![.NET](https://img.shields.io/badge/.NET-9.0)](https://dotnet.microsoft.com/)\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Key Features](#key-features)\n- [Installation](#installation)\n- [Logging Configuration](#logging-configuration)\n- [Core Concepts](#core-concepts)\n- [Quick Start](#quick-start)\n- [API Reference](#api-reference)\n- [Railway-Oriented Programming](#railway-oriented-programming)\n- [Command Pattern and Idempotency](#command-pattern-and-idempotency)\n  - [Command Correlation and Tracing Identifiers](#command-correlation-and-tracing-identifiers)\n  - [Idempotency Architecture Overview](#idempotency-architecture-overview)\n- [Error Handling Patterns](#error-handling-patterns)\n- [Integration Guides](#integration-guides)\n- [Performance](#performance)\n- [Comparison](#comparison)\n- [Best Practices](#best-practices)\n- [Examples](#examples)\n- [Migration Guide](#migration-guide)\n\n## Overview\n\nManagedCode.Communication brings functional error handling to .NET through the Result pattern. Instead of throwing exceptions, methods return Result types that explicitly indicate success or failure. This approach eliminates hidden control flow, improves performance, and makes error handling a first-class concern in your codebase.\n\n### Why Result Pattern?\n\nTraditional exception handling has several drawbacks:\n\n- **Performance overhead**: Throwing exceptions is expensive\n- **Hidden control flow**: Exceptions create invisible exit points in your code\n- **Unclear contracts**: Methods don't explicitly declare what errors they might produce\n- **Testing complexity**: Exception paths require separate test scenarios\n\nThe Result pattern solves these issues by:\n\n- **Explicit error handling**: Errors are part of the method signature\n- **Performance**: No exception throwing overhead\n- **Composability**: Chain operations using railway-oriented programming\n- **Type safety**: Compiler ensures error handling\n- **Testability**: All paths are explicit and easy to test\n\n## Key Features\n\n### 🎯 Core Result Types\n\n- **`Result`**: Represents success/failure without a value\n- **`Result\u003cT\u003e`**: Represents success with value `T` or failure\n- **`CollectionResult\u003cT\u003e`**: Represents collections with built-in pagination\n- **`Problem`**: RFC 7807 compliant error details\n\n### ⚙️ Static Factory Abstractions\n\n- Leverage C# static interface members to centralize factory overloads for every result, command, and collection type.\n- `IResultFactory\u003cT\u003e` and `ICommandFactory\u003cT\u003e` deliver a consistent surface while bridge helpers remove repetitive boilerplate.\n- Extending the library now only requires implementing the minimal `Succeed`/`Fail` contract—the shared helpers provide the rest.\n\n### 🧭 Pagination Utilities\n\n- `PaginationRequest` encapsulates skip/take semantics, built-in normalization, and clamping helpers.\n- `PaginationOptions` lets you define default, minimum, and maximum page sizes for a bounded API surface.\n- `PaginationCommand` captures pagination intent as a first-class command with generated overloads for skip/take, page numbers, and enum command types.\n- `CollectionResult\u003cT\u003e.Succeed(..., PaginationRequest request, int totalItems)` keeps result metadata aligned with pagination commands.\n\n### 🚂 Railway-Oriented Programming\n\nComplete set of functional combinators for composing operations:\n\n- `Map`: Transform success values\n- `Bind` / `Then`: Chain Result-returning operations\n- `Tap` / `Do`: Execute side effects\n- `Match`: Pattern matching on success/failure\n- `Compensate`: Recovery from failures\n- `Merge` / `Combine`: Aggregate multiple results\n\n### 🌐 Framework Integration\n\n- **ASP.NET Core**: Automatic HTTP response mapping\n- **SignalR**: Hub filters for real-time error handling\n- **Microsoft Orleans**: Grain call filters and surrogates\n- **Command Pattern**: Built-in command infrastructure with idempotency\n\n### 🔍 Observability Built In\n\n- Source-generated `LoggerCenter` APIs provide zero-allocation logging across ASP.NET Core filters, SignalR hubs, and command stores.\n- Call sites automatically check log levels, so you only pay for the logs you emit.\n- Extend logging with additional `[LoggerMessage]` partials to keep high-volume paths allocation free.\n\n### 🛡️ Error Types\n\nPre-defined error categories with appropriate HTTP status codes:\n\n- Validation errors (400 Bad Request)\n- Not Found (404)\n- Unauthorized (401)\n- Forbidden (403)\n- Internal Server Error (500)\n- Custom enum-based errors\n\n## Installation\n\n### Package Manager Console\n\n```powershell\n# Core library\nInstall-Package ManagedCode.Communication\n\n# ASP.NET Core integration\nInstall-Package ManagedCode.Communication.AspNetCore\n\n# Minimal API extensions\nInstall-Package ManagedCode.Communication.Extensions\n\n# Orleans integration\nInstall-Package ManagedCode.Communication.Orleans\n```\n\n### .NET CLI\n\n```bash\n# Core library\ndotnet add package ManagedCode.Communication\n\n# ASP.NET Core integration\ndotnet add package ManagedCode.Communication.AspNetCore\n\n# Minimal API extensions\ndotnet add package ManagedCode.Communication.Extensions\n\n# Orleans integration\ndotnet add package ManagedCode.Communication.Orleans\n```\n\n### PackageReference\n\n```xml\n\u003cPackageReference Include=\"ManagedCode.Communication\" Version=\"9.6.0\" /\u003e\n\u003cPackageReference Include=\"ManagedCode.Communication.AspNetCore\" Version=\"9.6.0\" /\u003e\n\u003cPackageReference Include=\"ManagedCode.Communication.Extensions\" Version=\"9.6.0\" /\u003e\n\u003cPackageReference Include=\"ManagedCode.Communication.Orleans\" Version=\"9.6.0\" /\u003e\n```\n\n## Logging Configuration\n\nThe library includes integrated logging for error scenarios. Configure logging to capture detailed error information:\n\n### ASP.NET Core Setup\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\n// Add your logging configuration\nbuilder.Logging.AddConsole();\nbuilder.Logging.AddDebug();\n\n// Register other services\nbuilder.Services.AddControllers();\n\n// Configure Communication library - this enables automatic error logging\nbuilder.Services.ConfigureCommunication();\n\nvar app = builder.Build();\n```\n\n### Minimal API Result Mapping\n\nAdd the optional `ManagedCode.Communication.Extensions` package to bridge Minimal API endpoints with the Result pattern. The\npackage provides the `ResultEndpointFilter` and a fluent helper `WithCommunicationResults` that wraps the endpoint builder and\nreturns `IResult` instances automatically:\n\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\nbuilder.Services.ConfigureCommunication();\n\nvar app = builder.Build();\n\n// Apply the filter to a single endpoint\napp.MapGet(\"/orders/{id}\", async (Guid id, IOrderService orders) =\u003e\n        await orders.GetAsync(id))\n   .WithCommunicationResults();\n\n// Or apply it to a group so every route inherits the conversion\napp.MapGroup(\"/orders\")\n   .WithCommunicationResults()\n   .MapPost(string.Empty, async (CreateOrder command, IOrderService orders) =\u003e\n        await orders.CreateAsync(command));\n\napp.Run();\n```\n\nHandlers can return any `Result` or `Result\u003cT\u003e` instance and the filter will reuse the existing ASP.NET Core converters so\nyou do not need to write manual `IResult` translations.\n\n### Resilient HTTP Clients\n\nThe extensions package also ships helpers that turn `HttpClient` calls directly into `Result` instances and optionally run\nthem through Polly resilience pipelines:\n\n```csharp\nusing ManagedCode.Communication.Extensions.Http;\nusing Polly;\nusing Polly.Retry;\n\nvar pipeline = new ResiliencePipelineBuilder\u003cHttpResponseMessage\u003e()\n    .AddRetry(new RetryStrategyOptions\u003cHttpResponseMessage\u003e\n    {\n        MaxRetryAttempts = 3,\n        Delay = TimeSpan.FromMilliseconds(200),\n        ShouldHandle = new PredicateBuilder\u003cHttpResponseMessage\u003e()\n            .HandleResult(response =\u003e !response.IsSuccessStatusCode)\n    })\n    .Build();\n\nvar result = await httpClient.SendForResultAsync\u003cOrderDto\u003e(\n    () =\u003e new HttpRequestMessage(HttpMethod.Get, $\"/orders/{orderId}\"),\n    pipeline);\n\nif (result.IsSuccess)\n{\n    // access result.Value without manually reading the HTTP payload\n}\n```\n\nThe helpers use the existing `HttpResponseMessage` converters, so non-success status codes automatically map to a\n`Problem` with the response body and status code.\nsuccess responses map to `200 OK`/`204 No Content` while failures become RFC 7807 problem details. Native `Microsoft.AspNetCore.Http.IResult`\nresponses pass through unchanged, so you can mix and match traditional Minimal API patterns with ManagedCode.Communication results.\n\n### Console Application Setup\n\n```csharp\nvar services = new ServiceCollection();\n\n// Add logging\nservices.AddLogging(builder =\u003e \n{\n    builder.AddConsole()\n           .SetMinimumLevel(LogLevel.Information);\n});\n\n// Configure Communication library\nservices.ConfigureCommunication();\n\nvar serviceProvider = services.BuildServiceProvider();\n```\n\nThe library automatically logs errors in Result factory methods (`From`, `Try`, etc.) with detailed context including file names, line numbers, and method names for easier debugging.\n\n## Core Concepts\n\n### Result Type\n\nThe `Result` type represents an operation that can either succeed or fail:\n\n```csharp\npublic struct Result\n{\n    public bool IsSuccess { get; }\n    public Problem? Problem { get; }\n}\n```\n\n### Result Type with Value\n\nThe generic `Result\u003cT\u003e` includes a value on success:\n\n```csharp\npublic struct Result\u003cT\u003e\n{\n    public bool IsSuccess { get; }\n    public T? Value { get; }\n    public Problem? Problem { get; }\n}\n```\n\n### Problem Type\n\nImplements RFC 7807 Problem Details for HTTP APIs:\n\n```csharp\npublic class Problem\n{\n    public string Type { get; set; }\n    public string Title { get; set; }\n    public int StatusCode { get; set; }\n    public string Detail { get; set; }\n    public Dictionary\u003cstring, object\u003e Extensions { get; set; }\n}\n```\n\n### Display Message Helpers\n\nUse built-in helpers to convert technical `Problem` payloads into UI-friendly messages:\n\n```csharp\nvar problem = Problem.Create(\"RegistrationUnavailable\", \"Service is temporarily unavailable\", 503);\nproblem.ErrorCode = \"RegistrationUnavailable\";\n\n// Default message resolution chain:\n// ErrorCode mapper -\u003e Detail -\u003e Title -\u003e defaultMessage -\u003e \"An error occurred\"\nvar message = problem.ToDisplayMessage(defaultMessage: \"Please try again later\");\n\nvar registrationMessages = new Dictionary\u003cstring, string\u003e\n{\n    [\"RegistrationUnavailable\"] = \"Registration is currently unavailable.\",\n    [\"RegistrationBlocked\"] = \"Registration is temporarily blocked.\",\n    [\"RegistrationInviteRequired\"] = \"Registration requires an invitation code.\"\n};\n\n// 1) Dictionary overload\nvar byDictionary = problem.ToDisplayMessage(\n    registrationMessages,\n    defaultMessage: \"Please try again later\");\n\n// 2) Tuple mappings overload\nvar byTuples = problem.ToDisplayMessage(\n    \"Please try again later\",\n    (\"RegistrationUnavailable\", \"Registration is currently unavailable.\"),\n    (\"RegistrationBlocked\", \"Registration is temporarily blocked.\"),\n    (\"RegistrationInviteRequired\", \"Registration requires an invitation code.\"));\n\n// 3) Delegate overload\nstatic string? ResolveRegistrationMessage(string code) =\u003e code switch\n{\n    \"RegistrationUnavailable\" =\u003e \"Registration is currently unavailable.\",\n    \"RegistrationBlocked\" =\u003e \"Registration is temporarily blocked.\",\n    \"RegistrationInviteRequired\" =\u003e \"Registration requires an invitation code.\",\n    _ =\u003e null\n};\n\nvar byDelegate = problem.ToDisplayMessage(\n    ResolveRegistrationMessage,\n    defaultMessage: \"Please try again later\");\n\n// The same overloads are available for Result, Result\u003cT\u003e and CollectionResult\u003cT\u003e\nvar resultMessage = Result.Fail(problem).ToDisplayMessage(\n    registrationMessages,\n    defaultMessage: \"Please try again later\");\n\n// Typed extension access\nif (problem.TryGetExtension(\"retryAfter\", out int retryAfterSeconds))\n{\n    Console.WriteLine($\"Retry after: {retryAfterSeconds}s\");\n}\n```\n\n## Quick Start\n\n### Basic Usage\n\n```csharp\nusing ManagedCode.Communication;\n\n// Creating Results\nvar success = Result.Succeed();\nvar failure = Result.Fail(\"Operation failed\");\n\n// Results with values\nvar userResult = Result\u003cUser\u003e.Succeed(new User { Id = 1, Name = \"John\" });\nvar notFound = Result\u003cUser\u003e.FailNotFound(\"User not found\");\n\n// Validation errors\nvar invalid = Result.FailValidation(\n    (\"email\", \"Email is required\"),\n    (\"age\", \"Age must be positive\")\n);\n\n// From exceptions\ntry\n{\n    // risky operation\n}\ncatch (Exception ex)\n{\n    var error = Result.Fail(ex);\n}\n```\n\n### Checking Result State\n\n```csharp\nif (result.IsSuccess)\n{\n    // Handle success\n}\n\nif (result.IsFailed)\n{\n    // Handle failure\n}\n\nif (result.IsInvalid)\n{\n    // Handle validation errors\n}\n\n// Pattern matching\nresult.Match(\n    onSuccess: () =\u003e Console.WriteLine(\"Success!\"),\n    onFailure: problem =\u003e Console.WriteLine($\"Failed: {problem.Detail}\")\n);\n```\n\n## API Reference\n\n### Result Creation Methods\n\n#### Success Methods\n\n```csharp\n// Basic success\nResult.Succeed()\nResult\u003cT\u003e.Succeed(T value)\nCollectionResult\u003cT\u003e.Succeed(T[] items, int pageNumber, int pageSize, int totalItems)\n\n// From operations\nResult.From(Action action)\nResult\u003cT\u003e.From(Func\u003cT\u003e func)\nResult\u003cT\u003e.From(Task\u003cT\u003e task)\n\n// Try pattern with exception catching\nResult.Try(Action action)\nResult\u003cT\u003e.Try(Func\u003cT\u003e func)\n```\n\n#### Failure Methods\n\n```csharp\n// Basic failures\nResult.Fail()\nResult.Fail(string title)\nResult.Fail(string title, string detail)\nResult.Fail(Problem problem)\nResult.Fail(Exception exception)\n\n// HTTP status failures\nResult.FailNotFound(string detail)\nResult.FailUnauthorized(string detail)\nResult.FailForbidden(string detail)\n\n// Validation failures\nResult.FailValidation(params (string field, string message)[] errors)\nResult.Invalid(string message)\nResult.Invalid(string field, string message)\n\n// Enum-based failures\nResult.Fail\u003cTEnum\u003e(TEnum errorCode) where TEnum : Enum\n```\n\n### Transformation Methods\n\n```csharp\n// Map: Transform the value\nResult\u003cint\u003e ageResult = userResult.Map(user =\u003e user.Age);\n\n// Bind: Chain operations that return Results\nResult\u003cOrder\u003e orderResult = userResult\n    .Bind(user =\u003e GetUserCart(user.Id))\n    .Bind(cart =\u003e CreateOrder(cart));\n\n// Tap: Execute side effects\nResult\u003cUser\u003e result = userResult\n    .Tap(user =\u003e _logger.LogInfo($\"Processing user {user.Id}\"))\n    .Tap(user =\u003e _cache.Set(user.Id, user));\n```\n\n### Validation Methods\n\n```csharp\n// Ensure: Add validation\nResult\u003cUser\u003e validUser = userResult\n    .Ensure(user =\u003e user.Age \u003e= 18, Problem.Create(\"User must be 18+\"))\n    .Ensure(user =\u003e user.Email.Contains(\"@\"), Problem.Create(\"Invalid email\"));\n\n// Where: Filter with predicate\nResult\u003cUser\u003e filtered = userResult\n    .Where(user =\u003e user.IsActive, \"User is not active\");\n\n// FailIf: Conditional failure\nResult\u003cOrder\u003e order = orderResult\n    .FailIf(o =\u003e o.Total \u003c= 0, \"Order total must be positive\");\n\n// OkIf: Must satisfy condition\nResult\u003cPayment\u003e payment = paymentResult\n    .OkIf(p =\u003e p.IsAuthorized, \"Payment not authorized\");\n```\n\n## Railway-Oriented Programming\n\nRailway-oriented programming treats operations as a series of tracks where success continues on the main track and failures switch to an error track:\n\n### Basic Chaining\n\n```csharp\npublic Result\u003cOrder\u003e ProcessOrder(int userId)\n{\n    return Result.From(() =\u003e GetUser(userId))\n        .Then(user =\u003e ValidateUser(user))\n        .Then(user =\u003e GetUserCart(user.Id))\n        .Then(cart =\u003e ValidateCart(cart))\n        .Then(cart =\u003e CreateOrder(cart))\n        .Then(order =\u003e ProcessPayment(order))\n        .Then(order =\u003e SendConfirmation(order));\n}\n```\n\n### Async Operations\n\n```csharp\npublic async Task\u003cResult\u003cOrder\u003e\u003e ProcessOrderAsync(int userId)\n{\n    return await Result.From(() =\u003e GetUserAsync(userId))\n        .ThenAsync(user =\u003e ValidateUserAsync(user))\n        .ThenAsync(user =\u003e GetUserCartAsync(user.Id))\n        .ThenAsync(cart =\u003e CreateOrderAsync(cart))\n        .ThenAsync(order =\u003e ProcessPaymentAsync(order))\n        .ThenAsync(order =\u003e SendConfirmationAsync(order));\n}\n```\n\n### Error Recovery\n\n```csharp\nvar result = await GetPrimaryService()\n    .CompensateAsync(async error =\u003e \n    {\n        _logger.LogWarning($\"Primary service failed: {error.Detail}\");\n        return await GetFallbackService();\n    })\n    .CompensateWith(defaultValue); // Final fallback\n```\n\n### Combining Multiple Results\n\n```csharp\n// Merge: Stop at first failure\nvar firstFailureResult = Result.Merge(\n    ValidateName(name),\n    ValidateEmail(email),\n    ValidateAge(age)\n);\n\n// MergeAll: aggregate all failures\nvar allFailuresResult = Result.MergeAll(\n    ValidateName(name),\n    ValidateEmail(email),\n    ValidateAge(age)\n);\n\nif (allFailuresResult.TryGetProblem(out var problem))\n{\n    // All failures were validation failures:\n    // problem.GetValidationErrors() returns merged field errors.\n    //\n    // Mixed failures (401/403/500/...) return aggregate problem:\n    // problem.StatusCode == 500\n    // problem.Extensions[\"errors\"] contains the original Problem[] list.\n}\n\nif (allFailuresResult.TryGetProblem(out var aggregateProblem) \u0026\u0026\n    aggregateProblem.TryGetExtension(\"errors\", out Problem[]? originalErrors))\n{\n    foreach (var error in originalErrors)\n    {\n        Console.WriteLine($\"{error.StatusCode}: {error.Title} - {error.Detail}\");\n    }\n}\n\n// Combine: Aggregate values\nvar combined = Result.Combine(\n    GetUserProfile(),\n    GetUserSettings(),\n    GetUserPermissions()\n); // Returns CollectionResult\u003cT\u003e\n\n// CombineAll: aggregate failures while preserving original errors\nvar combinedAll = Result.CombineAll(\n    GetUserProfile(),\n    GetUserSettings(),\n    GetUserPermissions()\n);\n```\n\n## Command Pattern and Idempotency\n\n### Command Infrastructure\n\nThe library includes built-in support for command pattern with distributed idempotency:\n\n```csharp\n// Basic command\npublic class CreateOrderCommand : Command\u003cOrder\u003e\n{\n    public CreateOrderCommand(string orderId, Order order) \n        : base(orderId, \"CreateOrder\")\n    {\n        Value = order;\n        UserId = \"user123\";\n        CorrelationId = Guid.NewGuid().ToString();\n    }\n}\n\n// Command with metadata\nvar command = new Command(\"command-id\", \"ProcessPayment\")\n{\n    UserId = \"user123\",\n    SessionId = \"session456\",\n    CorrelationId = \"correlation789\",\n    CausationId = \"parent-command-id\",\n    TraceId = Activity.Current?.TraceId.ToString(),\n    SpanId = Activity.Current?.SpanId.ToString()\n};\n```\n\n### Pagination Commands\n\nPagination is now a first-class command concept that keeps factories DRY and metadata consistent:\n\n```csharp\nvar options = new PaginationOptions(defaultPageSize: 25, maxPageSize: 100);\nvar request = PaginationRequest.Create(skip: 0, take: 0, options); // take defaults to 25\n\n// Rich factory surface without duplicate overloads\nvar paginationCommand = PaginationCommand.Create(request, options)\n    .WithCorrelationId(Guid.NewGuid().ToString());\n\n// Apply to results without manually recalculating metadata\nvar page = CollectionResult\u003cOrder\u003e.Succeed(orders, paginationCommand.Value!, totalItems: 275, options);\n\n// Use enum-based command types when desired\nenum PaginationCommandType { ListCustomers }\nvar typedCommand = PaginationCommand.Create(PaginationCommandType.ListCustomers);\n```\n\n`PaginationRequest` exposes helpers such as `Normalize`, `ClampToTotal`, and `ToSlice` to keep skip/take logic predictable. Configure bounds globally with `PaginationOptions` to protect APIs from oversized queries.\n\n### Idempotent Command Execution\n\n#### ASP.NET Core Idempotency\n\n```csharp\n// Register idempotency store\nbuilder.Services.AddSingleton\u003cICommandIdempotencyStore, InMemoryCommandIdempotencyStore\u003e();\n// Or use Orleans-based store\nbuilder.Services.AddSingleton\u003cICommandIdempotencyStore, OrleansCommandIdempotencyStore\u003e();\n\n// Service with idempotent operations\npublic class PaymentService\n{\n    private readonly ICommandIdempotencyStore _idempotencyStore;\n    \n    public async Task\u003cResult\u003cPayment\u003e\u003e ProcessPaymentAsync(ProcessPaymentCommand command)\n    {\n        // Automatic idempotency - returns cached result if already executed\n        return await _idempotencyStore.ExecuteIdempotentAsync(\n            command.Id,\n            async () =\u003e\n            {\n                // This code runs only once per command ID\n                var payment = await _paymentGateway.ChargeAsync(command.Amount);\n                await _repository.SavePaymentAsync(payment);\n                return Result\u003cPayment\u003e.Succeed(payment);\n            },\n            command.Metadata\n        );\n    }\n}\n```\n\n#### Orleans-Based Idempotency\n\n```csharp\n// Automatic idempotency with Orleans grains\npublic class OrderGrain : Grain, IOrderGrain\n{\n    private readonly ICommandIdempotencyStore _idempotencyStore;\n    \n    public async Task\u003cResult\u003cOrder\u003e\u003e CreateOrderAsync(CreateOrderCommand command)\n    {\n        // Uses ICommandIdempotencyGrain internally for distributed coordination\n        return await _idempotencyStore.ExecuteIdempotentAsync(\n            command.Id,\n            async () =\u003e\n            {\n                // Guaranteed to execute only once across the cluster\n                var order = new Order { /* ... */ };\n                await SaveOrderAsync(order);\n                return Result\u003cOrder\u003e.Succeed(order);\n            }\n        );\n    }\n}\n```\n\n### Command Execution Status\n\n```csharp\npublic enum CommandExecutionStatus\n{\n    NotStarted,    // Command hasn't been processed\n    Processing,    // Currently being processed\n    Completed,     // Successfully completed\n    Failed,        // Processing failed\n    Expired        // Result expired from cache\n}\n\n// Check command status\nvar status = await _idempotencyStore.GetCommandStatusAsync(\"command-id\");\nif (status == CommandExecutionStatus.Completed)\n{\n    var result = await _idempotencyStore.GetCommandResultAsync\u003cOrder\u003e(\"command-id\");\n}\n```\n\n### Command Correlation and Tracing Identifiers\n\nCommands implement `ICommand` and surface correlation, causation, trace, span, user, and session identifiers alongside optional metadata so every hop can attach observability context. The base `Command` and `Command\u003cT\u003e` types keep those properties on the\nroot object, and serializers/Orleans surrogates round-trip them without custom plumbing.\nroot object, and serializers/Orleans surrogates round-trip them without custom plumbing.\n\n#### Identifier lifecycle\n- Static command factories generate monotonic version 7 identifiers via `Guid.CreateVersion7()` and stamp a UTC timestamp so commands can be sorted chronologically even when sharded.\n- Factory helpers never mutate the correlation or trace identifiers; callers opt in by supplying values through fluent `WithCorrelationId`, `WithTraceId`, and similar extension methods that return the same command instance.\n- Metadata mirrors the trace/span identifiers for workload-specific diagnostics without coupling transport-level identifiers to\npayload annotations.\n\n#### Field reference\n\n| Field | Purpose | Typical source | Notes |\n| --- | --- | --- | --- |\n| `CommandId` | Unique, monotonic identifier for deduplication | Static command factories | Remains stable for retries and storage lookups. |\n| `CorrelationId` | Ties a command to an upstream workflow/request | HTTP `X-Correlation-Id`, message headers | Preserved through\n serialization and Orleans surrogates. |\n| `CausationId` | Records the predecessor command/event | Current command ID | Supports causal chains in telemetry. |\n| `TraceId` | Connects to distributed tracing spans | OpenTelemetry/`Activity` context | The library stores, but never generate\ns, trace identifiers. |\n| `SpanId` | Identifies the originating span | OpenTelemetry/`Activity` context | Often paired with `Metadata.TraceId` for deep\ner traces. |\n| `UserId` / `SessionId` | Attach security/session principals | Authentication middleware | Useful for multi-tenant auditing. |\n\n#### Trace vs. correlation\n- **Correlation IDs** bundle every command spawned from a single business request. Assign them at ingress and keep the value st\nable across retries so dashboards can answer “what commands ran because of this call?”.\n- **Trace/Span IDs** follow distributed tracing semantics. Commands avoid creating new traces and instead persist the ambient `A\nctivity` identifiers through serialization so telemetry back-ends can stitch spans together.\n- Both identifier sets are serialized together, enabling pivots between business-level correlation and technical call graphs wit\nhout extra configuration.\n\n#### Generation and propagation guidance\n- Use `Command.Create(...)` / `Command\u003cT\u003e.Create(...)` (or the matching `From(...)` helpers) to get a version 7 identifier and U\nTC timestamp automatically.\n- Read or generate correlation IDs from HTTP headers or upstream messages and apply them via `.WithCorrelationId(...)` before d\nispatching commands.\n- Capture `Activity.TraceId`/`Activity.SpanId` through `.WithTraceId(...)` and `.WithSpanId(...)` (and metadata counterparts) wh\nen bridging to queues, Orleans, or background pipelines.\n- Serialization tests verify the identifiers round-trip, so consumers can rely on receiving the same values they emitted.\n\n#### Operational considerations\n- Factory unit tests ensure commands created through the helpers carry version 7 identifiers, UTC timestamps, and derived `Comma\nndType` values for traceability.\n- Idempotency regression tests assert that concurrent callers reuse cached results and propagate failures consistently, preservi\nng correlation integrity when retry storms occur.\n\n### Idempotency Architecture Overview\n\n#### Scope\nThe shared idempotency helpers (`CommandIdempotencyExtensions`), default in-memory store, and test coverage work together to pro\ntect concurrency, caching, and retry behaviour across hosts.\n\n#### Strengths\n- **Deterministic status transitions.** `ExecuteIdempotentAsync` only invokes the provided delegate after atomically claiming th\ne command, writes the result, and then flips the status to `Completed`, so retries either reuse cached output or wait for the in\n-flight execution to finish.\n- **Batch reuse of cached outputs.** Batch helpers perform bulk status/result lookups and bypass execution for already completed\n commands, even when cached results are `null` or default values.\n- **Fine-grained locking in the memory store.** Per-command `SemaphoreSlim` instances eliminate global contention, and reference\n counting ensures locks are released once no callers use a key.\n- **Concurrency regression tests.** Dedicated unit tests confirm that concurrent callers share a single execution, failed primar\ny runs surface consistent exceptions, and the final status ends up in `Failed` when appropriate.\n\n#### Risks \u0026 considerations\n- **Missing-result ambiguity.** If a store reports `Completed` but the result entry expired, the extensions currently return the\n default value. Stores that can distinguish “missing” from “stored default” should override `TryGetCachedResultAsync` to trigger\n a re-execution.\n- **Wait semantics rely on polling.** Adaptive polling keeps responsiveness reasonable, but distributed stores can swap in push-\nstyle notifications if tail latency becomes critical.\n- **Status retention policies.** The memory store’s cleanup removes status and result after a TTL; other implementations must pr\novide similar hygiene to avoid unbounded growth while keeping enough history for retries.\n\n#### Recommendations\n1. Document store-specific retention guarantees so callers can tune retry windows.\n2. Consider extending the store contract with a boolean flag (or sentinel wrapper) that differentiates cached `default` values f\nrom missing entries.\n3. Monitor lock-pool growth in long-lived applications and log keys that never release to diagnose misbehaving callers before me\nmory pressure builds up.\n\n## Error Handling Patterns\n\n### Validation Pattern\n\n```csharp\npublic Result\u003cUser\u003e CreateUser(CreateUserDto dto)\n{\n    // Collect all validation errors\n    var errors = new List\u003c(string field, string message)\u003e();\n    \n    if (string.IsNullOrEmpty(dto.Email))\n        errors.Add((\"email\", \"Email is required\"));\n    \n    if (!dto.Email.Contains(\"@\"))\n        errors.Add((\"email\", \"Invalid email format\"));\n    \n    if (dto.Age \u003c 0)\n        errors.Add((\"age\", \"Age must be positive\"));\n    \n    if (dto.Age \u003c 18)\n        errors.Add((\"age\", \"Must be 18 or older\"));\n    \n    if (errors.Any())\n        return Result\u003cUser\u003e.FailValidation(errors.ToArray());\n    \n    var user = new User { /* ... */ };\n    return Result\u003cUser\u003e.Succeed(user);\n}\n```\n\n### Repository Pattern with Entity Framework\n\n```csharp\npublic class UserRepository\n{\n    private readonly AppDbContext _context;\n    private readonly ILogger\u003cUserRepository\u003e _logger;\n    \n    public async Task\u003cResult\u003cUser\u003e\u003e GetByIdAsync(int id)\n    {\n        try\n        {\n            var user = await _context.Users\n                .AsNoTracking()\n                .FirstOrDefaultAsync(u =\u003e u.Id == id);\n            \n            if (user == null)\n                return Result\u003cUser\u003e.FailNotFound($\"User {id} not found\");\n            \n            return Result\u003cUser\u003e.Succeed(user);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Database error getting user {UserId}\", id);\n            return Result\u003cUser\u003e.Fail(ex);\n        }\n    }\n    \n    public async Task\u003cCollectionResult\u003cUser\u003e\u003e GetPagedAsync(\n        int page, \n        int pageSize,\n        Expression\u003cFunc\u003cUser, bool\u003e\u003e? filter = null,\n        Expression\u003cFunc\u003cUser, object\u003e\u003e? orderBy = null)\n    {\n        try\n        {\n            // Build query with IQueryable for efficient SQL generation\n            IQueryable\u003cUser\u003e query = _context.Users.AsNoTracking();\n            \n            // Apply filter if provided\n            if (filter != null)\n                query = query.Where(filter);\n            \n            // Apply ordering\n            query = orderBy != null \n                ? query.OrderBy(orderBy) \n                : query.OrderBy(u =\u003e u.Id);\n            \n            // Get total count - generates COUNT(*) SQL query\n            var totalItems = await query.CountAsync();\n            \n            if (totalItems == 0)\n                return CollectionResult\u003cUser\u003e.Succeed(Array.Empty\u003cUser\u003e(), page, pageSize, 0);\n            \n            // Get page of data - generates SQL with OFFSET and FETCH\n            var users = await query\n                .Skip((page - 1) * pageSize)\n                .Take(pageSize)\n                .ToArrayAsync();\n            \n            return CollectionResult\u003cUser\u003e.Succeed(users, page, pageSize, totalItems);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Database error in GetPagedAsync\");\n            return CollectionResult\u003cUser\u003e.Fail(ex);\n        }\n    }\n    \n    // Example with complex query\n    public async Task\u003cCollectionResult\u003cUserDto\u003e\u003e SearchUsersAsync(\n        string searchTerm,\n        int page,\n        int pageSize)\n    {\n        try\n        {\n            var query = _context.Users\n                .AsNoTracking()\n                .Where(u =\u003e u.IsActive)\n                .Where(u =\u003e EF.Functions.Like(u.Name, $\"%{searchTerm}%\") ||\n                           EF.Functions.Like(u.Email, $\"%{searchTerm}%\"));\n            \n            // Count before projection for efficiency\n            var totalItems = await query.CountAsync();\n            \n            // Project to DTO and paginate - single SQL query\n            var users = await query\n                .OrderBy(u =\u003e u.Name)\n                .Skip((page - 1) * pageSize)\n                .Take(pageSize)\n                .Select(u =\u003e new UserDto\n                {\n                    Id = u.Id,\n                    Name = u.Name,\n                    Email = u.Email,\n                    LastLoginDate = u.LastLoginDate\n                })\n                .ToArrayAsync();\n            \n            return CollectionResult\u003cUserDto\u003e.Succeed(users, page, pageSize, totalItems);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Search failed for term: {SearchTerm}\", searchTerm);\n            return CollectionResult\u003cUserDto\u003e.Fail(ex);\n        }\n    }\n}\n```\n\n### Service Layer Pattern\n\n```csharp\npublic class OrderService\n{\n    public async Task\u003cResult\u003cOrder\u003e\u003e CreateOrderAsync(CreateOrderDto dto)\n    {\n        // Validate input\n        var validationResult = ValidateOrderDto(dto);\n        if (validationResult.IsFailed)\n            return validationResult;\n        \n        // Get user\n        var userResult = await _userRepo.GetByIdAsync(dto.UserId);\n        if (userResult.IsFailed)\n            return Result\u003cOrder\u003e.Fail(userResult.Problem);\n        \n        // Check permissions\n        var user = userResult.Value;\n        if (!user.CanCreateOrders)\n            return Result\u003cOrder\u003e.FailForbidden(\"User cannot create orders\");\n        \n        // Create order\n        return await Result.Try(async () =\u003e\n        {\n            var order = new Order\n            {\n                UserId = user.Id,\n                Items = dto.Items,\n                Total = CalculateTotal(dto.Items)\n            };\n            \n            await _orderRepo.SaveAsync(order);\n            return order;\n        });\n    }\n}\n```\n\n## Integration Guides\n\n### ASP.NET Core Integration\n\n#### Installation and Setup\n\n```csharp\n// 1. Install NuGet package\n// dotnet add package ManagedCode.Communication.AspNetCore\n\n// 2. Program.cs configuration\nvar builder = WebApplication.CreateBuilder(args);\n\n// Method 1: Simple configuration with auto-detection of environment\nbuilder.AddCommunication(); // ShowErrorDetails = IsDevelopment\n\n// Method 2: Custom configuration\nbuilder.Services.AddCommunication(options =\u003e\n{\n    options.ShowErrorDetails = true; // Show detailed error messages in responses\n});\n\n// 3. Add filters to MVC controllers (ORDER MATTERS!)\nbuilder.Services.AddControllers(options =\u003e\n{\n    options.AddCommunicationFilters();\n    // Filters are applied in this order:\n    // 1. CommunicationModelValidationFilter - Catches validation errors first\n    // 2. ResultToActionResultFilter - Converts Result to HTTP response\n    // 3. CommunicationExceptionFilter - Catches any unhandled exceptions last\n});\n\n// 4. Optional: Add filters to SignalR hubs\nbuilder.Services.AddSignalR(options =\u003e\n{\n    options.AddCommunicationFilters();\n});\n\nvar app = builder.Build();\n```\n\n#### Filter Execution Order\n\nThe order of filters is important for proper error handling:\n\n| Order | Filter | Purpose | When It Runs |\n|-------|--------|---------|--------------|\n| 1 | `CommunicationModelValidationFilter` | Converts ModelState errors to `Result.FailValidation` | Before action execution if model is invalid |\n| 2 | `ResultToActionResultFilter` | Maps `Result\u003cT\u003e` return values to HTTP responses | After action execution |\n| 3 | `CommunicationExceptionFilter` | Catches unhandled exceptions, returns Problem Details | On any exception |\n\n⚠️ **Important**: The filters must be registered using `AddCommunicationFilters()` to ensure correct ordering. Manual registration may cause unexpected behavior.\n\n#### Controller Implementation\n\n```csharp\n[ApiController]\n[Route(\"api/[controller]\")]\npublic class UsersController : ControllerBase\n{\n    private readonly IUserService _userService;\n    \n    [HttpGet(\"{id}\")]\n    [ProducesResponseType(typeof(User), 200)]\n    [ProducesResponseType(typeof(Problem), 404)]\n    public async Task\u003cResult\u003cUser\u003e\u003e GetUser(int id)\n    {\n        return await _userService.GetUserAsync(id);\n    }\n    \n    [HttpPost]\n    [ProducesResponseType(typeof(User), 201)]\n    [ProducesResponseType(typeof(Problem), 400)]\n    public async Task\u003cResult\u003cUser\u003e\u003e CreateUser([FromBody] CreateUserDto dto)\n    {\n        return await _userService.CreateUserAsync(dto);\n    }\n    \n    [HttpGet]\n    [ProducesResponseType(typeof(CollectionResult\u003cUser\u003e), 200)]\n    public async Task\u003cCollectionResult\u003cUser\u003e\u003e GetUsers(\n        [FromQuery] int page = 1,\n        [FromQuery] int pageSize = 10)\n    {\n        return await _userService.GetUsersAsync(page, pageSize);\n    }\n}\n```\n\n#### Automatic HTTP Response Mapping\n\nThe library automatically converts Result types to appropriate HTTP responses:\n\n| Result State | HTTP Status | Response Body |\n|-------------|-------------|---------------|\n| `Result.Succeed()` | 204 No Content | Empty |\n| `Result\u003cT\u003e.Succeed(value)` | 200 OK | `value` |\n| `Result.FailValidation(...)` | 400 Bad Request | Problem Details |\n| `Result.FailUnauthorized()` | 401 Unauthorized | Problem Details |\n| `Result.FailForbidden()` | 403 Forbidden | Problem Details |\n| `Result.FailNotFound()` | 404 Not Found | Problem Details |\n| `Result.Fail(...)` | 500 Internal Server Error | Problem Details |\n\n### SignalR Integration\n\n```csharp\npublic class ChatHub : Hub\n{\n    public async Task\u003cResult\u003cMessageDto\u003e\u003e SendMessage(string user, string message)\n    {\n        if (string.IsNullOrEmpty(message))\n            return Result\u003cMessageDto\u003e.FailValidation((\"message\", \"Message cannot be empty\"));\n        \n        var messageDto = new MessageDto\n        {\n            User = user,\n            Message = message,\n            Timestamp = DateTime.UtcNow\n        };\n        \n        await Clients.All.SendAsync(\"ReceiveMessage\", user, message);\n        return Result\u003cMessageDto\u003e.Succeed(messageDto);\n    }\n    \n    public async Task\u003cResult\u003e JoinGroup(string groupName)\n    {\n        if (string.IsNullOrEmpty(groupName))\n            return Result.FailValidation((\"groupName\", \"Group name is required\"));\n        \n        await Groups.AddToGroupAsync(Context.ConnectionId, groupName);\n        return Result.Succeed();\n    }\n}\n```\n\n### Microsoft Orleans Integration\n\n#### Setup\n\n```csharp\n// Silo configuration\nvar builder = Host.CreateDefaultBuilder(args)\n    .UseOrleans(silo =\u003e\n    {\n        silo.UseLocalhostClustering()\n            .UseOrleansCommunication(); // Required for Result serialization\n    });\n\n// Client configuration  \nvar clientBuilder = Host.CreateDefaultBuilder(args)\n    .UseOrleansClient(client =\u003e\n    {\n        client.UseOrleansCommunication(); // Required for Result serialization\n    });\n```\n\nThat's it! The `UseOrleansCommunication()` extension automatically configures:\n- Serialization for all Result types across grain boundaries\n- Proper handling of Problem Details in distributed calls\n- Support for CollectionResult with pagination\n\n#### Grain Implementation\n\n```csharp\npublic interface IUserGrain : IGrainWithStringKey\n{\n    Task\u003cResult\u003cUserState\u003e\u003e GetStateAsync();\n    Task\u003cResult\u003e UpdateProfileAsync(UpdateProfileDto dto);\n    Task\u003cCollectionResult\u003cActivity\u003e\u003e GetActivitiesAsync(int page, int pageSize);\n}\n\npublic class UserGrain : Grain, IUserGrain\n{\n    private readonly IPersistentState\u003cUserState\u003e _state;\n    \n    public UserGrain([PersistentState(\"user\")] IPersistentState\u003cUserState\u003e state)\n    {\n        _state = state;\n    }\n    \n    public Task\u003cResult\u003cUserState\u003e\u003e GetStateAsync()\n    {\n        if (!_state.RecordExists)\n            return Task.FromResult(Result\u003cUserState\u003e.FailNotFound(\"User not found\"));\n        \n        return Task.FromResult(Result\u003cUserState\u003e.Succeed(_state.State));\n    }\n    \n    public async Task\u003cResult\u003e UpdateProfileAsync(UpdateProfileDto dto)\n    {\n        if (!_state.RecordExists)\n            return Result.FailNotFound(\"User not found\");\n        \n        // Validate\n        if (string.IsNullOrEmpty(dto.DisplayName))\n            return Result.FailValidation((\"displayName\", \"Display name is required\"));\n        \n        // Update state\n        _state.State.DisplayName = dto.DisplayName;\n        _state.State.Bio = dto.Bio;\n        _state.State.UpdatedAt = DateTime.UtcNow;\n        \n        await _state.WriteStateAsync();\n        return Result.Succeed();\n    }\n    \n    public async Task\u003cCollectionResult\u003cActivity\u003e\u003e GetActivitiesAsync(int page, int pageSize)\n    {\n        if (!_state.RecordExists)\n            return CollectionResult\u003cActivity\u003e.FailNotFound(\"User not found\");\n        \n        // For real data, use a repository with Entity Framework\n        var repository = GrainFactory.GetGrain\u003cIActivityRepositoryGrain\u003e(0);\n        return await repository.GetUserActivitiesAsync(this.GetPrimaryKeyString(), page, pageSize);\n    }\n}\n```\n\n## Performance\n\n### Best Practices\n\n1. **Use structs**: `Result` and `Result\u003cT\u003e` are value types (structs) to avoid heap allocation\n2. **Avoid boxing**: Use generic methods to prevent boxing of value types\n3. **Chain operations**: Use railway-oriented programming to avoid intermediate variables\n4. **Async properly**: Use `ConfigureAwait(false)` in library code\n5. **Cache problems**: Reuse common Problem instances for frequent errors\n\n## Testing\n\nThe repository uses xUnit with [Shouldly](https://github.com/shouldly/shouldly) for assertions. Shared matchers such as `ShouldBeEquivalentTo` and `AssertProblem()` live in `ManagedCode.Communication.Tests/TestHelpers`, keeping tests fluent without FluentAssertions.\n\n- Run the full suite: `dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj`\n- Generate lcov coverage: `dotnet test ManagedCode.Communication.Tests/ManagedCode.Communication.Tests.csproj /p:CollectCoverage=true /p:CoverletOutputFormat=lcov`\n\nExecution helpers (`Result.From`, `Result\u003cT\u003e.From`, task/value-task shims) and the command metadata extensions now have direct tests, pushing the core assembly above 80% line coverage. Mirror those patterns when adding APIs—exercise both success and failure paths and prefer invoking the public fluent surface instead of internal helpers.\n\n## Comparison\n\n### Comparison with Other Libraries\n\n| Feature | ManagedCode.Communication | FluentResults | CSharpFunctionalExtensions | ErrorOr |\n|---------|--------------------------|---------------|---------------------------|---------|\n| **Multiple Errors** | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |\n| **Railway-Oriented** | ✅ Full | ✅ Full | ✅ Full | ⚠️ Limited |\n| **HTTP Integration** | ✅ Built-in | ❌ No | ⚠️ Extension | ❌ No |\n| **Orleans Support** | ✅ Built-in | ❌ No | ❌ No | ❌ No |\n| **SignalR Support** | ✅ Built-in | ❌ No | ❌ No | ❌ No |\n| **RFC 7807** | ✅ Full | ❌ No | ❌ No | ❌ No |\n| **Pagination** | ✅ Built-in | ❌ No | ❌ No | ❌ No |\n| **Command Pattern** | ✅ Built-in | ❌ No | ❌ No | ❌ No |\n| **Performance** | ✅ Struct-based | ❌ Class-based | ✅ Struct-based | ✅ Struct-based |\n| **Async Support** | ✅ Full | ✅ Full | ✅ Full | ✅ Full |\n\n### When to Use ManagedCode.Communication\n\nChoose this library when you need:\n\n- **Full-stack integration**: ASP.NET Core + SignalR + Orleans\n- **Standardized errors**: RFC 7807 Problem Details\n- **Pagination**: Built-in collection results with paging\n- **Command pattern**: Command infrastructure with idempotency\n- **Performance**: Struct-based implementation for minimal overhead\n\n## Best Practices\n\n### DO ✅\n\n```csharp\n// DO: Use Result for operations that can fail\npublic Result\u003cUser\u003e GetUser(int id)\n{\n    var user = _repository.FindById(id);\n    return user != null \n        ? Result\u003cUser\u003e.Succeed(user)\n        : Result\u003cUser\u003e.FailNotFound($\"User {id} not found\");\n}\n\n// DO: Chain operations using railway-oriented programming\npublic Result\u003cOrder\u003e ProcessOrder(OrderDto dto)\n{\n    return ValidateOrder(dto)\n        .Then(CreateOrder)\n        .Then(CalculateTotals)\n        .Then(ApplyDiscounts)\n        .Then(SaveOrder);\n}\n\n// DO: Provide specific error information\npublic Result ValidateEmail(string email)\n{\n    if (string.IsNullOrEmpty(email))\n        return Result.FailValidation((\"email\", \"Email is required\"));\n    \n    if (!email.Contains(\"@\"))\n        return Result.FailValidation((\"email\", \"Invalid email format\"));\n    \n    return Result.Succeed();\n}\n\n// DO: Use CollectionResult for paginated data\npublic CollectionResult\u003cProduct\u003e GetProducts(int page, int pageSize)\n{\n    var products = _repository.GetPaged(page, pageSize);\n    var total = _repository.Count();\n    return CollectionResult\u003cProduct\u003e.Succeed(products, page, pageSize, total);\n}\n```\n\n### DON'T ❌\n\n```csharp\n// DON'T: Throw exceptions from Result-returning methods\npublic Result\u003cUser\u003e GetUser(int id)\n{\n    if (id \u003c= 0)\n        throw new ArgumentException(\"Invalid ID\"); // ❌ Don't throw\n    \n    // Instead:\n    if (id \u003c= 0)\n        return Result\u003cUser\u003e.FailValidation((\"id\", \"ID must be positive\")); // ✅\n}\n\n// DON'T: Ignore Result values\nvar result = UpdateUser(user); // ❌ Result ignored\nDoSomethingElse();\n\n// Instead:\nvar result = UpdateUser(user);\nif (result.IsFailed)\n    return result; // ✅ Handle the failure\n\n// DON'T: Mix Result and exceptions\npublic async Task\u003cUser\u003e GetUserMixed(int id)\n{\n    var result = await GetUserAsync(id);\n    if (result.IsFailed)\n        throw new Exception(result.Problem.Detail); // ❌ Mixing patterns\n    \n    return result.Value;\n}\n\n// DON'T: Create generic error messages\nreturn Result.Fail(\"Error\"); // ❌ Too vague\n\n// Instead:\nreturn Result.Fail(\"User creation failed\", \"Email already exists\"); // ✅\n```\n\n## Examples\n\n### Complete Web API Example\n\n```csharp\n// Domain Model\npublic class Product\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public decimal Price { get; set; }\n    public int Stock { get; set; }\n}\n\n// Service Interface\npublic interface IProductService\n{\n    Task\u003cResult\u003cProduct\u003e\u003e GetByIdAsync(int id);\n    Task\u003cResult\u003cProduct\u003e\u003e CreateAsync(CreateProductDto dto);\n    Task\u003cResult\u003e UpdateStockAsync(int id, int quantity);\n    Task\u003cCollectionResult\u003cProduct\u003e\u003e SearchAsync(string query, int page, int pageSize);\n}\n\n// Service Implementation\npublic class ProductService : IProductService\n{\n    private readonly IProductRepository _repository;\n    private readonly ILogger\u003cProductService\u003e _logger;\n    \n    public async Task\u003cResult\u003cProduct\u003e\u003e GetByIdAsync(int id)\n    {\n        return await Result.Try(async () =\u003e\n        {\n            var product = await _repository.FindByIdAsync(id);\n            return product ?? throw new KeyNotFoundException($\"Product {id} not found\");\n        })\n        .CompensateAsync(async error =\u003e\n        {\n            _logger.LogWarning(\"Product {Id} not found, checking archive\", id);\n            var archived = await _repository.FindInArchiveAsync(id);\n            return archived != null\n                ? Result\u003cProduct\u003e.Succeed(archived)\n                : Result\u003cProduct\u003e.FailNotFound($\"Product {id} not found\");\n        });\n    }\n    \n    public async Task\u003cResult\u003cProduct\u003e\u003e CreateAsync(CreateProductDto dto)\n    {\n        // Validation\n        var validationResult = await ValidateProductDto(dto);\n        if (validationResult.IsFailed)\n            return Result\u003cProduct\u003e.Fail(validationResult.Problem);\n        \n        // Check for duplicates\n        var existing = await _repository.FindByNameAsync(dto.Name);\n        if (existing != null)\n            return Result\u003cProduct\u003e.Fail(\"Duplicate product\", \n                $\"Product with name '{dto.Name}' already exists\");\n        \n        // Create product\n        var product = new Product\n        {\n            Name = dto.Name,\n            Price = dto.Price,\n            Stock = dto.InitialStock\n        };\n        \n        await _repository.AddAsync(product);\n        await _repository.SaveChangesAsync();\n        \n        return Result\u003cProduct\u003e.Succeed(product);\n    }\n    \n    public async Task\u003cResult\u003e UpdateStockAsync(int id, int quantity)\n    {\n        return await GetByIdAsync(id)\n            .Then(product =\u003e\n            {\n                if (product.Stock + quantity \u003c 0)\n                    return Result.Fail(\"Insufficient stock\", \n                        $\"Cannot reduce stock by {Math.Abs(quantity)}. Current stock: {product.Stock}\");\n                \n                product.Stock += quantity;\n                return Result.Succeed();\n            })\n            .ThenAsync(async () =\u003e\n            {\n                await _repository.SaveChangesAsync();\n                return Result.Succeed();\n            });\n    }\n    \n    public async Task\u003cCollectionResult\u003cProduct\u003e\u003e SearchAsync(string query, int page, int pageSize)\n    {\n        try\n        {\n            var (products, total) = await _repository.SearchAsync(query, page, pageSize);\n            return CollectionResult\u003cProduct\u003e.Succeed(products, page, pageSize, total);\n        }\n        catch (Exception ex)\n        {\n            _logger.LogError(ex, \"Search failed for query: {Query}\", query);\n            return CollectionResult\u003cProduct\u003e.Fail(ex);\n        }\n    }\n    \n    private async Task\u003cResult\u003e ValidateProductDto(CreateProductDto dto)\n    {\n        var errors = new List\u003c(string field, string message)\u003e();\n        \n        if (string.IsNullOrWhiteSpace(dto.Name))\n            errors.Add((\"name\", \"Product name is required\"));\n        else if (dto.Name.Length \u003e 100)\n            errors.Add((\"name\", \"Product name must be 100 characters or less\"));\n        \n        if (dto.Price \u003c= 0)\n            errors.Add((\"price\", \"Price must be greater than zero\"));\n        \n        if (dto.InitialStock \u003c 0)\n            errors.Add((\"initialStock\", \"Initial stock cannot be negative\"));\n        \n        // Async validation\n        if (!string.IsNullOrWhiteSpace(dto.Name))\n        {\n            var categoryExists = await _repository.CategoryExistsAsync(dto.CategoryId);\n            if (!categoryExists)\n                errors.Add((\"categoryId\", \"Invalid category\"));\n        }\n        \n        return errors.Any() \n            ? Result.FailValidation(errors.ToArray())\n            : Result.Succeed();\n    }\n}\n\n// Controller\n[ApiController]\n[Route(\"api/[controller]\")]\npublic class ProductsController : ControllerBase\n{\n    private readonly IProductService _productService;\n    \n    [HttpGet(\"{id}\")]\n    public async Task\u003cResult\u003cProduct\u003e\u003e Get(int id)\n    {\n        return await _productService.GetByIdAsync(id);\n    }\n    \n    [HttpPost]\n    public async Task\u003cResult\u003cProduct\u003e\u003e Create([FromBody] CreateProductDto dto)\n    {\n        return await _productService.CreateAsync(dto);\n    }\n    \n    [HttpPatch(\"{id}/stock\")]\n    public async Task\u003cResult\u003e UpdateStock(int id, [FromBody] UpdateStockDto dto)\n    {\n        return await _productService.UpdateStockAsync(id, dto.Quantity);\n    }\n    \n    [HttpGet(\"search\")]\n    public async Task\u003cCollectionResult\u003cProduct\u003e\u003e Search(\n        [FromQuery] string q,\n        [FromQuery] int page = 1,\n        [FromQuery] int pageSize = 20)\n    {\n        return await _productService.SearchAsync(q, page, pageSize);\n    }\n}\n```\n\n### Complex Business Logic Example\n\n```csharp\npublic class OrderProcessingService\n{\n    public async Task\u003cResult\u003cOrder\u003e\u003e ProcessOrderAsync(ProcessOrderCommand command)\n    {\n        // Complete order processing pipeline\n        return await Result\n            // Validate command\n            .From(() =\u003e ValidateCommand(command))\n            \n            // Load user\n            .ThenAsync(async () =\u003e await _userRepository.GetByIdAsync(command.UserId))\n            \n            // Check user permissions\n            .Then(user =\u003e user.CanPlaceOrders \n                ? Result\u003cUser\u003e.Succeed(user)\n                : Result\u003cUser\u003e.FailForbidden(\"User cannot place orders\"))\n            \n            // Verify user credit\n            .ThenAsync(async user =\u003e await _creditService.CheckCreditAsync(user.Id))\n            .Then(creditResult =\u003e creditResult.AvailableCredit \u003e= command.TotalAmount\n                ? Result.Succeed()\n                : Result.Fail(\"Insufficient credit\"))\n            \n            // Check inventory\n            .ThenAsync(async () =\u003e await CheckInventoryAsync(command.Items))\n            \n            // Reserve inventory\n            .ThenAsync(async () =\u003e await ReserveInventoryAsync(command.Items))\n            \n            // Create order\n            .ThenAsync(async () =\u003e await CreateOrderAsync(command))\n            \n            // Process payment\n            .ThenAsync(async order =\u003e await ProcessPaymentAsync(order, command.PaymentMethod))\n            \n            // Send confirmation\n            .ThenAsync(async order =\u003e await SendOrderConfirmationAsync(order))\n            \n            // Handle any failures\n            .CompensateAsync(async problem =\u003e\n            {\n                _logger.LogError(\"Order processing failed: {Problem}\", problem.Detail);\n                \n                // Rollback inventory reservation\n                await ReleaseInventoryAsync(command.Items);\n                \n                // Notify user\n                await _notificationService.NotifyOrderFailedAsync(command.UserId, problem.Detail);\n                \n                return Result\u003cOrder\u003e.Fail(problem);\n            });\n    }\n    \n    private async Task\u003cResult\u003e CheckInventoryAsync(List\u003cOrderItem\u003e items)\n    {\n        var unavailable = new List\u003cstring\u003e();\n        \n        foreach (var item in items)\n        {\n            var stock = await _inventoryService.GetStockAsync(item.ProductId);\n            if (stock \u003c item.Quantity)\n            {\n                unavailable.Add($\"{item.ProductName}: requested {item.Quantity}, available {stock}\");\n            }\n        }\n        \n        return unavailable.Any()\n            ? Result.Fail(\"Insufficient inventory\", string.Join(\"; \", unavailable))\n            : Result.Succeed();\n    }\n}\n```\n\n## Migration Guide\n\n### Migrating from Exceptions\n\n#### Before (Exception-based)\n\n```csharp\npublic User GetUser(int id)\n{\n    if (id \u003c= 0)\n        throw new ArgumentException(\"Invalid ID\");\n    \n    var user = _repository.FindById(id);\n    if (user == null)\n        throw new NotFoundException($\"User {id} not found\");\n    \n    if (!user.IsActive)\n        throw new InvalidOperationException(\"User is not active\");\n    \n    return user;\n}\n\n// Usage\ntry\n{\n    var user = GetUser(id);\n    // Process user\n}\ncatch (ArgumentException ex)\n{\n    // Handle validation error\n}\ncatch (NotFoundException ex)\n{\n    // Handle not found\n}\ncatch (Exception ex)\n{\n    // Handle other errors\n}\n```\n\n#### After (Result-based)\n\n```csharp\npublic Result\u003cUser\u003e GetUser(int id)\n{\n    if (id \u003c= 0)\n        return Result\u003cUser\u003e.FailValidation((\"id\", \"ID must be positive\"));\n    \n    var user = _repository.FindById(id);\n    if (user == null)\n        return Result\u003cUser\u003e.FailNotFound($\"User {id} not found\");\n    \n    if (!user.IsActive)\n        return Result\u003cUser\u003e.Fail(\"User inactive\", \"User account is not active\");\n    \n    return Result\u003cUser\u003e.Succeed(user);\n}\n\n// Usage\nvar result = GetUser(id);\nresult.Match(\n    onSuccess: user =\u003e { /* Process user */ },\n    onFailure: problem =\u003e\n    {\n        if (result.IsInvalid)\n        {\n            // Handle validation error\n        }\n        else if (problem.StatusCode == 404)\n        {\n            // Handle not found\n        }\n        else\n        {\n            // Handle other errors\n        }\n    }\n);\n```\n\n### Gradual Migration Strategy\n\n1. **Start with new code**: Implement Result pattern in new features\n2. **Wrap existing methods**: Use `Result.Try()` to wrap exception-throwing code\n3. **Update interfaces**: Change return types from `T` to `Result\u003cT\u003e`\n4. **Convert controllers**: Update API endpoints to return Result types\n5. **Remove try-catch blocks**: Replace with Result pattern handling\n\n```csharp\n// Step 1: Wrap existing code\npublic Result\u003cUser\u003e GetUserSafe(int id)\n{\n    return Result.Try(() =\u003e GetUserUnsafe(id));\n}\n\n// Step 2: Gradually refactor internals\npublic Result\u003cUser\u003e GetUserRefactored(int id)\n{\n    // Refactored implementation without exceptions\n}\n\n// Step 3: Update consumers\npublic async Task\u003cIActionResult\u003e GetUser(int id)\n{\n    var result = await _service.GetUserRefactored(id);\n    return result.Match(\n        onSuccess: user =\u003e Ok(user),\n        onFailure: problem =\u003e Problem(problem)\n    );\n}\n```\n\n## Contributing\n\nContributions are welcome! Fork the repository and submit a pull request.\n\n### Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/managed-code-hub/Communication.git\n\n# Build the solution\ndotnet build\n\n# Run tests\ndotnet test\n\n# Run benchmarks\ndotnet run -c Release --project benchmarks/ManagedCode.Communication.Benchmarks\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/managed-code-hub/Communication/issues)\n- **Source Code**: [GitHub Repository](https://github.com/managed-code-hub/Communication)\n\n## Acknowledgments\n\n- Inspired by F# and Rust Result types\n- Railway-oriented programming concepts\n- RFC 7807 Problem Details for HTTP APIs\n- Built for seamless integration with Microsoft Orleans\n- Optimized for ASP.NET Core applications\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanagedcode%2Fcommunication","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmanagedcode%2Fcommunication","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmanagedcode%2Fcommunication/lists"}