A modular .NET utility library providing essential building blocks for enterprise applications.
Hexalith.Commons is a collection of focused .NET libraries that provide reusable utilities for common programming tasks. Each package is lightweight, well-tested, and easy to integrate.
| Package | Purpose | Key Features |
|---|---|---|
| Hexalith.Commons | Core utilities | String helpers, error handling, reflection, logging |
| Hexalith.Commons.Configurations | Configuration management | Type-safe settings, FluentValidation integration |
| Hexalith.Commons.StringEncoders | String encoding | RFC1123 encoding/decoding for restricted contexts |
| Hexalith.Commons.UniqueIds | ID generation | ULID, DateTime-based, and GUID-based unique identifiers with ULID-Guid conversion |
| Hexalith.Commons.Metadatas | Message metadata | Context tracking for distributed systems |
- .NET 10.0 or later
- Compatible with ASP.NET Core, Console, Worker Services, and library projects
Install packages via NuGet:
# Core utilities
dotnet add package Hexalith.Commons
# Configuration management
dotnet add package Hexalith.Commons.Configurations
# String encoding
dotnet add package Hexalith.Commons.StringEncoders
# Unique ID generation
dotnet add package Hexalith.Commons.UniqueIds
# Message metadata
dotnet add package Hexalith.Commons.MetadatasThe core library provides essential utilities organized into focused namespaces.
Namespace: Hexalith.Extensions.Helpers
using Hexalith.Extensions.Helpers;
// Format strings with named placeholders
string template = "Hello {name}, your order #{orderId} is ready";
string result = template.FormatWithNamedPlaceholders(
new Dictionary<string, object> { ["name"] = "John", ["orderId"] = 12345 }
);
// Result: "Hello John, your order #12345 is ready"
// Culture-invariant number conversions
string number = "42.5";
decimal value = number.ToDecimal(); // Works regardless of system culture
// RFC1123 hostname validation
bool isValid = "my-server.example.com".IsRfc1123Compliant(); // true
bool isInvalid = "my_server".IsRfc1123Compliant(); // falseNamespace: Hexalith.Commons.Errors
Structured error handling with railway-oriented programming support.
using Hexalith.Commons.Errors;
// Create structured errors
var error = new ApplicationError
{
Title = "Validation Failed",
Detail = "The field {fieldName} is required",
Category = ErrorCategory.Validation,
Arguments = new object[] { "Email" }
};
string message = error.GetDetailMessage();
// Result: "The field Email is required"
// Railway-oriented error handling with ValueOrError<T>
ValueOrError<User> result = await GetUserAsync(userId);
if (result.HasError)
{
// Handle error
logger.LogError(result.Error.GetDetailMessage());
}
else
{
// Use the value
User user = result.Value;
}Namespace: Hexalith.Commons.Objects
Deep equality comparison and object introspection.
using Hexalith.Commons.Objects;
// Deep equality comparison (supports nested objects, collections, dictionaries)
bool areEqual = EquatableHelper.AreSame(object1, object2);
// Attribute-based object description
var description = ObjectDescriptionHelper.Describe(typeof(MyClass));
// Returns: Name, DisplayName, Description from attributes
// Implement custom equality
public class Order : IEquatableObject
{
public string Id { get; set; }
public decimal Total { get; set; }
public IEnumerable<object?> GetEqualityComponents()
{
yield return Id;
yield return Total;
}
}Namespace: Hexalith.Commons.Reflections
Type discovery and mapping utilities.
using Hexalith.Commons.Reflections;
// Find all implementations of an interface
IEnumerable<Type> handlers = ReflectionHelper.GetInstantiableTypesOf<ICommandHandler>();
// Create instances of discovered types
IEnumerable<ICommandHandler> instances = ReflectionHelper.GetInstantiableObjectsOf<ICommandHandler>();
// Type name mapping
var mapper = new TypeMapper();
mapper.Register<OrderCreatedEvent>("order-created");
Type eventType = mapper.GetType("order-created");Namespace: Hexalith.Commons.Dates
Timezone-aware date operations.
using Hexalith.Commons.Dates;
// Convert DateOnly to DateTimeOffset with timezone
DateOnly date = new(2024, 1, 15);
TimeSpan offset = TimeSpan.FromHours(-5); // EST
DateTimeOffset result = DateHelper.ToLocalTime(date, offset);
// Convert to UTC
DateTimeOffset utc = DateHelper.ToUniversalTime(date);
// Calculate wait time between dates
TimeSpan waitTime = DateHelper.WaitTime(targetDate, currentDate);Namespace: Hexalith.Commons.Assemblies
Version information retrieval.
using Hexalith.Commons.Assemblies;
// Get entry assembly version
string? version = VersionHelper.EntryProductVersion();
// Get version from specific assembly
string? assemblyVersion = typeof(MyClass).Assembly.GetAssemblyVersion();Namespace: Hexalith.Commons.Helpers
Structured logging for application errors.
using Hexalith.Commons.Helpers;
// Log application errors with full context
logger.LogApplicationError(applicationError);Type-safe configuration management with validation support.
using Hexalith.Commons.Configurations;
public class DatabaseSettings : ISettings
{
public string ConnectionString { get; set; } = string.Empty;
public int CommandTimeout { get; set; } = 30;
public int MaxRetryCount { get; set; } = 3;
// Configuration section name in appsettings.json
public static string ConfigurationName() => "Database";
}appsettings.json:
{
"Database": {
"ConnectionString": "Server=localhost;Database=MyApp",
"CommandTimeout": 60,
"MaxRetryCount": 5
}
}// Program.cs - Register settings
builder.Services.ConfigureSettings<DatabaseSettings>(builder.Configuration);
// Service class - Inject and use
public class DataService
{
private readonly DatabaseSettings _settings;
public DataService(IOptions<DatabaseSettings> options)
{
_settings = options.Value;
// Validate required settings
SettingsException<DatabaseSettings>.ThrowIfUndefined(_settings.ConnectionString);
}
}using FluentValidation;
public class DatabaseSettingsValidator : AbstractValidator<DatabaseSettings>
{
public DatabaseSettingsValidator()
{
RuleFor(x => x.ConnectionString)
.NotEmpty()
.WithMessage("Database connection string is required");
RuleFor(x => x.CommandTimeout)
.InclusiveBetween(1, 300)
.WithMessage("Command timeout must be between 1 and 300 seconds");
}
}
// Registration with validation
services.ConfigureSettings<DatabaseSettings>(configuration);
services.AddValidatorsFromAssemblyContaining<DatabaseSettingsValidator>();Reversible string encoding for RFC1123-compliant contexts.
| Character | Encoded Form | Description |
|---|---|---|
| A-Z, a-z, 0-9, -, . | Unchanged | Allowed characters |
_ (underscore) |
__ |
Escaped as double underscore |
| Space | _20 |
UTF-8 hex encoding |
| Other characters | _XX |
UTF-8 byte hex encoding |
using Hexalith.Commons.StringEncoders;
// Basic encoding
string encoded = "Hello World!".ToRFC1123();
// Result: "Hello_20World_21"
// Unicode support
string chinese = "δ½ ε₯½".ToRFC1123();
// Result: "_E4_BD_A0_E5_A5_BD"
// Email addresses
string email = "user@example.com".ToRFC1123();
// Result: "user_40example.com"
// Decoding
string original = "Hello_20World_21".FromRFC1123();
// Result: "Hello World!"
// Round-trip guarantee
string input = "Any string with Γ©mojis π!";
string roundTrip = input.ToRFC1123().FromRFC1123();
Assert.Equal(input, roundTrip); // Always true- File system paths: Generate safe filenames from user input
- URL identifiers: Create URL-safe slugs from arbitrary text
- Message headers: Encode values for protocols with character restrictions
- Database keys: Create compliant identifiers from any string
Three ID strategies β DateTime, Base64URL, and ULID β plus bidirectional ULID-Guid conversion.
using Hexalith.Commons.UniqueIds;
// Sortable + distributed (event sourcing, DDD aggregates)
string ulidId = UniqueIdHelper.GenerateSortableUniqueStringId();
// "01HYX7QS3NP8M4KQJR5A7CVWKM" β 26-char ULID
// Distributed (legacy keys, session tokens)
string base64Id = UniqueIdHelper.GenerateUniqueStringId();
// "gZOW2EgVrEq5SBJLegYcVA" β 22-char Base64URL
// Human-readable (single machine, logs)
string dateId = UniqueIdHelper.GenerateDateTimeId();
// "20260314143052789" β 17-char timestamp| Feature | GenerateDateTimeId |
GenerateUniqueStringId |
GenerateSortableUniqueStringId |
|---|---|---|---|
| Format | yyyyMMddHHmmssfff |
Base64URL GUID | Crockford Base32 ULID |
| Length | 17 chars | 22 chars | 26 chars |
| Sortable | Yes (chronological) | No | Yes (chronological) |
| Distributed-safe | No (single machine) | Yes | Yes |
| Thread-safe | Yes (locked) | Yes (stateless) | Yes (monotonic) |
| Best for | Log entries, file names | Legacy keys, session tokens | Event sourcing, DDD aggregates |
Bidirectional conversion between ULID strings and System.Guid for interop with Guid-only systems.
// ULID β Guid (for external systems that require Guid)
string ulid = UniqueIdHelper.GenerateSortableUniqueStringId();
Guid guid = UniqueIdHelper.ToGuid(ulid);
// Guid β ULID (lossless round-trip)
string restored = UniqueIdHelper.ToSortableUniqueId(guid);
// restored == ulid (case-insensitive)
// Extract creation timestamp from any ULID
DateTimeOffset created = UniqueIdHelper.ExtractTimestamp(ulid);Note:
ToGuidpreserves identity but NOT lexicographic sort order. Converting a non-ULID Guid (e.g.,Guid.NewGuid()) produces a valid ULID string, but its embedded timestamp is meaningless.
| Use Case | Recommended Method |
|---|---|
| Event sourcing / DDD aggregates | GenerateSortableUniqueStringId() β sortable + distributed |
| Distributed keys / session tokens | GenerateUniqueStringId() β compact + GUID-backed |
| Log entries / file names | GenerateDateTimeId() β human-readable timestamps |
| Interop with Guid-only systems | ToGuid() / ToSortableUniqueId() β lossless round-trip |
Metadata structures for message tracking in distributed systems.
Metadata
βββ MessageMetadata
β βββ Id (string) - Unique message identifier
β βββ Name (string) - Message type name
β βββ Version (int) - Message schema version
β βββ CreatedDate (DateTimeOffset)
β βββ Domain (DomainMetadata)
β βββ Id (string) - Aggregate identifier
β βββ Name (string) - Aggregate type name
βββ ContextMetadata
βββ CorrelationId (string) - Request correlation
βββ UserId (string) - User performing action
βββ PartitionId (string) - Partition for distribution
βββ SessionId (string) - User session
βββ SequenceNumber (long) - Message ordering
βββ ReceivedDate (DateTimeOffset)
βββ Scopes (IEnumerable<string>)
using Hexalith.Commons.Metadatas;
// Create message metadata
var messageMetadata = new MessageMetadata(
Id: UniqueIdHelper.GenerateUniqueStringId(),
Name: "OrderCreated",
Version: 1,
Domain: new DomainMetadata(Id: "ORD-12345", Name: "Order"),
CreatedDate: DateTimeOffset.UtcNow
);
// Create context metadata
var contextMetadata = new ContextMetadata(
CorrelationId: correlationId,
UserId: currentUser.Id,
PartitionId: tenantId,
SessionId: sessionId,
SequenceNumber: 1,
ReceivedDate: DateTimeOffset.UtcNow,
Scopes: new[] { "orders", "write" }
);
// Combine into complete metadata
var metadata = new Metadata(messageMetadata, contextMetadata);
// Generate domain global identifier
string globalId = metadata.DomainGlobalId;
// Format: "{partitionId}-{aggregateName}-{aggregateId}"
// Logging-friendly representation
string logEntry = metadata.ToLogString();- Event sourcing: Track event origin and context
- Message routing: Route messages based on partition and domain
- Audit trails: Complete traceability of all operations
- Correlation: Link related messages across services
- Ordering: Maintain message sequence within partitions
# Clone the repository
git clone https://github.com/Hexalith/Hexalith.Commons.git
cd Hexalith.Commons
# Build
dotnet build
# Build the complete owned-project package-mode surface used by dependency governance
dotnet build Hexalith.Commons.Standalone.slnx -c Release -p:UseNuGetDeps=true
# Run tests
dotnet testHexalith.Commons.slnx remains the canonical development solution. The
Hexalith.Commons.Standalone.slnx solution is a governance-only inventory of all
20 owned projects and deliberately excludes projects and files under references/.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License. See the LICENSE file for details.