A comprehensive .NET library providing high-performance interfaces and implementations for common cross-cutting concerns including compression, encoding, obfuscation, encryption, hashing, serialization, caching, persistence, validation, logging, navigation, command execution, and filesystem access.
ktsu.Essentials defines a consistent, high-performance API for common cross-cutting concerns in .NET applications. Each provider interface follows a three-tier pattern: core Try* methods over Span<byte> and Stream that report how many bytes they wrote, convenient self-allocating methods, and async variants with CancellationToken support. Implementers only need to provide the core Try* methods — all convenience and async methods are provided via default interface implementations. The ktsu.Essentials package is interfaces only; implementations ship as separate ktsu.Essentials.<Category>.<Impl> packages, with ktsu.Essentials.All bundling every one of them. Higher-level concerns are expressed by composition rather than duplication — configuration is simply an IPersistenceProvider<TKey> over a serializer, and obfuscation composes encoding transforms.
- Compression:
ICompressionProviderwith Gzip, Brotli, Deflate, and ZLib implementations - Encoding:
IEncodingProviderwith Base64 and Hex implementations for format/transport encoding - Obfuscation:
IObfuscationProviderwith XOR, Caesar, bit-rotation, byte-reversal, Base64, and Hex implementations, plus aCompositeprovider that pipelines several together. Obfuscation is reversible but is not encryption — it provides no confidentiality - Dependency Injection: every provider package ships an
Add<Impl><Category>Provider()extension;ktsu.Essentials.Alladds per-category helpers and a singleAddEssentials(). Registrations are idempotent and expose each provider by both concrete type and interface - Encryption:
IEncryptionProviderwith AES implementation including key and IV generation - Hashing:
IHashProviderwith 15 implementations (MD5, SHA1/256/384/512, FNV1/FNV1a 32/64-bit, CRC32/64, XxHash32/64/3/128) - Serialization:
ISerializationProviderwith System.Text.Json, Newtonsoft.Json, YAML, and TOML implementations plus configurableISerializationOptions - Caching:
ICacheProvider<TKey, TValue>with in-memory implementation supporting expiration and get-or-add semantics - Persistence:
IPersistenceProvider<TKey>with DataHome, ConfigHome, FileSystem, InMemory, and Temp implementations.DataHomeandConfigHomefollow the XDG Base Directory layout on every platform —$XDG_DATA_HOMEor~/.local/share/<app>for application state,$XDG_CONFIG_HOMEor~/.config/<app>for user settings — with~resolving to%USERPROFILE%on Windows - Validation:
IValidationProvider<T>with structured results, error codes, and throw-on-failure support - Logging:
ILoggingProviderwith console implementation supporting six severity levels - Navigation:
INavigationProvider<T>with in-memory implementation for browser-like back/forward navigation - Command Execution:
ICommandExecutorwith native implementation for running shell commands and capturing output - Filesystem:
IFileSystemProviderextending Testably.Abstractions for testable filesystem access - Explicit Buffer Contract: every span operation is
bool TryX(source, destination, out int bytesWritten)and each category exposes aGetMax…Lengthbound, so callers can size a buffer up front and know exactly how much was written. Encoding, hashing and obfuscation run allocation-free on the span path; compression and encryption still buffer internally, because the underlying BCL APIs for those are stream-only - Minimal Implementation Burden: Default interface implementations reduce boilerplate — implement only the core
Try*methods - Comprehensive Async Support: Every operation has async variants with proper
CancellationTokensupport - Batteries-Included or Cherry-Pick: Each provider ships as its own
ktsu.Essentials.<Category>.<Impl>package; install thektsu.Essentials.Allmeta-package to get every provider at once, or reference only the ones you need
Install-Package ktsu.Essentialsdotnet add package ktsu.Essentials<PackageReference Include="ktsu.Essentials" Version="x.y.z" />using ktsu.Essentials;
using ktsu.Essentials.All;
using ktsu.Essentials.HashProviders.SHA256;
using Microsoft.Extensions.DependencyInjection;
// Each provider package ships its own registration extension
IServiceCollection services = new ServiceCollection();
services.AddSHA256HashProvider();
services.AddGzipCompressionProvider();
services.AddBase64EncodingProvider();
// ...or register everything at once with the ktsu.Essentials.All package
services.AddEssentials();
using ServiceProvider provider = services.BuildServiceProvider();
// Resolve a specific implementation by its concrete type...
SHA256HashProvider sha256 = provider.GetRequiredService<SHA256HashProvider>();
// ...or every registered implementation of an interface
IEnumerable<IHashProvider> allHashProviders = provider.GetServices<IHashProvider>();
IHashProvider hashProvider = sha256;
// Convenience method (auto-allocates buffer)
byte[] hash = hashProvider.Hash("Hello, World!");
// Buffer-based method — no allocation, and it tells you how much it wrote
Span<byte> buffer = stackalloc byte[hashProvider.HashLengthBytes];
if (hashProvider.TryHash("Hello, World!"u8, buffer, out int written))
{
string hex = Convert.ToHexString(buffer[..written]);
}
// Async method
byte[] asyncHash = await hashProvider.HashAsync("Hello, World!");ICompressionProvider compressor = provider.GetRequiredService<ICompressionProvider>();
byte[] compressed = compressor.Compress(originalData);
byte[] decompressed = compressor.Decompress(compressed);
// String convenience — compressed bytes are returned as Base64 so they survive as text
string compressedText = compressor.Compress("Large text content...");
string originalText = compressor.Decompress(compressedText);ISerializationProvider serializer = provider.GetRequiredService<ISerializationProvider>();
string json = serializer.Serialize(myObject);
MyClass? deserialized = serializer.Deserialize<MyClass>(json);
// Async
string asyncJson = await serializer.SerializeAsync(myObject);ICacheProvider<string, MyData> cache = provider.GetRequiredService<ICacheProvider<string, MyData>>();
cache.Set("key", myData, expiration: TimeSpan.FromMinutes(5));
MyData value = cache.GetOrAdd("key", k => LoadData(k));IPersistenceProvider<string> persistence = provider.GetRequiredService<IPersistenceProvider<string>>();
await persistence.StoreAsync("settings", mySettings);
MySettings? loaded = await persistence.RetrieveAsync<MySettings>("settings");
MySettings guaranteed = await persistence.RetrieveOrCreateAsync<MySettings>("settings");The DataHome and ConfigHome providers need an application name, so register them explicitly rather than
through AddEssentials():
using ktsu.Essentials.PersistenceProviders.ConfigHome;
using ktsu.Essentials.PersistenceProviders.DataHome;
// User settings -> $XDG_CONFIG_HOME/MyApp or ~/.config/MyApp
services.AddConfigHomePersistenceProvider<string>("MyApp");
// Application state -> $XDG_DATA_HOME/MyApp or ~/.local/share/MyApp
services.AddDataHomePersistenceProvider<string>("MyApp");Both use the same layout on every platform, with ~ resolving to %USERPROFILE% on Windows. If you need
the paths without a persistence provider, UserDirectories exposes them directly:
string dataDir = UserDirectories.GetApplicationDataDirectory("MyApp");
string configDir = UserDirectories.GetApplicationConfigDirectory("MyApp");Implementers only need to provide the core Try* methods — all other methods are inherited:
using ktsu.Essentials;
public sealed class MyHashProvider : IHashProvider
{
public int HashLengthBytes => 32;
public bool TryHash(ReadOnlySpan<byte> data, Span<byte> destination, out int bytesWritten)
{
bytesWritten = 0;
if (destination.Length < HashLengthBytes) return false;
// Custom hash logic here
bytesWritten = HashLengthBytes;
return true;
}
public bool TryHash(Stream data, Span<byte> destination, out int bytesWritten)
{
bytesWritten = 0;
if (destination.Length < HashLengthBytes) return false;
// Custom stream hash logic here
bytesWritten = HashLengthBytes;
return true;
}
// Hash(), HashAsync(), string overloads — all inherited
}Compress and decompress data with Span, Stream, and string support.
| Name | Return Type | Description |
|---|---|---|
GetMaxCompressedLength(int) |
int |
Buffer size that always fits the output |
TryCompress(ReadOnlySpan<byte>, Span<byte>, out int) |
bool |
Compress, reporting bytes written |
TryCompress(Stream, Stream) |
bool |
Stream-based compression |
Compress(ReadOnlySpan<byte>) |
byte[] |
Self-allocating compression |
Compress(string) |
string |
Compresses UTF8 text, returns Base64 |
TryDecompress(ReadOnlySpan<byte>, Span<byte>, out int) |
bool |
Decompress, reporting bytes written |
Decompress(ReadOnlySpan<byte>) |
byte[] |
Self-allocating decompression |
Decompress(string) |
string |
Reverses Compress(string) |
Format/transport encoding (Base64, Hex) — not text character encodings.
| Name | Return Type | Description |
|---|---|---|
GetMaxEncodedLength(int) / GetMaxDecodedLength(int) |
int |
Buffer sizes that always fit the output |
TryEncode(ReadOnlySpan<byte>, Span<byte>, out int) |
bool |
Encode, reporting bytes written |
TryEncode(Stream, Stream) |
bool |
Stream-based encoding |
Encode(ReadOnlySpan<byte>) |
byte[] |
Self-allocating encoding |
Encode(string) |
string |
Encodes UTF8 text |
TryDecode(ReadOnlySpan<byte>, Span<byte>, out int) |
bool |
Decode, reporting bytes written |
Decode(ReadOnlySpan<byte>) |
byte[] |
Self-allocating decoding |
Decode(string) |
string |
Reverses Encode(string) |
Encrypt and decrypt data with key and IV management.
| Name | Return Type | Description |
|---|---|---|
GetMaxEncryptedLength(int) |
int |
Buffer size that always fits the ciphertext |
TryEncrypt(ReadOnlySpan<byte>, …, Span<byte>, out int) |
bool |
Encrypt, reporting bytes written |
TryDecrypt(ReadOnlySpan<byte>, …, Span<byte>, out int) |
bool |
Decrypt, reporting bytes written |
Encrypt(string, ...) |
string |
Encrypts UTF8 text, returns Base64 |
Decrypt(string, ...) |
string |
Reverses Encrypt(string, ...) |
GenerateKey() |
byte[] |
Generates a new encryption key |
GenerateIV() |
byte[] |
Generates a new initialization vector |
Hash data with configurable output length. Exposes HashLengthBytes property for the output size in bytes.
| Name | Return Type | Description |
|---|---|---|
TryHash(ReadOnlySpan<byte>, Span<byte>, out int) |
bool |
Hash, reporting bytes written |
TryHash(Stream, Span<byte>, out int) |
bool |
Stream-based hashing |
Hash(ReadOnlySpan<byte>) |
byte[] |
Self-allocating hashing |
Hash(string) |
byte[] |
Hash a UTF8 string |
Serialize and deserialize objects supporting JSON, YAML, TOML, and other text-based formats.
| Name | Return Type | Description |
|---|---|---|
FileExtension |
string |
Conventional extension for the format, e.g. .yaml |
TrySerialize(object, TextWriter) |
bool |
Serialize to a TextWriter |
Serialize(object) |
string |
Serialize to a string |
Deserialize<T>(ReadOnlySpan<byte>) |
T? |
Deserialize from bytes |
Deserialize<T>(string) |
T? |
Deserialize from a string |
Deserialize<T>(TextReader) |
T? |
Deserialize from a TextReader |
Cache key-value pairs with optional expiration.
| Name | Return Type | Description |
|---|---|---|
TryGet(TKey, out TValue?) |
bool |
Try to get a cached value |
Get(TKey) |
TValue |
Get a value or throw |
Set(TKey, TValue, TimeSpan?) |
void |
Set a value with optional expiration |
GetOrAdd(TKey, Func<TKey, TValue>, TimeSpan?) |
TValue |
Get or create a value |
Remove(TKey) |
bool |
Remove a cached value |
Clear() |
void |
Clear all entries |
Store and retrieve objects with pluggable storage backends. Exposes ProviderName and IsPersistent properties.
| Name | Return Type | Description |
|---|---|---|
StoreAsync<T>(TKey, T) |
Task |
Store an object |
RetrieveAsync<T>(TKey) |
Task<T?> |
Retrieve an object |
RetrieveOrCreateAsync<T>(TKey) |
Task<T> |
Retrieve or create a new instance |
ExistsAsync(TKey) |
Task<bool> |
Check if a key exists |
RemoveAsync(TKey) |
Task<bool> |
Remove an object |
GetAllKeysAsync() |
Task<IEnumerable<TKey>> |
List all stored keys |
ClearAsync() |
Task |
Clear all stored objects |
Validate objects and return structured results.
| Name | Return Type | Description |
|---|---|---|
Validate(T) |
ValidationResult |
Validate and return result |
IsValid(T) |
bool |
Check validity |
ValidateAndThrow(T) |
void |
Validate or throw ValidationException |
Write structured log messages at various severity levels.
| Name | Return Type | Description |
|---|---|---|
Log(LogLevel, string) |
void |
Write a log entry |
Log(LogLevel, Exception, string) |
void |
Write a log entry with an exception |
IsEnabled(LogLevel) |
bool |
Check if a log level is enabled |
LogTrace(string) through LogCritical(string) |
void |
Level-specific convenience methods |
Browser-like back/forward navigation. Exposes Current, CanGoBack, and CanGoForward properties.
| Name | Return Type | Description |
|---|---|---|
NavigateTo(T) |
void |
Navigate to a destination |
GoBack() |
T? |
Navigate backward |
GoForward() |
T? |
Navigate forward |
Clear() |
void |
Clear all history |
Run shell commands and capture output.
| Name | Return Type | Description |
|---|---|---|
ExecuteAsync(string, string?) |
Task<CommandResult> |
Execute a command |
Execute(string, string?) |
CommandResult |
Execute a command synchronously |
ExecuteAndGetOutputAsync(string, string?) |
Task<string> |
Execute and return stdout or throw |
Extends Testably.Abstractions.IFileSystem for testable filesystem operations.
Contributions are welcome! Feel free to open issues or submit pull requests.
This project is licensed under the MIT License. See the LICENSE.md file for details.