ManagedCode.Communication
9.5.2
Prefix Reserved
dotnet add package ManagedCode.Communication --version 9.5.2
NuGet\Install-Package ManagedCode.Communication -Version 9.5.2
<PackageReference Include="ManagedCode.Communication" Version="9.5.2" />
<PackageVersion Include="ManagedCode.Communication" Version="9.5.2" />
<PackageReference Include="ManagedCode.Communication" />
paket add ManagedCode.Communication --version 9.5.2
#r "nuget: ManagedCode.Communication, 9.5.2"
#:package ManagedCode.Communication@9.5.2
#addin nuget:?package=ManagedCode.Communication&version=9.5.2
#tool nuget:?package=ManagedCode.Communication&version=9.5.2
ManagedCode.Communication
A powerful .NET library implementing the Result pattern with RFC 7807 Problem Details support for C# and ASP.NET Core applications. Replace exceptions with type-safe Result objects, making your error handling more predictable, testable, and maintainable. Perfect for building robust APIs with standardized error responses.
๐ฏ Why ManagedCode.Communication?
Traditional exception-based error handling in .NET and C# applications can make code difficult to follow and test. The Communication library introduces a Result pattern implementation with Problem Details (RFC 7807) support that transforms how you handle operations that might fail in ASP.NET Core, Orleans, and other .NET applications:
- โ No More Exceptions - Replace try-catch blocks with elegant Result objects
- ๐ Explicit Error Handling - Makes potential failures visible in method signatures
- ๐งช Better Testability - No need to test exception scenarios
- ๐ Improved Performance - Avoid the overhead of throwing exceptions
- ๐ Self-Documenting Code - Method signatures clearly indicate possible failures
- ๐ RFC 7807 Compliant - Standardized error responses for APIs
- ๐ Railway-Oriented Programming - Functional programming style with Bind, Map, Tap, and Match methods for C#
- ๐ญ Exception Recovery - Convert between exceptions and Results seamlessly
๐ฆ Installation
# Core library
dotnet add package ManagedCode.Communication
# ASP.NET Core integration
dotnet add package ManagedCode.Communication.Extensions
# Orleans integration
dotnet add package ManagedCode.Communication.Orleans
๐ Quick Start
Basic Usage
using ManagedCode.Communication;
// Simple success result
var success = Result.Succeed();
if (success.IsSuccess)
{
Console.WriteLine("Operation succeeded!");
}
// Failure with Problem Details
var failure = Result.Fail("Operation failed", "Details about the failure");
if (failure.IsFailed)
{
Console.WriteLine($"Error: {failure.Problem.Title} - {failure.Problem.Detail}");
}
// Different ways to create failures
var basicFail = Result.Fail(); // Simple failure
var withMessage = Result.Fail("Something went wrong");
var withDetails = Result.Fail("Operation failed", "Detailed error description");
var withStatus = Result.Fail("Not Found", "Resource does not exist", HttpStatusCode.NotFound);
var notFound = Result.FailNotFound("User not found");
var validation = Result.FailValidation(("field", "Field is required"));
// Try to get the problem details
if (failure.TryGetProblem(out var problem))
{
Console.WriteLine($"Status: {problem.StatusCode}, Type: {problem.Type}");
}
// Throw exception if failed (when you need to integrate with exception-based code)
failure.ThrowIfFail(); // Throws ProblemException
Generic Results with Values
// Success with value
var userResult = Result<User>.Succeed(new User { Id = 1, Name = "John" });
if (userResult.IsSuccess)
{
var user = userResult.Value; // Access the user object
Console.WriteLine($"Found user: {user.Name}");
}
// Failure with Problem Details
var notFound = Result<User>.FailNotFound("User not found");
if (notFound.IsFailed)
{
Console.WriteLine($"Error: {notFound.Problem.Title} (Status: {notFound.Problem.StatusCode})");
}
// Using Try pattern for exception-prone operations
var result = Result.Try(() =>
{
return JsonSerializer.Deserialize<User>(jsonString);
});
Collection Results
Perfect for paginated API responses:
var products = await GetProductsAsync(page: 1, pageSize: 20);
var result = CollectionResult<Product>.Succeed(
items: products,
pageNumber: 1,
pageSize: 20,
totalItems: 150
);
// Access pagination info
Console.WriteLine($"Page {result.PageNumber} of {result.TotalPages}");
Console.WriteLine($"Showing {result.Collection.Count()} of {result.TotalItems} products");
Problem Details (RFC 7807)
The library fully implements RFC 7807 Problem Details for standardized error responses:
// Create a problem with all details
var problem = Problem.Create(
type: "https://example.com/probs/out-of-credit",
title: "You do not have enough credit",
statusCode: 403,
detail: "Your current balance is 30, but that costs 50.",
instance: "/account/12345/msgs/abc"
);
// Add custom extensions
problem.Extensions["balance"] = 30;
problem.Extensions["accounts"] = new[] { "/account/12345", "/account/67890" };
// Convert to Result
var result = Result.Fail(problem);
// Create Problem from exception
var exception = new InvalidOperationException("Operation not allowed");
var problemFromException = Problem.FromException(exception);
// Create Problem from enum
public enum ApiError { InvalidInput, Unauthorized, RateLimitExceeded }
var problemFromEnum = Problem.FromEnum(ApiError.RateLimitExceeded, "Too many requests", 429);
// Validation problems
var validationResult = Result.FailValidation(
("email", "Email is required"),
("email", "Email format is invalid"),
("age", "Age must be greater than 18")
);
// Access validation errors
if (validationResult.Problem.GetValidationErrors() is var errors && errors != null)
{
foreach (var error in errors)
{
Console.WriteLine($"{error.Key}: {string.Join(", ", error.Value)}");
}
}
Railway-Oriented Programming
Chain operations elegantly:
var result = await GetUserAsync(userId)
.BindAsync(user => ValidateUserAsync(user))
.MapAsync(user => EnrichUserDataAsync(user))
.TapAsync(user => LogUserAccessAsync(user))
.Match(
onSuccess: user => Ok(user),
onFailure: problem => problem.StatusCode switch
{
404 => NotFound(problem),
403 => Forbid(problem),
_ => BadRequest(problem)
}
);
Exception Interoperability
The library provides seamless conversion between .NET exceptions and Result types, making it easy to integrate with existing codebases:
// Convert exception to Problem
var exception = new InvalidOperationException("Operation not allowed");
var problem = Problem.FromException(exception);
// Convert Problem back to exception
var reconstructedException = problem.ToException();
// If original was InvalidOperationException, it returns InvalidOperationException
// Otherwise returns ProblemException
// Use with Result
var result = Result.Fail(exception); // Automatically converts to Problem
result.ThrowIfFail(); // Throws the appropriate exception type
๐ ASP.NET Core Integration
Configure Services
using ManagedCode.Communication.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add Communication services
builder.Services.AddCommunication(options =>
{
options.ShowErrorDetails = builder.Environment.IsDevelopment();
});
// Add MVC with Communication filters
builder.Services.AddControllers(options =>
{
options.AddCommunicationFilters();
});
// Add SignalR with Communication filters
builder.Services.AddSignalR(options =>
{
options.AddCommunicationHubFilter();
});
var app = builder.Build();
// Use Communication middleware for global error handling
app.UseCommunication();
Controller Examples
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
[HttpGet("{id}")]
public async Task<Result<UserDto>> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user == null)
return Result<UserDto>.FailNotFound($"User with ID {id} not found");
return Result<UserDto>.Succeed(user.ToDto());
}
[HttpPost]
public async Task<Result<UserDto>> CreateUser([FromBody] CreateUserDto dto)
{
// Model validation is handled automatically by filters
var validationResult = await _userService.ValidateAsync(dto);
if (validationResult.IsFailed)
return validationResult;
var user = await _userService.CreateAsync(dto);
return Result<UserDto>.Succeed(user.ToDto());
}
[HttpGet]
public async Task<CollectionResult<UserDto>> GetUsers(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
var (users, totalCount) = await _userService.GetPagedAsync(page, pageSize);
return CollectionResult<UserDto>.Succeed(
users.Select(u => u.ToDto()),
page,
pageSize,
totalCount
);
}
}
SignalR Hub Example
public class NotificationHub : Hub
{
private readonly INotificationService _notificationService;
public NotificationHub(INotificationService notificationService)
{
_notificationService = notificationService;
}
public async Task<Result> SendNotification(string message)
{
if (string.IsNullOrWhiteSpace(message))
return Result.FailValidation(("message", "Message cannot be empty"));
await _notificationService.BroadcastAsync(message);
return Result.Succeed();
}
public async Task<Result<int>> GetUnreadCount()
{
var count = await _notificationService.GetUnreadCountAsync(Context.UserIdentifier);
return Result<int>.Succeed(count);
}
}
๐จ Advanced Features
Custom Error Enums
Define domain-specific errors:
public enum OrderError
{
InsufficientInventory,
PaymentFailed,
ShippingNotAvailable
}
// Use with Result
var result = Result.Fail(
OrderError.InsufficientInventory,
"Not enough items in stock"
);
// Check specific error
if (result.Problem?.HasErrorCode(OrderError.InsufficientInventory) == true)
{
// Handle inventory error
}
Problem and ProblemDetails Conversion
Seamless integration with ASP.NET Core's ProblemDetails:
// Convert between Problem and ProblemDetails
Problem problem = Result.Fail("Error", "Details").Problem;
ProblemDetails problemDetails = problem.ToProblemDetails();
// Convert back
Problem convertedProblem = problemDetails.AsProblem();
// Create Result from ProblemDetails
Result result = problemDetails.ToFailedResult();
Try Pattern for Exception Handling
Wrap exception-throwing code elegantly:
// Synchronous
var result = Result.Try(() =>
{
var config = JsonSerializer.Deserialize<Config>(json);
ValidateConfig(config);
return config;
});
// Asynchronous
var asyncResult = await Result.TryAsync(async () =>
{
var data = await httpClient.GetStringAsync(url);
return JsonSerializer.Deserialize<Data>(data);
}, HttpStatusCode.BadGateway);
// With specific value type
var parseResult = Result.Try<int>(() => int.Parse(userInput));
Result Extensions and Chaining
// Map successful results
var result = await GetUserAsync(id)
.Map(user => user.ToDto())
.Map(dto => new UserViewModel(dto));
// Bind operations (flatMap)
var finalResult = await GetUserAsync(userId)
.BindAsync(user => ValidateUserAsync(user))
.BindAsync(user => CreateOrderForUserAsync(user, orderDto))
.MapAsync(order => order.ToDto());
// Tap for side effects
var resultWithLogging = await ProcessOrderAsync(orderId)
.TapAsync(order => LogOrderProcessed(order))
.TapAsync(order => SendNotificationAsync(order));
// Pattern matching
var message = result.Match(
onSuccess: value => $"Success: {value}",
onFailure: problem => $"Error {problem.StatusCode}: {problem.Detail}"
);
Entity Framework Integration
public async Task<Result<Customer>> GetCustomerAsync(int id)
{
return await Result.TryAsync(async () =>
{
var customer = await _dbContext.Customers
.Include(c => c.Orders)
.FirstOrDefaultAsync(c => c.Id == id);
return customer ?? Result<Customer>.FailNotFound($"Customer {id} not found");
});
}
public async Task<Result> UpdateCustomerAsync(Customer customer)
{
return await Result.TryAsync(async () =>
{
_dbContext.Customers.Update(customer);
await _dbContext.SaveChangesAsync();
}, HttpStatusCode.InternalServerError);
}
๐๏ธ Orleans Integration
// Silo configuration
var builder = new HostBuilder()
.UseOrleans(siloBuilder =>
{
siloBuilder.UseOrleansCommunication();
});
// Client configuration
var client = new ClientBuilder()
.UseOrleansCommunication()
.Build();
// Grain implementation
public class UserGrain : Grain, IUserGrain
{
public async Task<Result<UserData>> GetUserDataAsync()
{
return await Result.TryAsync(async () =>
{
var userData = await LoadUserDataAsync();
return userData;
});
}
}
๐ Performance Benefits
The Result pattern provides significant performance improvements over traditional exception handling in .NET applications. Exceptions are expensive - they involve stack unwinding, object allocation, and can be 1000x slower than returning a Result object:
// โ Traditional approach - throwing exceptions
public User GetUser(int id)
{
var user = _repository.FindById(id);
if (user == null)
throw new NotFoundException($"User {id} not found"); // ~1000x slower!
return user;
}
// โ
Result pattern - no exceptions
public Result<User> GetUser(int id)
{
var user = _repository.FindById(id);
if (user == null)
return Result<User>.FailNotFound($"User {id} not found"); // Much faster!
return Result<User>.Succeed(user);
}
// Multiple ways to create failures
var notFound = Result<User>.FailNotFound("User not found");
var generalFail = Result<User>.Fail("Operation failed", "Detailed error description");
var withStatusCode = Result<User>.Fail("Forbidden", "Access denied", HttpStatusCode.Forbidden);
var fromProblem = Result<User>.Fail(Problem.Create("type", "title", 400, "detail"));
var fromException = Result<User>.Fail(new InvalidOperationException("Not allowed"));
๐งช Testing
The Result pattern makes unit testing in .NET much cleaner by eliminating the need to test exception scenarios. Instead of asserting on thrown exceptions, you can directly check Result properties:
[Test]
public async Task GetUser_WhenUserExists_ReturnsSuccess()
{
// Arrange
var userId = 123;
var expectedUser = new User { Id = userId, Name = "John" };
_mockRepository.Setup(x => x.FindById(userId)).Returns(expectedUser);
// Act
var result = await _userService.GetUser(userId);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.Name.Should().Be(expectedUser.Name);
}
[Test]
public async Task GetUser_WhenUserNotFound_ReturnsCorrectProblem()
{
// Arrange
var userId = 999;
_mockRepository.Setup(x => x.FindById(userId)).Returns((User)null);
// Act
var result = await _userService.GetUser(userId);
// Assert
result.IsFailed.Should().BeTrue();
result.Problem.StatusCode.Should().Be(404);
result.Problem.Title.Should().Be("Not Found");
}
๐ ๏ธ Configuration Options
services.AddCommunication(options =>
{
// Show detailed error information (disable in production)
options.ShowErrorDetails = false;
// Custom error response builder
options.ErrorResponseBuilder = (problem, context) =>
{
return new
{
type = problem.Type,
title = problem.Title,
status = problem.StatusCode,
detail = options.ShowErrorDetails ? problem.Detail : null,
instance = problem.Instance,
traceId = Activity.Current?.Id ?? context.TraceIdentifier,
extensions = problem.Extensions
};
};
});
๐ Best Practices for Result Pattern in .NET
Always return Result types from your service methods in C# applications
public interface IUserService { Task<Result<User>> GetByIdAsync(int id); Task<Result<User>> CreateAsync(CreateUserDto dto); Task<Result> DeleteAsync(int id); Task<CollectionResult<User>> GetPagedAsync(int page, int pageSize); }
Use specific Problem Details for different error scenarios
return Result<Order>.Fail(Problem.Create( type: "https://example.com/probs/insufficient-inventory", title: "Insufficient Inventory", statusCode: 422, detail: $"Product {productId} has only {available} items, but {requested} were requested", instance: $"/orders/{orderId}" ));
Leverage Railway-Oriented Programming for complex workflows
public async Task<Result<OrderConfirmation>> ProcessOrderAsync(OrderRequest request) { return await ValidateOrderRequest(request) .BindAsync(validRequest => CheckInventoryAsync(validRequest)) .BindAsync(inventory => CalculatePricingAsync(inventory)) .BindAsync(pricing => ProcessPaymentAsync(pricing)) .BindAsync(payment => CreateOrderAsync(payment)) .MapAsync(order => GenerateConfirmationAsync(order)); }
Use TryGetProblem for conditional error handling
if (result.TryGetProblem(out var problem)) { _logger.LogError("Operation failed: {Type} - {Detail}", problem.Type, problem.Detail); if (problem.StatusCode == 429) // Too Many Requests { var retryAfter = problem.Extensions.GetValueOrDefault("retryAfter"); // Handle rate limiting } }
๐ค Contributing
We welcome contributions! Please see our Contributing Guide for details.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
Special thanks to all contributors who have helped make this library better!
Made with โค๏ธ by ManagedCode
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 is compatible. 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.Extensions.Logging (>= 9.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.7)
-
net9.0
- Microsoft.Extensions.Logging (>= 9.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.7)
NuGet packages (13)
Showing the top 5 NuGet packages that depend on ManagedCode.Communication:
Package | Downloads |
---|---|
ManagedCode.Storage.Core
Base interfaces for ManagedCode.StorageS |
|
ManagedCode.Storage.Azure
Storage for Azure |
|
ManagedCode.Communication.Orleans
Communication for .NET |
|
ManagedCode.Storage.Aws
Storage for AWS |
|
ManagedCode.Storage.FileSystem
Storage for FileSystem |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
9.5.2 | 7 | 8/5/2025 |
9.5.1 | 10 | 8/5/2025 |
9.5.0 | 10 | 8/5/2025 |
9.0.1 | 14 | 8/4/2025 |
9.0.0 | 5,347 | 12/10/2024 |
8.0.7 | 3,099 | 9/12/2024 |
8.0.6 | 1,325 | 6/29/2024 |
8.0.5 | 186 | 6/27/2024 |
8.0.4 | 188 | 6/27/2024 |
8.0.3 | 3,803 | 6/14/2024 |
8.0.2 | 381 | 6/13/2024 |
8.0.1 | 361 | 5/6/2024 |
8.0.0 | 4,226 | 11/29/2023 |
2.0.26 | 5,329 | 7/12/2023 |
2.0.25 | 1,335 | 6/6/2023 |
2.0.24 | 39,591 | 5/22/2023 |
2.0.23 | 3,841 | 5/21/2023 |
2.0.22 | 278 | 5/17/2023 |
2.0.21 | 284 | 5/17/2023 |
2.0.20 | 244 | 5/17/2023 |
2.0.19 | 9,170 | 3/15/2023 |
2.0.18 | 401 | 3/14/2023 |
2.0.17 | 354 | 3/12/2023 |
2.0.16 | 479 | 2/21/2023 |
2.0.15 | 789 | 2/17/2023 |
2.0.14 | 876 | 2/7/2023 |
2.0.13 | 7,958 | 12/19/2022 |
2.0.12 | 438 | 12/19/2022 |
2.0.11 | 475 | 12/8/2022 |
2.0.10 | 537 | 12/3/2022 |
2.0.9 | 392 | 11/24/2022 |
2.0.8 | 402 | 11/24/2022 |
2.0.7 | 417 | 11/24/2022 |
2.0.5 | 411 | 11/24/2022 |
2.0.3 | 403 | 11/24/2022 |
2.0.2 | 406 | 11/24/2022 |
2.0.1 | 424 | 11/24/2022 |
2.0.0 | 415 | 11/22/2022 |
1.0.3 | 2,607 | 11/1/2022 |
1.0.2 | 2,272 | 10/27/2022 |
1.0.1 | 473 | 10/26/2022 |
1.0.0 | 8,120 | 10/17/2022 |
0.1.2 | 13,422 | 8/11/2022 |
0.1.1 | 491 | 8/11/2022 |
0.1.0 | 480 | 8/11/2022 |
0.0.8 | 8,759 | 7/27/2022 |
0.0.7 | 496 | 7/27/2022 |
0.0.6 | 7,236 | 7/19/2022 |
0.0.5 | 519 | 7/19/2022 |
0.0.4 | 480 | 7/19/2022 |
0.0.3 | 2,290 | 7/16/2022 |
0.0.2 | 511 | 7/15/2022 |
0.0.1 | 508 | 7/15/2022 |