EonaCat.Blocks
1.0.0
Prefix Reserved
dotnet add package EonaCat.Blocks --version 1.0.0
NuGet\Install-Package EonaCat.Blocks -Version 1.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.Blocks" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EonaCat.Blocks" Version="1.0.0" />
<PackageReference Include="EonaCat.Blocks" />
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EonaCat.Blocks --version 1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
#r "nuget: EonaCat.Blocks, 1.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EonaCat.Blocks@1.0.0
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EonaCat.Blocks&version=1.0.0
#tool nuget:?package=EonaCat.Blocks&version=1.0.0
The NuGet Team does not provide support for this client. Please contact its maintainers for support.
EonaCat.Blocks
Building blocks for a modular application using C# and dependency injection.
Example block that demonstrates how to use the provided building blocks to create a simple modular application:
using System;
using EonaCat.Blocks;
public class UserCreatedEvent
{
public Guid UserId { get; }
public string UserName { get; }
public UserCreatedEvent(Guid userId, string userName)
{
UserId = userId;
UserName = userName;
}
}
public class UserComponent : IComponent
{
private readonly IEventBus _eventBus;
public UserComponent(IEventBus eventBus)
{
_eventBus = eventBus;
}
public void Initialize()
{
Console.WriteLine("UserComponent Initialized");
}
public void Execute()
{
// Simulate user creation
var userId = Guid.NewGuid();
var userName = "Alice";
Console.WriteLine($"User created: {userName}");
_eventBus.Publish(new UserCreatedEvent(userId, userName));
}
public void Dispose()
{
Console.WriteLine("UserComponent Disposed");
}
}
Example on how to use the building blocks in a modular application:
var serviceProvider = new ServiceProvider();
// Register services
var eventBus = new EventBus();
serviceProvider.RegisterSingleton<IEventBus>(eventBus);
serviceProvider.RegisterSingleton<IConfiguration>(new InMemoryConfiguration());
// Register UserComponent with injected eventBus
var userComponent = new UserComponent(serviceProvider.GetService<IEventBus>());
serviceProvider.RegisterSingleton<IComponent>(userComponent);
// Initialize and execute
var logger = serviceProvider.GetService<ILogger>();
var component = serviceProvider.GetService<IComponent>();
component.Initialize();
logger.LogInfo("Starting component execution...");
component.Execute();
component.Dispose();
Example on how to create a validator:
public class User
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "";
public int Age { get; set; }
}
public interface IValidator<T>
{
IEnumerable<string> Validate(T instance);
}
public class RegisterUserCommandValidator : IValidator<RegisterUserCommand>
{
public IEnumerable<string> Validate(RegisterUserCommand command)
{
if (string.IsNullOrWhiteSpace(command.Name))
yield return "Name is required.";
if (command.Age < 0)
yield return "Age must be non-negative.";
}
}
Example on how to use Commands and Handlers:
public class RegisterUserCommand
{
public string Name { get; }
public int Age { get; }
public RegisterUserCommand(string name, int age)
{
Name = name;
Age = age;
}
}
public interface ICommandHandler<TCommand>
{
Task HandleAsync(TCommand command);
}
public class RegisterUserCommandHandler : ICommandHandler<RegisterUserCommand>
{
private readonly DomainEventDispatcher _dispatcher;
private readonly ICache _cache;
private readonly ILogger _logger;
public RegisterUserCommandHandler(DomainEventDispatcher dispatcher, ICache cache, ILogger logger)
{
_dispatcher = dispatcher;
_cache = cache;
_logger = logger;
}
public async Task HandleAsync(RegisterUserCommand command)
{
_logger.LogInfo($"Registering user {command.Name}");
var user = new User { Name = command.Name, Age = command.Age };
// Simulate DB save with cache
_cache.Set(user.Id.ToString(), user);
// Raise domain event
await _dispatcher.DispatchAsync(new UserCreatedEvent(user.Id, user.Name));
_logger.LogInfo("User registration completed");
}
}
Example on how to use the Event Aggegregator:
public class UserCreated
{
public Guid Id { get; }
public string Name { get; }
public UserCreated(Guid id, string name)
{
Id = id;
Name = name;
}
}
public class UserNameChanged
{
public string NewName { get; }
public UserNameChanged(string newName) => NewName = newName;
}
public class UserAggregate : AggregateRoot
{
public string Name { get; private set; }
// Constructor for creating new user
public UserAggregate(Guid id, string name)
{
ApplyChange(new UserCreated(id, name));
}
// Empty constructor for rehydration
public UserAggregate() { }
public void ChangeName(string newName)
{
if (string.IsNullOrWhiteSpace(newName)) throw new ArgumentException("Name required");
ApplyChange(new UserNameChanged(newName));
}
// Event handlers to update state
public void Apply(UserCreated e)
{
Id = e.Id;
Name = e.Name;
Version++;
}
public void Apply(UserNameChanged e)
{
Name = e.NewName;
Version++;
}
}
var user = new UserAggregate(Guid.NewGuid(), "Alice");
user.ChangeName("Alice Smith");
foreach (var evt in user.GetUncommittedChanges())
{
Console.WriteLine(evt.GetType().Name);
}
Outputs:
UserCreated
UserNameChanged
Example on how to use Create a Domain Event:
public class UserCreatedEvent : IDomainEvent
{
public Guid UserId { get; }
public string UserName { get; }
public UserCreatedEvent(Guid userId, string userName)
{
UserId = userId;
UserName = userName;
}
}
public class WelcomeEmailSender
{
private readonly ILogger _logger;
public WelcomeEmailSender(ILogger logger)
{
_logger = logger;
}
public Task OnUserCreated(UserCreatedEvent e)
{
_logger.LogInfo($"Sending welcome email to {e.UserName}");
// Simulate sending email
return Task.CompletedTask;
}
}
Example on how to use the middleware pipeline component:
public class LoggingMiddleware : IMiddleware
{
private readonly ILogger _logger;
public LoggingMiddleware(ILogger logger)
{
_logger = logger;
}
public async Task InvokeAsync(RequestDelegate next)
{
_logger.LogInfo("Middleware: Before command");
await next();
_logger.LogInfo("Middleware: After command");
}
}
public class ValidationMiddleware<TCommand> : IMiddleware
{
private readonly IValidator<TCommand> _validator;
private readonly TCommand _command;
public ValidationMiddleware(IValidator<TCommand> validator, TCommand command)
{
_validator = validator;
_command = command;
}
public Task InvokeAsync(RequestDelegate next)
{
var errors = _validator.Validate(_command);
foreach (var error in errors)
{
throw new Exception($"Validation failed: {error}");
}
return next();
}
}
Example on how to use create a background Cache Cleaner Task:
public class CacheCleanerTask : IBackgroundTask
{
private readonly ICache _cache;
private readonly ILogger _logger;
public CacheCleanerTask(ICache cache, ILogger logger)
{
_cache = cache;
_logger = logger;
}
public Task ExecuteAsync(CancellationToken token)
{
_logger.LogInfo("Running cache cleaning task...");
// Here you'd implement real cache cleanup, just simulating:
return Task.CompletedTask;
}
}
Example on how to use the retry policy:
var pipeline = new MiddlewarePipeline();
pipeline.Use(new LoggingMiddleware(logger));
pipeline.Use(new ValidationMiddleware<RegisterUserCommand>(validator, command));
try
{
// Run pipeline with retry policy wrapping command execution
await pipeline.ExecuteAsync(async () =>
{
await RetryPolicy.RetryAsync(() => handler.HandleAsync(command), retryCount: 3);
});
}
catch (Exception ex)
{
logger.LogError("Command execution failed.", ex);
}
Example on how to use the in-memory cache:
var cache = new InMemoryCache();
cache.Set("key1", "value1");
var value = cache.Get<string>("key1");
Console.WriteLine($"Cached value: {value}"); // Output: Cached value: value1
cache.Remove("key1");
Example on how to use the fluent validator:
var validator = new FluentValidator<User>()
.Rule(u => !string.IsNullOrWhiteSpace(u.Name), "Name is required")
.Rule(u => u.Age >= 0, "Age must be non-negative");
var errors = validator.Validate(user);
Example on how to use the AsyncEventBus and the CommandHandler:
var eventBus = new AsyncEventBus();
var commandBus = new CommandBus();
// Subscribe to UserCreatedEvent
eventBus.Subscribe<UserCreatedEvent>(async e =>
{
Console.WriteLine($"Event handler: Welcome {e.UserName}!");
await Task.CompletedTask;
});
// Register command handler for RegisterUserCommand
commandBus.RegisterHandler<RegisterUserCommand>(async cmd =>
{
Console.WriteLine($"Command handler: Registering user {cmd.UserName}...");
// After processing command, publish event
await eventBus.PublishAsync(new UserCreatedEvent(cmd.UserName));
});
// Send command
await commandBus.SendAsync(new RegisterUserCommand("Alice"));
Example on how to use the Circuit breaker:
public class FlakyService
{
private readonly Random _rnd = new();
public async Task<string> CallAsync(CancellationToken token)
{
await Task.Delay(300, token);
if (_rnd.NextDouble() < 0.6) throw new Exception("Service failed");
return "β
Success!";
}
}
class Program
{
static async Task Main()
{
var breaker = new CircuitBreaker(
failureThreshold: 3,
resetTimeout: TimeSpan.FromSeconds(5)
);
var service = new FlakyService();
for (int i = 1; i <= 10; i++)
{
try
{
Console.WriteLine($"\nβ‘ Attempt {i}");
var result = await breaker.ExecuteAsync(
action: service.CallAsync,
timeout: TimeSpan.FromSeconds(1),
fallback: () => Task.FromResult("π Fallback result"),
retryCount: 1
);
Console.WriteLine($"β
Got result: {result}");
}
catch (Exception ex)
{
Console.WriteLine($"β Final failure: {ex.Message}");
}
await Task.Delay(1000); // delay between attempts
}
}
}
Outputs:
β‘ Attempt 1
β Attempt 1 failed: Service failed
β
Got result: π Fallback result
β‘ Attempt 2
β Attempt 1 failed: Service failed
β
Got result: π Fallback result
β‘ Attempt 3
β Attempt 1 failed: Service failed
π Circuit breaker OPENED
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 4
β Circuit breaker is OPEN
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 5
β Circuit breaker is OPEN
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 6
β Circuit breaker is OPEN
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 7
π Reset timeout expired. Trying HALF-OPEN state...
β Attempt 1 failed: Service failed
π Circuit breaker OPENED
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 8
β Circuit breaker is OPEN
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 9
β Circuit breaker is OPEN
β‘ Executing fallback
β
Got result: π Fallback result
β‘ Attempt 10
π Reset timeout expired. Trying HALF-OPEN state...
β
Got result: π Success!
Example on how to use the Audit trail:
class BankService
{
private readonly AuditTrail _audit;
private decimal _balance = 1000;
public BankService(AuditTrail audit)
{
_audit = audit;
}
public void Deposit(string user, decimal amount)
{
_balance += amount;
_audit.Record(user, "Deposit", $"Deposited {amount:C}. New Balance: {_balance:C}");
}
public void Withdraw(string user, decimal amount)
{
if (amount > _balance)
{
_audit.Record(user, "Failed Withdraw", $"Attempted to withdraw {amount:C} with insufficient funds.");
throw new InvalidOperationException("Insufficient funds.");
}
_balance -= amount;
_audit.Record(user, "Withdraw", $"Withdrew {amount:C}. New Balance: {_balance:C}");
}
public decimal GetBalance() => _balance;
}
var auditTrail = new AuditTrail();
var bank = new BankService(auditTrail);
try
{
bank.Deposit("alice", 250);
bank.Withdraw("alice", 100);
bank.Withdraw("bob", 1500); // Will fail and audit that too
}
catch (Exception ex)
{
Console.WriteLine($"β Error: {ex.Message}");
}
Console.WriteLine("\nπ Audit Trail:");
foreach (var entry in auditTrail.GetAll())
{
Console.WriteLine($"{entry.Timestamp:u} | {entry.User} | {entry.Action} | {entry.Details}");
}
Outputs:
π Audit Trail:
2025-06-28 16:10:21Z | alice | Deposit | Deposited $250.00. New Balance: $1,250.00
2025-06-28 16:10:21Z | alice | Withdraw | Withdrew $100.00. New Balance: $1,150.00
2025-06-28 16:10:21Z | bob | Failed Withdraw | Attempted to withdraw $1,500.00 with insufficient funds.
Example on how to use the outbox pattern:
public class UserService
{
private readonly Outbox _outbox;
public UserService(Outbox outbox)
{
_outbox = outbox;
}
public void ChangeEmail(string userId, string newEmail)
{
// Imagine DB update here...
Console.WriteLine($"π₯ User {userId} email changed to {newEmail}");
var payload = $"{{ \"userId\": \"{userId}\", \"newEmail\": \"{newEmail}\" }}";
_outbox.SaveEvent("UserEmailChanged", payload);
}
}
public class OutboxDispatcher
{
private readonly Outbox _outbox;
public OutboxDispatcher(Outbox outbox)
{
_outbox = outbox;
}
public void Dispatch()
{
foreach (var msg in _outbox.GetPending())
{
// Simulate publishing
Console.WriteLine($"π€ Dispatching Event: {msg.EventType} => {msg.Payload}");
_outbox.MarkAsDispatched(msg.Id);
}
}
}
var outbox = new Outbox();
var userService = new UserService(outbox);
var dispatcher = new OutboxDispatcher(outbox);
// Simulate business logic
userService.ChangeEmail("user-123", "alice@example.com");
userService.ChangeEmail("user-456", "bob@example.com");
Console.WriteLine("\nβ³ Outbox before dispatch:");
foreach (var msg in outbox.GetPending())
{
Console.WriteLine($"βΊοΈ {msg.EventType} - {msg.Payload}");
}
// Now dispatch
Console.WriteLine("\nπ Dispatching Outbox...");
dispatcher.Dispatch();
Console.WriteLine("\nβ
Final Outbox State:");
foreach (var msg in outbox.GetAll())
{
Console.WriteLine($"π§Ύ {msg.EventType} | Sent: {msg.Dispatched}");
}
Outputs:
π₯ User user-123 email changed to alice@example.com
π₯ User user-456 email changed to bob@example.com
β³ Outbox before dispatch:
βΊοΈ UserEmailChanged - { "userId": "user-123", "newEmail": "alice@example.com" }
βΊοΈ UserEmailChanged - { "userId": "user-456", "newEmail": "bob@example.com" }
π Dispatching Outbox...
π€ Dispatching Event: UserEmailChanged => { "userId": "user-123", "newEmail": "alice@example.com" }
π€ Dispatching Event: UserEmailChanged => { "userId": "user-456", "newEmail": "bob@example.com" }
β
Final Outbox State:
π§Ύ UserEmailChanged | Sent: True
π§Ύ UserEmailChanged | Sent: True
Example on how to use the Saga Orchestrator:
public class PaymentStep : ISagaStep
{
public async Task<bool> ExecuteAsync()
{
Console.WriteLine("Processing payment...");
await Task.Delay(500);
Console.WriteLine("Payment processed.");
return true; // success
}
public async Task CompensateAsync()
{
Console.WriteLine("Compensating payment...");
await Task.Delay(300);
Console.WriteLine("Payment refunded.");
}
}
public class InventoryStep : ISagaStep
{
public async Task<bool> ExecuteAsync()
{
Console.WriteLine("Updating inventory...");
await Task.Delay(500);
Console.WriteLine("Inventory updated.");
return false; // simulate failure to trigger compensation
}
public async Task CompensateAsync()
{
Console.WriteLine("Reverting inventory update...");
await Task.Delay(300);
Console.WriteLine("Inventory reverted.");
}
}
class Program
{
static async Task Main()
{
var saga = new SagaOrchestrator();
saga.AddStep(new PaymentStep());
saga.AddStep(new InventoryStep());
bool result = await saga.RunAsync();
Console.WriteLine(result ? "Saga completed successfully." : "Saga failed and compensated.");
}
}
Outputs:
Processing payment...
Payment processed.
Updating inventory...
Inventory updated.
Step failed, compensating previous steps...
Payment refunded.
Saga failed and compensated.
Example on how to use the Bulkhead:
class Program
{
static Bulkhead bulkhead = new Bulkhead(2);
static async Task<string> ProtectedCall(int id)
{
Console.WriteLine($"[{id}] Waiting for slot...");
var result = await bulkhead.ExecuteAsync(async () =>
{
Console.WriteLine($"[{id}] Running...");
await Task.Delay(1000);
Console.WriteLine($"[{id}] Done.");
return $"Result from {id}";
});
return result;
}
static async Task Main()
{
var tasks = new Task<string>[5];
for (int i = 0; i < 5; i++)
{
int capture = i;
tasks[i] = ProtectedCall(capture);
}
var results = await Task.WhenAll(tasks);
foreach (var res in results)
{
Console.WriteLine(res);
}
}
}
Example on how to use the lazy initialization:
class Program
{
static AsyncLazy<string> lazyResource = new AsyncLazy<string>(async () =>
{
Console.WriteLine("Starting async initialization...");
await Task.Delay(2000);
Console.WriteLine("Initialization complete.");
return "Resource Ready";
});
static async Task UseResource(int id)
{
Console.WriteLine($"[{id}] Requesting resource...");
var resource = await lazyResource.Value;
Console.WriteLine($"[{id}] Got resource: {resource}");
}
static async Task Main()
{
var tasks = new Task[]
{
UseResource(1),
UseResource(2),
UseResource(3)
};
await Task.WhenAll(tasks);
}
}
Outputs:
[1] Requesting resource...
Starting async initialization...
[2] Requesting resource...
[3] Requesting resource...
Initialization complete.
[1] Got resource: Resource Ready
[2] Got resource: Resource Ready
[3] Got resource: Resource Ready
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. |
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
-
net8.0
- No dependencies.
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.0 | 88 | 6/28/2025 |