SmartWorkz.Shared
1.0.1
dotnet add package SmartWorkz.Shared --version 1.0.1
NuGet\Install-Package SmartWorkz.Shared -Version 1.0.1
<PackageReference Include="SmartWorkz.Shared" Version="1.0.1" />
<PackageVersion Include="SmartWorkz.Shared" Version="1.0.1" />
<PackageReference Include="SmartWorkz.Shared" />
paket add SmartWorkz.Shared --version 1.0.1
#r "nuget: SmartWorkz.Shared, 1.0.1"
#:package SmartWorkz.Shared@1.0.1
#addin nuget:?package=SmartWorkz.Shared&version=1.0.1
#tool nuget:?package=SmartWorkz.Shared&version=1.0.1
SmartWorkz.Shared Library
Overview
SmartWorkz.Shared is a comprehensive utility library designed to streamline common development tasks across multiple projects and applications. It provides a collection of robust, performance-optimized components for various integration scenarios.
Library Components
The SmartWorkz.Shared library includes several specialized modules:
API Integration
- ApiClient: High-performance HTTP client for RESTful API integration
- ApiClientFactory: Support for working with multiple API endpoints
- OAuth Helpers: Simplified OAuth authentication flows
Data Access
- MongoDb: Simplified MongoDB access and query capabilities
- ADO.NET: Enhanced data access with connection pooling and retry logic
- Entity Framework: Additional extensions and utilities
File Transfer
- FTP Client: Modern FTP/FTPS implementation with async support
- SFTP Client: Secure file transfer over SSH
- File Processing: Utilities for batch processing and transformations
Messaging
- Email Service: SMTP email sending with templates and attachments
- SMS Service: Text message integration with major providers
- Push Notifications: Mobile push notification support
Common Utilities
- Encryption: Secure data protection and hashing
- Logging: Consistent logging abstractions
- Caching: Multi-level caching framework
- Configuration: Enhanced config management
- Serialization: JSON, XML, and binary serialization helpers
Installation
Package Manager Console
Install-Package SmartWorkz.Shared
.NET CLI
dotnet add package SmartWorkz.Shared
Individual Components
You can also install specific components separately:
Install-Package SmartWorkz.Shared.ApiClient
Install-Package SmartWorkz.Shared.Data.MongoDB
Install-Package SmartWorkz.Shared.FileTransfer
Install-Package SmartWorkz.Shared.Messaging
Table of Contents
Enhanced ConfigHelper Documentation
Overview
The ConfigHelper
class provides a robust solution for accessing configuration values in .NET applications. It features built-in caching with automatic invalidation on configuration changes, support for custom expiration times, and strongly-typed access with fallback defaults.
Features
- Strongly-typed configuration access with default fallback values
- Intelligent caching for optimal performance
- Automatic cache invalidation when configuration changes
- Custom expiration times for cached values
- Thread-safe implementation for concurrent access
- Manual cache management options
- Diagnostic tools for monitoring cache size
Installation
The ConfigHelper is part of the SmartWorkz.Shared.Core namespace. To use it in your project:
dotnet add package SmartWorkz.Shared
Basic Usage
using Microsoft.Extensions.Configuration;
using SmartWorkz.Shared.Core;
// Standard usage with automatic caching
string apiUrl = ConfigHelper.Get<string>(configuration, "Api:BaseUrl", "https://default.com");
// Access with custom expiration time
int timeout = ConfigHelper.GetWithExpiration<int>(
configuration,
"Api:TimeoutSeconds",
30,
TimeSpan.FromMinutes(10)
);
// Boolean values
bool enableCache = ConfigHelper.Get<bool>(configuration, "Cache:Enabled", true);
// Date and time
DateTime expiryDate = ConfigHelper.Get<DateTime>(
configuration,
"License:ExpirationDate",
DateTime.UtcNow.AddYears(1)
);
Caching Behavior
How Caching Works
- When requesting a configuration value, ConfigHelper first checks its internal cache
- If the value exists in cache and hasn't expired, it returns the cached value
- If the value isn't cached or has expired, it reads from the configuration provider
- The new value is then cached (with optional expiration) for future requests
- When configuration changes are detected, the cache is automatically cleared
Cache Keys
Cache keys are constructed using both the configuration key and the requested type:
"{configKey}:{typeName}"
This allows different types to be cached separately for the same configuration key.
Advanced Features
Custom Cache Expiration
You can specify custom expiration times for frequently changing values:
// Cache for 5 minutes
var quota = ConfigHelper.GetWithExpiration<int>(
configuration,
"Api:RemainingQuota",
1000,
TimeSpan.FromMinutes(5)
);
// Cache for 1 hour
var connectionString = ConfigHelper.GetWithExpiration<string>(
configuration,
"Database:ConnectionString",
"Server=localhost;Database=master;Trusted_Connection=True;",
TimeSpan.FromHours(1)
);
// Cache until midnight
var expiryTime = DateTime.Today.AddDays(1) - DateTime.Now;
var dailyLimit = ConfigHelper.GetWithExpiration<int>(
configuration,
"Api:DailyLimit",
10000,
expiryTime
);
Automatic Reload Support
The ConfigHelper automatically registers for configuration change notifications and clears the cache when changes are detected:
// In Program.cs
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
// When appsettings.json changes, ConfigHelper will automatically clear its cache
Manual Cache Management
You can manually manage the cache when needed:
// Clear the entire cache
ConfigHelper.ClearCache();
// Remove specific section from cache
ConfigHelper.RemoveFromCache("Database");
// Clean up expired entries only
ConfigHelper.CleanExpiredEntries();
// Check current cache size
int cacheSize = ConfigHelper.CacheCount;
Console.WriteLine($"Cache contains {cacheSize} items");
Best Practices
Configuration Key Organization
Group related settings with consistent key prefixes:
// Database settings
var connectionString = ConfigHelper.Get<string>(configuration, "Database:ConnectionString", "default");
var commandTimeout = ConfigHelper.Get<int>(configuration, "Database:CommandTimeout", 30);
// API settings
var apiKey = ConfigHelper.Get<string>(configuration, "Api:Key", "");
var apiBaseUrl = ConfigHelper.Get<string>(configuration, "Api:BaseUrl", "https://api.default.com");
Expiration Strategies
Choose appropriate expiration times based on the nature of the configuration:
- Static configuration: No expiration needed (use standard
Get<T>
method) - Semi-dynamic configuration: Longer expiration (hours/days)
- Frequently changing values: Short expiration (minutes)
- Real-time values: Very short expiration (seconds) or no caching
Memory Management
For applications with many configuration values:
- Periodically call
CleanExpiredEntries()
to remove expired cache entries - Monitor the cache size with
CacheCount
property - Consider adding a background job to clean the cache regularly
Common Scenarios
Working with Connection Strings
var connectionString = ConfigHelper.Get<string>(
configuration,
"ConnectionStrings:DefaultConnection",
"Server=localhost;Database=master;Trusted_Connection=True;"
);
Application Settings
var appName = ConfigHelper.Get<string>(configuration, "AppSettings:Name", "Default App");
var maxUsers = ConfigHelper.Get<int>(configuration, "AppSettings:MaxUsers", 100);
var isProduction = ConfigHelper.Get<bool>(configuration, "AppSettings:IsProduction", false);
Feature Flags
bool isFeatureEnabled = ConfigHelper.GetWithExpiration<bool>(
configuration,
"Features:NewReporting",
false,
TimeSpan.FromMinutes(5) // Check for updates every 5 minutes
);
if (isFeatureEnabled)
{
// Implement new feature
}
API Settings with Fallbacks
var settings = new ApiSettings
{
BaseUrl = ConfigHelper.Get<string>(configuration, "Api:BaseUrl", "https://api.default.com"),
Timeout = ConfigHelper.Get<int>(configuration, "Api:TimeoutSeconds", 30),
RetryCount = ConfigHelper.Get<int>(configuration, "Api:RetryCount", 3),
ApiKey = ConfigHelper.Get<string>(configuration, "Api:Key", "")
};
Performance Tuning
Monitoring and Diagnostics
public class ConfigMonitor
{
private readonly ILogger<ConfigMonitor> _logger;
public ConfigMonitor(ILogger<ConfigMonitor> logger)
{
_logger = logger;
}
public void LogCacheStatistics()
{
int cacheCount = ConfigHelper.CacheCount;
_logger.LogInformation("Configuration cache contains {Count} items", cacheCount);
// Clean expired entries
ConfigHelper.CleanExpiredEntries();
int newCount = ConfigHelper.CacheCount;
if (newCount < cacheCount)
{
_logger.LogInformation("Removed {Count} expired cache entries", cacheCount - newCount);
}
}
}
Strategic Cache Expiration
For optimal performance, consider these cache duration strategies:
- High-volume, static config: No expiration (default)
- Low-volume, dynamic config: Short expiration (1-5 minutes)
- Security-related config: Shorter expiration (1-2 minutes)
- Resource limits/quotas: Very short expiration (30-60 seconds)
Error Handling
The ConfigHelper is designed to handle errors gracefully:
- If the configuration key doesn't exist, it returns the default value
- If conversion fails, it returns the default value
- If an exception occurs during retrieval, it returns the default value
This means you can safely use ConfigHelper without extensive try/catch blocks.
Complete Implementation
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace SmartWorkz.Shared.Core
{
/// <summary>
/// Provides strongly-typed access to configuration values with default fallbacks, caching, and cache expiration
/// </summary>
public static class ConfigHelper
{
// Internal cache entry class to track expiration
private class CacheEntry
{
public object Value { get; set; }
public DateTime? ExpiresAt { get; set; }
public bool HasExpired => ExpiresAt.HasValue && DateTime.UtcNow > ExpiresAt.Value;
}
// Thread-safe cache for configuration values
private static readonly ConcurrentDictionary<string, CacheEntry> _cache = new ConcurrentDictionary<string, CacheEntry>();
// Lock for change token registration
private static readonly object _registrationLock = new object();
// Track if we've registered for configuration change notifications
private static bool _changeTokenRegistered = false;
/// <summary>
/// Gets a typed configuration value with fallback and caching
/// </summary>
public static T Get<T>(IConfiguration config, string key, T defaultValue)
{
return GetWithExpiration<T>(config, key, defaultValue, null);
}
/// <summary>
/// Gets a typed configuration value with fallback, caching, and custom expiration
/// </summary>
public static T GetWithExpiration<T>(IConfiguration config, string key, T defaultValue, TimeSpan? expiration)
{
if (config == null)
throw new ArgumentNullException(nameof(config));
if (string.IsNullOrEmpty(key))
throw new ArgumentNullException(nameof(key));
// Register for configuration changes if not already done
RegisterForConfigChanges(config);
// Generate a cache key that includes the type information
string cacheKey = $"{key}:{typeof(T).FullName}";
// Check cache for valid entry
if (_cache.TryGetValue(cacheKey, out var entry) && !entry.HasExpired)
{
return (T)entry.Value;
}
// Not in cache or expired, get from configuration
return GetAndCacheValue<T>(config, key, defaultValue, cacheKey, expiration);
}
/// <summary>
/// Gets a value from configuration and caches it
/// </summary>
private static T GetAndCacheValue<T>(IConfiguration config, string key, T defaultValue, string cacheKey, TimeSpan? expiration)
{
string value = config[key];
if (string.IsNullOrEmpty(value)) return defaultValue;
try
{
Type type = typeof(T);
T result;
switch (Type.GetTypeCode(type))
{
case TypeCode.String:
result = (T)(object)value;
break;
case TypeCode.Int32:
result = int.TryParse(value, out var i) ? (T)(object)i : defaultValue;
break;
case TypeCode.Boolean:
result = bool.TryParse(value, out var b) ? (T)(object)b : defaultValue;
break;
case TypeCode.Double:
result = double.TryParse(value, out var d) ? (T)(object)d : defaultValue;
break;
case TypeCode.Decimal:
result = decimal.TryParse(value, out var m) ? (T)(object)m : defaultValue;
break;
case TypeCode.Int64:
result = long.TryParse(value, out var l) ? (T)(object)l : defaultValue;
break;
case TypeCode.DateTime:
result = DateTime.TryParse(value, out var dt) ? (T)(object)dt : defaultValue;
break;
default:
if (type == typeof(Guid))
result = Guid.TryParse(value, out var g) ? (T)(object)g : defaultValue;
else if (type == typeof(TimeSpan))
result = TimeSpan.TryParse(value, out var ts) ? (T)(object)ts : defaultValue;
else
result = (T)Convert.ChangeType(value, type);
break;
}
// Calculate expiration if provided
DateTime? expiresAt = expiration.HasValue
? DateTime.UtcNow.Add(expiration.Value)
: null;
// Cache the result
_cache[cacheKey] = new CacheEntry { Value = result, ExpiresAt = expiresAt };
return result;
}
catch
{
return defaultValue;
}
}
/// <summary>
/// Register for configuration change notifications
/// </summary>
private static void RegisterForConfigChanges(IConfiguration config)
{
if (_changeTokenRegistered) return;
lock (_registrationLock)
{
if (_changeTokenRegistered) return;
if (config is IConfigurationRoot configRoot)
{
var token = configRoot.GetReloadToken();
token.RegisterChangeCallback(OnConfigChanged, configRoot);
_changeTokenRegistered = true;
}
}
}
/// <summary>
/// Handle configuration change events
/// </summary>
private static void OnConfigChanged(object state)
{
// Clear all cache entries
_cache.Clear();
// Re-register for the next change
if (state is IConfigurationRoot configRoot)
{
_changeTokenRegistered = false;
RegisterForConfigChanges(configRoot);
}
}
/// <summary>
/// Clears the entire configuration cache
/// </summary>
public static void ClearCache()
{
_cache.Clear();
}
/// <summary>
/// Removes cache entries for a specific configuration key
/// </summary>
public static void RemoveFromCache(string key)
{
if (string.IsNullOrEmpty(key)) return;
// Remove all entries that start with this key
foreach (var cacheKey in _cache.Keys)
{
if (cacheKey.StartsWith(key + ":"))
{
_cache.TryRemove(cacheKey, out _);
}
}
}
/// <summary>
/// Removes expired entries from the cache
/// </summary>
public static void CleanExpiredEntries()
{
foreach (var key in _cache.Keys)
{
if (_cache.TryGetValue(key, out var entry) && entry.HasExpired)
{
_cache.TryRemove(key, out _);
}
}
}
/// <summary>
/// Gets the number of items in the cache
/// </summary>
public static int CacheCount => _cache.Count;
}
}
License
This component is part of the SmartWorkz.Shared library and is licensed under MIT.
Email Service Documentation
Overview
The EmailService
component provides a robust solution for sending emails with attachments from .NET applications. It's designed to be configurable, simple to use, and includes support for various attachment types, CC/BCC recipients, and HTML content.
Features
- Send emails with plain text or HTML content
- Support for multiple attachment types (files, streams, or byte arrays)
- CC and BCC recipient functionality
- Both synchronous and asynchronous sending methods
- Configuration through appsettings.json with sensible defaults
- Comprehensive logging
- Automatic MIME type detection for attachments
- Strongly typed configuration with fallbacks
Installation
The EmailService is part of the SmartWorkz.Shared library. To use it in your project:
dotnet add package SmartWorkz.Shared
Configuration
Configure the email service in your appsettings.json file:
{
"Email": {
"SmtpServer": "smtp.office365.com",
"SmtpPort": 587,
"EnableSsl": true,
"Username": "your-email@example.com",
"Password": "your-password",
"DefaultSender": "noreply@example.com",
"DefaultSenderName": "System Notification",
"UseDefaultCredentials": false,
"Timeout": 100000
}
}
Configuration Properties
Property | Description | Default |
---|---|---|
SmtpServer | SMTP server address | smtp.office365.com |
SmtpPort | SMTP server port | 587 |
EnableSsl | Whether to use SSL for connection | true |
Username | Username for SMTP authentication | null |
Password | Password for SMTP authentication | null |
DefaultSender | Default sender email address | noreply@example.com |
DefaultSenderName | Default sender display name | System Notification |
UseDefaultCredentials | Use default credentials for authentication | false |
Timeout | Timeout for SMTP operations (milliseconds) | 100000 |
Registration with Dependency Injection
Add the email service to your dependency injection container:
// In Program.cs or Startup.cs
services.AddSingleton<EmailService>();
Basic Usage
Sending a Simple Email
// Using dependency injection
public class NotificationService
{
private readonly EmailService _emailService;
public NotificationService(EmailService emailService)
{
_emailService = emailService;
}
public void SendNotification(string recipient, string subject, string message)
{
_emailService.SendEmail(
to: recipient,
subject: subject,
body: message,
isHtml: true
);
}
}
Creating and Using Manually
// Create with configuration
var emailService = new EmailService(configuration);
// Send a simple email
emailService.SendEmail(
to: "recipient@example.com",
subject: "Test Email",
body: "<p>This is a test email</p>",
isHtml: true
);
Working with Attachments
The EmailAttachment
class provides several factory methods to create attachments from different sources:
From File
var attachment = EmailAttachment.FromFile("C:\\path\\to\\document.pdf");
From Stream
using var stream = new FileStream("document.pdf", FileMode.Open);
var attachment = EmailAttachment.FromStream(stream, "document.pdf");
From Byte Array
byte[] pdfData = GetPdfData();
var attachment = EmailAttachment.FromBytes(pdfData, "report.pdf");
Sending Email with Attachments
var attachments = new List<EmailAttachment>
{
EmailAttachment.FromFile("C:\\path\\to\\document.pdf"),
EmailAttachment.FromBytes(pdfBytes, "report.pdf", "application/pdf")
};
await emailService.SendEmailAsync(
to: "recipient@example.com",
subject: "Report Attached",
body: "<p>Please find the attached report.</p>",
isHtml: true,
attachments: attachments,
cc: new List<string> { "manager@example.com" }
);
Advanced Usage
Multiple Recipients
You can specify multiple recipients by separating email addresses with commas or semicolons:
emailService.SendEmail(
to: "recipient1@example.com; recipient2@example.com",
subject: "Team Notification",
body: "This is a team-wide notification"
);
CC and BCC Recipients
emailService.SendEmail(
to: "recipient@example.com",
subject: "Meeting Invitation",
body: "You're invited to a meeting",
cc: new List<string> { "manager@example.com" },
bcc: new List<string> { "records@example.com" }
);
Overriding the Default Sender
emailService.SendEmail(
to: "recipient@example.com",
subject: "Custom Sender",
body: "This is a message with a custom sender",
from: "custom@example.com",
fromName: "Custom Department"
);
Asynchronous Sending
await emailService.SendEmailAsync(
to: "recipient@example.com",
subject: "Async Email",
body: "This email was sent asynchronously"
);
Error Handling
The send methods return a boolean indicating success or failure:
bool success = emailService.SendEmail(
to: "recipient@example.com",
subject: "Test Email",
body: "This is a test email"
);
if (!success)
{
// Handle sending failure
logger.LogWarning("Failed to send email to recipient@example.com");
}
Best Practices
- Security: Store credentials securely using secrets management or environment variables.
- Attachments: Dispose of attachment streams properly when needed.
- Templating: Consider using a templating engine for creating HTML emails.
- Batching: For large recipient lists, consider batching emails to avoid throttling.
- Retry Logic: Implement retry logic for transient failures.
Troubleshooting
Common issues and solutions:
- Authentication failures: Verify credentials and check if your email provider requires app passwords.
- Timeout errors: Increase the timeout value in configuration for large attachments.
- Emails marked as spam: Make sure sender domain has proper SPF, DKIM, and DMARC records.
- Attachment issues: Check if attachment streams are positioned correctly and not disposed.
License
This component is part of the SmartWorkz.Shared library and is licensed under MIT.
EncryptionHelper Documentation
Overview
The EncryptionHelper
is a robust security component that provides symmetric encryption, decryption, and secure password generation capabilities for .NET applications. It's designed to be configurable, easy to use, and follows cryptographic best practices.
Features
- AES-256 encryption and decryption
- Secure password generation with customizable requirements
- Configuration through appsettings.json with sensible defaults
- Support for fixed or random initialization vectors (IV)
- Hash generation and verification
- Thread-safe implementation
- Asynchronous methods for better performance
- Comprehensive logging
Installation
The EncryptionHelper is part of the SmartWorkz.Shared library. To use it in your project:
dotnet add package SmartWorkz.Shared
Configuration
Configure the encryption service in your appsettings.json file:
{
"Encryption": {
"Key": "YourSecretEncryptionKey!",
"UseFixedIV": true,
"KeySize": 256,
"IV": "Base64EncodedIVString=="
},
"Password": {
"Length": 8,
"MinUppercase": 1,
"MinLowercase": 3,
"MinSpecial": 2,
"MinDigits": 2,
"UppercaseChars": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"LowercaseChars": "abcdefghijklmnopqrstuvwxyz",
"SpecialChars": "!@#$%^&*?/",
"Digits": "0123456789"
}
}
Configuration Properties
Encryption Settings
Property | Description | Default |
---|---|---|
Key | Secret key used for encryption/decryption | "KrishnayanAstro!@#AZ" |
UseFixedIV | Whether to use a fixed IV (less secure but compatible) | true |
KeySize | Size of the encryption key in bits (128, 192, or 256) | 256 |
IV | Base64-encoded initialization vector | null (zeros) |
Password Generation Settings
Property | Description | Default |
---|---|---|
Length | Length of generated passwords | 8 |
MinUppercase | Minimum number of uppercase characters | 1 |
MinLowercase | Minimum number of lowercase characters | 3 |
MinSpecial | Minimum number of special characters | 2 |
MinDigits | Minimum number of digits | 2 |
UppercaseChars | Characters to use for uppercase letters | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
LowercaseChars | Characters to use for lowercase letters | "abcdefghijklmnopqrstuvwxyz" |
SpecialChars | Characters to use for special characters | "!@#$%^&*?/" |
Digits | Characters to use for digits | "0123456789" |
Registration with Dependency Injection
Add the encryption helper to your dependency injection container:
// In Program.cs or Startup.cs
services.AddSingleton<EncryptionHelper>();
Basic Usage
Encrypting and Decrypting Data
// Using dependency injection
public class UserService
{
private readonly EncryptionHelper _encryptionHelper;
public UserService(EncryptionHelper encryptionHelper)
{
_encryptionHelper = encryptionHelper;
}
public string SecureUserData(string sensitiveData)
{
return _encryptionHelper.Encrypt(sensitiveData);
}
public string RetrieveUserData(string encryptedData)
{
return _encryptionHelper.Decrypt(encryptedData);
}
}
Creating and Using Manually
// Create with configuration
var encryptionHelper = new EncryptionHelper(configuration);
// Encrypt data
string encrypted = encryptionHelper.Encrypt("Sensitive information");
Console.WriteLine($"Encrypted: {encrypted}");
// Decrypt data
string decrypted = encryptionHelper.Decrypt(encrypted);
Console.WriteLine($"Decrypted: {decrypted}");
Password Generation
Generating Passwords with Default Settings
var encryptionHelper = new EncryptionHelper(configuration);
// Generate password with configured settings
string password = encryptionHelper.GeneratePassword();
Console.WriteLine($"Generated password: {password}");
Generating Passwords with Custom Length
// Generate a 12-character password
string password = encryptionHelper.GeneratePassword(12);
Hash Generation and Verification
Creating and Verifying Hashes
// Hash a password with a user-specific salt (e.g., user ID)
string userId = "user123";
string passwordHash = encryptionHelper.GenerateHash("mySecurePassword", userId);
// Later, verify the password
bool isValid = encryptionHelper.VerifyHash("mySecurePassword", passwordHash, userId);
Advanced Usage
Asynchronous Encryption and Decryption
// Encrypt data asynchronously
string encrypted = await encryptionHelper.EncryptAsync("Sensitive information");
// Decrypt data asynchronously
string decrypted = await encryptionHelper.DecryptAsync(encrypted);
Using a Different Encryption Key
// Option 1: Configure in appsettings.json
// "Encryption": { "Key": "YourCustomKey" }
// Option 2: Create with custom settings
var options = new EncryptionOptions
{
Key = "YourCustomKey",
UseFixedIV = false, // More secure - uses random IV
KeySize = 256
};
var encryptionHelper = new EncryptionHelper(options, new PasswordGenerationOptions());
Custom Password Generation
// Create custom password options
var passwordOptions = new PasswordGenerationOptions
{
Length = 16,
MinUppercase = 2,
MinLowercase = 8,
MinSpecial = 3,
MinDigits = 3,
SpecialChars = "@#$%&*!"
};
var encryptionHelper = new EncryptionHelper(new EncryptionOptions(), passwordOptions);
string strongPassword = encryptionHelper.GeneratePassword();
Security Considerations
Key Management
For production deployments, never hardcode encryption keys or store them in source control. Instead:
Azure Key Vault: Store keys in Azure Key Vault and access them securely
var secretClient = new SecretClient(new Uri("https://your-key-vault.vault.azure.net/"), new DefaultAzureCredential()); KeyVaultSecret secret = await secretClient.GetSecretAsync("EncryptionKey"); string key = secret.Value;
Environment Variables: Use environment variables on your server
string key = Environment.GetEnvironmentVariable("ENCRYPTION_KEY");
User Secrets: During development, use user secrets
{ "Encryption": { "Key": "development-only-key" } }
Advanced Security Options
For highest security, consider these options:
- Disable fixed IV: Set
UseFixedIV
tofalse
to use random IVs - Use PBKDF2: Enable key derivation with PBKDF2 for stronger keys
{ "Encryption": { "UsePbkdf2": true, "Salt": "YourSaltValue", "Iterations": 10000 } }
Performance Considerations
- The
EncryptionHelper
caches derived keys for better performance - For bulk operations, reuse the same
EncryptionHelper
instance - Asynchronous methods (
EncryptAsync
/DecryptAsync
) are recommended for I/O-bound operations - Consider using a memory cache for frequently encrypted/decrypted values
Best Practices
- Secure Configuration: Store encryption keys securely using secrets management
- Error Handling: Always handle encryption/decryption exceptions gracefully
- Key Rotation: Implement a strategy for rotating encryption keys periodically
- Testing: Thoroughly test encryption/decryption with various input types
- Logging: Enable logging but be careful not to log sensitive information
Troubleshooting
Common issues and solutions:
- Decryption failures: Ensure you're using the same key and IV that were used for encryption
- Invalid padding: Usually indicates the encrypted text was modified or corrupted
- Performance issues: Make sure you're reusing the EncryptionHelper instance rather than creating new ones
Complete Implementation Example
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
public class Program
{
public static void Main(string[] args)
{
// Setup configuration
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddUserSecrets<Program>()
.AddEnvironmentVariables()
.Build();
// Setup DI
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(configuration);
services.AddSingleton<EncryptionHelper>();
var serviceProvider = services.BuildServiceProvider();
// Use the encryption helper
var encryptionHelper = serviceProvider.GetRequiredService<EncryptionHelper>();
// Test functionality
TestEncryption(encryptionHelper);
TestPasswordGeneration(encryptionHelper);
}
private static void TestEncryption(EncryptionHelper encryptionHelper)
{
string original = "Sensitive data to encrypt";
string encrypted = encryptionHelper.Encrypt(original);
string decrypted = encryptionHelper.Decrypt(encrypted);
Console.WriteLine($"Original: {original}");
Console.WriteLine($"Encrypted: {encrypted}");
Console.WriteLine($"Decrypted: {decrypted}");
Console.WriteLine($"Matches: {original == decrypted}");
}
private static void TestPasswordGeneration(EncryptionHelper encryptionHelper)
{
string password = encryptionHelper.GeneratePassword();
Console.WriteLine($"Generated password: {password}");
string password12 = encryptionHelper.GeneratePassword(12);
Console.WriteLine($"12-char password: {password12}");
}
}
License
This component is part of the SmartWorkz.Shared library and is licensed under MIT.
GenericApiClient
The GenericApiClient
is the core class for making HTTP requests to RESTful APIs. It handles authentication, serialization, caching, and error handling.
Constructor
The GenericApiClient
can be instantiated in two ways:
// Using IOptions pattern (recommended for DI)
public GenericApiClient(IOptions<ApiClientSettings> settings, IMultiLevelCache cache = null)
// Using direct settings
public GenericApiClient(ApiClientSettings settings, IMultiLevelCache cache = null)
Basic Usage
GET Requests
// Simple GET request
var weather = await _apiClient.GetAsync<WeatherForecast>("/weather/forecast");
// GET with query parameters
var searchResults = await _apiClient.GetAsync<SearchResults>(
"/search",
new Dictionary<string, string> { ["query"] = "test", ["page"] = "1" }
);
// GET with cancellation token
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var timeBoxedResult = await _apiClient.GetAsync<ApiResult>(
"/endpoint",
cancellationToken: cts.Token
);
POST Requests
// POST with data
var newUser = new UserCreateRequest { Name = "John Doe", Email = "john@example.com" };
var result = await _apiClient.PostAsync<UserResponse>("/users", newUser);
// POST with data and query parameters
var createResult = await _apiClient.PostAsync<CreateResult>(
"/items",
new ItemRequest { Name = "New Item" },
new Dictionary<string, string> { ["mode"] = "quick" }
);
PUT and DELETE Requests
// PUT request
var updateResult = await _apiClient.PutAsync<UpdateResult>(
$"/users/{userId}",
new UserUpdateRequest { Name = "Updated Name" }
);
// DELETE request
var deleteResult = await _apiClient.DeleteAsync<DeleteResult>($"/users/{userId}");
Advanced Features
Custom Headers
var result = await _apiClient.GetAsync<ApiResult>(
"/special-endpoint",
headers: new Dictionary<string, string>
{
["X-Custom-Header"] = "CustomValue",
["X-Correlation-Id"] = correlationId
}
);
Raw HTTP Methods
For more control, you can use the base SendAsync
methods:
// Get raw response string
var responseString = await _apiClient.SendAsync(
HttpMethod.Get,
"/endpoint",
queryParams: new Dictionary<string, string> { ["param"] = "value" }
);
// Deserialize to specific type
var result = await _apiClient.SendAsync<ApiResult>(
HttpMethod.Post,
"/endpoint",
data: new { key = "value" },
additionalHeaders: new Dictionary<string, string> { ["X-Custom"] = "Value" }
);
Performance Considerations
- The client maintains a single
HttpClient
instance for connection pooling - Rate limiting is controlled via
MaxConcurrentRequests
setting - Response caching is handled automatically based on cache attributes
- Compression is enabled by default to reduce bandwidth usage
Implementing in DI Container
// In Program.cs or Startup.cs
services.Configure<ApiClientSettings>(Configuration.GetSection("ApiSettings"));
services.AddSingleton<IMultiLevelCache, YourCacheImplementation>(); // Optional
services.AddSingleton<GenericApiClient>();
// In your service
public class MyService
{
private readonly GenericApiClient _apiClient;
public MyService(GenericApiClient apiClient)
{
_apiClient = apiClient;
}
// Use the client in your methods
}
Error Handling
The client throws exceptions for HTTP errors. Common patterns for handling:
try
{
var result = await _apiClient.GetAsync<ApiResult>("/endpoint");
// Process successful result
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
// Handle 404 Not Found
}
catch (HttpRequestException ex)
{
// Handle other HTTP errors
logger.LogError(ex, "Error calling API");
}
catch (Exception ex)
{
// Handle other exceptions
logger.LogError(ex, "Unexpected error");
}
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
If you encounter any issues or have questions, please file an issue on the GitHub repository.
Acknowledgments
- Thanks to all contributors who have helped shape this library
- Inspired by best practices in HTTP client libraries
- Built with ❤️ by the SmartWorkz team
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net9.0 is compatible. 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. |
-
net9.0
- itext7.pdfhtml (>= 6.1.0)
- iTextSharp (>= 5.5.13.4)
- Microsoft.AspNetCore.Mvc.RazorPages (>= 2.3.0)
- Microsoft.Extensions.Options (>= 9.0.3)
- Newtonsoft.Json (>= 13.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.