Klab.Toolkit.Common
2.10.0
dotnet add package Klab.Toolkit.Common --version 2.10.0
NuGet\Install-Package Klab.Toolkit.Common -Version 2.10.0
<PackageReference Include="Klab.Toolkit.Common" Version="2.10.0" />
<PackageVersion Include="Klab.Toolkit.Common" Version="2.10.0" />
<PackageReference Include="Klab.Toolkit.Common" />
paket add Klab.Toolkit.Common --version 2.10.0
#r "nuget: Klab.Toolkit.Common, 2.10.0"
#:package Klab.Toolkit.Common@2.10.0
#addin nuget:?package=Klab.Toolkit.Common&version=2.10.0
#tool nuget:?package=Klab.Toolkit.Common&version=2.10.0
Klab.Toolkit.Common
Overview
The Klab.Toolkit.Common
package provides essential abstractions, utilities, and services that promote testability, consistency, and reusability across .NET applications. This package serves as the foundation for building maintainable applications by offering well-designed interfaces and implementations for common cross-cutting concerns.
Purpose
The primary purpose of the Klab.Toolkit.Common
package is to:
- Promote Testability: Abstract system dependencies like time, tasks, and I/O operations
- Ensure Consistency: Provide standardized interfaces for common operations
- Enable Reusability: Offer utility classes that solve frequent development challenges
- Support Modularity: Define clear contracts between application components
Key Features
Core Abstractions
ITimeProvider
: Abstracts system time operations for testable time-dependent codeITaskProvider
: Provides thread-safe task and locking operationsIRetryService
: Implements configurable retry policies with exponential backoffJobProcessor<T>
: Generic background job processing with channels
Utility Classes
- Time Operations: Testable alternatives to
DateTime.Now
andTask.Delay
- Retry Logic: Robust error handling with configurable retry strategies
- Background Processing: High-performance job queuing and processing
- Resource Locking: Thread-safe resource access with proper cleanup
Installation
dotnet add package Klab.Toolkit.Common
Setup
Register all services in your dependency injection container:
using Klab.Toolkit.Common;
// In Program.cs or Startup.cs
services.AddKlabToolkitCommon();
This registers:
ITimeProvider
→TimeProvider
ITaskProvider
→TaskProvider
IRetryService
→RetryService
Core Components
ITimeProvider - Testable Time Operations
The ITimeProvider
interface abstracts system time, making your code testable and time-zone aware.
Interface
public interface ITimeProvider
{
DateTimeOffset GetCurrentTime();
Task<Result> WaitAsync(TimeSpan timeSpan, CancellationToken cancellationToken = default);
}
Usage Examples
public class OrderService
{
private readonly ITimeProvider _timeProvider;
public OrderService(ITimeProvider timeProvider)
{
_timeProvider = timeProvider;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
var now = _timeProvider.GetCurrentTime();
var order = new Order
{
Id = Guid.NewGuid(),
CreatedAt = now,
ExpiresAt = now.AddDays(30),
Items = request.Items
};
// Wait before processing (respects cancellation)
await _timeProvider.WaitAsync(TimeSpan.FromSeconds(1));
return order;
}
public bool IsOrderExpired(Order order)
{
return order.ExpiresAt < _timeProvider.GetCurrentTime();
}
}
Testing with ITimeProvider
public class TestTimeProvider : ITimeProvider
{
public DateTimeOffset CurrentTime { get; set; } = DateTimeOffset.UtcNow;
public DateTimeOffset GetCurrentTime() => CurrentTime;
public Task<Result> WaitAsync(TimeSpan timeSpan, CancellationToken cancellationToken = default)
{
// In tests, we can simulate time passing instantly
CurrentTime = CurrentTime.Add(timeSpan);
return Task.FromResult(Result.Success());
}
}
[Test]
public void CreateOrder_ShouldSetCorrectExpirationDate()
{
// Arrange
var timeProvider = new TestTimeProvider
{
CurrentTime = new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero)
};
var orderService = new OrderService(timeProvider);
// Act
var order = orderService.CreateOrder(new CreateOrderRequest());
// Assert
order.ExpiresAt.Should().Be(new DateTimeOffset(2024, 1, 31, 0, 0, 0, TimeSpan.Zero));
}
IRetryService - Robust Error Handling
The IRetryService
provides configurable retry logic with exponential backoff for handling transient failures.
Interface
public interface IRetryService
{
Task<Result> TryCallAsync(
Func<CancellationToken, Task> callback,
TimeSpan timeout,
int retryCount = 3);
}
Usage Examples
public class PaymentService
{
private readonly IRetryService _retryService;
private readonly IPaymentGateway _paymentGateway;
public PaymentService(IRetryService retryService, IPaymentGateway paymentGateway)
{
_retryService = retryService;
_paymentGateway = paymentGateway;
}
public async Task<Result> ProcessPaymentAsync(Payment payment)
{
// Retry payment processing up to 3 times with 30-second timeout
return await _retryService.TryCallAsync(
async cancellationToken =>
{
await _paymentGateway.ChargeAsync(payment, cancellationToken);
},
timeout: TimeSpan.FromSeconds(30),
retryCount: 3
);
}
public async Task<Result<User>> FetchUserDataAsync(Guid userId)
{
var result = await _retryService.TryCallAsync(
async cancellationToken =>
{
var userData = await _externalApi.GetUserAsync(userId, cancellationToken);
if (userData == null)
throw new UserNotFoundException($"User {userId} not found");
},
timeout: TimeSpan.FromSeconds(10),
retryCount: 5
);
return result.IsSuccess
? Result.Success(userData)
: Result.Failure<User>(result.Error);
}
}
Custom Retry Policies
public class CustomRetryService
{
public async Task<Result> RetryWithBackoffAsync<T>(
Func<Task<T>> operation,
int maxRetries = 3,
TimeSpan baseDelay = default)
{
baseDelay = baseDelay == default ? TimeSpan.FromMilliseconds(100) : baseDelay;
for (int attempt = 0; attempt <= maxRetries; attempt++)
{
try
{
await operation();
return Result.Success();
}
catch (Exception ex) when (attempt < maxRetries)
{
var delay = TimeSpan.FromTicks(baseDelay.Ticks * (long)Math.Pow(2, attempt));
await Task.Delay(delay);
}
catch (Exception ex)
{
return Error.FromException("RETRY_EXHAUSTED", ErrorType.Error, ex);
}
}
return Result.Success();
}
}
ITaskProvider - Thread-Safe Operations
The ITaskProvider
offers abstractions for task delays and resource locking.
Interface
public interface ITaskProvider : IDisposable
{
Task DelayAsync(TimeSpan delay, CancellationToken token);
Task LockAsync(CancellationToken token);
Task LockReleaseAsync();
Task ReleaseSyncLockAsync();
// ... other methods
}
Usage Examples
public class ResourceManager
{
private readonly ITaskProvider _taskProvider;
private readonly Dictionary<string, object> _resources = new();
public ResourceManager(ITaskProvider taskProvider)
{
_taskProvider = taskProvider;
}
public async Task<T> AccessResourceSafelyAsync<T>(
string resourceKey,
Func<Task<T>> operation,
CancellationToken cancellationToken = default)
{
await _taskProvider.LockAsync(cancellationToken);
try
{
// Simulate processing delay
await _taskProvider.DelayAsync(TimeSpan.FromMilliseconds(100), cancellationToken);
return await operation();
}
finally
{
await _taskProvider.LockReleaseAsync();
}
}
public async Task ProcessBatchAsync<T>(
IEnumerable<T> items,
Func<T, Task> processor,
TimeSpan delayBetweenItems = default)
{
foreach (var item in items)
{
await processor(item);
if (delayBetweenItems > TimeSpan.Zero)
{
await _taskProvider.DelayAsync(delayBetweenItems, CancellationToken.None);
}
}
}
}
JobProcessor<T> - Background Job Processing
The JobProcessor<T>
provides high-performance background job processing using .NET channels.
Usage Examples
public class EmailService
{
private readonly JobProcessor<EmailJob> _emailProcessor;
public EmailService(ILogger<JobProcessor<EmailJob>> logger)
{
_emailProcessor = new JobProcessor<EmailJob>(logger);
_emailProcessor.Init(ProcessEmailAsync);
}
public async Task QueueEmailAsync(string to, string subject, string body)
{
var emailJob = new EmailJob(to, subject, body);
await _emailProcessor.EnqueueAsync(emailJob);
}
private async Task ProcessEmailAsync(EmailJob job)
{
try
{
// Simulate email sending
await Task.Delay(1000);
Console.WriteLine($"Email sent to {job.To}: {job.Subject}");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to send email: {ex.Message}");
}
}
}
public record EmailJob(string To, string Subject, string Body);
Advanced Job Processing
public class OrderProcessingService
{
private readonly JobProcessor<OrderProcessingJob> _orderProcessor;
private readonly IOrderRepository _orderRepository;
private readonly IPaymentService _paymentService;
public OrderProcessingService(
ILogger<JobProcessor<OrderProcessingJob>> logger,
IOrderRepository orderRepository,
IPaymentService paymentService)
{
_orderRepository = orderRepository;
_paymentService = paymentService;
_orderProcessor = new JobProcessor<OrderProcessingJob>(logger);
_orderProcessor.Init(ProcessOrderAsync);
}
public async Task EnqueueOrderProcessingAsync(Guid orderId)
{
await _orderProcessor.EnqueueAsync(new OrderProcessingJob(orderId));
}
private async Task ProcessOrderAsync(OrderProcessingJob job)
{
var order = await _orderRepository.GetByIdAsync(job.OrderId);
if (order == null) return;
// Process payment
var paymentResult = await _paymentService.ProcessPaymentAsync(order.Payment);
if (!paymentResult.IsSuccess)
{
order.MarkAsFailed(paymentResult.Error.Message);
await _orderRepository.UpdateAsync(order);
return;
}
// Update inventory
foreach (var item in order.Items)
{
await UpdateInventoryAsync(item);
}
order.MarkAsCompleted();
await _orderRepository.UpdateAsync(order);
}
}
public record OrderProcessingJob(Guid OrderId);
Advanced Patterns
Service Layer with Common Abstractions
public class UserService
{
private readonly ITimeProvider _timeProvider;
private readonly IRetryService _retryService;
private readonly IUserRepository _userRepository;
private readonly JobProcessor<UserNotificationJob> _notificationProcessor;
public UserService(
ITimeProvider timeProvider,
IRetryService retryService,
IUserRepository userRepository,
ILogger<JobProcessor<UserNotificationJob>> logger)
{
_timeProvider = timeProvider;
_retryService = retryService;
_userRepository = userRepository;
_notificationProcessor = new JobProcessor<UserNotificationJob>(logger);
_notificationProcessor.Init(SendNotificationAsync);
}
public async Task<Result<User>> CreateUserAsync(CreateUserRequest request)
{
var now = _timeProvider.GetCurrentTime();
var user = new User
{
Id = Guid.NewGuid(),
Email = request.Email,
CreatedAt = now,
LastLoginAt = now
};
// Save with retry logic
var saveResult = await _retryService.TryCallAsync(
async ct => await _userRepository.SaveAsync(user, ct),
timeout: TimeSpan.FromSeconds(5),
retryCount: 3
);
if (!saveResult.IsSuccess)
return Result.Failure<User>(saveResult.Error);
// Queue welcome notification
await _notificationProcessor.EnqueueAsync(
new UserNotificationJob(user.Id, NotificationType.Welcome)
);
return Result.Success(user);
}
private async Task SendNotificationAsync(UserNotificationJob job)
{
// Implementation for sending notifications
await Task.Delay(100); // Simulate work
}
}
public record UserNotificationJob(Guid UserId, NotificationType Type);
public enum NotificationType { Welcome, PasswordReset, AccountUpdate }
Testing Strategy
public class UserServiceTests
{
private readonly Mock<IUserRepository> _userRepository;
private readonly TestTimeProvider _timeProvider;
private readonly Mock<IRetryService> _retryService;
private readonly UserService _userService;
public UserServiceTests()
{
_userRepository = new Mock<IUserRepository>();
_timeProvider = new TestTimeProvider();
_retryService = new Mock<IRetryService>();
_userService = new UserService(_timeProvider, _retryService.Object, _userRepository.Object, Mock.Of<ILogger<JobProcessor<UserNotificationJob>>>());
}
[Test]
public async Task CreateUser_WithValidRequest_ShouldSetCorrectTimestamps()
{
// Arrange
var fixedTime = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero);
_timeProvider.CurrentTime = fixedTime;
_retryService
.Setup(x => x.TryCallAsync(It.IsAny<Func<CancellationToken, Task>>(), It.IsAny<TimeSpan>(), It.IsAny<int>()))
.ReturnsAsync(Result.Success());
var request = new CreateUserRequest { Email = "test@example.com" };
// Act
var result = await _userService.CreateUserAsync(request);
// Assert
result.IsSuccess.Should().BeTrue();
result.Value.CreatedAt.Should().Be(fixedTime);
result.Value.LastLoginAt.Should().Be(fixedTime);
}
}
Best Practices
Time Operations
- Always use
ITimeProvider
instead ofDateTime.Now
orDateTimeOffset.Now
- Prefer
DateTimeOffset
overDateTime
for time zone awareness - Use the
WaitAsync
method for delays that need to respect cancellation tokens
Retry Logic
- Use
IRetryService
for external API calls and database operations - Set appropriate timeouts based on operation type
- Consider exponential backoff for high-traffic scenarios
- Log retry attempts for debugging
Background Processing
- Use
JobProcessor<T>
for fire-and-forget operations - Keep job handlers lightweight and fast
- Implement proper error handling in job processors
- Consider job persistence for critical operations
Resource Management
- Always dispose of
ITaskProvider
andJobProcessor<T>
instances - Use
using
statements or dependency injection container for lifecycle management - Implement proper cancellation token support
Performance Considerations
- JobProcessor: Uses unbounded channels for maximum throughput
- Retry Service: Implements efficient exponential backoff
- Task Provider: Minimal overhead for lock operations
- Time Provider: Direct system calls with no caching overhead
Thread Safety
All implementations in this package are thread-safe:
TimeProvider
: Stateless operationsRetryService
: No shared state between callsTaskProvider
: Uses thread-safe synchronization primitivesJobProcessor<T>
: Built on thread-safe channels
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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. |
.NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.1 is compatible. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- Klab.Toolkit.Results (>= 2.10.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.2)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.2)
- System.Threading.Channels (>= 9.0.2)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Klab.Toolkit.Common:
Package | Downloads |
---|---|
Klab.Toolkit.DI
Package Description |
|
Klab.Toolkit.Logging
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
2.10.0 | 74 | 8/22/2025 |
2.9.0 | 154 | 8/3/2025 |
2.8.2 | 351 | 5/15/2025 |
2.8.1 | 219 | 4/24/2025 |
2.8.0 | 198 | 4/22/2025 |
2.7.3 | 174 | 4/13/2025 |
2.7.2 | 188 | 4/6/2025 |
2.7.1 | 187 | 4/3/2025 |
2.7.0 | 198 | 4/3/2025 |
2.6.0 | 517 | 3/24/2025 |
2.5.1 | 160 | 3/14/2025 |
2.5.0 | 150 | 2/24/2025 |
2.4.1 | 174 | 10/2/2024 |
2.4.0 | 161 | 10/2/2024 |
2.3.0 | 159 | 10/1/2024 |
2.2.4 | 166 | 9/30/2024 |
2.2.3 | 144 | 9/28/2024 |
2.2.2 | 189 | 9/20/2024 |
2.2.1 | 175 | 9/17/2024 |
2.2.0 | 176 | 9/17/2024 |
2.1.0 | 199 | 8/12/2024 |
2.0.0 | 239 | 12/28/2023 |
1.0.0 | 210 | 5/28/2023 |
0.3.0 | 206 | 5/28/2023 |
0.2.0 | 213 | 5/28/2023 |
0.1.0 | 197 | 5/28/2023 |