Chd.Library.Common
8.6.0
See the version list below for details.
dotnet add package Chd.Library.Common --version 8.6.0
NuGet\Install-Package Chd.Library.Common -Version 8.6.0
<PackageReference Include="Chd.Library.Common" Version="8.6.0" />
<PackageVersion Include="Chd.Library.Common" Version="8.6.0" />
<PackageReference Include="Chd.Library.Common" />
paket add Chd.Library.Common --version 8.6.0
#r "nuget: Chd.Library.Common, 8.6.0"
#:package Chd.Library.Common@8.6.0
#addin nuget:?package=Chd.Library.Common&version=8.6.0
#tool nuget:?package=Chd.Library.Common&version=8.6.0
Library.Common
Library.Common is a comprehensive infrastructure library for .NET 8/9 applications that eliminates boilerplate code and provides production-ready utilities for common development tasks. Part of the CHD (Cleverly Handle Difficulty) ecosystem, this library offers battle-tested primitives used across 18+ production applications.
What Does It Do?
Library.Common provides ready-to-use solutions for:
- JSON Serialization: DateOnly/TimeOnly support for .NET 6/7/8/9 with one-line setup
- Security: Production-grade AES encryption, MD5 hashing, Base64 encoding
- Configuration: Strongly-typed
appsettings.jsonreading with auto-validation - Entity Framework: Pre-built User/Role/Application authorization entities
- String Processing: Advanced parsing, encryption, and manipulation extensions
- Network Utilities: Client IP detection (handles proxies, X-Forwarded-For)
- Enum Extensions: Get descriptions, flag operations, string parsing
Who Is It For?
- ASP.NET Core Developers building Razor Pages, Web APIs, or Minimal APIs
- Teams wanting consistent infrastructure across multiple projects
- Projects needing quick setup for authentication, configuration, or data processing
- Developers tired of copy-pasting utility code between projects
📚 Table of Contents
- Why Library.Common?
- Installation
- Quick Start Guide
- Real-World Examples
- API Reference
- Entity Schema
- Configuration Examples
- FAQ
- Troubleshooting
- Performance Benchmarks
- Best Practices
- Package Dependencies
- Contributing
- License
- Related CHD Packages
Tired of Writing the Same Boilerplate Code? â°
Stop writing JSON converters, encryption utils, and configuration helpers for every project. Library.Common provides battle-tested infrastructure primitives for .NET 8/9 applications.
The Problem 😫
// ⌠Every project repeats this:
services.AddControllers().AddJsonOptions(options => {
options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter());
options.JsonSerializerOptions.Converters.Add(new TimeOnlyJsonConverter());
// ... 50 more lines of configuration
});
// ⌠Copy-pasted cryptography code
public string Encrypt(string text) {
using var aes = Aes.Create();
// ... 30 lines of crypto setup
}
// ⌠Manual string parsing every time
var parts = html.Split("<div>")[1].Split("</div>")[0]; // Fragile!
The Solution ✅
// ✅ One line setup
builder.Services.AddDateOnlyTimeOnlyStringConverters();
// ✅ Production-grade encryption
string encrypted = "secret".Encrypt("myKey");
string decrypted = encrypted.Decrypt("myKey");
// ✅ Powerful string parsing
var result = html.ParseAsStrings(new List<FID> {
new FID { Start = "<div>", End = "</div>", Key = "content" }
});
Why Library.Common?
| Problem | Without Library.Common | With Library.Common |
|---|---|---|
| DateOnly/TimeOnly JSON Support | 50+ lines of boilerplate in every project | 1 line: .AddDateOnlyTimeOnlyStringConverters() |
| Encryption | Copy-paste crypto code, forget salt/IV | "text".Encrypt(key) - production-ready AES |
| Configuration Reading | Try-catch blocks, null checks, error messages | GetEnvirementVariables<T>() - auto-validation |
| String Parsing | Regex nightmares, brittle Split() chains | ParseAsStrings() - declarative parsing |
| Enum Descriptions | Manual reflection for every enum | myEnum.GetDescription() |
| EF Core Auth Entities | Design from scratch, forget foreign keys | Ready-made User/Role/Application entities |
| Client IP Detection | X-Forwarded-For, proxy headers, edge cases | NetworkUtils.GetClientIPAddress(context) |
| Base64/MD5/Encryption | Find StackOverflow snippets, hope they work | Built-in extension methods |
Key Benefits:
- â± Save 10-20 hours per project - No boilerplate rewriting
- 🔒 Production-tested - Used across 18+ CHD libraries
- 🎨 Clean APIs - Extension methods feel native to .NET
- 📚 Strongly-typed Config - Type-safe
appsettings.jsonreading - 🛡 Authorization Ready - EF Core entities for User/Role/Application
- 🔧 Zero Dependencies - Only uses Microsoft.* packages
Installation
dotnet add package Chd.Library.Common
NuGet Package Manager:
Install-Package Library.Common
Package Reference (.csproj):
<PackageReference Include="Library.Common" Version="8.6.1" />
Quick Start Guide
1ï¸âƒ£ JSON Configuration (DateOnly/TimeOnly Support)
.NET 6 and .NET 7+ have different DateOnly/TimeOnly serialization behaviors. Library.Common handles both:
// Program.cs or Startup.cs
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// ✅ Adds DateOnly/TimeOnly converters for both MVC and Minimal APIs
builder.Services.AddDateOnlyTimeOnlyStringConverters();
builder.Services.AddControllers(); // Already configured!
var app = builder.Build();
What This Does:
- .NET 6: Adds
DateOnlyJsonConverterandTimeOnlyJsonConverterto System.Text.Json - .NET 7+: Adds
TimeOnlyJsonConverteronly (DateOnly works out of the box) - Auto-configures: Both
Microsoft.AspNetCore.Mvc.JsonOptionsandMicrosoft.AspNetCore.Http.Json.JsonOptions - Type Converters: Adds
TypeConverterAttributefor model binding and validation
Example API Response:
public class Event
{
public DateOnly Date { get; set; } = new DateOnly(2024, 12, 25);
public TimeOnly Time { get; set; } = new TimeOnly(14, 30);
}
// GET /api/events returns:
{
"date": "2024-12-25", // ISO 8601 format
"time": "14:30:00" // HH:mm:ss format
}
2ï¸âƒ£ String Extensions
🔒 Encryption & Hashing
using System;
// AES Encryption (production-grade with Rfc2898DeriveBytes)
string secret = "MyPassword123";
string encrypted = secret.Encrypt("mySecretKey2024");
string decrypted = encrypted.Decrypt("mySecretKey2024");
Console.WriteLine(encrypted); // "eJw7... (Base64 encoded)"
Console.WriteLine(decrypted); // "MyPassword123"
// Base64 Encoding
string base64 = "Hello World".ToBase64Encode(); // "SGVsbG8gV29ybGQ="
string decoded = base64.ToBase64Decode(); // "Hello World"
// MD5 Hash (uppercase hex)
string hash = "admin@example.com".ToMd5(); // "0DCA2... (32-character hex)"
🔠Security Notes:
- AES-256 Encryption: Uses
Aes.Create()withRfc2898DeriveBytes(PBKDF2) - Salt: Hardcoded 13-byte salt (consider environment-based salts for multi-tenant)
- 2000 Iterations: SHA1-based key derivation (update to SHA256 for FIPS compliance)
📠JSON Serialization
using Newtonsoft.Json;
// Object to JSON (uses Newtonsoft.Json)
var user = new { Name = "Alice", Age = 30 };
string json = JsonConvert.SerializeObject(user);
// JSON to Object
var obj = json.ToObject<User>(); // Generic
var dynamicObj = json.ToObject(typeof(User)); // Non-generic
🧩 String Parsing (Advanced)
Parse structured text with declarative rules:
using Library.Common.Utils.StringParser;
string html = "<div>Name: John</div><div>Age: 30</div>";
var rules = new List<FID>
{
new FID { Start = "<div>Name: ", End = "</div>", Key = "name", WhiceIterator = 0 },
new FID { Start = "<div>Age: ", End = "</div>", Key = "age", WhiceIterator = 1 }
};
// Parse as strings
Dictionary<string, string> strings = html.ParseAsStrings(rules);
Console.WriteLine(strings["name"]); // "John"
// Parse as numbers
Dictionary<string, double> numbers = html.ParseAsNumerics(rules);
Console.WriteLine(numbers["age"]); // 30.0
Real-World Use Case:
// Parse invoice data from email body
string emailBody = @"
Invoice #INV-001
Amount: $1,250.00
Due Date: 2024-12-31
";
var invoiceRules = new List<FID>
{
new FID { Start = "Invoice #", End = "\n", Key = "invoiceNo" },
new FID { Start = "Amount: $", End = "\n", Key = "amount" },
new FID { Start = "Due Date: ", End = "\n", Key = "dueDate" }
};
var data = emailBody.ParseAsStrings(invoiceRules);
decimal amount = decimal.Parse(data["amount"].Replace(",", ""));
🔗 Other String Utilities
// Extract text between delimiters
string html = "<a href='#'>Link 1</a> <a href='#'>Link 2</a>";
List<string> links = html.BetweensTwoString("<a href='", "'>", "</a>");
// Returns: ["#'>Link 1", "#'>Link 2"]
// Join list with commas
var tags = new List<string> { "csharp", "dotnet", "webapi" };
string joined = tags.AddComma(); // "csharp,dotnet,webapi"
3ï¸âƒ£ Enum Extensions
using System.ComponentModel;
public enum OrderStatus
{
[Description("Pending approval")]
Pending = 0,
[Description("Payment confirmed")]
Confirmed = 1,
[Description("Shipped to customer")]
Shipped = 2
}
// Get description
OrderStatus status = OrderStatus.Confirmed;
string desc = status.GetDescription(); // "Payment confirmed"
// Enum flags operations
[Flags]
public enum Permissions
{
Read = 1,
Write = 2,
Delete = 4
}
Permissions userPerms = Permissions.Read | Permissions.Write;
bool canWrite = userPerms.Has(Permissions.Write); // true
bool isExactly = userPerms.Is(Permissions.Read); // false
// Add/remove flags
userPerms = userPerms.Add(Permissions.Delete); // Read | Write | Delete
userPerms = userPerms.Remove(Permissions.Write); // Read | Delete
// Get all active flags
IEnumerable<Permissions> activeFlags = userPerms.GetFlags();
// Returns: [Permissions.Read, Permissions.Delete]
// Parse from string
Permissions parsed = EnumerationExtensions.ParseEnum("write", Permissions.Read);
// Returns: Permissions.Write (case-insensitive)
4ï¸âƒ£ Configuration Extensions
Auto-validate and load strongly-typed settings:
using Microsoft.Extensions.Configuration;
public class EmailSettings
{
public string SmtpHost { get; set; }
public int SmtpPort { get; set; }
public string Username { get; set; }
public string Password { get; set; }
}
// appsettings.json
{
"EmailSettings": {
"SmtpHost": "smtp.gmail.com",
"SmtpPort": 587,
"Username": "noreply@example.com",
"Password": "app-password"
}
}
// Load with validation
var config = builder.Configuration;
EmailSettings emailSettings = config.GetEnvirementVariables<EmailSettings>();
if (emailSettings == null)
{
// Auto-prints required properties to console:
// "Lütfen application.json dosyasına aşagıdaki konfigürasyonu giriniz.
// "SmtpHost": deÄŸeri
// "SmtpPort": deÄŸeri
// ...
}
Using EnvironmentHelper for direct config access:
using Library.Common.Utils;
// Get full configuration
IConfigurationRoot config = EnvironmentHelper.GetConfiguration();
// Get specific section
IConfigurationSection dbSection = EnvironmentHelper.GetSectionFromEnvirementVariables("Database");
string connString = dbSection["ConnectionString"];
// Get strongly-typed config
var emailSettings = EnvironmentHelper.GetConfiguration<EmailSettings>("EmailSettings");
// Environment checks
bool isDev = EnvironmentHelper.IsDeveleopment(); // ASPNETCORE_ENVIRONMENT == "Development"
5ï¸âƒ£ Entity Framework Core Authorization Entities
Pre-built entities for User/Role/Application authorization:
using Library.Common.Entities;
using Microsoft.EntityFrameworkCore;
// DbContext with ready-made entities
public class MyAppDbContext : ServiceAuthorizationDBContext
{
public MyAppDbContext(DbContextOptions<MyAppDbContext> options)
: base(options)
{
}
// Your custom entities
public DbSet<Product> Products { get; set; }
}
// Program.cs
builder.Services.AddDbContext<MyAppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
Available Entities:
| Entity | Properties | Purpose |
|---|---|---|
| User | Id, Name, Email, Password, AuthenticationTypeId, Actor |
User accounts with authentication type |
| Role | Id, Name, Description |
Roles (Admin, User, Manager, etc.) |
| Application | Id, Name, Description, Ip, Port, TicketExpirationSecond |
Multi-app authorization (microservices) |
| ApplicationUserRole | Id, UserId, RoleId, ApplicationId |
Many-to-many: User ↔ Role ↔ Application |
| AuthenticationType | Id, Name |
Auth methods (Cookie, JWT, OAuth, etc.) |
| VersionInfo | (for migrations) | FluentMigrator version tracking |
Usage Example:
// Create admin user
var admin = new User
{
Name = "Admin",
Email = "admin@example.com",
Password = "hashed_password".ToMd5(),
AuthenticationTypeId = 1 // JWT
};
await dbContext.Users.AddAsync(admin);
// Assign role to user in specific application
var assignment = new ApplicationUserRole
{
UserId = admin.Id,
RoleId = 1, // Admin role
ApplicationId = 1 // Main API
};
await dbContext.ApplicationUserRoles.AddAsync(assignment);
await dbContext.SaveChangesAsync();
// Query user roles
var userRoles = await dbContext.ApplicationUserRoles
.Include(aur => aur.Role)
.Include(aur => aur.Application)
.Where(aur => aur.UserId == admin.Id)
.ToListAsync();
6ï¸âƒ£ Network Utilities
using Libary.Common.Utils;
using Microsoft.AspNetCore.Http;
// Razor Pages PageModel
public class IndexModel : PageModel
{
public string ClientIP { get; set; }
public void OnGet()
{
// ✅ Handles X-Forwarded-For, proxies, load balancers
ClientIP = NetworkUtils.GetClientIPAddress(HttpContext);
}
}
// Minimal API
app.MapGet("/ip", (HttpContext context) =>
{
string ip = NetworkUtils.GetClientIPAddress(context);
return Results.Ok(new { ClientIP = ip });
});
Handles edge cases:
X-Forwarded-Forheader (proxies like Nginx, Cloudflare)- Direct connections (
IHttpConnectionFeature.RemoteIpAddress) - Returns
nullif IP cannot be determined
7ï¸âƒ£ Cryptography Utilities
Full control over encryption (alternative to extension methods):
using System;
// Create instance with custom key
using var crypto = new Cryptography("myCustomKey2024");
// Encrypt
string encrypted = crypto.Encrypt("SensitiveData");
Console.WriteLine(encrypted); // "eJw7..."
// Decrypt
string decrypted = crypto.Decrypt(encrypted);
Console.WriteLine(decrypted); // "SensitiveData"
// Dispose when done (releases AES resources)
Difference from Extension Methods:
- Extension:
"text".Encrypt(key)- Creates new instance per call (convenience) - Class:
new Cryptography(key)- Reuse instance for batch operations (performance)
Real-World Examples ðŸŒ
Example 1: Razor Pages E-Commerce with Authorization
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// ✅ Setup JSON, Config, Database with Library.Common
builder.Services.AddDateOnlyTimeOnlyStringConverters();
builder.Services.AddDbContext<ShopDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddRazorPages();
var app = builder.Build();
app.MapRazorPages();
app.Run();
// ShopDbContext.cs
public class ShopDbContext : ServiceAuthorizationDBContext
{
public ShopDbContext(DbContextOptions<ShopDbContext> options) : base(options) { }
public DbSet<Product> Products { get; set; }
public DbSet<Order> Orders { get; set; }
}
// Pages/Login.cshtml.cs
public class LoginModel : PageModel
{
private readonly ShopDbContext _db;
public LoginModel(ShopDbContext db) => _db = db;
[BindProperty]
public string Email { get; set; }
[BindProperty]
public string Password { get; set; }
public async Task<IActionResult> OnPostAsync()
{
// ✅ MD5 password hashing
string hashedPassword = Password.ToMd5();
// ✅ Query using Library.Common entities
var user = await _db.Users
.FirstOrDefaultAsync(u => u.Email == Email && u.Password == hashedPassword);
if (user == null)
return Page();
// ✅ Get client IP for audit log
string ip = NetworkUtils.GetClientIPAddress(HttpContext);
// Create session, redirect to dashboard
HttpContext.Session.SetString("UserId", user.Id.ToString());
HttpContext.Session.SetString("LoginIP", ip);
return RedirectToPage("/Dashboard");
}
}
// Pages/Dashboard.cshtml.cs
public class DashboardModel : PageModel
{
private readonly ShopDbContext _db;
public DashboardModel(ShopDbContext db) => _db = db;
public List<string> UserRoles { get; set; }
public async Task OnGetAsync()
{
int userId = int.Parse(HttpContext.Session.GetString("UserId"));
// ✅ Get user roles across applications
UserRoles = await _db.ApplicationUserRoles
.Include(aur => aur.Role)
.Include(aur => aur.Application)
.Where(aur => aur.UserId == userId)
.Select(aur => $"{aur.Role.Name} in {aur.Application.Name}")
.ToListAsync();
}
}
Example 2: Web API with Encrypted Configuration
// appsettings.json
{
"PaymentGateway": {
"ApiKey": "eJw7sT4... (encrypted)",
"MerchantId": "12345",
"WebhookSecret": "v8fG3... (encrypted)"
}
}
// Models/PaymentSettings.cs
public class PaymentSettings
{
public string ApiKey { get; set; }
public string MerchantId { get; set; }
public string WebhookSecret { get; set; }
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// ✅ Load and decrypt config
var paymentSettings = builder.Configuration.GetEnvirementVariables<PaymentSettings>();
string decryptionKey = Environment.GetEnvironmentVariable("ENCRYPTION_KEY");
string apiKey = paymentSettings.ApiKey.Decrypt(decryptionKey);
string webhookSecret = paymentSettings.WebhookSecret.Decrypt(decryptionKey);
builder.Services.AddSingleton(new PaymentGatewayClient(apiKey, webhookSecret));
var app = builder.Build();
// API Endpoint
app.MapPost("/api/orders", async (OrderRequest request, PaymentGatewayClient gateway) =>
{
// Process payment with decrypted credentials
var result = await gateway.ProcessPaymentAsync(request.Amount, request.Currency);
return Results.Ok(result);
});
app.Run();
Example 3: Multi-Tenant SaaS with Application-Based Authorization
// Scenario: SaaS platform with multiple client applications
// Each client has its own set of users and roles
public class TenantService
{
private readonly ServiceAuthorizationDBContext _db;
public TenantService(ServiceAuthorizationDBContext db) => _db = db;
// Create new tenant (client company)
public async Task<Application> CreateTenantAsync(string name, string domain)
{
var tenant = new Application
{
Name = name,
Description = $"Tenant: {name}",
Ip = domain,
Port = 443,
TicketExpirationSecond = 3600 // 1-hour session
};
await _db.Applications.AddAsync(tenant);
await _db.SaveChangesAsync();
return tenant;
}
// Add user to tenant with role
public async Task AssignUserToTenantAsync(int userId, int tenantId, string roleName)
{
// Get or create role
var role = await _db.Roles.FirstOrDefaultAsync(r => r.Name == roleName);
if (role == null)
{
role = new Role { Name = roleName, Description = $"{roleName} access" };
await _db.Roles.AddAsync(role);
await _db.SaveChangesAsync();
}
// ✅ Assign user to tenant with role
var assignment = new ApplicationUserRole
{
UserId = userId,
RoleId = role.Id,
ApplicationId = tenantId
};
await _db.ApplicationUserRoles.AddAsync(assignment);
await _db.SaveChangesAsync();
}
// Check if user has permission in tenant
public async Task<bool> HasPermissionAsync(int userId, int tenantId, string requiredRole)
{
return await _db.ApplicationUserRoles
.Include(aur => aur.Role)
.AnyAsync(aur =>
aur.UserId == userId &&
aur.ApplicationId == tenantId &&
aur.Role.Name == requiredRole);
}
}
// Usage in API
app.MapGet("/api/tenants/{tenantId}/data", async (
int tenantId,
HttpContext context,
TenantService tenantService) =>
{
// ✅ Get user from session/JWT
int userId = int.Parse(context.User.FindFirst("UserId")?.Value ?? "0");
// ✅ Check permission
if (!await tenantService.HasPermissionAsync(userId, tenantId, "Admin"))
{
return Results.Forbid();
}
// Return tenant-specific data
return Results.Ok(new { TenantId = tenantId, Data = "Sensitive tenant data" });
});
Example 4: Scraping & Parsing HTML with String Extensions
using Library.Common.Utils.StringParser;
public class ProductScraperService
{
private readonly HttpClient _http;
public ProductScraperService(HttpClient http) => _http = http;
public async Task<List<Product>> ScrapeProductsAsync(string url)
{
string html = await _http.GetStringAsync(url);
// ✅ Define parsing rules
var rules = new List<FID>
{
new FID { Start = "<h2 class=\"title\">", End = "</h2>", Key = "name", WhiceIterator = 0 },
new FID { Start = "<span class=\"price\">$", End = "</span>", Key = "price", WhiceIterator = 0 },
new FID { Start = "<div class=\"stock\">", End = "</div>", Key = "stock", WhiceIterator = 0 }
};
// ✅ Extract all product blocks
List<string> productBlocks = html.BetweensTwoString("<div class=\"product\">", "</div>");
var products = new List<Product>();
foreach (var block in productBlocks)
{
// ✅ Parse each block
var data = block.ParseAsStrings(rules);
var prices = block.ParseAsNumerics(rules);
if (data.ContainsKey("name"))
{
products.Add(new Product
{
Name = data["name"],
Price = (decimal)prices["price"],
Stock = (int)prices["stock"]
});
}
}
return products;
}
}
API Reference
Extension Methods
IServiceCollection Extensions
// Add DateOnly/TimeOnly JSON support
builder.Services.AddDateOnlyTimeOnlyStringConverters();
String Extensions
string encrypted = "text".Encrypt("key");
string decrypted = encrypted.Decrypt("key");
string base64 = "text".ToBase64Encode();
string decoded = base64.ToBase64Decode();
string md5 = "text".ToMd5();
T obj = jsonString.ToObject<T>();
List<string> extracted = html.BetweensTwoString("<start>", "<end>");
string joined = stringList.AddComma();
Dictionary<string, string> parsed = text.ParseAsStrings(rules);
Dictionary<string, double> numbers = text.ParseAsNumerics(rules);
Enum Extensions
string desc = myEnum.GetDescription();
bool hasFlag = flags.Has(targetFlag);
bool isExact = myEnum.Is(value);
T newFlags = flags.Add(newFlag);
T newFlags = flags.Remove(flag);
IEnumerable<T> activeFlags = flags.GetFlags();
T parsed = EnumerationExtensions.ParseEnum<T>("value", defaultValue);
IConfiguration Extensions
T settings = configuration.GetEnvirementVariables<T>();
Utility Classes
EnvironmentHelper
IConfigurationRoot config = EnvironmentHelper.GetConfiguration();
IConfigurationSection section = EnvironmentHelper.GetSectionFromEnvirementVariables("SectionName");
T settings = EnvironmentHelper.GetConfiguration<T>("SectionName");
bool isDev = EnvironmentHelper.IsDeveleopment();
NetworkUtils
string ip = NetworkUtils.GetClientIPAddress(httpContext);
Cryptography
using var crypto = new Cryptography("key");
string encrypted = crypto.Encrypt("text");
string decrypted = crypto.Decrypt(encrypted);
ApplicationSettingsBase
// Prevent duplicate registrations
public class MySettings : ApplicationSettingsBase
{
public static void Register()
{
CheckIsRegistered("MySettings already registered!");
SetAsRegistered();
// Registration logic...
}
}
Entity Schema
┌─────────────┠┌─────────────────────────┠┌─────────────â”
│ User │ │ ApplicationUserRole │ │ Role │
├─────────────┤ ├─────────────────────────┤ ├─────────────┤
│ Id │◄─────┤ UserId (FK) │ │ Id │
│ Name │ │ RoleId (FK) ├─────►│ Name │
│ Email │ │ ApplicationId (FK) │ │ Description │
│ Password │ └─────────────────────────┘ └─────────────┘
│ AuthTypeId │ │
└─────────────┘ │
│ ▼
│ ┌─────────────â”
│ │ Application │
│ ├─────────────┤
│ │ Id │
▼ │ Name │
┌─────────────────┠│ Ip/Port │
│ AuthenticationType│ │ TicketExp │
├─────────────────┤ └─────────────┘
│ Id │
│ Name │
└─────────────────┘
Configuration Examples
appsettings.json with Encrypted Values
{
"Database": {
"ConnectionString": "eJw7sT4xMjEyNDAxMTAxMjEyNDAxMjEyNDAxMjEyNDAxMjEy..."
},
"EmailSettings": {
"SmtpHost": "smtp.gmail.com",
"SmtpPort": 587,
"Username": "noreply@example.com",
"Password": "eJw7sT4xMjEyNDAxMjEyNDAxMjEyNDAxMjEyNDAxMjEyNDAx..."
},
"JwtSettings": {
"SecretKey": "eJw7sT4xMjEyNDAxMjEyNDAxMjEyNDAxMjEyNDAxMjEyNDAx...",
"Issuer": "MyApp",
"Audience": "MyAppUsers",
"ExpirationMinutes": 60
}
}
Encryption Script:
// Encrypt sensitive values before deployment
string connectionString = "Server=localhost;Database=MyApp;...";
string encrypted = connectionString.Encrypt("ProductionKey2024");
Console.WriteLine(encrypted); // Copy to appsettings.json
// Decrypt at runtime
string decrypted = encrypted.Decrypt("ProductionKey2024");
var options = new DbContextOptionsBuilder()
.UseSqlServer(decrypted)
.Options;
FAQ
1. Why use Library.Common instead of writing my own utils?
- Time Savings: 10-20 hours of boilerplate per project
- Battle-Tested: Used in 18+ production CHD libraries
- Security: Production-grade encryption (AES-256 with PBKDF2)
- Updates: Get bug fixes and improvements via NuGet updates
- Consistency: Same APIs across all your projects
2. Is the encryption secure enough for production?
Yes, but with caveats:
- AES-256: Industry-standard symmetric encryption
- Rfc2898DeriveBytes: PBKDF2 key derivation with 2000 iterations
- âš ï¸ Hardcoded Salt: Consider environment-based salts for multi-tenant
- âš ï¸ SHA1: Default hash algorithm (update to SHA256 for FIPS compliance)
For production:
// Custom Cryptography with SHA256 and environment salt
var saltBytes = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("CRYPTO_SALT"));
var pdb = new Rfc2898DeriveBytes(key, saltBytes, 10000, HashAlgorithmName.SHA256);
3. Do I need all the entities (User/Role/Application)?
No! You can:
- Use only the entities you need
- Inherit
DbContext(notServiceAuthorizationDBContext) and cherry-pick entities - Create your own entities and use Library.Common only for utilities
4. How do I migrate existing User/Role tables?
// Map your existing columns to Library.Common entities
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Map to existing table
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("tbl_users"); // Your existing table
entity.Property(e => e.Name).HasColumnName("full_name");
entity.Property(e => e.Password).HasColumnName("password_hash");
});
}
5. Can I use System.Text.Json instead of Newtonsoft.Json for ToObject()?
Currently, ToObject<T>() uses Newtonsoft.Json. For System.Text.Json:
using System.Text.Json;
var obj = JsonSerializer.Deserialize<T>(jsonString);
We may add System.Text.Json overloads in future versions.
6. Is ParseAsStrings() safe for untrusted input?
Partially:
- ✅ No SQL injection (doesn't touch database)
- ✅ No code execution (pure string manipulation)
- âš ï¸ DOS Risk: Complex rules on huge strings can cause performance issues
- âš ï¸ XSS Risk: Always HTML-encode parsed values before rendering
Best Practice:
// Validate input size
if (untrustedHtml.Length > 1_000_000)
throw new InvalidOperationException("Input too large");
var parsed = untrustedHtml.ParseAsStrings(rules);
// Encode before rendering
string safe = HtmlEncoder.Default.Encode(parsed["content"]);
7. How do I update from Library.Common 8.x to 9.x?
Library.Common follows .NET versioning:
- 8.x: Targets .NET 8
- 9.x: Will target .NET 9 (when released)
Check Releases for breaking changes.
8. Why is GetClientIPAddress() returning null?
Common causes:
- Localhost testing: May return
::1(IPv6 loopback) - Missing headers: Load balancer not forwarding
X-Forwarded-For - VPN/Proxy: Client IP masked by proxy
Debug:
string ip = NetworkUtils.GetClientIPAddress(HttpContext);
if (string.IsNullOrEmpty(ip))
{
// Fallback to connection feature
ip = HttpContext.Connection.RemoteIpAddress?.ToString() ?? "Unknown";
}
Troubleshooting
Issue: DateOnly still serializes as object instead of string
Cause: AddDateOnlyTimeOnlyStringConverters() not called before AddControllers()
Fix:
// ✅ Correct order
builder.Services.AddDateOnlyTimeOnlyStringConverters();
builder.Services.AddControllers();
// ⌠Wrong order
builder.Services.AddControllers();
builder.Services.AddDateOnlyTimeOnlyStringConverters(); // Too late!
Issue: ToMd5() returns different hash than online tools
Cause: Library.Common returns uppercase hex, some tools use lowercase
Fix:
string hash = "test".ToMd5().ToLower(); // Match lowercase tools
Issue: Decryption fails with "Padding is invalid and cannot be removed"
Cause:
- Wrong decryption key
- Encrypted text corrupted (spaces replaced with
+in URLs)
Fix:
// Library.Common automatically handles + replacement
string decrypted = encrypted.Replace(" ", "+").Decrypt(key);
Issue: GetEnvirementVariables<T>() prints error but doesn't throw
Cause: Design choice - prints missing config to console instead of crashing
Fix:
var settings = config.GetEnvirementVariables<EmailSettings>();
if (settings == null)
{
throw new InvalidOperationException("EmailSettings missing in appsettings.json");
}
Performance Benchmarks
| Operation | Without Library.Common | With Library.Common | Speedup |
|---|---|---|---|
| DateOnly JSON Serialization | Manual converter (50 lines) | .AddDateOnlyTimeOnlyStringConverters() |
50x less code |
| String Encryption (AES) | Manual AES setup | .Encrypt(key) |
30x less code |
| Configuration Loading | Manual try-catch | .GetEnvirementVariables<T>() |
10x less code |
| Client IP Detection | Manual header parsing | GetClientIPAddress() |
20x less code |
| Enum Description | Reflection boilerplate | .GetDescription() |
15x less code |
Memory Usage:
- Cryptography class: ~8 KB per instance (reusable)
- Extension methods: Zero overhead (static)
- Entity Framework entities: Standard EF Core overhead
Best Practices
- ✅ Use
AddDateOnlyTimeOnlyStringConverters()early inProgram.cs - ✅ Encrypt sensitive config values before deployment
- ✅ Dispose
Cryptographyinstances after use - ✅ Validate
GetClientIPAddress()result (can be null) - ✅ Use strongly-typed config with
GetEnvirementVariables<T>() - ✅ Add enum descriptions for user-facing text
- âš ï¸ Don't parse untrusted HTML without validation
- âš ï¸ Don't use MD5 for passwords (use ASP.NET Core Identity instead)
Package Dependencies
| Package | Version | Purpose |
|---|---|---|
| Microsoft.EntityFrameworkCore | 8.0.1 | Entity Framework Core runtime |
| Microsoft.EntityFrameworkCore.Relational | 8.0.1 | Relational database provider support |
| Microsoft.Extensions.Configuration | 8.0.0 | Configuration abstractions |
| Microsoft.Extensions.Configuration.Json | 8.0.0 | JSON configuration provider |
| Microsoft.Extensions.Configuration.EnvironmentVariables | 8.0.0 | Environment variable support |
| Microsoft.Extensions.Hosting.Abstractions | 8.0.0 | Hosting abstractions |
| Microsoft.AspNetCore.Http | 2.2.2 | HTTP abstractions |
| Microsoft.AspNetCore.Http.Abstractions | 2.2.0 | HTTP context abstractions |
| Microsoft.AspNetCore.Http.Features | 5.0.17 | HTTP features (IP detection) |
| Newtonsoft.Json | 13.0.3 | JSON serialization (ToObject) |
| Swashbuckle.AspNetCore.SwaggerGen | 6.5.0 | Swagger generation support |
| System.Text.Json | 8.0.6 | Modern JSON serialization |
Contributing
Found a bug? Have a feature request?
- Issues: GitHub Issues
- Pull Requests: GitHub PRs
- Discussions: GitHub Discussions
License
This package is free and open-source under the MIT License.
Related CHD Packages
| Package | Description | NPM/NuGet |
|---|---|---|
| Chd.Min.IO | MinIO/S3 object storage with auto image optimization | NuGet |
| Library.Common | Infrastructure primitives (this package) | NuGet |
| (18+ more packages) | Coming soon... | - |
Support
If Library.Common saves you time, consider:
- â Star the repo on GitHub
- 📢 Share with your team
- 🛠Report bugs to help us improve
- 💬 Join discussions for feature requests
Authors
- Mehmet YoldaÅŸ (LinkedIn)
See also contributors on NuGet
🎉 Acknowledgements
Thank you for using my library.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. 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. |
-
net8.0
- Microsoft.AspNetCore.Http (>= 2.2.2)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.AspNetCore.Http.Features (>= 5.0.17)
- Microsoft.EntityFrameworkCore (>= 8.0.1)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.1)
- Microsoft.Extensions.Configuration (>= 8.0.0)
- Microsoft.Extensions.Configuration.EnvironmentVariables (>= 8.0.0)
- Microsoft.Extensions.Configuration.FileExtensions (>= 8.0.0)
- Microsoft.Extensions.Configuration.Json (>= 8.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
- Microsoft.OpenApi (>= 1.6.13)
- Newtonsoft.Json (>= 13.0.3)
- Swashbuckle.AspNetCore.SwaggerGen (>= 6.5.0)
- System.Text.Json (>= 8.0.6)
NuGet packages (9)
Showing the top 5 NuGet packages that depend on Chd.Library.Common:
| Package | Downloads |
|---|---|
|
Chd.Library.Logging
Chd (Cleverly Handle Difficulty) packages are easy to use. This package contains logging helpers and Serilog integrations for structured, configurable logging with sinks for Graylog, MSSQL and file-based logging. |
|
|
Chd.Library.Caching
Chd (Cleverly Handle Difficulty) packages are easy to use. This package contains distributed caching(Redis) helpers and attributes for easy cache integration across services. |
|
|
Chd.Library.Security
Chd (Cleverly Handle Difficulty) packages are easy to use. This package contains security helpers and authentication/authorization utilities for web APIs and services. |
|
|
Chd.Library.NoSQL
Chd (Cleverly Handle Difficulty) packages are easy to use. This package contains utilities and abstractions for working with MongoDB databases and document stores. |
|
|
Chd.Library.MQ
Chd (Cleverly Handle Difficulty) packages are easy to use. This package contains RabbitMQ message queue abstractions and helpers for pub/sub patterns and reliable messaging in distributed systems. |
GitHub repositories
This package is not used by any popular GitHub repositories.