FatCat.Toolkit.WebServer 1.0.335

dotnet add package FatCat.Toolkit.WebServer --version 1.0.335
                    
NuGet\Install-Package FatCat.Toolkit.WebServer -Version 1.0.335
                    
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="FatCat.Toolkit.WebServer" Version="1.0.335" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FatCat.Toolkit.WebServer" Version="1.0.335" />
                    
Directory.Packages.props
<PackageReference Include="FatCat.Toolkit.WebServer" />
                    
Project file
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 FatCat.Toolkit.WebServer --version 1.0.335
                    
#r "nuget: FatCat.Toolkit.WebServer, 1.0.335"
                    
#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 FatCat.Toolkit.WebServer@1.0.335
                    
#: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=FatCat.Toolkit.WebServer&version=1.0.335
                    
Install as a Cake Addin
#tool nuget:?package=FatCat.Toolkit.WebServer&version=1.0.335
                    
Install as a Cake Tool

FatCat.Toolkit

A comprehensive C# utility library and ASP.NET Core web framework for .NET 10.0. Provides common helpers, extension methods, dependency injection, data access, cryptography, caching, messaging, TCP communication, and a configurable web server with SignalR and JWT authentication.

License: MIT

Installation

# Core library
dotnet add package FatCat.Toolkit

# Web server (includes core)
dotnet add package FatCat.Toolkit.WebServer

Packages

Package Description
FatCat.Toolkit Core utilities, extensions, DI, caching, crypto, data access, messaging, threading
FatCat.Toolkit.WebServer ASP.NET Core web framework with authentication, SignalR, CORS, and static files

Quick Start

Dependency Injection with SystemScope

FatCat.Toolkit uses Autofac for dependency injection via SystemScope, a global singleton container.

// Initialize the container with your assemblies
var builder = new ContainerBuilder();

var assemblies = new List<Assembly>
{
    typeof(MyService).Assembly,
    typeof(Program).Assembly
};

SystemScope.Initialize(builder, assemblies, new ScopeOptions { SetLifetimeScope = true });

// Resolve services
var myService = SystemScope.Container.Resolve<IMyService>();

// Safe resolution when the service might not be registered
if (SystemScope.Container.TryResolve<IOptionalService>(out var service))
{
    service.DoWork();
}

Register custom Autofac modules for advanced scenarios:

public class MyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<MyService>().As<IMyService>().SingleInstance();

        // Factory registration
        builder.Register<CacheManager>((scope) =>
        {
            var connection = scope.Resolve<IConnection>();
            return new CacheManager(connection);
        }).SingleInstance();

        // Open generic registration
        builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>));
    }
}

Web Server

Basic Web Application

var settings = new ToolkitWebApplicationSettings
{
    Options = WebApplicationOptions.CommonOptions,
    ContainerAssemblies = new List<Assembly> { typeof(Program).Assembly }
};

ToolkitWebApplication.Run(settings);

With Authentication and SignalR

var settings = new ToolkitWebApplicationSettings
{
    Options = WebApplicationOptions.Authentication | WebApplicationOptions.SignalR | WebApplicationOptions.Cors,
    ContainerAssemblies = new List<Assembly> { typeof(Program).Assembly },
    SignalRPath = "/hub",
    CorsSevers = new List<string> { "https://myapp.com", "https://admin.myapp.com" },
    ToolkitTokenParameters = new ToolkitTokenParameters
    {
        Issuer = "MyApp",
        Audience = "MyAppUsers",
        SecretKey = "your-secret-key-at-least-32-characters-long"
    },
    OnWebApplicationStarted = () =>
    {
        Console.WriteLine("Server is running!");
    },
    OnOnClientDataBufferMessage = async (message, buffer) =>
    {
        // Handle SignalR messages from clients
        Console.WriteLine($"Received message type: {message.MessageType}");
        return "acknowledged";
    }
};

ToolkitWebApplication.Run(settings);

Web Results in Controllers

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet("{id}")]
    public WebResult GetUser(string id)
    {
        var user = FindUser(id);

        if (user == null) return WebResult.NotFound();

        return WebResult.Ok(user);
    }

    [HttpPost]
    public WebResult CreateUser([FromBody] CreateUserRequest request)
    {
        if (string.IsNullOrEmpty(request.Name))
            return WebResult.BadRequest("Name is required");

        var user = SaveUser(request);

        return WebResult.Ok(user);
    }
}

Data Access

MongoDB Repository

All repository types implement IDataRepository<T> with full CRUD and filtering.

// 1. Define your data object
public class UserData : MongoObject
{
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
}

// 2. Implement connection information
public class MyMongoConnection : IMongoConnectionInformation
{
    public string ConnectionString { get; set; } = "mongodb://localhost:27017";
    public string DatabaseName { get; set; } = "myapp";
}

// 3. Use the repository (auto-registered via DataModule)
public class UserService(IMongoRepository<UserData> userRepository)
{
    public async Task<UserData> GetById(string id)
    {
        return await userRepository.Get(id);
    }

    public async Task<List<UserData>> GetActiveUsers()
    {
        return await userRepository.GetAllByFilter(u => u.Email != null);
    }

    public async Task<UserData> FindOrCreate(string email)
    {
        return await userRepository.GetFirstOrCreate(
            u => u.Email == email,
            new UserData { Email = email, Name = "New User", CreatedAt = DateTime.UtcNow }
        );
    }

    public async Task Save(UserData user)
    {
        await userRepository.Create(user);
    }

    public async Task SaveMany(List<UserData> users)
    {
        await userRepository.Create(users);
    }

    public async Task Remove(UserData user)
    {
        await userRepository.Delete(user);
    }
}

File System Repository

Store objects as JSON files on disk:

public class AppSettings : FileSystemDataObject
{
    public string Theme { get; set; }
    public int MaxRetries { get; set; }
}

// Use just like MongoDB — same IDataRepository<T> interface
public class SettingsService(IFileSystemRepository<AppSettings> settingsRepository)
{
    public async Task<AppSettings> Load()
    {
        return await settingsRepository.GetFirst();
    }

    public async Task Save(AppSettings settings)
    {
        await settingsRepository.Update(settings);
    }
}

Caching

Thread-safe in-memory cache with optional expiration:

public class ProductCache
{
    private readonly IFatCatCache<Product> cache = new FatCatCache<Product>();

    public void Add(Product product)
    {
        // Cache for 5 minutes
        cache.Add(product, TimeSpan.FromMinutes(5));
    }

    public void AddPermanent(Product product)
    {
        // No expiration
        cache.Add(product);
    }

    public Product Get(string cacheId)
    {
        // Returns null if expired or not found
        return cache.Get(cacheId);
    }

    public bool Exists(string cacheId)
    {
        return cache.InCache(cacheId);
    }

    public void Invalidate(string cacheId)
    {
        cache.Remove(cacheId);
    }

    public void Clear()
    {
        cache.Clear();
    }
}

// Items must implement ICacheItem
public class Product : ICacheItem
{
    public string CacheId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Messaging (Pub/Sub)

Decoupled async publish/subscribe messaging:

// Define a message type
public class OrderPlacedMessage : Message
{
    public string OrderId { get; set; }
    public decimal Total { get; set; }
}

// Subscribe to messages
Messenger.Subscribe<OrderPlacedMessage>(async message =>
{
    Console.WriteLine($"Order {message.OrderId} placed for {message.Total:C}");
    await SendConfirmationEmail(message.OrderId);
});

// Publish a message — all subscribers are notified
Messenger.Send(new OrderPlacedMessage { OrderId = "ORD-123", Total = 99.99m });

// Unsubscribe when done (important to avoid memory leaks)
Messenger.Unsubscribe<OrderPlacedMessage>(handler);

HTTP Client (WebCaller)

var caller = new WebCaller(new Uri("https://api.example.com"), jsonOps, logger);

// Authentication
caller.UseBasicAuthorization("username", "password");
// or
caller.UserBearerToken("your-jwt-token");

// GET
var response = await caller.Get("/users");

if (response.IsSuccessful)
{
    var users = response.To<List<User>>();
}

// POST with custom timeout
var newUser = new { Name = "Alice", Email = "alice@example.com" };
var response = await caller.Post("/users", newUser, TimeSpan.FromSeconds(30));

// PUT
await caller.Put($"/users/{id}", updatedUser);

// DELETE
await caller.Delete($"/users/{id}");

Cryptography

AES-GCM Encryption

var encryption = new FatCatAesEncryption();

var key = new byte[32]; // 256-bit key
var iv = new byte[12];  // 96-bit nonce (must be unique per encryption)

// Fill with cryptographically random bytes
RandomNumberGenerator.Fill(key);
RandomNumberGenerator.Fill(iv);

// Encrypt — returns ciphertext with 16-byte auth tag appended
byte[] plainData = Encoding.UTF8.GetBytes("sensitive information");
byte[] encrypted = await encryption.Encrypt(plainData, key, iv);

// Decrypt
byte[] decrypted = await encryption.Decrypt(encrypted, key, iv);
string original = Encoding.UTF8.GetString(decrypted);

Password Hashing (Argon2id)

var hashTools = new HashTools();

// Hash a password
byte[] salt = new byte[16];
RandomNumberGenerator.Fill(salt);
byte[] hash = hashTools.HashPassword("user-password", salt);

// Verify a password (constant-time comparison)
byte[] attemptHash = hashTools.HashPassword("user-input", salt);
bool isValid = hashTools.HashEquals(hash, attemptHash);

SHA256 Hashing

string hash = HashTools.CalculateHash("data to hash");
byte[] hashBytes = HashTools.CalculateHash(dataBytes);

Extension Methods

String Extensions

// Null/empty checking
"hello".IsNullOrEmpty();        // false
"  ".IsNullOrEmpty();           // true (checks whitespace too)
"hello".IsNotNullOrEmpty();     // true

// Type conversions with safe defaults
"42".ToInt();                   // 42
"bad".ToInt();                  // 0 (default)
"true".ToBool();                // true
"3.14".ToDouble();              // 3.14

// Encoding
"hello".ToBase64Encoded();      // "aGVsbG8="
"aGVsbG8=".FromBase64Encoded(); // "hello"
"hello".ToByteArray();          // UTF-8 bytes
"hello".ToAsciiByteArray();     // ASCII bytes

// Text manipulation
"hello world".ToSlug();                  // "hello-world"
"hello world".FirstLetterToUpper();      // "Hello world"
"My:Bad*File".MakeSafeFileName();        // sanitized filename
"hello".FixedLength(10);                 // "hello     "
"  lots  of  spaces  ".RemoveAllWhitespace(); // "lotsofspaces"

// Splitting
"line1\nline2\nline3".SplitByLine();     // ["line1", "line2", "line3"]

// Hex parsing (for protocol work)
"0x02AB÷0x04".WithEmbeddedHexCodesToByteArray(); // parses hex codes to bytes

Object Extensions

// Deep clone any serializable object
var original = new MyComplexObject { Name = "test", Items = new List<int> { 1, 2, 3 } };
var clone = original.DeepCopy();
// clone is a completely independent copy

Collection Extensions

var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 1, 2, 3 };

list1.ListsAreEqual(list2);                     // true (order matters by default)
list1.ListsAreEqual(list2, ignoreOrder: true);   // true (ignores order)

Threading and Async Utilities

Retry with Backoff

var retry = new FatRetry();

// Retry up to 5 times with 2-second delay between attempts
bool success = await retry.Execute(
    async () => await CallUnreliableApi(),
    maxRetries: 5,
    delay: TimeSpan.FromSeconds(2)
);

// Synchronous version
bool success = retry.Execute(
    () => TryOperation(),
    maxRetries: 10,
    delay: TimeSpan.FromSeconds(5)
);

Polling / Waiting

var waiter = new FatWaiter();

// Wait for a condition to become true, polling every 100ms
await waiter.Wait(
    () => server.IsReady,
    TimeSpan.FromMilliseconds(100)
);

// With timeout
await waiter.Wait(
    () => server.IsReady,
    TimeSpan.FromMilliseconds(100),
    TimeSpan.FromSeconds(30)
);

// Async condition with cancellation
await waiter.Wait(
    async () => await CheckHealthEndpoint(),
    TimeSpan.FromSeconds(1),
    TimeSpan.FromMinutes(5),
    cancellationToken
);

Background Threading

// Fire-and-forget with exception handling
var thread = new Thread(logger);
thread.Run(async () =>
{
    await ProcessInBackground();
});

await thread.Sleep(TimeSpan.FromSeconds(5));

TCP Communication

TCP Client

var client = new FatTcpClient();

client.TcpMessageReceivedEvent += (data) =>
{
    Console.WriteLine($"Received: {Encoding.UTF8.GetString(data)}");
};

client.Reconnect = true;
client.ReconnectDelay = TimeSpan.FromSeconds(5);

client.Connect("192.168.1.100", 9000, bufferSize: 4096);

client.Send("Hello server!");
client.Send(new byte[] { 0x01, 0x02, 0x03 });

// Cleanup
client.Disconnect();

TCP Server

var server = new OpenFatTcpServer();
server.Start(9000, Encoding.UTF8, bufferSize: 4096);

// Handle connections and messages via events

server.Stop();

Utility Classes

ApplicationTools

// System information
var exePath = ApplicationTools.ExecutableFullPath;
var machineName = ApplicationTools.MachineName;
var mac = ApplicationTools.MacAddress;
bool inDocker = ApplicationTools.InContainer;

// Network
var ip = ApplicationTools.GetIPAddress();
var allIps = ApplicationTools.GetIPList();
ushort openPort = ApplicationTools.FindNextOpenPort(8000);

Generator

var id = Generator.NewId();           // Short unique ID
var guid = Generator.NewGuid();       // Standard GUID
var objectId = Generator.NewObjectId(); // MongoDB-style ObjectId
var randomStr = Generator.RandomString(16);
var randomNum = Generator.RandomNumber(1, 100);
var randomBytes = Generator.Bytes(32);

bool valid = Generator.IsValidObjectId("507f1f77bcf86cd799439011");

FileSystemTools

Abstracted file I/O for testability (uses System.IO.Abstractions):

var fsTools = new FileSystemTools(fileSystem);

// Async operations
await fsTools.WriteAllText("/data/config.json", jsonContent);
var content = await fsTools.ReadAllText("/data/config.json");
var bytes = await fsTools.ReadAllBytes("/data/image.png");
await fsTools.AppendToFile("/logs/app.log", "New log entry\n");

// Directory management
fsTools.EnsureDirectory("/data/output");
bool exists = fsTools.DirectoryExists("/data/output");
var files = fsTools.GetFiles("/data/output");
var filesWithMeta = fsTools.GetFilesWithMetaData("/data/output");

// File operations
fsTools.MoveFile("/tmp/upload.dat", "/data/upload.dat");
fsTools.DeleteFile("/tmp/upload.dat");

FatResult

Generic result pattern for operations that can succeed or fail:

public FatResult<User> GetUser(string id)
{
    var user = FindUser(id);

    if (user == null)
        return FatResult<User>.Failed("User not found");

    return FatResult<User>.Success(user);
}

// Usage
var result = GetUser("123");

if (result.IsSuccessful)
{
    Console.WriteLine(result.Data.Name);
}
else
{
    Console.WriteLine(result.Message);
}

EqualObject

Base class providing automatic value-based equality using reflection:

public class Address : EqualObject
{
    public string Street { get; set; }
    public string City { get; set; }

    [CompareExclude] // Excluded from equality comparison
    public DateTime LastUpdated { get; set; }
}

var a = new Address { Street = "123 Main", City = "Springfield" };
var b = new Address { Street = "123 Main", City = "Springfield" };

bool equal = a == b; // true — compares all fields except LastUpdated

JSON Operations

var jsonOps = new JsonOperations();

// Serialize / Deserialize
string json = jsonOps.Serialize(myObject, indented: true);
var obj = jsonOps.Deserialize<MyType>(json);

// Safe deserialization
if (jsonOps.TryDeserialize<MyType>(json, out var result))
{
    // use result
}

// Stream variants (for large payloads)
await jsonOps.SerializeToStreamAsync(myObject, stream);
var obj = await jsonOps.DeserializeFromStreamAsync<MyType>(stream);

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  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.

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.335 76 3/31/2026
1.0.334 75 3/25/2026
1.0.333 72 3/25/2026
1.0.332 81 3/25/2026
1.0.331 79 3/24/2026
1.0.330 102 3/16/2026
1.0.329 94 3/2/2026
1.0.328 92 3/2/2026
1.0.327 94 3/2/2026
1.0.326 126 1/24/2026
1.0.325 104 1/14/2026
1.0.324 133 1/3/2026
1.0.323 114 1/3/2026
1.0.322 309 12/16/2025
1.0.321 213 12/5/2025
1.0.320 228 12/5/2025
1.0.319 707 12/3/2025
1.0.318 153 11/29/2025
1.0.317 440 11/20/2025
1.0.316 215 11/15/2025
Loading failed