A comprehensive .NET 8.0 library providing essential utilities, extensions, and patterns for modern .NET applications. This library simplifies common development tasks and provides robust, production-ready components for enterprise applications.
Install the package via NuGet:
dotnet add package Bounteous.CoreOr via Package Manager Console:
Install-Package Bounteous.Coreusing Bounteous.Core;
using Bounteous.Core.Extensions;
using Bounteous.Core.Time;
// Use dependency injection utilities
IoC.ConfigureServiceCollection(services);
// Use time utilities
var now = Clock.Utc.Now;
var localTime = Clock.Local.Now;
// Use string extensions
var result = "Hello World".ToBase64();Bounteous.Core follows modern .NET patterns and best practices:
- Dependency Injection: Built-in IoC container with auto-registration capabilities
- Repository Pattern: Abstract data access with Unit of Work support
- Command Pattern: Composite commands for complex operations
- Strategy Pattern: Pluggable algorithms and behaviors
- Extension Methods: Fluent APIs for common operations
- Validation Framework: Comprehensive validation with fluent syntax
- Auto-registration of services
- Service resolution with fallback defaults
- Scoped service management
- Repository pattern with Dapper integration
- Unit of Work for transaction management
- Connection builder abstraction
- Memory cache with wait-to-finish semantics
- Configurable expiration policies
- Thread-safe cache operations
- Clock abstraction for testability
- Timezone-aware operations
- Time manipulation for testing
- Fluent validation API
- Built-in validators for common scenarios
- Custom validation support
- String manipulation and encoding
- DateTime operations
- Enum utilities
- Collection extensions
- Reflection helpers
The IoC module provides a set of helpers to manage your IServiceCollection during application startup.
Sets up the IServiceCollection to be used by the IoC container.
Resolves and returns an instance of the specified service type.
Attempts to resolve an instance of the specified service type. If the service is not found, it returns a default implementation.
Creates and returns a new service scope, allowing for scoped service lifetimes.
Resets the IoC container with a new IServiceCollection, optionally provided by the caller.
Automatically registers all implementations of interfaces found in the specified assembly into the IServiceCollection.
Automatically registers all implementations of a specified type found in the specified assembly into the IServiceCollection.
Finds and returns all implementations of a specified type within the specified assembly.
Usage:
// Configure service collection
IoC.ConfigureServiceCollection(services);
// Resolve services
var service = IoC.Resolve<IMyService>();
var allServices = IoC.ResolveAll<IMyService>();
// Try resolve with fallback
var service = IoC.TryResolve<IMyService, DefaultMyService>();
// Create scope
using var scope = IoC.CreateScope();
var scopedService = scope.ServiceProvider.GetService<IMyService>();The Cache module provides caching strategies and implementations for .NET applications, with support for memory caching and configurable expiration policies.
public interface ICache
{
Task<TItem> GetOrCreate<TItem>(object key, Func<Task<TItem>> createItem);
}A thread-safe memory cache that ensures only one thread creates a value for a given key at a time.
Features:
- Thread-safe operations using
SemaphoreSlim - Configurable sliding and absolute expiration
- Memory pressure handling with size limits
- High priority for cache eviction
Usage:
var cache = new WaitToFinishMemoryCache(
slidingExpirationInMinutes: 2,
absoluteExpirationInMinutes: 15
);
var result = await cache.GetOrCreate("user:123", async () =>
{
// This will only execute once per key, even with concurrent requests
return await userService.GetUserAsync(123);
});A no-op implementation that always executes the factory method without caching.
Usage:
var cache = new DisabledCache();
// Always executes the factory method
var result = await cache.GetOrCreate("key", async () =>
{
return await expensiveOperation();
});The Commands module provides implementations of the Command pattern, enabling you to encapsulate operations as objects and compose them into complex workflows.
public interface ICommand
{
void Run();
}public interface IWaitedCommand
{
Task RunAsync();
}public interface ICommand<in TInput>
{
Task RunAsync(TInput data);
}Executes multiple synchronous commands in sequence.
Usage:
var command = new CompositeCommand();
command.Add(new LogCommand("Starting process"));
command.Add(new ProcessDataCommand());
command.Add(new LogCommand("Process completed"));
command.Run(); // Executes all commands in orderExecutes multiple async commands in sequence.
Usage:
var command = new CompositeWaitedCommand();
command.Add(new LoadDataCommand());
command.Add(new ProcessDataCommand());
command.Add(new SaveDataCommand());
await command.RunAsync(); // Executes all commands in orderExecutes multiple typed commands with the same input data.
Usage:
var command = new TypedCompositeCommand<User>();
command.Add(new ValidateUserCommand());
command.Add(new EnrichUserCommand());
command.Add(new LogUserCommand());
await command.RunAsync(user); // Executes all commands with the same user dataUse the Then extension method to chain commands fluently:
// Synchronous chaining
var syncCommand = new LogCommand("Start")
.Then(new ProcessCommand())
.Then(new LogCommand("End"));
// Async chaining
var asyncCommand = new LoadDataCommand()
.Then(new ProcessDataCommand())
.Then(new SaveDataCommand());
// Typed chaining
var typedCommand = new ValidateUserCommand()
.Then(new EnrichUserCommand())
.Then(new LogUserCommand());The Data Access module provides a robust data access layer using the Repository pattern with Unit of Work support, built on top of Dapper for high-performance data operations.
public interface IUnitOfWork : IDisposable
{
IDbTransaction Transaction { get; }
IDbConnection Connection { get; }
void Commit();
void Rollback();
}public interface IUnitOfWorkProvider
{
Task<IUnitOfWork> CreateTransactional();
Task<IUnitOfWork> CreateNonTransactional();
}public interface IConnectionBuilder
{
Task<IDbConnection> CreateConnectionAsync();
Task<IDbConnection> CreateReadConnectionAsync();
}Manages database transactions with automatic rollback on disposal if not committed.
Features:
- Automatic transaction management
- Rollback on disposal if not committed
- Comprehensive logging with Serilog
- Exception handling with proper cleanup
Usage:
using var uow = await unitOfWorkProvider.CreateTransactional();
try
{
// Perform database operations
await repository.InsertAsync(entity, uow.Connection, uow.Transaction);
await repository.UpdateAsync(entity, uow.Connection, uow.Transaction);
uow.Commit(); // Transaction committed successfully
}
catch
{
// Transaction will be rolled back automatically
throw;
}
// Unit of work disposed automaticallyAbstract base class providing common data access operations using Dapper.
Usage:
public class UserRepository : BaseRepository
{
public UserRepository(IConnectionBuilder connectionBuilder)
: base(connectionBuilder)
{
}
public async Task<User> GetByIdAsync(int id)
{
const string sql = "SELECT * FROM Users WHERE Id = @Id";
var results = await QueryAsync<User>(sql, new { Id = id });
return results.FirstOrDefault();
}
public async Task<int> InsertAsync(User user, IDbConnection connection = null, IDbTransaction transaction = null)
{
const string sql = @"
INSERT INTO Users (Name, Email, CreatedDate)
VALUES (@Name, @Email, @CreatedDate);
SELECT CAST(SCOPE_IDENTITY() as int);";
return await QueryAsync<int>(sql, user, connection, transaction).ContinueWith(t => t.Result.First());
}
}The Extensions module provides a comprehensive set of extension methods that enhance the functionality of common .NET types, making code more readable and reducing boilerplate.
// Base64 encoding
var encoded = "Hello World".ToBase64();
var decoded = encoded.FromBase64();
// Compression
var compressed = "Large text content".Zip();
var decompressed = compressed.Unzip();
// Byte conversion
var bytes = "Hello".GetBytes();
var text = bytes.FromBytes();// Default value handling
var result = stringValue.UseDefault("default value");
// Safe string operations
var safeValue = potentiallyNullString.SafeTrim();// Truncation
var truncated = DateTime.Now.TruncateMilliseconds();
// Range operations
var start = DateTime.Now.Earliest();
var end = DateTime.Now.Latest();
// Timezone conversions
var utc = localTime.ToUtc();
var local = utcTime.ToLocal();// Get description from DescriptionAttribute
var description = MyEnum.Value.GetDescription();
// Get custom attributes
var attribute = MyEnum.Value.GetAttribute<MyCustomAttribute>();
// Parse from description
var parsed = EnumExtensions.ParseFromDescription<MyEnum>("Description Text");
// Sorted enumeration
var sortedValues = EnumExtensions.GetSortedList<MyEnum>();// Safe operations
var safeList = potentiallyNullEnumerable.EmptyIfNull();
// Conditional operations
var filtered = collection.WhereIf(condition, predicate);
// Batch operations
var batches = largeCollection.Batch(100);// Find implementing types
var implementations = typeof(IMyInterface).GetImplementingTypes();
// Get parent assemblies
var parents = assembly.GetParentAssemblies();
// Property information from expressions
var propertyInfo = expression.GetProperty();
var propertyName = expression.NameOfProperty();
var propertyValue = expression.GetValue(myObject);The Time Management module provides a comprehensive clock abstraction system that enables testable time-dependent code and supports multiple timezone operations.
public interface IClock
{
DateTime Now { get; }
DateTime NowUtc { get; }
DateTime Today { get; }
void Freeze();
void Freeze(DateTime timeToFreeze);
void Thaw();
}Provides access to predefined clock instances for common timezones.
// Available clock instances
var localTime = Clock.Local.Now;
var utcTime = Clock.Utc.Now;
var mountainTime = Clock.MountainTime.Now;
var centralTime = Clock.CentralTime.Now;
var australianTime = Clock.AustralianEasternTime.Now;
// Special values
var endOfTime = Clock.EndOfTime; // 9999-12-31 23:59:59.999
// Clock manipulation control
Clock.AllowClockManipulation = true; // Enable for testingTimezone-aware clock implementation that converts between local and target timezones.
Usage:
var mountainClock = new TimezoneClock(TimeZoneInfo.FindSystemTimeZoneById("Mountain Standard Time"));
var localTime = mountainClock.Now;
var utcTime = mountainClock.NowUtc;
var today = mountainClock.Today;
// Convert to specific timezone
var pacificTime = mountainClock.NowToTimeZone(TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"));
// Work with timezone offsets
var todayAtOffset = mountainClock.TodayAt(TimezoneOffset.Pacific);
var nowAtOffset = mountainClock.NowAt(TimezoneOffset.Mountain);Test clock that allows time manipulation for unit testing.
var freezeClock = new FreezeClock();
// Freeze time
freezeClock.Freeze(new DateTime(2024, 1, 1, 12, 0, 0));
var frozenTime = freezeClock.Now; // Always returns frozen time
// Thaw time
freezeClock.Thaw();
var realTime = freezeClock.Now; // Returns current timeProvides predefined timezone offsets for common US timezones.
// Predefined offsets
var pacificOffset = TimezoneOffset.Pacific; // -8
var mountainOffset = TimezoneOffset.Mountain; // -7
var centralOffset = TimezoneOffset.Central; // -6
var easternOffset = TimezoneOffset.Eastern; // -5
// Convert UTC to timezone
var pacificTime = pacificOffset.From(DateTime.UtcNow);
var todayInPacific = pacificOffset.TodayFrom(DateTime.UtcNow);The Strategies module provides implementations of the Strategy pattern, enabling you to define families of algorithms and make them interchangeable at runtime.
public interface IStrategy<T>
{
Task<T> RunAsync(T subject);
}Executes multiple strategies in sequence, passing the result of each strategy to the next one.
Usage:
// Two strategies
var strategy = new CompositeStrategy<User>(
new ValidateUserStrategy(),
new EnrichUserStrategy()
);
// Multiple strategies
var multiStrategy = new CompositeStrategy<User>(
new ValidateUserStrategy(),
new EnrichUserStrategy(),
new LogUserStrategy(),
new CacheUserStrategy()
);
// Execute strategies
var result = await strategy.RunAsync(user);Use the Then extension method to chain strategies fluently:
var strategy = new ValidateUserStrategy()
.Then(new EnrichUserStrategy())
.Then(new LogUserStrategy());
var result = await strategy.RunAsync(user);public class ValidateUserStrategy : IStrategy<User>
{
public async Task<User> RunAsync(User user)
{
if (string.IsNullOrEmpty(user.Email))
throw new ValidationException("Email is required");
if (user.Age < 18)
throw new ValidationException("User must be 18 or older");
return user;
}
}public class EnrichUserStrategy : IStrategy<User>
{
private readonly IUserService userService;
public EnrichUserStrategy(IUserService userService)
{
this.userService = userService;
}
public async Task<User> RunAsync(User user)
{
// Enrich user with additional data
user.FullName = $"{user.FirstName} {user.LastName}";
user.DisplayName = await userService.GenerateDisplayNameAsync(user);
return user;
}
}The Serialization module provides utilities for JSON serialization with custom naming policies and specialized converters for common data types.
Provides predefined JSON serialization options with custom naming policies.
public class SerializationSettings
{
public static readonly JsonSerializerOptions LongNameSerializerOptions = new()
{
PropertyNamingPolicy = new LongNameContractResolver()
};
}Usage:
// Use predefined settings
var json = JsonSerializer.Serialize(data, SerializationSettings.LongNameSerializerOptions);
// Custom settings
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = new LongNameContractResolver(),
WriteIndented = true
};
var json = JsonSerializer.Serialize(data, options);Custom JSON naming policy that preserves original property names without transformation.
Usage:
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = new LongNamingContractResolver()
};
// Serializes with original property names
var json = JsonSerializer.Serialize(new { FirstName = "John", LastName = "Doe" }, options);
// Result: {"FirstName":"John","LastName":"Doe"}Specialized JSON converter for DateTimeRange objects that handles serialization and deserialization with proper format.
Usage:
var options = new JsonSerializerOptions
{
Converters = { new DateTimeRangeConverter() }
};
var range = new DateTimeRange(DateTime.Now, DateTime.Now.AddDays(1));
var json = JsonSerializer.Serialize(range, options);
var deserialized = JsonSerializer.Deserialize<DateTimeRange>(json, options);The Utilities module provides a comprehensive collection of utility classes and helpers that solve common programming problems and provide reusable functionality across different application domains.
Generic range class for working with ranges of comparable values.
Usage:
// Basic range
var numberRange = new Range<int>(1, 100);
var isIncluded = numberRange.Includes(50); // true
// Date range
var dateRange = new Range<DateTime>(DateTime.Now, DateTime.Now.AddDays(30));
var isDateIncluded = dateRange.Includes(DateTime.Now.AddDays(15)); // true
// Custom incrementor
var dayRange = new Range<DateTime>(
DateTime.Now,
DateTime.Now.AddDays(7),
d => d.AddDays(1)
);
// Iterate through range
foreach (var day in dayRange.Iterate)
{
Console.WriteLine(day);
}
// Range operations
var range1 = new Range<int>(1, 10);
var range2 = new Range<int>(5, 15);
var overlaps = range1.Overlaps(range2); // true
var includes = range1.Includes(range2); // falseApplication event tracking for monitoring and auditing.
Usage:
var appEvent = new ApplicationEvent
{
User = "john.doe",
Operation = "ProcessOrder",
Details = "Processing order #12345"
};
// Start timing
appEvent.StartEvent();
try
{
// Perform operation
await ProcessOrderAsync();
appEvent.Outcome = Outcome.Successful;
}
catch (Exception ex)
{
appEvent.Outcome = Outcome.Failed;
appEvent.FailureCause = ex.Message;
}
finally
{
// Stop timing and generate identifier
appEvent.StopEvent();
// Log the event
Log.Information("Event completed: {Event}", appEvent);
}Performance monitoring utilities for measuring execution time.
Usage:
// Synchronous performance tracing
var result = this.PerformanceTrace(() =>
{
return ExpensiveOperation();
}, "ExpensiveOperation");
// Asynchronous performance tracing
await this.PerformanceTraceAsync(async () =>
{
await ExpensiveAsyncOperation();
}, "ExpensiveAsyncOperation");
// With return value
var result = await this.PerformanceTraceAsync(async () =>
{
return await ExpensiveAsyncOperationWithResult();
}, "ExpensiveAsyncOperationWithResult");Retry and error handling utilities for resilient operations.
Usage:
// Basic retry
await Endeavor.Go<HttpRequestException>(
async () => await MakeHttpRequestAsync(),
retries: 3,
delay: 1000
);
// With custom exception handling
await Endeavor.Go<TimeoutException>(
async () => await ProcessDataAsync(),
async (ex) =>
{
Log.Warning("Timeout occurred, retrying...");
await Task.Delay(500);
},
retries: 5,
delay: 2000
);Base class for creating object mappers with automatic property mapping.
Usage:
public class UserMapper : AbstractMapper<UserDto, User>
{
protected override void Initialize()
{
// Automatic mapping for same property names
Map(u => u.FirstName, u => u.FirstName);
Map(u => u.LastName, u => u.LastName);
Map(u => u.Email, u => u.Email);
// Custom mapping
Map(u => u.FullName, u => $"{u.FirstName} {u.LastName}");
// Type conversion
Map(u => u.CreatedDate, u => DateTime.Parse(u.CreatedDateString));
}
protected override User Create()
{
return new User();
}
}
// Usage
var mapper = new UserMapper();
var user = mapper.Build(userDto);The Validations module provides a comprehensive validation framework with fluent syntax, built-in validators for common scenarios, and support for custom validation rules.
Main validation container that collects and manages validation exceptions.
Usage:
var validation = new Validation();
// Add validation exceptions
validation.Add(new ValidationException("Field is required"));
validation.Add(new ValidationException("Invalid format").Warning());
// Check validation state
var isValid = validation.IsValid();
var errors = validation.Errors;
var warnings = validation.Warnings;
// Pretty print all issues
var message = validation.PrettyPrint();Represents a validation error with severity levels and custom formatting.
Usage:
// Basic validation exception
var exception = new ValidationException("Field is required");
// Warning level
var warning = new ValidationException("Field is deprecated").Warning();
// Static factory methods
var required = ValidationException.IsRequired("Email");
var tooLong = ValidationException.ExceedsMaximumLength("Name");
var invalidFormat = ValidationException.MustBeEmail("Email");var validation = Validate.Begin()
.IsNotEmpty(value, "Field is required")
.IsTrue(condition, "Condition must be true")
.IsEqual(left, right, "Values must be equal")
.IsNotEqual(left, right, "Values must not be equal")
.Check();var validation = Validate.Begin()
.IsNotEmpty(email, "Email is required")
.IsEmail(email, "Email format is invalid")
.IsMinimumLength(name, 2, "Name must be at least 2 characters")
.IsMaximumLength(description, 500, "Description cannot exceed 500 characters")
.IsLengthOf(phone, 10, "Phone number must be exactly 10 digits")
.Check();var validation = Validate.Begin()
.IsDecimal(priceString, "Price must be a valid decimal")
.IsPrice(priceString, "Price must be greater than 0")
.IsInteger(quantityString, "Quantity must be a valid integer")
.GreaterThan(age, 18, "Age must be greater than 18")
.LessThan(score, 100, "Score must be less than 100")
.IsBetween(value, 1, 100, "Value must be between 1 and 100")
.Check();var validation = Validate.Begin()
.IsDate(dateString, "Date format is invalid")
.IsCloseEnough(startDate, endDate, TimeSpan.FromMinutes(5), "Dates must be within 5 minutes")
.IsAfter(startDate, DateTime.Now, "Start date must be in the future")
.IsBefore(endDate, DateTime.Now.AddYears(1), "End date must be within one year")
.Check();var validation = Validate.Begin()
.IsNotEmpty(collection, "Collection cannot be empty")
.IsNotNull(collection, "Collection cannot be null")
.HasMinimumCount(items, 1, "Must have at least one item")
.HasMaximumCount(items, 10, "Cannot have more than 10 items")
.Check();var validation = Validate.Begin()
.IsTrue(customCondition, "Custom validation failed")
.ContinueIfValid(v => v.IsNotEmpty(conditionalField, "Field required when condition is true"))
.Check();var validation = Validate.Begin()
.IsEmail(email, "Email format is invalid")
.Check();var validation = Validate.Begin()
.IsPhoneNumber(phone, "Phone number format is invalid")
.Check();var validation = Validate.Begin()
.IsPostalCode(postalCode, "Postal code format is invalid")
.Check();var validation = Validate.Begin()
.IsNumeric(numericString, "Must be a valid number")
.IsDecimal(decimalString, "Must be a valid decimal")
.IsInteger(integerString, "Must be a valid integer")
.Check();public static class CustomValidationExtensions
{
public static Validation IsValidPassword(this Validation validation, string password, string property)
{
return validation
.IsNotEmpty(password, $"{property} is required")
.IsMinimumLength(password, 8, $"{property} must be at least 8 characters")
.IsTrue(password.Any(char.IsUpper), $"{property} must contain uppercase letter")
.IsTrue(password.Any(char.IsLower), $"{property} must contain lowercase letter")
.IsTrue(password.Any(char.IsDigit), $"{property} must contain digit");
}
}
// Usage
var validation = Validate.Begin()
.IsValidPassword(password, "Password")
.Check();- .NET 8.0 and later
- Dapper (2.1.66) - Micro ORM
- Microsoft.Extensions.Caching.Memory (9.0.9) - Memory caching
- Microsoft.Extensions.Configuration (9.0.9) - Configuration management
- Microsoft.Extensions.DependencyInjection (9.0.9) - Dependency injection
- Serilog (4.3.0) - Structured logging
- System.Text.Json (9.0.9) - JSON serialization
This library is maintained by Xerris Inc. For contributions, please contact the development team.
See LICENSE file for details.
- Sample API - Example implementation
- Test Suite - Comprehensive test coverage
- Test Support - Testing utilities
This comprehensive documentation covers all major features and modules of the Bounteous.Core library, providing both high-level overview and detailed implementation guidance for .NET engineers.