From 0191ee12092c28ccf5a578e59977583117a3ff00 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 22 Jun 2026 15:32:49 -0600 Subject: [PATCH 01/63] =?UTF-8?q?chore:=20ignore=20marketing/=20folder=20?= =?UTF-8?q?=E2=80=94=20private=20content=20not=20for=20public=20repo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add marketing/ to .gitignore - Untrack all previously committed marketing files (files remain on disk locally) --- .gitignore | 3 + marketing/devto-posts.md | 530 ------- marketing/dynamic-linq-banner-simple.html | 176 --- marketing/dynamic-linq-banner.html | 263 --- marketing/email-campaign.md | 726 --------- marketing/github-release-post.md | 157 -- marketing/linkedin-article-promotion.md | 16 - marketing/linkedin-posts.md | 584 ------- marketing/linkedin-strategy.md | 251 --- marketing/medium-posts.md | 1759 --------------------- marketing/reddit-compliant-strategy.md | 267 ---- marketing/reddit-posts-compliant.md | 360 ----- marketing/reddit-posts.md | 679 -------- marketing/twitter-posts.md | 305 ---- 14 files changed, 3 insertions(+), 6073 deletions(-) delete mode 100644 marketing/devto-posts.md delete mode 100644 marketing/dynamic-linq-banner-simple.html delete mode 100644 marketing/dynamic-linq-banner.html delete mode 100644 marketing/email-campaign.md delete mode 100644 marketing/github-release-post.md delete mode 100644 marketing/linkedin-article-promotion.md delete mode 100644 marketing/linkedin-posts.md delete mode 100644 marketing/linkedin-strategy.md delete mode 100644 marketing/medium-posts.md delete mode 100644 marketing/reddit-compliant-strategy.md delete mode 100644 marketing/reddit-posts-compliant.md delete mode 100644 marketing/reddit-posts.md delete mode 100644 marketing/twitter-posts.md diff --git a/.gitignore b/.gitignore index dfe793f..e578ce9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Private marketing content โ€” not for public repo +marketing/ + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## diff --git a/marketing/devto-posts.md b/marketing/devto-posts.md deleted file mode 100644 index 6167732..0000000 --- a/marketing/devto-posts.md +++ /dev/null @@ -1,530 +0,0 @@ - -https://dev-to-uploads.s3.amazonaws.com/uploads/articles/juydtkp1tfzr6ms5l14y.png - - -![PowerCSharp: a comprehensive library of extension methods, utilities, and helper classes designed to enhance your C# development experience](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/juydtkp1tfzr6ms5l14y.png) - - -/// - - -# Dev.to Posts for PowerCSharp v1.0.0 - -## ๐Ÿ“‹ Posting Strategy - -**Current Status:** -- โœ… Post 1: "After 20+ Years of C# Development, I Finally Solved These Annoying Problems" (Published) -- ๐Ÿ“ Post 2-7: Planned specialized posts (below) - -**Posting Cadence:** -- **Week 1**: v1.0.0 announcement (if recommended) -- **Week 2**: Dynamic LINQ deep dive -- **Week 3**: Security-focused post -- **Week 4**: Performance engineering -- **Week 5**: Migration story -- **Week 6**: Architecture deep dive - ---- - -## ๐Ÿš€ Post 1: v1.0.0 Announcement (Evaluation Needed) - -**Title:** PowerCSharp v1.0.0: Production-Ready C# Extensions Library is Here! - -**Question:** Is this good practice for Dev.to? - -**Analysis:** -- โœ… **Pros**: Celebrates milestone, creates excitement, drives adoption -- โŒ **Cons**: Might feel promotional, Dev.to prefers technical content -- ๐ŸŽฏ **Verdict**: Better to focus on technical value rather than pure announcement - -**Recommended Approach:** Frame as technical milestone rather than pure announcement - ---- - -## ๐Ÿ” Post 2: Dynamic LINQ Deep Dive - -**Title:** Dynamic LINQ in PowerCSharp: How I Made Runtime Queries Production-Ready - -**Body:** -# Dynamic LINQ in PowerCSharp: How I Made Runtime Queries Production-Ready - -After 20+ years building enterprise C# applications, one of the most common challenges I've faced is the need for dynamic queries. Users want to search, filter, and sort data in ways we can't predict at compile time. - -Today, I'm going to show you how I implemented production-ready dynamic LINQ in PowerCSharp v1.0.0, complete with performance benchmarks and real-world use cases. - -## The Dynamic LINQ Problem - -Traditional LINQ is powerful but compile-time bound: - -```csharp -// Static LINQ - only works with known expressions -var adults = people.Where(p => p.Age > 18 && p.Name.Contains("John")); -``` - -But what happens when your users need to build their own queries? - -```csharp -// User input: "Age > 18 && Name.Contains('John') && Status == 'Active'" -// How do we safely execute this? -``` - -## PowerCSharp Dynamic LINQ Solution - -### Core Implementation - -PowerCSharp provides two key methods for dynamic LINQ: - -```csharp -using PowerCSharp.Extensions; - -// Parse string expressions into compiled delegates -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -var filtered = people.Where(predicate); - -// Dynamic ordering -string orderExpression = "Name DESC, Age ASC"; -var orderDelegates = orderExpression.GetOrderDelegates(); -var ordered = people.OrderByMultiple(orderDelegates); -``` - -### Architecture Deep Dive - -The implementation uses System.Linq.Dynamic.Core as the foundation, but with enterprise-grade enhancements: - -```csharp -public static class DynamicExpressionExtensions -{ - public static Func GetExpressionDelegate(this string stringExpression) - { - // Parse and compile the expression - var parameter = Expression.Parameter(typeof(T), "x"); - var lambda = DynamicExpressionParser.ParseLambda( - new ParsingConfig(), false, stringExpression, parameter); - - return lambda.Compile(); - } - - public static List<(Func, bool)> GetOrderDelegates( - this string stringExpression) - { - var delegates = new List<(Func, bool)>(); - var orderParts = stringExpression.Split(','); - - foreach (var part in orderParts) - { - var trimmed = part.Trim(); - var descending = trimmed.EndsWith(" DESC", StringComparison.OrdinalIgnoreCase); - var propertyName = trimmed.Replace(" DESC", "").Replace(" ASC", "").Trim(); - - var parameter = Expression.Parameter(typeof(TSource), "x"); - var property = Expression.Property(parameter, propertyName); - var lambda = Expression.Lambda>( - Expression.Convert(property, typeof(object)), parameter); - - delegates.Add((lambda.Compile(), descending)); - } - - return delegates; - } -} -``` - -## Performance Optimizations - -### Benchmark Results - -I tested PowerCSharp Dynamic LINQ against traditional approaches with 100,000 records: - -| Method | Execution Time | Memory Usage | Compile Time | -|--------|----------------|--------------|--------------| -| Static LINQ | 45ms | 12MB | N/A | -| PowerCSharp Dynamic | 52ms | 14MB | 8ms (first time) | -| Reflection-based | 230ms | 28MB | N/A | -| Manual Expression Building | 180ms | 22MB | N/A | - -**Key Insights:** -- **95% faster** than reflection-based approaches -- **Only 15% overhead** compared to static LINQ -- **Expression caching** eliminates compile cost after first use - -### Caching Strategy - -```csharp -public static class ExpressionCache -{ - private static readonly ConcurrentDictionary _cache = new(); - - public static Func GetOrCreate(string expression) - { - return (Func)_cache.GetOrAdd( - $"{typeof(T).Name}_{expression}", - exp => exp.GetExpressionDelegate()); - } -} -``` - -## Real-World Use Cases - -### 1. Advanced Search Systems - -```csharp -public class ProductSearchService -{ - public IEnumerable SearchProducts(SearchCriteria criteria) - { - var products = _repository.GetAllProducts(); - - // Build dynamic filter from user criteria - var filters = new List(); - - if (criteria.MinPrice.HasValue) - { - filters.Add($"Price >= {criteria.MinPrice}"); - } - - if (criteria.MaxPrice.HasValue) - { - filters.Add($"Price <= {criteria.MaxPrice}"); - } - - if (!string.IsNullOrEmpty(criteria.Category)) - { - filters.Add($"Category.Contains(\"{criteria.Category}\")"); - } - - if (criteria.InStockOnly) - { - filters.Add("Stock > 0"); - } - - var expression = string.Join(" && ", filters); - return products.Where(expression.GetExpressionDelegate()); - } -} -``` - -### 2. Admin Panel Filtering - -```csharp -public class UserManagementService -{ - public IEnumerable GetFilteredUsers(UserFilter filter) - { - var users = _userRepository.GetAll(); - - // Dynamic filtering from admin panel - var filterExpression = BuildFilterExpression(filter); - var sortExpression = BuildSortExpression(filter); - - var filtered = users.Where(filterExpression.GetExpressionDelegate()); - var sorted = filtered.OrderByMultiple(sortExpression.GetOrderDelegates()); - - return sorted; - } - - private string BuildFilterExpression(UserFilter filter) - { - var conditions = new List(); - - if (filter.Role != null) - { - conditions.Add($"Role == \"{filter.Role}\""); - } - - if (filter.Status.HasValue) - { - conditions.Add($"Status == {(int)filter.Status}"); - } - - if (filter.CreatedAfter.HasValue) - { - conditions.Add($"CreatedDate >= DateTime(\"{filter.CreatedAfter:yyyy-MM-dd}\")"); - } - - return conditions.Any() ? string.Join(" && ", conditions) : "true"; - } -} -``` - -### 3. API Endpoint Flexibility - -```csharp -[HttpGet("users")] -public IActionResult GetUsers([FromQuery] string filter, [FromQuery] string sort) -{ - try - { - var users = _userService.GetAllUsers(); - - // Apply dynamic filtering - if (!string.IsNullOrEmpty(filter)) - { - var predicate = filter.GetExpressionDelegate(); - users = users.Where(predicate); - } - - // Apply dynamic sorting - if (!string.IsNullOrEmpty(sort)) - { - var orderDelegates = sort.GetOrderDelegates(); - users = users.OrderByMultiple(orderDelegates); - } - - return Ok(users.ToList()); - } - catch (Exception ex) - { - return BadRequest($"Invalid filter/sort expression: {ex.Message}"); - } -} -``` - -## Security Considerations - -### Input Validation - -Dynamic LINQ can be vulnerable to injection attacks if not properly secured: - -```csharp -public static class SecureDynamicLinq -{ - private static readonly string[] _allowedProperties = { "Name", "Age", "Email", "Status" }; - private static readonly string[] _allowedOperators = { ">", "<", ">=", "<=", "==", "!=", "Contains", "StartsWith" }; - - public static bool IsValidExpression(string expression, Type targetType) - { - // Basic validation - in production, use more sophisticated parsing - return !expression.Contains("new ") && - !expression.Contains("typeof(") && - !expression.Contains("DateTime.Now") && - _allowedProperties.Any(prop => expression.Contains(prop)); - } -} -``` - -### Error Handling - -```csharp -public static Func SafeGetExpressionDelegate(this string expression) -{ - try - { - if (!SecureDynamicLinq.IsValidExpression(expression, typeof(T))) - throw new SecurityException("Invalid expression detected"); - - return expression.GetExpressionDelegate(); - } - catch (Exception ex) - { - // Log security events - _logger.LogWarning("Dynamic LINQ expression validation failed: {Expression}", expression); - throw new InvalidOperationException("Invalid filter expression", ex); - } -} -``` - -## Advanced Patterns - -### 1. Type-Safe Dynamic Builders - -```csharp -public class DynamicQueryBuilder -{ - private readonly List _conditions = new(); - private readonly List _orderings = new(); - - public DynamicQueryBuilder Where(string condition) - { - _conditions.Add(condition); - return this; - } - - public DynamicQueryBuilder OrderBy(string property, bool descending = false) - { - _orderings.Add($"{property} {(descending ? "DESC" : "ASC")}"); - return this; - } - - public IQueryable Apply(IQueryable source) - { - if (_conditions.Any()) - { - var expression = string.Join(" && ", _conditions); - source = source.Where(expression.GetExpressionDelegate()); - } - - if (_orderings.Any()) - { - var ordering = string.Join(", ", _orderings); - var orderDelegates = ordering.GetOrderDelegates(); - source = source.OrderByMultiple(orderDelegates); - } - - return source; - } -} -``` - -### 2. Expression Templates - -```csharp -public class ExpressionTemplate -{ - private readonly Dictionary _templates = new() - { - ["DateRange"] = "Date >= \"{Start}\" && Date <= \"{End}\"", - ["TextSearch"] = "Name.Contains(\"{Search}\") || Description.Contains(\"{Search}\")", - ["NumericRange"] = "Value >= {Min} && Value <= {Max}" - }; - - public string Render(string template, Dictionary parameters) - { - var expression = _templates[template]; - - foreach (var param in parameters) - { - expression = expression.Replace($"{{{param.Key}}}", param.Value.ToString()); - } - - return expression; - } -} -``` - -## Testing Dynamic LINQ - -### Unit Tests - -```csharp -[Test] -public void DynamicLinq_ShouldFilterCorrectly() -{ - // Arrange - var people = new List - { - new() { Name = "John", Age = 25 }, - new() { Name = "Jane", Age = 30 }, - new() { Name = "Bob", Age = 20 } - }; - - // Act - var expression = "Age > 21 && Name.Contains(\"J\")"; - var predicate = expression.GetExpressionDelegate(); - var result = people.Where(predicate).ToList(); - - // Assert - result.Should().HaveCount(2); - result.Should().Contain(p => p.Name == "John"); - result.Should().Contain(p => p.Name == "Jane"); -} -``` - -### Performance Tests - -```csharp -[Test] -public void DynamicLinq_ShouldPerformWell() -{ - // Arrange - var data = Enumerable.Range(1, 100000) - .Select(i => new Person { Name = $"Person{i}", Age = i % 50 }) - .ToList(); - - var stopwatch = Stopwatch.StartNew(); - - // Act - var expression = "Age > 25 && Name.Contains(\"1\")"; - var predicate = expression.GetExpressionDelegate(); - var result = data.Where(predicate).ToList(); - - stopwatch.Stop(); - - // Assert - stopwatch.ElapsedMilliseconds.Should().BeLessThan(100); - result.Should().NotBeEmpty(); -} -``` - -## Best Practices - -1. **Always validate input** - Prevent injection attacks -2. **Cache compiled expressions** - Avoid repeated compilation -3. **Use type-safe builders** - Reduce runtime errors -4. **Implement proper error handling** - Graceful failures -5. **Monitor performance** - Track execution times -6. **Document supported expressions** - Clear API contracts - -## Conclusion - -Dynamic LINQ in PowerCSharp bridges the gap between the flexibility users demand and the performance developers need. With proper security measures and performance optimizations, it's ready for production use. - -The key is balancing power with safety - giving users the flexibility they need while protecting your application from injection attacks and performance issues. - -**Try it out:** -```bash -dotnet add package PowerCSharp.Extensions -``` - -**What dynamic query challenges have you faced? Share your experiences in the comments!** - -#DynamicLINQ #CSharp #DotNet #PowerCSharp #Performance #Security - ---- - -## ๐Ÿ”’ Post 3: Security-Focused Post - -**Title:** CWE-73 Compliant Path Operations: How PowerCSharp Prevents Directory Traversal Attacks - -**Body:** -[Full security-focused post about path operations, CWE-73 compliance, real attack scenarios, prevention strategies] - ---- - -## โšก Post 4: Performance Engineering - -**Title:** Optimizing C# Extension Methods: Performance Lessons from Building PowerCSharp - -**Body:** -[Full performance post about memory optimization, benchmarking, Span usage, async patterns] - ---- - -## ๐Ÿ“ˆ Post 5: Migration Story - -**Title:** From 47 Lines to 12: How PowerCSharp Transformed Our Production Code - -**Body:** -[Full migration story with before/after examples, team adoption, measurable gains] - ---- - -## ๐Ÿ—๏ธ Post 6: Architecture Deep Dive - -**Title:** Building Modular NuGet Packages: PowerCSharp Architecture Decisions - -**Body:** -[Full architecture post about package design, interface centralization, dependency management] - ---- - -## ๐Ÿ“… Posting Schedule Recommendations - -### Week 1: Foundation -- **Post 1**: v1.0.0 technical milestone (if recommended) - -### Week 2-3: Core Features -- **Post 2**: Dynamic LINQ deep dive -- **Post 3**: Security-focused post - -### Week 4-5: Advanced Topics -- **Post 4**: Performance engineering -- **Post 5**: Migration story - -### Week 6: Architecture -- **Post 6**: Architecture deep dive - -### Ongoing: Community Engagement -- Respond to all comments -- Share insights from discussions -- Reference posts in future content diff --git a/marketing/dynamic-linq-banner-simple.html b/marketing/dynamic-linq-banner-simple.html deleted file mode 100644 index 2b9d7e9..0000000 --- a/marketing/dynamic-linq-banner-simple.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Dynamic LINQ Banner - Simple - - - - - - diff --git a/marketing/dynamic-linq-banner.html b/marketing/dynamic-linq-banner.html deleted file mode 100644 index 2641986..0000000 --- a/marketing/dynamic-linq-banner.html +++ /dev/null @@ -1,263 +0,0 @@ - - - - - - Dynamic LINQ Banner - - - - - - - - diff --git a/marketing/email-campaign.md b/marketing/email-campaign.md deleted file mode 100644 index 65bfd04..0000000 --- a/marketing/email-campaign.md +++ /dev/null @@ -1,726 +0,0 @@ -# Email Marketing Campaign for PowerCSharp v1.0.0 - -## ๐Ÿ“ง Email Template 1: Launch Announcement - -**Subject:** ๐Ÿš€ PowerCSharp v1.0.0 - Production-Ready C# Extensions Library - -**Body:** - -Hi [Name], - -I'm thrilled to announce the official release of PowerCSharp v1.0.0 - a comprehensive C# extensions library built with 20+ years of enterprise development experience. - -After months of development, testing, and refinement, PowerCSharp is now production-ready and designed to enhance your C# development productivity. - -## ๐ŸŽฏ What Makes PowerCSharp Special? - -**100+ Extension Methods** organized into 6 modular packages: -- PowerCSharp.Core (Foundation) -- PowerCSharp.Extensions (Cross-platform extensions) -- PowerCSharp.Extensions.AspNetCore (Web utilities) -- PowerCSharp.Utilities (Validation, file ops) -- PowerCSharp.Helpers (JSON, crypto, environment) -- PowerCSharp.Compatibility (.NET Framework support) - -## โšก Key Features - -- **Dynamic LINQ** - Runtime expression parsing -- **Security First** - CWE-73 compliant path operations -- **Performance Optimized** - .NET 8.0 ready -- **Enterprise Ready** - 100+ unit tests, >90% coverage -- **Clean Architecture** - Centralized interfaces, modular design - -## ๐Ÿš€ Quick Start - -```bash -dotnet add package PowerCSharp.Extensions -``` - -```csharp -using PowerCSharp.Extensions; - -// String utilities -string title = "hello world".ToTitleCase(); // "Hello World" -bool isValid = "https://example.com".IsValidUrl(); // true - -// Dynamic LINQ -var filtered = people.Where("Age > 18 && Name.Contains('John')"); - -// Secure operations -string safePath = PathExtensions.CombineAndValidate(basePath, userInput); -``` - -## ๐Ÿ”ฎ What's Coming? - -- **PowerCSharp Features** - Feature flags package -- **Clean Architecture Template** - Complete starter repository - -## ๐Ÿ“š Resources - -- **GitHub**: [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -- **Documentation**: Complete API reference and examples -- **NuGet**: All packages available on NuGet Gallery - -Transform your C# development experience today! - -Best regards, -Mario Arce -Creator of PowerCSharp - ---- - -## ๐Ÿ“ง Email Template 2: Technical Deep Dive - -**Subject:** ๐Ÿ” Deep Dive: PowerCSharp Architecture & Security Features - -**Body:** - -Hi [Name], - -Following the exciting launch of PowerCSharp v1.0.0, I wanted to share some insights into the architectural decisions and security features that make this library enterprise-ready. - -## ๐Ÿ—๏ธ Architecture Excellence - -PowerCSharp exemplifies clean architecture principles: - -**Centralized Interfaces** -- All contracts in PowerCSharp.Core -- Single source of truth for APIs -- Clear dependency management - -**Modular Design** -- 6 focused packages with single responsibilities -- Install only what you need -- Clear separation of concerns - -**Semantic Versioning** -- Predictable updates -- Backward compatibility -- Long-term maintenance - -## ๐Ÿ”’ Security-First Approach - -Security isn't an afterthought - it's built into every method: - -**CWE-73 Compliant Path Operations** -```csharp -// Prevents directory traversal attacks -string safePath = PathExtensions.CombineAndValidate(basePath, userInput); -``` - -**Input Validation** -```csharp -bool isValidEmail = ValidationHelper.IsValidEmail(userInput); -bool isValidUrl = ValidationHelper.IsValidUrl(userInput); -bool isNumeric = ValidationHelper.IsNumeric(value); -``` - -**Safe Operations** -- Comprehensive null checking -- Graceful error handling -- Security event logging - -## โšก Performance Optimizations - -**Memory Efficiency** -- Minimal allocations -- Efficient algorithms -- Span usage for string operations - -**Async Support** -```csharp -await originalStream.CloneAsync(destinationStream); -``` - -**Thread Safety** -All methods designed for concurrent environments - -## ๐Ÿงช Comprehensive Testing - -- 100+ unit tests -- >90% code coverage -- Edge case handling -- Performance benchmarks -- Security validation - -## ๐ŸŽฏ Real-World Impact - -Teams using PowerCSharp report: -- 50% reduction in boilerplate code -- Faster development cycles -- Fewer bugs in production -- Improved code consistency - -## ๐Ÿ“Š Package Statistics - -[![NuGet Downloads](https://img.shields.io/nuget/dt/PowerCSharp.Core.svg)](https://www.nuget.org/packages/PowerCSharp.Core) -[![NuGet Downloads](https://img.shields.io/nuget/dt/PowerCSharp.Extensions.svg)](https://www.nuget.org/packages/PowerCSharp.Extensions) - -## ๐Ÿค Join the Community - -- **Contributors**: Help shape the future -- **Feedback**: Share your experience -- **Feature Requests**: Influence the roadmap - -Explore the technical details and see how PowerCSharp can enhance your applications! - -Best regards, -Mario Arce -Creator of PowerCSharp - ---- - -## ๐Ÿ“ง Email Template 3: Use Case Focus - -**Subject:** ๐Ÿ’ผ PowerCSharp in Action: Real-World Use Cases & Benefits - -**Body:** - -Hi [Name], - -PowerCSharp v1.0.0 is already being adopted by developers worldwide. Let me share some real-world use cases and the benefits teams are experiencing. - -## ๐Ÿฆ Financial Services - -**Challenge:** Secure transaction processing with regulatory compliance - -**Solution:** -```csharp -// Secure input validation -bool isValidEmail = ValidationHelper.IsValidEmail(customerEmail); -bool isValidAmount = ValidationHelper.IsNumeric(transactionAmount); - -// Secure path operations -string safePath = PathExtensions.CombineAndValidate(basePath, fileName); - -// Audit logging -string hash = transaction.ComputeHash(); // For change tracking -``` - -**Benefits:** -- Enhanced security compliance -- Reduced validation code -- Improved audit trails - -## ๐Ÿ›’ E-commerce Platforms - -**Challenge:** Dynamic product search and filtering - -**Solution:** -```csharp -// Dynamic LINQ for flexible search -var products = catalog.Where("Price > 50 && Category.Contains('Electronics')"); - -// Pagination -var page = products.Page(currentPage, pageSize); - -// URL manipulation for search -Uri searchUrl = baseUrl.AddParameter("q", searchTerm).AddParameter("page", "1"); -``` - -**Benefits:** -- Flexible search capabilities -- Faster feature development -- Improved user experience - -## ๐Ÿฅ Healthcare Applications - -**Challenge:** Data validation and secure operations - -**Solution:** -```csharp -// Patient data validation -bool isValidEmail = ValidationHelper.IsValidEmail(patientEmail); -bool isValidPhone = ValidationHelper.IsValidPhoneNumber(patientPhone); - -// Secure file operations -string safePath = PathExtensions.CombineAndValidate(secureBase, patientFile); - -// Data integrity -string recordHash = patientRecord.ComputeHash(); -``` - -**Benefits:** -- HIPAA compliance support -- Data integrity validation -- Secure file handling - -## ๐ŸŽฎ Gaming & Entertainment - -**Challenge:** Performance-critical operations - -**Solution:** -```csharp -// Fast object hashing for caching -string cacheKey = gameState.ComputeHash(); - -// Efficient string operations -string normalized = playerName.NormalizeKey(); - -// Collection operations -var leaderboard = scores.OrderByDescendingDescending(x => x.Score).Take(10); -``` - -**Benefits:** -- Improved performance -- Reduced memory usage -- Faster development - -## ๐Ÿ“Š Measurable Benefits - -**Development Productivity** -- 50% less boilerplate code -- 40% faster feature development -- 60% reduction in common bugs - -**Code Quality** -- Improved consistency -- Better error handling -- Enhanced security - -**Team Collaboration** -- Shared patterns -- Clear documentation -- Easy onboarding - -## ๐ŸŽฏ Success Story: Tech Startup - -*"We integrated PowerCSharp into our SaaS platform and reduced our development time by 40%. The validation utilities alone saved us weeks of work."* - CTO, Tech Startup - -## ๐Ÿš€ Getting Started - -```bash -# Install for your project type -dotnet add package PowerCSharp.Extensions # Core functionality -dotnet add package PowerCSharp.Extensions.AspNetCore # Web applications -dotnet add package PowerCSharp.Utilities # Validation & file ops -``` - -## ๐Ÿ’ก Pro Tips - -1. **Start with Core** - Add PowerCSharp.Extensions first -2. **Use Dynamic LINQ** - For flexible user interfaces -3. **Secure Everything** - Use validation helpers everywhere -4. **Profile Performance** - Monitor improvements - -## ๐Ÿ”ฎ Future Enhancements - -Coming soon to address more use cases: -- **PowerCSharp Features** - Feature flags for A/B testing -- **Clean Architecture Template** - Complete project starter - -What use cases will you solve with PowerCSharp? - -Best regards, -Mario Arce -Creator of PowerCSharp - ---- - -## ๐Ÿ“ง Email Template 4: Community & Contribution - -**Subject:** ๐Ÿค Join the PowerCSharp Community: Shape the Future of C# Development - -**Body:** - -Hi [Name], - -PowerCSharp v1.0.0 is more than just a library - it's a growing community of passionate C# developers. I'm inviting you to be part of this exciting journey! - -## ๐ŸŒŸ Community Growth - -Since launching, we've seen: -- โญ 100+ GitHub stars -- ๐Ÿ“ฆ Growing NuGet downloads -- ๐Ÿ› Active issue discussions -- ๐Ÿ’ก Feature requests from developers -- ๐Ÿค New contributor interest - -## ๐Ÿš€ Contribution Opportunities - -**Core Development** -- New extension methods -- Performance optimizations -- Security enhancements -- Bug fixes - -**Documentation** -- API reference improvements -- Tutorial creation -- Example code -- Translation efforts - -**Community Support** -- Forum moderation -- Issue triage -- User assistance -- Feedback collection - -**Testing & Quality** -- Unit test expansion -- Integration testing -- Performance benchmarking -- Security auditing - -## ๐ŸŽฏ Current Priority Areas - -1. **String Extensions** - More manipulation methods -2. **Crypto Helpers** - Enhanced security utilities -3. **Async Extensions** - More async/await support -4. **Configuration** - Enhanced configuration binding -5. **Validation** - Additional validation rules - -## ๐Ÿ† Contributor Recognition - -**What Contributors Receive:** -- GitHub contributor badges -- Recognition in release notes -- Speaking opportunities -- Networking with C# experts -- Portfolio enhancement - -**Featured Contributors:** -- [Your Name Here] - String extensions -- [Your Name Here] - Security improvements -- [Your Name Here] - Documentation enhancements - -## ๐Ÿ“‹ Getting Started Guide - -**1. Fork & Clone** -```bash -git clone https://github.com/marioarce/PowerCSharp.git -``` - -**2. Set Up Development** -```bash -dotnet restore -dotnet test -``` - -**3. Find an Issue** -- Browse [GitHub Issues](https://github.com/marioarce/PowerCSharp/issues) -- Look for "good first issue" labels -- Comment on issues you want to work on - -**4. Make Your Contribution** -- Create a feature branch -- Write tests for new functionality -- Submit a pull request - -## ๐Ÿ’ก Contribution Ideas - -**Beginner Friendly** -- Add missing XML documentation -- Improve error messages -- Write additional unit tests -- Create usage examples - -**Intermediate** -- Implement new extension methods -- Optimize existing code -- Add performance benchmarks -- Improve documentation - -**Advanced** -- Architectural improvements -- Security enhancements -- Performance optimizations -- Integration with other libraries - -## ๐ŸŽ Community Perks - -**Early Access** -- Beta releases -- Feature previews -- Roadmap input -- Direct communication - -**Learning Opportunities** -- Code reviews with experts -- Architecture discussions -- Best practice sharing -- Career guidance - -**Professional Growth** -- Open source portfolio -- Networking opportunities -- Speaking engagements -- Mentorship programs - -## ๐Ÿ“… Community Events - -**Weekly** -- Contributor office hours -- Code review sessions -- Planning discussions - -**Monthly** -- Feature showcases -- Contributor spotlights -- Roadmap reviews -- Q&A sessions - -**Quarterly** -- Community meetings -- Release planning -- Goal setting -- Achievement celebrations - -## ๐Ÿ”ฎ Community Roadmap - -**Q3 2026** -- PowerCSharp Features package -- Community documentation site -- Contributor onboarding program - -**Q4 2026** -- Clean Architecture Template -- Integration partnerships -- Conference presentations - -**2027** -- Regional meetups -- Workshop series -- Certification program - -## ๐Ÿค Join Us Today - -1. **Star the Repository** - Show your support -2. **Join Discussions** - Share your ideas -3. **Report Issues** - Help improve quality -4. **Submit PRs** - Make your mark -5. **Spread the Word** - Grow the community - -## ๐Ÿ“ž Stay Connected - -- **GitHub**: [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -- **Discussions**: [GitHub Discussions](https://github.com/marioarce/PowerCSharp/discussions) -- **Issues**: [GitHub Issues](https://github.com/marioarce/PowerCSharp/issues) -- **Twitter**: [@marioarce](https://twitter.com/marioarce) - -Your contribution, no matter how small, helps make PowerCSharp better for everyone! - -Ready to join the community? - -Best regards, -Mario Arce -Creator of PowerCSharp - ---- - -## ๐Ÿ“ง Email Template 5: Future Roadmap - -**Subject:** ๐Ÿ”ฎ The Future of PowerCSharp: Features Package & Clean Architecture Template - -**Body:** - -Hi [Name], - -PowerCSharp v1.0.0 is just the beginning! I'm excited to share our vision for the future and how you can benefit from what's coming next. - -## ๐Ÿš€ What's Next for PowerCSharp - -### PowerCSharp Features (Coming Q3 2026) - -**Feature Flags Made Easy** -```csharp -// Enable/disable features at runtime -Features.Enable("AdvancedSearch"); -Features.Disable("BetaFeature"); - -// Configuration-driven -bool isEnabled = Features.IsEnabled("NewUI"); - -// Conditional logic -if (Features.IsEnabled("DarkMode")) -{ - // Apply dark mode -} -``` - -**Key Benefits:** -- **Runtime Control** - Enable/disable without redeployment -- **Configuration-Driven** - JSON-based feature management -- **Zero Downtime** - Hot-swappable features -- **A/B Testing** - Built-in experiment support -- **Progressive Rollout** - Gradual feature deployment - -**Use Cases:** -- Beta feature management -- A/B testing campaigns -- Progressive feature rollout -- Emergency feature disable -- User tier-based features - -### Clean Architecture Template (Coming Q4 2026) - -**Complete .NET 8.0 Starter Repository** -- **PowerCSharp Integration** - Pre-configured with all packages -- **Enterprise Patterns** - DDD, CQRS, Event Sourcing ready -- **Production Ready** - Best practices built-in -- **Jumpstart Development** - Start coding business logic immediately - -**Template Features:** -```csharp -// Clean Architecture with PowerCSharp -public class ProductService -{ - private readonly IRepository _repository; - private readonly IFeatureService _features; - - public async Task> SearchAsync(SearchCriteria criteria) - { - // Dynamic filtering with PowerCSharp - var products = await _repository.GetAllAsync(); - - if (_features.IsEnabled("AdvancedSearch")) - { - return products.Where(criteria.BuildExpression()); - } - - return products.Where(p => p.IsActive); - } -} -``` - -**Architecture Highlights:** -- **Domain-Driven Design** - Proper domain modeling -- **CQRS Pattern** - Command/Query separation -- **Event Sourcing** - Audit trail and replay capability -- **Microservices Ready** - Distributed architecture support -- **Testing Infrastructure** - Comprehensive test setup - -## ๐ŸŽฏ Strategic Vision - -**Ecosystem Growth** -- **Package Expansion** - More specialized packages -- **Third-Party Integration** - Compatibility with popular libraries -- **Tooling Support** - IDE extensions and tooling -- **Educational Resources** - Tutorials, courses, certifications - -**Enterprise Adoption** -- **Large-Scale Support** - Designed for enterprise workloads -- **Compliance Features** - Industry-specific compliance -- **Professional Support** - Enterprise support options -- **Consulting Services** - Architecture and implementation guidance - -**Community Development** -- **Contributor Program** - Structured contribution pathways -- **Ambassador Program** - Community advocates -- **Regional Meetups** - Local community building -- **Conference Presence** - Industry thought leadership - -## ๐Ÿ“… Development Timeline - -**Q3 2026** -- PowerCSharp Features v1.0.0 release -- Feature documentation and tutorials -- Community feedback integration -- Performance optimization - -**Q4 2026** -- Clean Architecture Template v1.0.0 -- Integration guides and best practices -- Workshop series launch -- Partner program announcement - -**Q1 2027** -- PowerCSharp Features v1.1.0 (advanced features) -- Template extensions (microservices, serverless) -- Certification program launch -- Enterprise support offerings - -**Q2 2027** -- PowerCSharp v2.0.0 planning -- Community governance model -- International expansion -- Industry-specific packages - -## ๐Ÿ’ก Early Access Opportunities - -**Beta Testing** -- Early access to new features -- Direct influence on development -- Recognition as early adopter -- Priority support - -**Partnership Program** -- Integration opportunities -- Co-marketing initiatives -- Technical collaboration -- Business development - -**Advisory Council** -- Strategic input on roadmap -- Industry insight sharing -- Networking with peers -- Thought leadership platform - -## ๐ŸŽ Special Launch Offers - -**Early Adopter Benefits** -- Free consultation session -- Priority feature requests -- Enhanced support -- Recognition in launch announcements - -**Contributor Rewards** -- Free commercial licenses -- Speaking opportunities -- Conference invitations -- Professional development - -**Organization Benefits** -- Team training sessions -- Architecture review -- Implementation guidance -- Custom feature development - -## ๐Ÿš€ Get Involved Early - -**1. Join the Waitlist** -- Be first to try new features -- Receive exclusive updates -- Get early-bird pricing -- Influence development priorities - -**2. Provide Feedback** -- Share your use cases -- Suggest feature improvements -- Report bugs and issues -- Contribute to discussions - -**3. Contribute to Development** -- Help build the future -- Gain valuable experience -- Build your portfolio -- Network with experts - -**4. Spread the Word** -- Share with your network -- Write about your experience -- Present at meetups -- Mentor new users - -## ๐Ÿ“ž Stay Connected - -- **Roadmap Updates**: [GitHub Projects](https://github.com/marioarce/PowerCSharp/projects) -- **Beta Sign-up**: [Google Form](https://forms.gle/example) -- **Community Discussions**: [GitHub Discussions](https://github.com/marioarce/PowerCSharp/discussions) -- **Newsletter**: Subscribe for updates - -## ๐Ÿ”ฎ Vision for 2027 - -By the end of 2027, PowerCSharp aims to be: -- **The Go-To Library** for C# productivity -- **Industry Standard** for extension libraries -- **Community-Driven** open source project -- **Enterprise-Ready** development platform - -The future is bright, and I want you to be part of it! - -Ready to shape the future of C# development? - -Best regards, -Mario Arce -Creator of PowerCSharp diff --git a/marketing/github-release-post.md b/marketing/github-release-post.md deleted file mode 100644 index 771a0f2..0000000 --- a/marketing/github-release-post.md +++ /dev/null @@ -1,157 +0,0 @@ -# ๐ŸŽ‰ PowerCSharp v1.0.0 - Production Ready! - -I'm incredibly excited to announce the first official stable release of PowerCSharp! After months of development, testing, and refinement, PowerCSharp v1.0.0 is now ready for production use in enterprise applications. - -## ๐Ÿš€ What is PowerCSharp? - -PowerCSharp is a comprehensive library of extension methods, utilities, and helper classes designed to enhance your C# development experience. Built with 20+ years of C# architecture experience, this library provides practical, well-tested solutions for common programming challenges. - -## ๐Ÿ“ฆ Six Focused Packages - -### ๐Ÿ”ง PowerCSharp.Core -- **Foundation**: Centralized interfaces and models -- **Architecture**: Clean separation of concerns -- **Dependencies**: None (dependency-free) - -### โšก PowerCSharp.Extensions -- **100+ Extension Methods**: String, DateTime, Collections, LINQ, JSON, XML, Objects, Types, Streams -- **Dynamic LINQ**: Runtime expression parsing and filtering -- **Security**: CWE-73 compliant path operations -- **Performance**: Optimized for .NET 8.0 - -### ๐ŸŒ PowerCSharp.Extensions.AspNetCore -- **HTTP Utilities**: Status code helpers, URI manipulation, request cloning -- **Configuration**: Enhanced ASP.NET Core configuration binding -- **Web-Focused**: Tailored for modern web applications - -### ๐Ÿ› ๏ธ PowerCSharp.Utilities -- **Validation**: Email, URL, numeric validation -- **File Operations**: Safe file operations and size formatting -- **Mathematics**: Mathematical helpers and conversions - -### ๐Ÿ” PowerCSharp.Helpers -- **JSON**: Safe serialization/deserialization -- **Crypto**: SHA-256, MD5 hashing, random strings -- **Environment**: System information and environment variables - -### ๐Ÿ”„ PowerCSharp.Compatibility -- **.NET Framework**: Legacy support for 4.6.2+ -- **Migration Path**: Seamless upgrade to modern .NET -- **System.Web**: Full compatibility with legacy applications - -## ๐ŸŽฏ Key Features - -### ๐Ÿ”’ Security First -```csharp -// CWE-73 compliant path operations -string safePath = PathExtensions.CombineAndValidate(basePath, userFile); -``` - -### โšก Dynamic LINQ -```csharp -// Runtime expression parsing -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -``` - -### ๐Ÿ›ก๏ธ Type Safety -```csharp -// Centralized interfaces in PowerCSharp.Core -var filterProvider = new DynamicFilterProvider(); -var ordered = people.Order(orderProvider); -``` - -### ๐Ÿš€ Performance Optimized -- Minimal memory allocations -- Efficient algorithms -- .NET 8.0 optimizations -- Thread-safe operations - -## ๐Ÿ—๏ธ Architecture Highlights - -- **Centralized Interfaces**: All contracts in PowerCSharp.Core -- **Modular Design**: Install only what you need -- **Clean Namespaces**: Consistent organization -- **Dependency Management**: Clear separation of concerns - -## ๐ŸŽฏ Target Frameworks - -- **Modern .NET**: .NET 8.0 (full support) -- **Cross-Platform**: .NET Standard 2.0 -- **Legacy**: .NET Framework 4.6.2+ (via Compatibility package) -- **ASP.NET Core**: .NET 8.0 optimized - -## ๐Ÿ“ˆ Production Ready - -โœ… **Comprehensive Testing**: 100+ unit tests with >90% coverage -โœ… **Security Audit**: CWE compliance and vulnerability review -โœ… **Performance Validation**: Load testing and optimization -โœ… **Documentation**: Complete API reference and examples -โœ… **NuGet Icons**: Professional package presentation - -## ๐Ÿš€ Quick Start - -```bash -# Install the complete suite -dotnet add package PowerCSharp.Core -dotnet add package PowerCSharp.Extensions -dotnet add package PowerCSharp.Extensions.AspNetCore -dotnet add package PowerCSharp.Utilities -dotnet add package PowerCSharp.Helpers -dotnet add package PowerCSharp.Compatibility -``` - -```csharp -using PowerCSharp.Extensions; - -// String utilities -string title = "hello world".ToTitleCase(); // "Hello World" -bool isValid = "https://example.com".IsValidUrl(); // true - -// DateTime utilities -int age = DateTime.Now.GetAge(); -bool isWeekend = DateTime.Now.IsWeekend(); - -// Collection utilities -var numbers = new List { 1, 2, 3, 4, 5 }; -var page = numbers.Page(1, 2); // [1, 2] - -// Dynamic LINQ -var filtered = people.Where("Age > 18 && Name.Contains('John')"); -``` - -## ๐Ÿ”ฎ What's Next? - -### PowerCSharp Features (Coming Soon) -- **Feature Flags**: Easy package management with enable/disable functionality -- **Modular Architecture**: Selective feature activation -- **Configuration-Driven**: Runtime feature management - -### Clean Architecture Template (Coming Soon) -- **Complete Repository**: .NET Core Clean Architecture implementation -- **PowerCSharp Integration**: Pre-configured with all packages -- **Production Ready**: Jumpstart your next project -- **Best Practices**: Enterprise-grade architecture patterns - -## ๐Ÿค Join the Community - -- **โญ Star the Repository**: [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -- **๐Ÿ› Report Issues**: [GitHub Issues](https://github.com/marioarce/PowerCSharp/issues) -- **๐Ÿ’ก Feature Requests**: [GitHub Discussions](https://github.com/marioarce/PowerCSharp/discussions) -- **๐Ÿ“ง Contributing**: [Contributing Guide](https://github.com/marioarce/PowerCSharp/blob/main/CONTRIBUTING.md) - -## ๐Ÿ“Š NuGet Statistics - -[![NuGet Downloads](https://img.shields.io/nuget/dt/PowerCSharp.Core.svg)](https://www.nuget.org/packages/PowerCSharp.Core) -[![NuGet Downloads](https://img.shields.io/nuget/dt/PowerCSharp.Extensions.svg)](https://www.nuget.org/packages/PowerCSharp.Extensions) -[![NuGet Downloads](https://img.shields.io/nuget/dt/PowerCSharp.Extensions.AspNetCore.svg)](https://www.nuget.org/packages/PowerCSharp.Extensions.AspNetCore) - -## ๐Ÿ™ Acknowledgments - -Built with passion by [Mario Arce](https://github.com/marioarce) with 20+ years of C# development experience. Thank you to all the early adopters and contributors who helped make this release possible! - ---- - -**PowerCSharp v1.0.0** - Making C# development more powerful, one extension at a time! ๐Ÿš€ - -#PowerCSharp #CSharp #DotNet #OpenSource #NuGet #Productivity diff --git a/marketing/linkedin-article-promotion.md b/marketing/linkedin-article-promotion.md deleted file mode 100644 index 9cfc5a2..0000000 --- a/marketing/linkedin-article-promotion.md +++ /dev/null @@ -1,16 +0,0 @@ -# LinkedIn Article Promotion Messages - -## ๐Ÿš€ Option 1: Comprehensive (Recommended) -I'm excited to announce PowerCSharp v1.0.0 - a production-ready C# extensions library built from 20+ years of enterprise experience. This article shares how 100+ extension methods can transform your development productivity, enhance security, and reduce boilerplate code by 50%. Whether you're an engineer looking to code smarter, a leader seeking team productivity gains, or exploring consulting opportunities - there's something here for you. #PowerCSharp #CSharp #DotNet - -## ๐Ÿ”ง Option 2: Engineer-Focused -Just launched PowerCSharp v1.0.0 on NuGet! After 20+ years building enterprise C# applications, I've packaged 100+ production-tested extension methods that eliminate the boilerplate code we write every day. Dynamic LINQ, secure operations, HTTP utilities - all optimized for .NET 8.0. This article shows you how to stop writing repetitive code and focus on what matters. #CSharp #DotNet #DeveloperTools - -## ๐Ÿ’ผ Option 3: Business-Focused -Your development teams are wasting 50% of their time on boilerplate code. PowerCSharp v1.0.0 addresses this with enterprise-grade stability, security-first design, and measurable productivity gains. This article breaks down the ROI, implementation strategy, and business impact of adopting modern C# practices. #EnterpriseSoftware #Productivity #ROI - -## ๐ŸŽฏ Option 4: Consulting-Focused -PowerCSharp v1.0.0 demonstrates what enterprise architecture expertise looks like in practice. This article showcases how I solved a $2M+ productivity problem across enterprises through clean architecture, security-first development, and performance optimization. If you need someone who can transform your C# development practices - this is what I deliver. #Consulting #EnterpriseArchitecture #Expertise - -## โšก Option 5: Quick & Punchy -๐Ÿš€ PowerCSharp v1.0.0 is LIVE! Production-ready C# extensions library with 100+ methods built from 20+ years enterprise experience. Stop writing boilerplate code. Start shipping value faster. This article shows you how. #PowerCSharp #CSharp #DotNet diff --git a/marketing/linkedin-posts.md b/marketing/linkedin-posts.md deleted file mode 100644 index 56bd47a..0000000 --- a/marketing/linkedin-posts.md +++ /dev/null @@ -1,584 +0,0 @@ -# LinkedIn Posts for PowerCSharp v1.0.0 - -## ๐ŸŽฏ Main Launch Announcement - -**Post 1: The Declaration - Multi-Audience Launch Announcement** - -๐Ÿš€ **A Declaration: PowerCSharp v1.0.0 is Here - Public NuGet Package Available!** - -Today marks a significant milestone in my 20+ year journey as a C# architect. I'm proud to announce that PowerCSharp v1.0.0 is now publicly available on NuGet - a production-ready extensions library built from real-world enterprise experience. - -**To My Engineering Connections:** -This is for you. 100+ production-tested extension methods that eliminate the repetitive code we write every day. Dynamic LINQ, secure path operations, HTTP utilities, JSON processing - all optimized for .NET 8.0. Try it in your next project: - -```bash -dotnet add package PowerCSharp.Extensions -``` - -**To My Decision-Maker Connections:** -Your development teams are spending 50% of their time on boilerplate code. PowerCSharp addresses this directly with enterprise-grade stability, comprehensive testing, and security-first design. Tell your engineers to evaluate this - the productivity gains are measurable and immediate. - -**To Recruiters and Consulting Opportunities:** -This isn't just a library - it's a demonstration of enterprise architecture expertise. I've solved the problems that cost organizations millions in development overhead. If you need someone who can transform your C# development practices, build productivity platforms, or architect enterprise solutions - that's what I do. - -**What Makes This Different:** -โ€ข **Built by Enterprise Architects** - Not just another utility library -โ€ข **Production-Ready** - 100+ unit tests, CWE-73 compliance, enterprise stability -โ€ข **6 Modular Packages** - Install only what you need -โ€ข **Future-Proof** - Roadmap includes feature flags and clean architecture template - -**The Impact:** -Teams using PowerCSharp report significant reductions in development time, fewer bugs, and improved code consistency across projects. - -**Next Steps:** -- **Engineers**: Install the package and share your feedback -- **Leaders**: Schedule a demo for your teams -- **Recruiters**: Let's discuss how this expertise can benefit your organization - -This is more than a product launch - it's a statement about what's possible when experience meets innovation. - -#PowerCSharp #CSharp #DotNet #EnterpriseSoftware #SoftwareDevelopment #Productivity #OpenSource #NuGet #Consulting #Architecture - -๐Ÿ”— **GitHub**: github.com/marioarce/PowerCSharp -๐Ÿ”— **NuGet**: nuget.org/packages/PowerCSharp.Extensions - ---- - -**Post 2: For Engineers - Let's Build Better C# Code Together** - -๐Ÿ”ง **Engineers, Stop Writing Boilerplate Code - PowerCSharp v1.0.0 is Here** - -I've been in your shoes. Writing the same validation logic, string manipulation, and collection operations across projects. After 20+ years of C# development, I decided to solve this problem once and for all. - -**What PowerCSharp Gives You:** - -**Dynamic LINQ That Actually Works:** -```csharp -// No more compiled expressions only! -var filtered = users.Where("Age > 18 && Name.Contains('John')"); -var ordered = users.OrderBy("Name DESC, Age ASC"); -``` - -**Security Built-In:** -```csharp -// CWE-73 compliant - prevents directory traversal -string safePath = PathExtensions.CombineAndValidate(basePath, userInput); -bool isValidEmail = ValidationHelper.IsValidEmail(email); -``` - -**String Operations You'll Use Daily:** -```csharp -"hello world".ToTitleCase(); // "Hello World" -"https://example.com".IsValidUrl(); // true -"User Name".NormalizeKey(); // "userName" -``` - -**HTTP Utilities for Web Devs:** -```csharp -response.StatusCode.IsSuccessful(); // true -uri.AddParameter("search", "query"); // Add query params -request.Clone(); // Safe request duplication -``` - -**Performance Optimized for .NET 8.0:** -- Minimal memory allocations -- Async/await support throughout -- Thread-safe operations -- 100+ unit tests ensuring reliability - -**Why This Matters to You:** -- **Less Time on Boilerplate** - Focus on business logic -- **Fewer Bugs** - Production-tested methods -- **Better Code Consistency** - Standardized patterns -- **Career Growth** - Use modern, enterprise-grade libraries - -**Installation (30 seconds):** -```bash -dotnet add package PowerCSharp.Extensions -dotnet add package PowerCSharp.Extensions.AspNetCore # For web apps -``` - -**Real Talk:** -This isn't just another utility library. It's built from the patterns I've used in enterprise applications that handle millions of transactions. Every method solves a real problem I've encountered in production. - -**Call to Action:** -1. Try it in your next feature -2. Share your experience in the comments -3. Request features you need -4. Contribute to the project - -Let's build better C# applications together. - -#CSharp #DotNet #SoftwareEngineering #DeveloperTools #Productivity #OpenSource #NuGet #WebDevelopment - -๐Ÿ”— **GitHub**: github.com/marioarce/PowerCSharp -๐Ÿ”— **NuGet**: nuget.org/packages/PowerCSharp.Extensions -๐Ÿ’ฌ **Questions? Ask me anything in the comments!** - ---- - -**Post 3: For Decision-Makers - Transform Your Development Teams** - -๐Ÿ’ผ **Decision-Makers: Your Development Teams Are Wasting 50% of Their Time on Boilerplate Code** - -As a C# architect with 20+ years of enterprise experience, I've seen this pattern repeatedly: talented developers spending countless hours on repetitive, non-business-critical code. - -**The Problem You're Facing:** -- Your teams write the same validation logic across projects -- String manipulation and data processing code is duplicated -- Security vulnerabilities creep into custom implementations -- Onboarding time is high due to inconsistent patterns -- Technical debt accumulates from quick fixes - -**PowerCSharp v1.0.0 Solves This:** - -**Measurable Business Impact:** -โ€ข **50% Reduction** in boilerplate code development time -โ€ข **40% Fewer Bugs** from production-tested implementations -โ€ข **60% Faster Onboarding** for new team members -โ€ข **Enhanced Security** with CWE-73 compliance built-in -โ€ข **Consistent Code Quality** across all projects - -**Enterprise-Ready Features:** -- **6 Modular Packages** - Install only what you need -- **Comprehensive Testing** - 100+ unit tests with >90% coverage -- **Security First** - Built-in validation and safe operations -- **Production Stability** - Semantic versioning for long-term support -- **.NET 8.0 Optimized** - Modern performance with backward compatibility - -**Real-World Use Cases:** -- **Financial Services** - Secure transaction processing -- **E-commerce** - Dynamic search and filtering -- **Healthcare** - HIPAA-compliant data handling -- **SaaS Platforms** - Rapid feature development - -**The ROI Calculation:** -If your team of 5 developers saves 2 hours per week on boilerplate code, that's 520 hours annually - equivalent to 13 weeks of full-time development capacity. - -**Implementation Strategy:** -1. **Pilot with One Team** - 2-week evaluation period -2. **Measure Productivity Gains** - Track development velocity -3. **Scale Organization-Wide** - Standardize across teams -4. **Custom Extensions** - Add organization-specific methods - -**Next Step:** -Schedule a 30-minute demo for your technical leadership team. I'll show you exactly how PowerCSharp can transform your development productivity and reduce technical debt. - -**This Isn't Just a Library - It's a Productivity Platform for Your Entire Organization.** - -#EnterpriseSoftware #DigitalTransformation #SoftwareDevelopment #Productivity #CTO #EngineeringLeadership #ROI #BusinessValue #PowerCSharp - -๐Ÿ”— **Schedule Demo**: [Link to your calendar/booking system] -๐Ÿ”— **Case Studies**: Available upon request -๐Ÿ’ผ **Enterprise Support**: Contact for licensing options - ---- - -**Post 4: For Recruiters - Expertise in Action** - -๐ŸŽฏ **Recruiters: This is What Enterprise Architecture Expertise Looks Like in Practice** - -PowerCSharp v1.0.0 isn't just a NuGet package - it's a demonstration of the enterprise architecture and development expertise I bring to organizations. - -**What PowerCSharp Demonstrates:** - -**Enterprise Architecture Skills:** -- **Clean Architecture Implementation** - Centralized interfaces, modular design -- **Security-First Development** - CWE-73 compliance, input validation -- **Performance Engineering** - .NET 8.0 optimization, memory efficiency -- **API Design** - Consistent, maintainable interfaces -- **Testing Strategy** - Comprehensive unit testing with >90% coverage - -**Business Problem Solving:** -- Identified a $2M+ productivity problem across enterprises -- Designed a scalable solution with measurable ROI -- Built a product that addresses real business needs -- Created a roadmap for future growth (feature flags, clean architecture template) - -**Technical Leadership:** -- **20+ Years C# Experience** - From .NET Framework to .NET 8.0 -- **Enterprise-Scale Projects** - Systems handling millions of transactions -- **Open Source Management** - Community building, contributor engagement -- **Product Thinking** - Beyond code to business value delivery - -**Consulting & Advisory Services I Offer:** - -**Architecture Modernization:** -- Legacy .NET Framework to .NET 8.0 migration -- Microservices architecture design -- Performance optimization and scalability -- Security assessment and remediation - -**Development Team Transformation:** -- Code quality improvement programs -- Development productivity enhancement -- Best practices implementation -- Team training and mentorship - -**Product Strategy:** -- Technical feasibility assessment -- Architecture roadmap development -- Technology stack evaluation -- Build vs. buy analysis - -**Recent Achievements:** -- Launched production-ready library with 100+ methods -- Built enterprise-grade security features -- Designed scalable architecture patterns -- Created comprehensive testing strategies - -**Why This Matters for Your Clients:** -I don't just write code - I solve business problems through technology. PowerCSharp demonstrates my ability to: -- Identify systemic issues in development practices -- Design solutions that scale across organizations -- Build products that deliver measurable business value -- Lead technical initiatives from concept to production - -**Looking For:** -- **Consulting Engagements** - Architecture reviews, modernization projects -- **Advisory Roles** - CTO/VP Engineering advisory positions -- **Speaking Opportunities** - Conferences, corporate training -- **Partnership Opportunities** - Technology companies, consulting firms - -**Let's Discuss How This Expertise Can Benefit Your Organization.** - -#Consulting #EnterpriseArchitecture #SoftwareArchitecture #CTO #EngineeringLeadership #TechnologyConsulting #PowerCSharp #Expertise #Hiring - -๐Ÿ”— **Portfolio**: github.com/marioarce/PowerCSharp -๐Ÿ”— **LinkedIn DM**: Open for consulting discussions -๐Ÿ’ผ **Calendar**: [Link to your consulting calendar] - ---- - -**Post 5: Technical Leadership Perspective** -๐Ÿ—๏ธ **Architectural Excellence: How PowerCSharp v1.0.0 Sets New Standards for C# Libraries** - -As software architects, we know that library design impacts maintainability, scalability, and team productivity. PowerCSharp v1.0.0 demonstrates enterprise-grade architectural patterns: - -**Clean Architecture Principles:** -โ€ข **Centralized Interfaces** - All contracts in PowerCSharp.Core ensure consistency -โ€ข **Separation of Concerns** - 6 focused packages with clear responsibilities -โ€ข **Dependency Management** - Zero external dependencies for core package -โ€ข **Namespace Organization** - Logical, hierarchical structure -โ€ข **API Stability** - Semantic versioning for long-term maintenance - -**Technical Excellence:** -โ€ข **Thread Safety** - All methods designed for concurrent environments -โ€ข **Performance Optimization** - Minimal allocations, efficient algorithms -โ€ข **Security First** - CWE-73 compliance, input validation, safe operations -โ€ข **Testing Coverage** - 100+ unit tests with >90% code coverage -โ€ข **Documentation** - Complete API reference with examples - -**Real-World Impact:** -Teams using PowerCSharp report significant reductions in development time for common operations like string manipulation, validation, LINQ queries, and HTTP operations. - -**For Technical Leaders:** -This library exemplifies how to build maintainable, scalable software that serves both immediate needs and long-term architectural goals. - -#SoftwareArchitecture #CSharp #DotNet #EnterpriseArchitecture #CleanCode #TechnicalLeadership - ---- - -**Post 3: Business Value Proposition** -๐Ÿ’ผ **The Business Case for PowerCSharp: ROI Through Developer Productivity** - -In today's competitive landscape, developer productivity directly impacts business outcomes. PowerCSharp v1.0.0 delivers measurable business value: - -**Quantifiable Benefits:** -โ€ข **Reduced Development Time** - 100+ pre-built extensions eliminate repetitive coding -โ€ข **Lower Bug Rates** - Production-tested methods reduce common errors -โ€ข **Faster Onboarding** - Consistent API patterns accelerate team integration -โ€ข **Enhanced Security** - Built-in validation prevents common vulnerabilities -โ€ข **Future-Proof** - .NET 8.0 optimization with backward compatibility - -**Enterprise-Ready Features:** -โ€ข **Comprehensive Testing** - Reduces QA cycles and production issues -โ€ข **Security Compliance** - CWE-73 compliance meets enterprise standards -โ€ข **Modular Architecture** - Pay only for what you use -โ€ข **Professional Support** - Active community and documentation - -**Use Case Examples:** -โ€ข **E-commerce Platforms** - Enhanced validation and string processing -โ€ข **Financial Systems** - Secure operations and comprehensive testing -โ€ข **SaaS Applications** - Rapid feature development with reliable utilities -โ€ข **Legacy Modernization** - Compatibility package smooths migration - -**Investment Protection:** -Semantic versioning and stable APIs ensure your investment in PowerCSharp continues to pay dividends as your applications evolve. - -**For Decision Makers:** -PowerCSharp represents a strategic investment in development efficiency that scales with your organization. - -#BusinessValue #ROI #SoftwareDevelopment #EnterpriseSoftware #CSharp #DotNet #Productivity - ---- - -## ๐Ÿ’ก Technical Deep Dives - -**Post 4: Security Focus** -๐Ÿ”’ **Security-First Development: How PowerCSharp v1.0.0 Protects Your Applications** - -In enterprise software, security can't be an afterthought. PowerCSharp v1.0.0 was designed with security as a core principle: - -**Built-in Security Features:** -โ€ข **CWE-73 Compliance** - Path operations prevent directory traversal attacks -โ€ข **Input Validation** - Comprehensive validation helpers for common data types -โ€ข **Safe Operations** - All methods include null checking and error handling -โ€ข **Security Testing** - Automated security analysis in CI/CD pipeline - -**Real-World Security Scenarios:** -```csharp -// Prevents directory traversal attacks -string safePath = PathExtensions.CombineAndValidate(basePath, userInput); - -// Validates user input securely -bool isValidEmail = ValidationHelper.IsValidEmail(userInput); -bool isValidUrl = ValidationHelper.IsValidUrl(userInput); - -// Safe JSON operations with error handling -var obj = JsonHelper.SafeDeserialize(jsonInput); -``` - -**Enterprise Security Standards:** -โ€ข **OWASP Compliance** - Built-in protections against common vulnerabilities -โ€ข **Audit Trail** - Security events logged for monitoring -โ€ข **Dependency Management** - Regular security updates and vulnerability scanning -โ€ข **Code Review** - Security-focused development practices - -**For Security Teams:** -PowerCSharp provides a foundation of secure coding practices that your development teams can build upon. - -#Cybersecurity #ApplicationSecurity #SecureCoding #CSharp #DotNet #EnterpriseSecurity - ---- - -**Post 5: Performance Optimization** -โšก **Performance Engineering: PowerCSharp v1.0.0 Optimized for Modern .NET** - -Performance matters in enterprise applications. PowerCSharp v1.0.0 was engineered for optimal performance: - -**Performance Optimizations:** -โ€ข **Memory Efficiency** - Minimal allocations and optimized algorithms -โ€ข **Async/Await Support** - Non-blocking operations for I/O-bound tasks -โ€ข **Thread Safety** - Safe concurrent usage patterns -โ€ข **.NET 8.0 Optimization** - Leverages latest runtime improvements - -**Benchmark Results:** -โ€ข **String Operations**: 40% faster than manual implementations -โ€ข **Collection Processing**: 25% reduction in memory usage -โ€ข **LINQ Operations**: 30% faster dynamic expression parsing -โ€ข **JSON Processing**: 35% improvement in serialization speed - -**Performance Features:** -```csharp -// Efficient stream cloning -await originalStream.CloneAsync(destinationStream); - -// Optimized collection operations -var page = numbers.Page(1, 2); // Memory-efficient pagination - -// Fast object hashing for caching -string hash = complexObject.ComputeHash(); // Optimized algorithm -``` - -**Monitoring & Diagnostics:** -โ€ข **Performance Counters** - Built-in metrics for monitoring -โ€ข **Memory Profiling** - Tools for optimization analysis -โ€ข **Benchmark Testing** - Continuous performance validation - -**For Performance Engineers:** -PowerCSharp provides a foundation of optimized operations that scale under load. - -#Performance #Optimization #DotNet8 #CSharp #SoftwareEngineering #HighPerformance - ---- - -## ๐ŸŽฏ Industry-Specific Posts - -**Post 6: Financial Services** -๐Ÿฆ **PowerCSharp for Financial Services: Security, Reliability, Compliance** - -Financial applications demand the highest standards of security and reliability. PowerCSharp v1.0.0 delivers: - -**Financial Services Features:** -โ€ข **Secure Operations** - CWE-73 compliance prevents path traversal attacks -โ€ข **Validation Excellence** - Email, URL, and numeric validation for regulatory compliance -โ€ข **Error Handling** - Graceful failure modes prevent data corruption -โ€ข **Audit Support** - Comprehensive logging for compliance requirements - -**Use Cases in Finance:** -โ€ข **Transaction Processing** - Safe string manipulation and validation -โ€ข **Report Generation** - Efficient data processing and formatting -โ€ข **API Integration** - Secure HTTP operations and request handling -โ€ข **Data Migration** - Compatible with legacy .NET Framework systems - -**Compliance & Security:** -โ€ข **SOX Compliance** - Audit trails and error logging -โ€ข **PCI DSS** - Secure data handling practices -โ€ข **GDPR** - Safe data processing and validation -โ€ข **Enterprise Standards** - Meets banking industry requirements - -**Risk Mitigation:** -โ€ข **Production Tested** - 100+ unit tests ensure reliability -โ€ข **Semantic Versioning** - Predictable updates and maintenance -โ€ข **Backward Compatibility** - Smooth upgrade paths -โ€ข **Professional Support** - Active community and documentation - -#FinTech #Banking #FinancialServices #Compliance #CSharp #DotNet - ---- - -**Post 7: E-commerce & Retail** -๐Ÿ›’ **PowerCSharp for E-commerce: Accelerating Digital Commerce Development** - -E-commerce platforms need speed, reliability, and security. PowerCSharp v1.0.0 enhances retail digital transformation: - -**E-commerce Features:** -โ€ข **Product Data Processing** - Advanced string and validation operations -โ€ข **Search & Filtering** - Dynamic LINQ for flexible product searches -โ€ข **API Integration** - HTTP utilities for payment gateway connections -โ€ข **Performance Optimization** - Faster page loads and reduced server load - -**Real-World Applications:** -```csharp -// Product search with dynamic filtering -var products = catalog.Where("Price > 50 && Category.Contains('Electronics')"); - -// Secure payment processing -var paymentRequest = request.Clone(); // Safe request duplication - -// Product data validation -bool isValidPrice = ValidationHelper.IsNumeric(productPrice); -bool isValidEmail = ValidationHelper.IsValidEmail(customerEmail); -``` - -**Business Benefits:** -โ€ข **Faster Feature Development** - 100+ pre-built utilities -โ€ข **Improved User Experience** - Faster response times -โ€ข **Enhanced Security** - Built-in validation and safe operations -โ€ข **Scalability** - Performance optimized for high traffic - -**Integration Capabilities:** -โ€ข **Payment Gateways** - Secure HTTP operations -โ€ข **Inventory Systems** - Efficient data processing -โ€ข **Customer Management** - Advanced string and validation operations -โ€ข **Analytics** - Object hashing for tracking and caching - -#Ecommerce #Retail #DigitalCommerce #CSharp #DotNet #SoftwareDevelopment - ---- - -## ๐Ÿš€ Future Vision Posts - -**Post 8: Roadmap Announcement** -๐Ÿ”ฎ **The Future of PowerCSharp: Features Package & Clean Architecture Template** - -PowerCSharp v1.0.0 is just the beginning. Here's our vision for the future: - -**Coming Soon: PowerCSharp Features** -โ€ข **Feature Flags** - Runtime enable/disable functionality -โ€ข **Modular Architecture** - Selective package activation -โ€ข **Configuration-Driven** - JSON-based feature management -โ€ข **Zero-Downtime** - Hot-swappable features - -**Coming Soon: Clean Architecture Template** -โ€ข **Complete Repository** - Production-ready .NET 8.0 template -โ€ข **PowerCSharp Integration** - Pre-configured with all packages -โ€ข **Enterprise Patterns** - DDD, CQRS, Event Sourcing ready -โ€ข **Best Practices** - Jumpstart your next project - -**Strategic Vision:** -โ€ข **Ecosystem Growth** - Community-driven feature development -โ€ข **Enterprise Adoption** - Large-scale deployment support -โ€ข **Educational Resources** - Tutorials, workshops, certifications -โ€ข **Partner Integration** - Third-party tool compatibility - -**Community Opportunities:** -โ€ข **Contributor Program** - Join the development team -โ€ข **Feature Requests** - Shape the product roadmap -โ€ข **Case Studies** - Share your success stories -โ€ข **Speaking Opportunities** - Conference presentations - -**For Organizations:** -Position your team at the forefront of modern C# development with PowerCSharp's evolving ecosystem. - -#ProductRoadmap #FutureTech #SoftwareDevelopment #CSharp #DotNet #Innovation - ---- - -## ๐Ÿค Community & Networking Posts - -**Post 9: Call for Contributors** -๐Ÿค **Join the PowerCSharp Community: Shape the Future of C# Development** - -PowerCSharp v1.0.0 is production-ready, but our journey is just beginning. We're building a community of passionate C# developers: - -**Contributor Opportunities:** -โ€ข **Core Development** - New extension methods and features -โ€ข **Documentation** - API reference, tutorials, and examples -โ€ข **Testing** - Unit tests, integration tests, performance benchmarks -โ€ข **Community Support** - Forums, issues, and discussions -โ€ข **Evangelism** - Blog posts, conference talks, workshops - -**What We're Looking For:** -โ€ข **C# Experts** - Deep knowledge of .NET ecosystem -โ€ข **Architecture Enthusiasts** - Clean code and design patterns -โ€ข **Security Specialists** - Vulnerability assessment and mitigation -โ€ข **Performance Engineers** - Optimization and benchmarking -โ€ข **Technical Writers** - Documentation and tutorials - -**Benefits of Contributing:** -โ€ข **Professional Growth** - Work with experienced architects -โ€ข **Portfolio Building** - Open source contributions -โ€ข **Networking** - Connect with C# community leaders -โ€ข **Learning** - Advanced C# techniques and patterns -โ€ข **Recognition** - Contributor badges and acknowledgments - -**Getting Started:** -1. Fork the repository -2. Review contributing guidelines -3. Join our discussions -4. Submit your first pull request - -**For Organizations:** -Support open source while gaining access to cutting-edge C# tools and expertise. - -#OpenSource #CSharp #DotNet #Community #SoftwareDevelopment #Contributing - ---- - -**Post 10: Success Stories Invitation** -๐Ÿ“ˆ **Share Your PowerCSharp Success Stories** - -Have you used PowerCSharp in your projects? We want to hear from you! - -**What We're Looking For:** -โ€ข **Use Cases** - How PowerCSharp solved your challenges -โ€ข **Performance Gains** - Measurable improvements in development speed -โ€ข **Bug Reductions** - Fewer issues in production -โ€ข **Team Productivity** - Faster onboarding and development -โ€ข **Innovative Applications** - Creative uses of our extensions - -**Benefits of Sharing:** -โ€ข **Community Recognition** - Featured in our showcase -โ€ข **Networking** - Connect with other PowerCSharp users -โ€ข **Influence** - Shape future development priorities -โ€ข **Professional Exposure** - Demonstrate your expertise - -**How to Participate:** -1. **Document Your Experience** - Blog post, case study, or video -2. **Share Metrics** - Performance improvements, time savings -3. **Include Code Examples** - Show how you used PowerCSharp -4. **Tag Us** - Use #PowerCSharp and mention @marioarce -5. **Submit to Discussions** - Share in our GitHub community - -**Featured Opportunities:** -โ€ข **Blog Showcase** - Featured on our GitHub page -โ€ข **Conference Presentations** - Speaking opportunities -โ€ข **Case Study Library** - Permanent recognition -โ€ข **Community Spotlight** - Social media features - -**For Organizations:** -Demonstrate your commitment to modern development practices while helping the community grow. - -#SuccessStories #CaseStudy #CSharp #DotNet #SoftwareDevelopment #Community diff --git a/marketing/linkedin-strategy.md b/marketing/linkedin-strategy.md deleted file mode 100644 index 8525244..0000000 --- a/marketing/linkedin-strategy.md +++ /dev/null @@ -1,251 +0,0 @@ -# LinkedIn Strategy & Posting Schedule for PowerCSharp v1.0.0 - -## ๐ŸŽฏ Strategic Overview - -**Multi-Audience Approach:** -1. **Engineers** - Technical benefits, code examples, productivity gains -2. **Decision-Makers** - Business value, ROI, team transformation -3. **Recruiters** - Expertise demonstration, consulting opportunities - -**Campaign Goals:** -- Drive NuGet package downloads and adoption -- Generate consulting leads and opportunities -- Establish thought leadership in C#/.NET space -- Build community around PowerCSharp - -## ๐Ÿ“… Posting Schedule - -### Week 1: Launch Week - -**Day 1 (Monday): Post 1 - The Declaration** -- **Time**: 9:00 AM EST (Prime engagement time) -- **Audience**: All connections (multi-audience approach) -- **Goal**: Maximum visibility and initial buzz -- **CTA**: Install package, schedule demo, consulting inquiries - -**Day 2 (Tuesday): Post 2 - For Engineers** -- **Time**: 12:00 PM EST (Lunch reading for developers) -- **Audience**: Technical/engineering connections -- **Goal**: Drive technical adoption and feedback -- **CTA**: Try the package, share experience, request features - -**Day 3 (Wednesday): Post 3 - For Decision-Makers** -- **Time**: 8:30 AM EST (Before workday for executives) -- **Audience**: CTOs, VPs, Directors, Managers -- **Goal**: Generate demo requests and enterprise interest -- **CTA**: Schedule demo, ROI discussion - -**Day 4 (Thursday): Post 4 - For Recruiters** -- **Time**: 2:00 PM EST (Afternoon for recruiters) -- **Audience**: Recruiters, hiring managers, consulting firms -- **Goal**: Generate consulting opportunities and speaking engagements -- **CTA**: Portfolio review, consulting discussions - -**Day 5 (Friday): Post 5 - Technical Leadership** -- **Time**: 10:00 AM EST (Mid-momentum post) -- **Audience**: Architects, senior developers, tech leads -- **Goal**: Establish thought leadership -- **CTA**: Architecture discussions, best practices - -### Week 2: Engagement & Follow-up - -**Day 8 (Monday): Success Stories & Early Adopters** -- Share early feedback and usage examples -- Tag users who have tried PowerCSharp -- Highlight specific use cases and benefits - -**Day 9 (Tuesday): Technical Deep Dive** -- Focus on a specific feature (Dynamic LINQ, Security, etc.) -- Code examples and performance benchmarks -- Technical Q&A in comments - -**Day 10 (Wednesday): Business Impact** -- Share ROI calculations and case studies -- Before/after scenarios -- Team productivity metrics - -**Day 11 (Thursday): Community Building** -- Highlight contributors and supporters -- Announce roadmap priorities based on feedback -- Call for feature requests and contributions - -**Day 12 (Friday): Future Vision** -- Tease PowerCSharp Features package -- Clean Architecture Template preview -- Long-term vision and roadmap - -### Week 3: Expansion & Authority - -**Day 15 (Monday): Industry-Specific Applications** -- Financial services use cases -- Healthcare compliance examples -- E-commerce implementations - -**Day 16 (Tuesday): Performance & Security** -- Deep dive on security features -- Performance benchmarks and comparisons -- Compliance and audit capabilities - -**Day 17 (Wednesday): Integration Patterns** -- How to integrate with existing systems -- Migration strategies -- Best practices for enterprise adoption - -**Day 18 (Thursday): Team Training & Onboarding** -- How to train teams on PowerCSharp -- Documentation and resources -- Support and maintenance strategies - -**Day 19 (Friday): Consulting Success Stories** -- Share anonymized consulting success stories -- Problem-solving methodologies -- Value delivery examples - -## ๐ŸŽฏ Content Strategy - -### Post Structure Template - -**Hook (First 2 lines):** -- Compelling statement or question -- Target audience pain point -- Bold claim or statistic - -**Body:** -- Problem identification -- Solution explanation -- Specific examples and benefits -- Code snippets (for technical posts) -- ROI calculations (for business posts) - -**Call to Action:** -- Clear next steps -- Multiple options (try, schedule, discuss) -- Easy access to resources - -**Hashtags:** -- Primary: #PowerCSharp #CSharp #DotNet -- Secondary: #SoftwareDevelopment #Productivity #OpenSource -- Audience-specific: #EnterpriseSoftware #Consulting #Engineering - -### Engagement Strategy - -**Pre-Posting:** -- Research trending topics in C#/.NET -- Identify key influencers in the space -- Prepare responses to common questions - -**During Posting:** -- Monitor comments actively -- Respond within 1 hour to all comments -- Tag relevant people and companies -- Share to relevant groups - -**Post-Posting:** -- Analyze engagement metrics -- Follow up with commenters via DM -- Share insights in subsequent posts -- Adjust strategy based on performance - -## ๐Ÿ“Š Success Metrics - -### Primary KPIs -- **NuGet Downloads**: Track package adoption -- **Demo Requests**: Measure business interest -- **Consulting Inquiries**: Track opportunity generation -- **LinkedIn Engagement**: Likes, comments, shares - -### Secondary KPIs -- **GitHub Stars**: Community growth -- **Contributor Sign-ups**: Community engagement -- **Speaking Inquiries**: Thought leadership recognition -- **Media Mentions**: PR and brand awareness - -### Benchmarks -- **Week 1**: 500+ downloads, 10+ demo requests -- **Week 2**: 1,000+ downloads, 25+ consulting inquiries -- **Week 3**: 2,000+ downloads, 50+ GitHub stars -- **Month 1**: 5,000+ downloads, 100+ total engagements - -## ๐Ÿ”ฅ Engagement Amplification - -### Cross-Platform Promotion -- **Twitter**: Share LinkedIn posts with technical focus -- **GitHub**: Reference LinkedIn discussions in issues -- **Reddit**: Share insights in relevant communities -- **Email**: Include LinkedIn content in newsletters - -### Community Building -- **Tagging**: Mention key influencers and companies -- **Groups**: Share in C#/.NET LinkedIn groups -- **Comments**: Engage in related discussions -- **DMs**: Personal follow-up with engaged users - -### Content Repurposing -- **Blog Posts**: Expand LinkedIn posts into detailed articles -- **Videos**: Create video tutorials based on popular content -- **Presentations**: Develop slide decks from key concepts -- **Case Studies**: Document success stories in detail - -## ๐ŸŽฏ Audience Targeting - -### Engineers -- **Content Focus**: Code examples, performance, features -- **Timing**: Lunch hours, evenings -- **Language**: Technical, direct, code-heavy -- **CTA**: Try now, contribute, share feedback - -### Decision-Makers -- **Content Focus**: ROI, productivity, business value -- **Timing**: Early morning, business hours -- **Language**: Business-focused, metrics-driven -- **CTA**: Schedule demo, discuss implementation - -### Recruiters -- **Content Focus**: Expertise, achievements, capabilities -- **Timing**: Business hours, afternoon -- **Language**: Professional, achievement-oriented -- **CTA**: Portfolio review, consulting discussion - -## ๐Ÿš€ Optimization Strategy - -### A/B Testing -- **Headlines**: Test different opening hooks -- **CTAs**: Vary call-to-action language -- **Timing**: Experiment with posting times -- **Content**: Mix technical and business content - -### Performance Analysis -- **Engagement Rate**: Track likes/comments per follower -- **Click-Through Rate**: Monitor link clicks -- **Conversion Rate**: Track demo requests per post -- **Follower Growth**: Measure audience expansion - -### Iterative Improvement -- **Weekly Reviews**: Analyze what resonates -- **Content Adjustments**: Refine based on feedback -- **Strategy Pivots**: Adapt to market response -- **Resource Allocation**: Focus on high-performing content - -## ๐ŸŽ Special Initiatives - -### Launch Week Boost -- **LinkedIn Ads**: Target C# developers and decision-makers -- **Influencer Outreach**: Partner with .NET influencers -- **Community Contests**: Reward early adopters and contributors -- **Media Outreach**: Tech blog and publication coverage - -### Ongoing Engagement -- **Weekly Tips**: Regular PowerCSharp tips and tricks -- **Monthly Webinars**: Deep-dive technical sessions -- **Quarterly Updates**: Roadmap and milestone announcements -- **Annual Summit**: Community conference and networking - -## ๐Ÿ“ž Next Steps - -1. **Schedule Posts**: Set up LinkedIn content calendar -2. **Prepare Assets**: Create images, code snippets, links -3. **Set Up Tracking**: Implement analytics and monitoring -4. **Engagement Plan**: Prepare response templates and FAQ -5. **Follow-Up Strategy**: Plan nurture sequences for each audience - -This comprehensive LinkedIn strategy will maximize the impact of PowerCSharp v1.0.0's launch and establish a strong foundation for continued growth and community building. diff --git a/marketing/medium-posts.md b/marketing/medium-posts.md deleted file mode 100644 index d006c2e..0000000 --- a/marketing/medium-posts.md +++ /dev/null @@ -1,1759 +0,0 @@ -# Medium Posts for PowerCSharp v1.0.0 - -## ๐Ÿ“‹ Medium Strategy Overview - -**Platform Advantages:** -- **Open Access**: No registration required for reading -- **SEO Friendly**: High discoverability through search engines -- **Professional Audience**: Similar to LinkedIn but more accessible -- **Long-Form Content**: Supports detailed technical articles -- **Monetization Potential**: Medium Partner Program eligibility - -**Content Strategy:** -- **Technical Depth**: More detailed than LinkedIn posts -- **SEO Optimized**: Keywords for discoverability -- **Story-Driven**: Personal experience and journey -- **Educational Focus**: Teach while promoting - ---- - -## ๐Ÿš€ Post 1: v1.0.0 Launch Announcement - -**Title:** PowerCSharp v1.0.0: After 20 Years of C# Development, I Finally Solved These Annoying Problems - -**Subtitle:** A comprehensive C# extensions library that eliminates boilerplate code, enhances security, and boosts developer productivity - -**Tags:** CSharp, DotNet, SoftwareDevelopment, Productivity, OpenSource, Programming - -**Body:** - -# PowerCSharp v1.0.0: After 20 Years of C# Development, I Finally Solved These Annoying Problems - -After spending over two decades writing C# codeโ€”from .NET Framework 1.1 all the way to .NET 8โ€”I've accumulated quite a collection of pet peeves. You know the ones I'm talking about: - -- **String manipulation gymnastics**: `string.IsNullOrEmpty()` vs `string.IsNullOrWhiteSpace()`, trimming, case conversions... -- **Collection null checks**: The dreaded `if (list != null && list.Count > 0)` dance -- **HTTP status code spaghetti**: `if (statusCode >= 200 && statusCode < 300)` gets old fast -- **JSON parsing nightmares**: Try-catch blocks everywhere for safe deserialization -- **DateTime calculations**: Getting age, checking if it's weekend, finding month boundaries... - -Sound familiar? I've been there. Countless times. - -## My Breaking Point - -Last year, while mentoring a group of junior developers, I watched them write the same boilerplate code I'd written thousands of times before. That's when it hit me: Why are we still solving these problems in 2025? - -With 20+ years of experience, I had developed personal utility libraries, helper methods, and extension patterns. But they were scattered across projects, inconsistent, andโ€”let's be honestโ€”not always well-tested. - -So I decided to do something about it. I poured my experience into PowerCSharpโ€”a comprehensive suite of NuGet packages that solves these everyday C# headaches. - -## What Makes PowerCSharp Different? - -Unlike other utility libraries, PowerCSharp isn't just a random collection of methods. Each extension is battle-tested from real-world production code and addresses specific pain points I've encountered throughout my career. - -Let me show you what I mean. - -## String Extensions That Just Make Sense - -**Before: The verbose way** -```csharp -string input = GetUserInput(); -if (string.IsNullOrWhiteSpace(input)) -{ - input = input.Trim(); - if (input.Length > 0) - { - // Process the input - } -} -``` - -**After: Clean and expressive** -```csharp -string input = GetUserInput(); -if (!input.IsNullOrWhiteSpace()) -{ - string clean = input.SafeSubstring(0, 50); - string titleCase = clean.ToTitleCase(); - // Process the input -} -``` - -But here's my favoriteโ€”URL validation that actually works: - -```csharp -// No more regex headaches! -bool isValid = "https://example.com".IsValidUrl(); // true -bool isInvalid = "not-a-url".IsValidUrl(); // false -``` - -## Collection Extensions That Reduce Boilerplate - -**Before: The null-check dance** -```csharp -var users = GetUsers(); -if (users != null && users.Count > 0) -{ - var firstUser = users[0]; - // ... -} -``` - -**After: Safe and concise** -```csharp -var users = GetUsers(); -if (!users.IsNullOrEmpty()) -{ - var firstUser = users.FirstOrDefaultSafe(null); - // ... -} -``` - -## HTTP Status Code Extensions - -This one saves me so much time in API development: - -**Before: Magic numbers everywhere** -```csharp -if (response.StatusCode >= 200 && response.StatusCode < 300) -{ - // Success handling -} -else if (response.StatusCode >= 400 && response.StatusCode < 500) -{ - // Client error handling -} -``` - -**After: Self-documenting code** -```csharp -if (response.StatusCode.IsSuccessful()) -{ - // Success handling -} -else if (response.StatusCode.IsClientError()) -{ - // Client error handling -} -``` - -## Dynamic LINQ That Actually Works - -One of the most powerful featuresโ€”dynamic query building: - -```csharp -// Build filters from user input -string filterExpression = "Age > 18 && Name.Contains('John')"; -var predicate = filterExpression.GetExpressionDelegate(); -var adults = people.Where(predicate); - -// Dynamic ordering too -string orderExpression = "Name DESC, Age ASC"; -var ordered = people.OrderByDynamic(orderExpression); -``` - -## The PowerCSharp Package Family - -I organized PowerCSharp into focused packages so you only install what you need: - -### ๐Ÿ“ฆ PowerCSharp.Extensions -The star of the showโ€”comprehensive extension methods for: -- **Strings**: Validation, manipulation, case conversion -- **Collections**: Safe operations, paging, filtering -- **HTTP**: Status code helpers, URI manipulation, request cloning -- **LINQ**: Dynamic queries, expression parsing -- **JSON/XML**: Safe serialization, case-insensitive access -- **DateTime**: Age calculation, weekend checks, month boundaries -- **Objects**: Property copying, null checking, type operations - -### ๐Ÿ“ฆ PowerCSharp.Utilities -Production-ready utilities: -- **ValidationHelper**: Email, URL, numeric validation -- **FileHelper**: Safe file operations with proper error handling -- **MathHelper**: Common mathematical operations - -### ๐Ÿ“ฆ PowerCSharp.Helpers -Specialized helpers: -- **JsonHelper**: Safe JSON operations with pretty printing -- **CryptoHelper**: SHA256, MD5, random string generation -- **EnvironmentHelper**: Environment variable management - -### ๐Ÿ“ฆ PowerCSharp.Core -The foundation that ties everything togetherโ€”centralized interfaces and models with zero external dependencies. - -### ๐Ÿ“ฆ PowerCSharp.Extensions.AspNetCore -Web-specific utilities for ASP.NET Core applications: -- **HTTP utilities**: Enhanced request/response handling -- **Configuration helpers**: Better configuration binding -- **Middleware utilities**: Common middleware patterns - -### ๐Ÿ“ฆ PowerCSharp.Compatibility -Legacy support for .NET Framework 4.6.2+ applications. - -## Real-World Impact - -Here's a recent example from a production application: - -**Before PowerCSharp: 47 lines of code** -```csharp -public IHttpActionResult GetUser(int id) -{ - try - { - if (id <= 0) - { - return BadRequest("Invalid ID"); - } - - var user = _repository.GetUserById(id); - if (user == null) - { - return NotFound(); - } - - var json = JsonConvert.SerializeObject(user); - return Ok(json); - } - catch (Exception ex) - { - return InternalServerError(ex); - } -} -``` - -**After PowerCSharp: 12 lines of code** -```csharp -public IHttpActionResult GetUser(int id) -{ - if (id <= 0) - { - return BadRequest("Invalid ID"); - } - - var user = _repository.GetUserById(id); - if (user == null) - { - return NotFound(); - } - - return Ok(JsonHelper.SafeSerialize(user)); -} -``` - -75% less code and much more readable. - -## Built for Production - -I didn't just throw this together. PowerCSharp includes: -- โœ… **Comprehensive unit tests** (90%+ coverage) -- โœ… **.NET 8 and .NET Standard 2.0 support** -- โœ… **MIT License** (commercial-friendly) -- โœ… **Symbol packages** for debugging -- โœ… **Continuous integration** with GitHub Actions - -## Getting Started - -Installation is simple: - -```bash -# Install the complete suite -dotnet add package PowerCSharp.Core -dotnet add package PowerCSharp.Extensions -dotnet add package PowerCSharp.Utilities -dotnet add package PowerCSharp.Helpers - -# Or just what you need -dotnet add package PowerCSharp.Extensions -``` - -Then start using: - -```csharp -using PowerCSharp.Extensions; -using PowerCSharp.Utilities; -using PowerCSharp.Helpers; - -// Your code becomes instantly cleaner! -``` - -## Why I'm Sharing This - -After 20+ years in this industry, I believe in giving back. These aren't just random utility methodsโ€”they're solutions to problems I've encountered thousands of times in production environments. - -PowerCSharp represents the culmination of my C# experience, distilled into reusable, well-tested extensions that I hope will save you the same headaches they've saved me. - -## What's Next? - -I'm already working on: -- **PowerCSharp Features** - Feature flags package for runtime configuration -- **Clean Architecture Template** - Complete starter repository -- **Entity Framework Core extensions** - More database utilities -- **Blazor-specific utilities** - Web component helpers - -## Join the Community - -I'm actively maintaining PowerCSharp and welcome contributions: -- ๐Ÿ› [Found a bug?](https://github.com/marioarce/PowerCSharp/issues) -- ๐Ÿ’ก [Feature ideas?](https://github.com/marioarce/PowerCSharp/discussions) -- ๐Ÿค [Want to contribute?](https://github.com/marioarce/PowerCSharp/blob/main/CONTRIBUTING.md) - -## Your Turn - -I'd love to hear from you: -- What are your biggest C# pet peeves? -- Which extensions would you find most useful? -- Have you built similar utilities? - -Drop a comment below or hit me up on [GitHub](https://github.com/marioarce/PowerCSharp). Let's make C# development better together! - ---- - -**Try PowerCSharp today:** -```bash -dotnet add package PowerCSharp.Extensions -``` - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -**NuGet:** [PowerCSharp.Extensions](https://www.nuget.org/packages/PowerCSharp.Extensions) - -*PowerCSharp v1.0.0 - Making C# development more powerful, one extension at a time.* - ---- - -## ๐Ÿ” Post 2: Technical Deep Dive - Dynamic LINQ - -**Title:** Dynamic LINQ in Production: How I Built Runtime Query Parsing That Doesn't Suck - -**Subtitle:** Building secure, performant dynamic LINQ expressions for enterprise applications - -**Tags:** CSharp, DynamicLINQ, DotNet, Performance, Security, SoftwareArchitecture - -**Body:** - -# Dynamic LINQ in Production: How I Built Runtime Query Parsing That Doesn't Suck - -Dynamic LINQ is one of those features that sounds amazing in theory but often falls short in practice. The promise of building queries from user input is compelling, but the reality often involves security vulnerabilities, performance issues, and maintenance nightmares. - -After implementing dynamic LINQ systems for multiple enterprise applications over the past decade, I've learned what works and what doesn't. Today, I'm sharing the approach I've refined for PowerCSharpโ€”one that balances flexibility with security and performance. - -## The Dynamic LINQ Problem Space - -Let's start with the common scenarios where dynamic LINQ shines: - -### Use Case 1: Advanced Search Systems -Users want to search by multiple criteria: -```csharp -// User input: "Age > 25 && Status == 'Active' && Name.Contains('John')" -string filterExpression = "Age > 25 && Status == 'Active' && Name.Contains('John')"; -``` - -### Use Case 2: Admin Panel Filtering -Administrators need flexible data filtering: -```csharp -// Dynamic filtering from UI controls -var filters = new List(); -if (minAge.HasValue) -{ - filters.Add($"Age >= {minAge}"); -} -if (status != null) -{ - filters.Add($"Status == \"{status}\""); -} -string expression = string.Join(" && ", filters); -``` - -### Use Case 3: API Endpoint Flexibility -REST APIs need to support various query parameters: -```csharp -// GET /api/users?filter=Age>18&sort=Name DESC, Age ASC -string filter = "Age > 18"; -string sort = "Name DESC, Age ASC"; -``` - -## The Challenges - -### 1. Security Vulnerabilities -The most dangerous aspect of dynamic LINQ is injection attacks: - -```csharp -// Malicious input -string maliciousInput = "Age > 0 || 1 == 1"; // Bypasses age filter -string injectionAttack = "Age > 0; DROP TABLE Users; --"; // SQL injection style - -// Without proper validation, these can execute successfully -``` - -### 2. Performance Overhead -Poorly implemented dynamic LINQ can be 10-20x slower than compiled expressions: - -```csharp -// Benchmark results (100,000 records) -Static LINQ: 45ms -Poor Dynamic LINQ: 890ms -Optimized Dynamic LINQ: 52ms -``` - -### 3. Error Handling -Invalid expressions should fail gracefully: -```csharp -string invalidExpression = "Age > 'twenty'"; // Type mismatch -string malformedExpression = "Age > && Name"; // Syntax error -``` - -## PowerCSharp's Approach - -### Core Implementation - -The foundation of PowerCSharp's dynamic LINQ is built on System.Linq.Dynamic.Core, but with enterprise-grade enhancements: - -```csharp -public static class DynamicExpressionExtensions -{ - private static readonly ConcurrentDictionary _expressionCache = new(); - - public static Func GetExpressionDelegate(this string stringExpression) - { - var cacheKey = $"{typeof(T).Name}_{stringExpression}"; - - return (Func)_expressionCache.GetOrAdd(cacheKey, key => - { - // Validate expression before parsing - if (!IsValidExpression(stringExpression)) - throw new ArgumentException($"Invalid expression: {stringExpression}"); - - // Parse and compile the expression - var parameter = Expression.Parameter(typeof(T), "x"); - var lambda = DynamicExpressionParser.ParseLambda( - new ParsingConfig(), false, stringExpression, parameter); - - return lambda.Compile(); - }); - } - - public static IQueryable Where(this IQueryable source, string expression) - { - var predicate = expression.GetExpressionDelegate(); - return source.Where(predicate); - } -} -``` - -### Security Validation - -Security is paramount. Here's the validation approach: - -```csharp -private static bool IsValidExpression(string expression) -{ - // Basic security checks - if (expression.Contains("new ") || - expression.Contains("typeof(") || - expression.Contains("DateTime.Now") || - expression.Contains(";") || - expression.Contains("--")) - { - return false; - } - - // Type-specific validation - var allowedProperties = typeof(T).GetProperties() - .Select(p => p.Name) - .ToHashSet(StringComparer.OrdinalIgnoreCase); - - // Check if all referenced properties exist - var propertyPattern = @"\b([a-zA-Z_][a-zA-Z0-9_]*)\b"; - var matches = Regex.Matches(expression, propertyPattern); - - foreach (Match match in matches) - { - var propertyName = match.Groups[1].Value; - if (!allowedProperties.Contains(propertyName)) - { - return false; - } - } - - return true; -} -``` - -### Performance Optimization - -Three key optimizations make PowerCSharp's dynamic LINQ fast: - -#### 1. Expression Caching -```csharp -// Cache compiled expressions to avoid repeated compilation -private static readonly ConcurrentDictionary _expressionCache = new(); - -// First call: 8ms compilation + 2ms execution -// Subsequent calls: 0ms compilation + 2ms execution -``` - -#### 2. Memory Pool Allocation -```csharp -// Use ArrayPool for string operations to reduce GC pressure -private static readonly ArrayPool CharArrayPool = ArrayPool.Shared; - -public static string OptimizeExpression(string expression) -{ - var buffer = CharArrayPool.Rent(expression.Length); - try - { - // Process expression with rented buffer - // ... - return optimizedExpression; - } - finally - { - CharArrayPool.Return(buffer); - } -} -``` - -#### 3. Compiled Expression Trees -```csharp -// Pre-compile common expression patterns -private static readonly Dictionary CommonPatterns = new() -{ - ["GreaterThan"] = Expression.GreaterThan, - ["LessThan"] = Expression.LessThan, - ["Contains"] = typeof(string).GetMethod("Contains") -}; -``` - -## Real-World Implementation - -### Advanced Search Service - -Here's how PowerCSharp's dynamic LINQ works in a production search system: - -```csharp -public class AdvancedSearchService -{ - private readonly IRepository _userRepository; - - public SearchResults SearchUsers(SearchCriteria criteria) - { - var query = _userRepository.GetAll(); - - // Apply dynamic filtering - if (!string.IsNullOrEmpty(criteria.FilterExpression)) - { - query = query.Where(criteria.FilterExpression); - } - - // Apply dynamic sorting - if (!string.IsNullOrEmpty(criteria.SortExpression)) - { - query = query.OrderByDynamic(criteria.SortExpression); - } - - // Apply pagination - var page = query - .Skip((criteria.Page - 1) * criteria.PageSize) - .Take(criteria.PageSize) - .ToList(); - - return new SearchResults - { - Items = page, - TotalCount = query.Count(), - Page = criteria.Page, - PageSize = criteria.PageSize - }; - } -} -``` - -### API Controller - -```csharp -[ApiController] -[Route("api/[controller]")] -public class UsersController : ControllerBase -{ - private readonly AdvancedSearchService _searchService; - - [HttpGet] - public IActionResult GetUsers( - [FromQuery] string filter = null, - [FromQuery] string sort = "Name ASC", - [FromQuery] int page = 1, - [FromQuery] int pageSize = 20) - { - try - { - var criteria = new SearchCriteria - { - FilterExpression = filter, - SortExpression = sort, - Page = page, - PageSize = pageSize - }; - - var results = _searchService.SearchUsers(criteria); - return Ok(results); - } - catch (ArgumentException ex) - { - return BadRequest($"Invalid filter expression: {ex.Message}"); - } - } -} -``` - -## Performance Benchmarks - -I tested PowerCSharp's dynamic LINQ against various approaches with 100,000 User records: - -| Method | Execution Time | Memory Usage | First Compile | Subsequent | -|--------|----------------|--------------|--------------|------------| -| Static LINQ | 45ms | 12MB | N/A | 45ms | -| PowerCSharp Dynamic | 52ms | 14MB | 8ms | 52ms | -| Reflection-based | 890ms | 28MB | N/A | 890ms | -| Manual Expression | 180ms | 22MB | 15ms | 180ms | -| Entity Framework Dynamic | 120ms | 18MB | 25ms | 120ms | - -**Key Insights:** -- Only 15% overhead compared to static LINQ -- 95% faster than reflection-based approaches -- Compilation cost is one-time with caching - -## Advanced Patterns - -### 1. Type-Safe Query Builders - -```csharp -public class QueryBuilder -{ - private readonly List _filters = new(); - private readonly List _orderings = new(); - - public QueryBuilder Where(string property, object value, string operation = "==") - { - _filters.Add($"{property} {operation} \"{value}\""); - return this; - } - - public QueryBuilder WhereGreaterThan(string property, object value) - { - return Where(property, value, ">"); - } - - public QueryBuilder OrderBy(string property, bool descending = false) - { - _orderings.Add($"{property} {(descending ? "DESC" : "ASC")}"); - return this; - } - - public IQueryable Apply(IQueryable source) - { - if (_filters.Any()) - { - var filterExpression = string.Join(" && ", _filters); - source = source.Where(filterExpression); - } - - if (_orderings.Any()) - { - var sortExpression = string.Join(", ", _orderings); - source = source.OrderByDynamic(sortExpression); - } - - return source; - } -} -``` - -Usage: -```csharp -var users = _repository.GetAll() - .Where("Age", 25, ">") - .Where("Status", "Active") - .OrderBy("Name") - .Apply(); -``` - -### 2. Expression Templates - -```csharp -public class ExpressionTemplate -{ - private static readonly Dictionary Templates = new() - { - ["DateRange"] = "CreatedDate >= \"{Start}\" && CreatedDate <= \"{End}\"", - ["TextSearch"] = "Name.Contains(\"{Search}\") || Description.Contains(\"{Search}\")", - ["NumericRange"] = "Value >= {Min} && Value <= {Max}" - }; - - public static string Render(string template, Dictionary parameters) - { - var expression = Templates[template]; - - foreach (var param in parameters) - { - expression = expression.Replace($"{{{param.Key}}}", param.Value.ToString()); - } - - return expression; - } -} -``` - -Usage: -```csharp -var dateRange = ExpressionTemplate.Render("DateRange", new() -{ - ["Start"] = startDate.ToString("yyyy-MM-dd"), - ["End"] = endDate.ToString("yyyy-MM-dd") -}); -``` - -## Testing Strategy - -Dynamic LINQ requires comprehensive testing: - -### Unit Tests - -```csharp -[Test] -public void DynamicLinq_ShouldFilterCorrectly() -{ - // Arrange - var people = new List - { - new() { Name = "John", Age = 25 }, - new() { Name = "Jane", Age = 30 }, - new() { Name = "Bob", Age = 20 } - }; - - // Act - var expression = "Age > 21 && Name.Contains(\"J\")"; - var result = people.AsQueryable().Where(expression).ToList(); - - // Assert - result.Should().HaveCount(2); - result.Should().Contain(p => p.Name == "John"); - result.Should().Contain(p => p.Name == "Jane"); -} -``` - -### Security Tests - -```csharp -[Test] -public void DynamicLinq_ShouldRejectMaliciousExpressions() -{ - // Arrange - var maliciousExpressions = new[] - { - "Age > 0 || 1 == 1", - "Age > 0; DROP TABLE Users; --", - "new { Name = \"Hack\" }", - "typeof(User)", - "DateTime.Now" - }; - - // Act & Assert - foreach (var expression in maliciousExpressions) - { - Action act = () => expression.GetExpressionDelegate(); - act.Should().Throw(); - } -} -``` - -### Performance Tests - -```csharp -[Test] -public void DynamicLinq_ShouldPerformWell() -{ - // Arrange - var data = Enumerable.Range(1, 100000) - .Select(i => new User { Name = $"User{i}", Age = i % 50 }) - .ToList(); - - var stopwatch = Stopwatch.StartNew(); - - // Act - var expression = "Age > 25 && Name.Contains(\"1\")"; - var result = data.AsQueryable().Where(expression).ToList(); - - stopwatch.Stop(); - - // Assert - stopwatch.ElapsedMilliseconds.Should().BeLessThan(100); - result.Should().NotBeEmpty(); -} -``` - -## Best Practices - -### 1. Always Validate Input -```csharp -// Never trust user input directly -var safeExpression = SanitizeExpression(userInput); -if (!IsValidExpression(safeExpression)) -{ - throw new SecurityException("Invalid filter expression"); -} -``` - -### 2. Use Whitelisting -```csharp -// Only allow known properties and operations -var allowedProperties = new[] { "Name", "Age", "Status", "CreatedDate" }; -var allowedOperations = new[] { "==", "!=", ">", "<", ">=", "<=", "Contains" }; -``` - -### 3. Implement Rate Limiting -```csharp -// Prevent abuse with rate limiting -if (!_rateLimiter.IsAllowed(user.Id)) -{ - throw new RateLimitExceededException("Too many requests"); -} -``` - -### 4. Log Security Events -```csharp -// Log suspicious activity -if (IsSuspiciousExpression(expression)) -{ - _logger.LogWarning("Suspicious dynamic LINQ expression: {Expression} from user {UserId}", - expression, user.Id); -} -``` - -## Common Pitfalls to Avoid - -### 1. Don't Allow Arbitrary Code Execution -```csharp -// BAD: Allows method calls -string malicious = "File.Delete(\"important.txt\")"; - -// GOOD: Only property access and basic operations -string safe = "Age > 25 && Name.Contains(\"John\")"; -``` - -### 2. Don't Ignore Type Safety -```csharp -// BAD: Type mismatch at runtime -string invalid = "Age > \"twenty\""; // Runtime error - -// GOOD: Type-safe validation -if (!IsValidTypeExpression(expression)) -{ - throw new ArgumentException("Invalid expression"); -} -``` - -### 3. Don't Forget About Null Handling -```csharp -// BAD: Null reference exceptions -string problematic = "Name.Length > 5"; // Fails if Name is null - -// GOOD: Null-safe expressions -string safe = "Name != null && Name.Length > 5"; -``` - -## Monitoring and Observability - -Dynamic LINQ systems need monitoring: - -### Performance Metrics -```csharp -public class DynamicLinqMetrics -{ - public int ExpressionCacheHitCount { get; set; } - public int ExpressionCacheMissCount { get; set; } - public double AverageExecutionTime { get; set; } - public int SecurityViolationCount { get; set; } -} -``` - -### Health Checks -```csharp -public class DynamicLinqHealthCheck : IHealthCheck -{ - public Task CheckHealthAsync( - HealthCheckContext context, - CancellationToken cancellationToken = default) - { - try - { - // Test basic functionality - var testExpression = "Id > 0"; - var result = testExpression.GetExpressionDelegate(); - - return Task.FromResult(HealthCheckResult.Healthy()); - } - catch (Exception ex) - { - return Task.FromResult(HealthCheckResult.Unhealthy("Dynamic LINQ failed", ex)); - } - } -} -``` - -## Conclusion - -Dynamic LINQ doesn't have to be a security nightmare or performance bottleneck. With proper validation, caching, and monitoring, it can be a powerful tool for building flexible, user-friendly applications. - -PowerCSharp's approach balances flexibility with security, making dynamic LINQ production-ready for enterprise applications. The key is to treat user input as potentially malicious while providing the flexibility users need. - -**Try PowerCSharp's dynamic LINQ:** -```bash -dotnet add package PowerCSharp.Extensions -``` - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -**Documentation:** [Dynamic LINQ Guide](https://github.com/marioarce/PowerCSharp/docs/dynamic-linq.md) - ---- - -## ๐Ÿ”’ Post 3: Security-Focused Deep Dive - -**Title:** CWE-73 Compliant Path Operations: How I Built Bulletproof File Handling in C# - -**Subtitle:** Preventing directory traversal attacks with secure path validation and CWE-73 compliance - -**Tags:** CSharp, Security, CWE73, CyberSecurity, DotNet, SoftwareSecurity - -**Body:** - -# CWE-73 Compliant Path Operations: How I Built Bulletproof File Handling in C# - -Directory traversal attacks (CWE-73) are among the most common and dangerous vulnerabilities in web applications. According to OWASP, path traversal vulnerabilities rank #4 in the OWASP Top 10, responsible for countless data breaches and system compromises. - -After implementing secure file handling systems for financial applications, healthcare systems, and SaaS platforms, I've developed a comprehensive approach to CWE-73 compliance. Today, I'm sharing the battle-tested patterns I've implemented in PowerCSharp. - -## Understanding CWE-73: External Control of File Name or Path - -CWE-73 occurs when an application uses external input to construct file paths without proper validation or neutralization. Here's what that looks like in practice: - -### The Classic Vulnerability - -```csharp -// VULNERABLE CODE - DO NOT USE -public IActionResult GetFile(string fileName) -{ - var basePath = "/var/www/uploads"; - var fullPath = Path.Combine(basePath, fileName); - - // Attacker provides: "../../etc/passwd" - // Result: "/var/www/uploads/../../etc/passwd" = "/etc/passwd" - return PhysicalFile(fullPath, "application/octet-stream"); -} -``` - -### Real-World Attack Scenarios - -**Scenario 1: Configuration File Access** -``` -GET /files?fileName=../../appsettings.json -Result: Database connection strings exposed -``` - -**Scenario 2: System File Access** -``` -GET /files?fileName=../../../../etc/passwd -Result: System user accounts exposed -``` - -**Scenario 3: Log File Access** -``` -GET /files?fileName=../../logs/access.log -Result: User activity and IP addresses exposed -``` - -## The Security-First Solution - -PowerCSharp's approach to secure path operations involves multiple layers of defense: - -### Layer 1: Path Canonicalization - -```csharp -public static string CanonicalizePath(string path) -{ - try - { - // Resolve ".." and "." sequences - var fullPath = Path.GetFullPath(path); - - // Convert to consistent format - return fullPath.TrimEnd(Path.DirectorySeparatorChar); - } - catch (Exception ex) - { - throw new SecurityException($"Invalid path format: {path}", ex); - } -} -``` - -### Layer 2: Base Path Validation - -```csharp -public static void ValidatePathWithinBase(string basePath, string userPath) -{ - var canonicalBase = CanonicalizePath(basePath); - var canonicalUser = CanonicalizePath(Path.Combine(basePath, userPath)); - - // Ensure the user path is within the base path - if (!canonicalUser.StartsWith(canonicalBase, StringComparison.OrdinalIgnoreCase)) - { - throw new SecurityException( - $"Path traversal detected. Attempted access to: {canonicalUser}"); - } -} -``` - -### Layer 3: Character and Pattern Validation - -```csharp -private static readonly Regex DangerousPatterns = new Regex( - @"(\.\.[\\/])|[\\/]\.\.[\\/]|[\\/]\.\.$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - -public static bool ContainsDangerousPatterns(string path) -{ - return DangerousPatterns.IsMatch(path); -} - -private static readonly char[] InvalidCharacters = Path.GetInvalidFileNameChars() - .Concat(Path.GetInvalidPathChars()) - .Distinct() - .ToArray(); - -public static bool ContainsInvalidCharacters(string path) -{ - return path.IndexOfAny(InvalidCharacters) >= 0; -} -``` - -### Complete Secure Implementation - -```csharp -public static class SecurePathExtensions -{ - private static readonly object _lockObject = new object(); - private static readonly Dictionary _pathCache = new(); - - public static string CombineAndValidate(string basePath, string relativePath) - { - lock (_lockObject) - { - // Input validation - if (string.IsNullOrEmpty(basePath)) - throw new ArgumentException("Base path cannot be null or empty"); - - if (string.IsNullOrEmpty(relativePath)) - throw new ArgumentException("Relative path cannot be null or empty"); - - // Check for dangerous patterns - if (ContainsDangerousPatterns(relativePath)) - { - LogSecurityEvent("Dangerous path pattern detected", relativePath); - throw new SecurityException($"Dangerous path pattern detected: {relativePath}"); - } - - // Check for invalid characters - if (ContainsInvalidCharacters(relativePath)) - { - LogSecurityEvent("Invalid characters in path", relativePath); - throw new SecurityException($"Invalid characters in path: {relativePath}"); - } - - // Canonicalize paths - var canonicalBase = CanonicalizePath(basePath); - var combinedPath = Path.Combine(canonicalBase, relativePath); - var canonicalCombined = CanonicalizePath(combinedPath); - - // Validate path is within base directory - if (!canonicalCombined.StartsWith(canonicalBase, StringComparison.OrdinalIgnoreCase)) - { - LogSecurityEvent("Path traversal attempt detected", - $"Base: {canonicalBase}, Attempted: {canonicalCombined}"); - throw new SecurityException($"Path traversal detected: {relativePath}"); - } - - // Additional validation for specific scenarios - ValidateAdditionalConstraints(canonicalCombined); - - return canonicalCombined; - } - } - - private static void ValidateAdditionalConstraints(string path) - { - // Check for hidden files (Unix/Linux) - if (Path.GetFileName(path).StartsWith(".")) - { - LogSecurityEvent("Hidden file access attempt", path); - throw new SecurityException($"Access to hidden files not allowed: {path}"); - } - - // Check for system directories - var systemDirectories = new[] { "bin", "sbin", "etc", "var", "usr" }; - var pathParts = path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries); - - foreach (var part in pathParts) - { - if (systemDirectories.Contains(part.ToLowerInvariant())) - { - LogSecurityEvent("System directory access attempt", path); - throw new SecurityException($"Access to system directories not allowed: {path}"); - } - } - } - - private static void LogSecurityEvent(string eventType, string details) - { - // Log to security monitoring system - var logEntry = new - { - Timestamp = DateTime.UtcNow, - EventType = eventType, - Details = details, - Source = "SecurePathExtensions", - Severity = "High" - }; - - // Send to SIEM, security team, etc. - _logger.LogWarning("Security event: {EventType} - {Details}", eventType, details); - } -} -``` - -## Real-World Implementation Examples - -### Secure File Upload Service - -```csharp -public class SecureFileUploadService -{ - private readonly string _uploadBasePath; - private readonly ILogger _logger; - - public SecureFileUploadService(IConfiguration configuration, - ILogger logger) - { - _uploadBasePath = configuration["UploadBasePath"]; - _logger = logger; - - // Ensure base directory exists and is secure - Directory.CreateDirectory(_uploadBasePath); - SetSecurePermissions(_uploadBasePath); - } - - public async Task UploadFileAsync(IFormFile file, string subdirectory = "") - { - try - { - // Validate file - ValidateFile(file); - - // Generate safe filename - var safeFileName = GenerateSafeFileName(file.FileName); - - // Combine with subdirectory and validate - var relativePath = Path.Combine(subdirectory, safeFileName); - var fullPath = SecurePathExtensions.CombineAndValidate(_uploadBasePath, relativePath); - - // Ensure directory exists - Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); - - // Save file - using var stream = new FileStream(fullPath, FileMode.Create); - await file.CopyToAsync(stream); - - // Set secure permissions - SetSecureFilePermissions(fullPath); - - return relativePath; - } - catch (Exception ex) - { - _logger.LogError(ex, "File upload failed: {FileName}", file.FileName); - throw; - } - } - - private void ValidateFile(IFormFile file) - { - // File size validation - if (file.Length > 50 * 1024 * 1024) // 50MB limit - { - throw new SecurityException($"File too large: {file.Length} bytes"); - } - - // File type validation - var allowedExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".pdf", ".txt" }; - var fileExtension = Path.GetExtension(file.FileName).ToLowerInvariant(); - - if (!allowedExtensions.Contains(fileExtension)) - { - throw new SecurityException($"File type not allowed: {fileExtension}"); - } - - // Content type validation - var allowedContentTypes = new[] { "image/jpeg", "image/png", "image/gif", "application/pdf", "text/plain" }; - - if (!allowedContentTypes.Contains(file.ContentType.ToLowerInvariant())) - { - throw new SecurityException($"Content type not allowed: {file.ContentType}"); - } - } - - private string GenerateSafeFileName(string originalFileName) - { - var extension = Path.GetExtension(originalFileName); - var nameWithoutExtension = Path.GetFileNameWithoutExtension(originalFileName); - - // Remove dangerous characters - var safeName = Regex.Replace(nameWithoutExtension, @"[^a-zA-Z0-9_-]", ""); - - // Limit length - if (safeName.Length > 50) - { - safeName = safeName.Substring(0, 50); - } - - // Add timestamp to prevent collisions - var timestamp = DateTime.UtcNow.ToString("yyyyMMddHHmmss"); - var randomSuffix = Guid.NewGuid().ToString("N")[..8]; - - return $"{safeName}_{timestamp}_{randomSuffix}{extension}"; - } - - private void SetSecurePermissions(string path) - { - try - { - // On Unix/Linux systems, set restrictive permissions - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || - RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = "chmod", - Arguments = "750 " + path, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - } - }; - - process.Start(); - process.WaitForExit(); - } - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to set secure permissions: {Path}", path); - } - } -} -``` - -### Secure File Download Controller - -```csharp -[ApiController] -[Route("api/[controller]")] -public class FilesController : ControllerBase -{ - private readonly SecureFileUploadService _uploadService; - private readonly ILogger _logger; - - public FilesController(SecureFileUploadService uploadService, - ILogger logger) - { - _uploadService = uploadService; - _logger = logger; - } - - [HttpGet("{**path}")] - public IActionResult GetFile(string path) - { - try - { - // Validate the requested path - var basePath = _configuration["UploadBasePath"]; - var fullPath = SecurePathExtensions.CombineAndValidate(basePath, path); - - // Check if file exists - if (!System.IO.File.Exists(fullPath)) - { - _logger.LogWarning("File not found: {Path}", path); - return NotFound(); - } - - // Additional security checks - var fileInfo = new FileInfo(fullPath); - - // Check file size (prevent serving huge files) - if (fileInfo.Length > 100 * 1024 * 1024) // 100MB limit - { - _logger.LogWarning("File too large: {Path}, Size: {Size}", path, fileInfo.Length); - return StatusCode(StatusCodes.Status413PayloadTooLarge); - } - - // Determine content type - var contentType = GetContentType(fileInfo.Extension); - - // Log access for audit - _logger.LogInformation("File accessed: {Path} by {User}", path, User.Identity.Name); - - return PhysicalFile(fullPath, contentType, enableRangeProcessing: true); - } - catch (SecurityException ex) - { - _logger.LogWarning(ex, "Security violation accessing file: {Path}", path); - return StatusCode(StatusCodes.Status403Forbidden); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error accessing file: {Path}", path); - return StatusCode(StatusCodes.Status500InternalServerError); - } - } - - private string GetContentType(string extension) - { - return extension.ToLowerInvariant() switch - { - ".jpg" or ".jpeg" => "image/jpeg", - ".png" => "image/png", - ".gif" => "image/gif", - ".pdf" => "application/pdf", - ".txt" => "text/plain", - _ => "application/octet-stream" - }; - } -} -``` - -## Advanced Security Features - -### Rate Limiting for File Operations - -```csharp -public class FileOperationRateLimiter -{ - private readonly MemoryCache _cache; - private readonly TimeSpan _window = TimeSpan.FromMinutes(1); - private readonly int _maxRequestsPerWindow = 100; - - public bool IsAllowed(string userId, string operation) - { - var key = $"file_ops_{userId}_{operation}"; - var counter = _cache.GetOrCreate(key, entry => - { - entry.AbsoluteExpirationRelativeToNow = _window; - return 0; - }); - - if (counter >= _maxRequestsPerWindow) - { - return false; - } - - _cache.Set(key, counter + 1, _window); - return true; - } -} -``` - -### File Content Scanning - -```csharp -public class FileContentScanner -{ - private readonly ILogger _logger; - - public async Task ScanFileAsync(string filePath) - { - try - { - // Check for malware signatures - if (await ContainsMalwareSignatures(filePath)) - { - _logger.LogWarning("Malware detected in file: {Path}", filePath); - return false; - } - - // Check for suspicious content patterns - if (await ContainsSuspiciousPatterns(filePath)) - { - _logger.LogWarning("Suspicious content detected in file: {Path}", filePath); - return false; - } - - return true; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error scanning file: {Path}", filePath); - return false; - } - } - - private async Task ContainsMalwareSignatures(string filePath) - { - // Implement malware scanning logic - // This could integrate with antivirus APIs - return false; - } - - private async Task ContainsSuspiciousPatterns(string filePath) - { - var content = await System.IO.File.ReadAllTextAsync(filePath); - - // Check for suspicious patterns - var suspiciousPatterns = new[] - { - @"]*>", - @"javascript:", - @"eval\s*\(", - @"document\.", - @"window\." - }; - - foreach (var pattern in suspiciousPatterns) - { - if (Regex.IsMatch(content, pattern, RegexOptions.IgnoreCase)) - { - return true; - } - } - - return false; - } -} -``` - -## Testing Security Implementations - -### Security Unit Tests - -```csharp -[TestFixture] -public class SecurePathExtensionsTests -{ - private string _testBasePath; - - [SetUp] - public void Setup() - { - _testBasePath = Path.GetTempPath(); - } - - [Test] - public void CombineAndValidate_ShouldAllowValidPaths() - { - // Arrange - var relativePath = "documents/file.txt"; - - // Act - var result = SecurePathExtensions.CombineAndValidate(_testBasePath, relativePath); - - // Assert - result.Should().StartWith(_testBasePath); - result.Should().EndWith("documents/file.txt"); - } - - [Test] - public void CombineAndValidate_ShouldBlockPathTraversal() - { - // Arrange - var maliciousPaths = new[] - { - "../../etc/passwd", - "..\\..\\windows\\system32\\config\\sam", - "/etc/passwd", - "documents/../../../etc/shadow" - }; - - // Act & Assert - foreach (var path in maliciousPaths) - { - Action act = () => SecurePathExtensions.CombineAndValidate(_testBasePath, path); - act.Should().Throw(); - } - } - - [Test] - public void CombineAndValidate_ShouldBlockInvalidCharacters() - { - // Arrange - var invalidPaths = new[] - { - "file.txt", - "file|name.txt", - "file?.txt", - "file*.txt", - "file\".txt" - }; - - // Act & Assert - foreach (var path in invalidPaths) - { - Action act = () => SecurePathExtensions.CombineAndValidate(_testBasePath, path); - act.Should().Throw(); - } - } -} -``` - -### Integration Tests - -```csharp -[TestFixture] -public class FileSecurityIntegrationTests -{ - private TestServer _server; - private HttpClient _client; - - [SetUp] - public void Setup() - { - var builder = new WebHostBuilder() - .ConfigureServices(services => - { - services.AddSingleton(); - services.AddControllers(); - }) - .Configure(app => - { - app.UseRouting(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - }); - - _server = new TestServer(builder); - _client = _server.CreateClient(); - } - - [Test] - public async Task FileUpload_ShouldBlockMaliciousFiles() - { - // Arrange - var content = new ByteArrayContent(Encoding.UTF8.GetBytes("test content")); - content.Headers.ContentType = new MediaTypeHeaderValue("text/plain"); - - var form = new MultipartFormDataContent(); - form.Add(content, "file", "../../etc/passwd"); - - // Act - var response = await _client.PostAsync("/api/files/upload", form); - - // Assert - response.StatusCode.Should().Be(HttpStatusCode.BadRequest); - } - - [Test] - public async Task FileDownload_ShouldBlockPathTraversal() - { - // Act - var response = await _client.GetAsync("/api/files/../../etc/passwd"); - - // Assert - response.StatusCode.Should().Be(HttpStatusCode.Forbidden); - } -} -``` - -## Monitoring and Alerting - -### Security Dashboard - -```csharp -public class SecurityMetrics -{ - public int PathTraversalAttempts { get; set; } - public int InvalidCharacterAttempts { get; set; } - public int MaliciousFileUploads { get; set; } - public int TotalFileOperations { get; set; } - public Dictionary AttackSources { get; set; } = new(); - - public double GetSecurityScore() - { - if (TotalFileOperations == 0) - { - return 100.0; - } - - var violations = PathTraversalAttempts + InvalidCharacterAttempts + MaliciousFileUploads; - return Math.Max(0, 100.0 - (violations * 100.0 / TotalFileOperations)); - } -} -``` - -### Real-time Alerting - -```csharp -public class SecurityAlertService -{ - private readonly ILogger _logger; - private readonly IEmailSender _emailSender; - - public async Task HandleSecurityEvent(SecurityEvent securityEvent) - { - // Log the event - _logger.LogWarning("Security event: {Type} - {Details}", - securityEvent.EventType, securityEvent.Details); - - // Check if this requires immediate attention - if (securityEvent.Severity == "Critical") - { - await SendCriticalAlert(securityEvent); - } - - // Update security metrics - UpdateSecurityMetrics(securityEvent); - - // Check for attack patterns - await AnalyzeAttackPatterns(securityEvent); - } - - private async Task SendCriticalAlert(SecurityEvent securityEvent) - { - var message = $@" -Critical Security Alert: -Type: {securityEvent.EventType} -Details: {securityEvent.Details} -Timestamp: {securityEvent.Timestamp} -Source IP: {securityEvent.SourceIP} -User: {securityEvent.UserId} - "; - - await _emailSender.SendEmailAsync( - "security@company.com", - "Critical Security Alert", - message); - } -} -``` - -## Best Practices Summary - -### 1. Defense in Depth -- Multiple validation layers -- Input sanitization -- Output encoding -- Access controls - -### 2. Principle of Least Privilege -- Minimal file permissions -- Restricted directory access -- Time-limited access tokens - -### 3. Comprehensive Logging -- All file operations logged -- Security events monitored -- Regular audit trails - -### 4. Regular Security Testing -- Penetration testing -- Vulnerability scanning -- Code security reviews - -### 5. Monitoring and Response -- Real-time alerting -- Automated blocking -- Incident response procedures - -## Conclusion - -CWE-73 compliance isn't just about preventing directory traversal attacksโ€”it's about building a comprehensive security posture around file operations. PowerCSharp's secure path extensions provide a solid foundation, but they're most effective when combined with proper security architecture, monitoring, and incident response procedures. - -The key is to treat all external input as potentially malicious while providing the functionality users need. With proper validation, logging, and monitoring, you can build file handling systems that are both secure and user-friendly. - -**Try PowerCSharp's secure path operations:** -```bash -dotnet add package PowerCSharp.Extensions -``` - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -**Security Documentation:** [Security Guide](https://github.com/marioarce/PowerCSharp/docs/security.md) - ---- - -## ๐Ÿ“… Medium Posting Strategy - -### Week 1: Launch Week -- **Post 1**: v1.0.0 Launch Announcement (Monday) -- **Promotion**: Share on LinkedIn, Twitter, relevant communities - -### Week 2: Technical Deep Dive -- **Post 2**: Dynamic LINQ Deep Dive (Wednesday) -- **Promotion**: Technical communities, developer forums - -### Week 3: Security Focus -- **Post 3**: CWE-73 Security Deep Dive (Friday) -- **Promotion**: Security communities, DevSecOps groups - -### Week 4-8: Additional Posts -- Performance optimization -- Architecture patterns -- Migration stories -- Community contributions - -### Ongoing: Engagement -- Respond to all comments -- Share insights from discussions -- Reference posts in future content -- Cross-promote with other platforms - -## ๐ŸŽฏ Medium SEO Strategy - -### Keywords to Target -- "C# extensions" -- "PowerCSharp library" -- "Dynamic LINQ C#" -- "C# security practices" -- "C# productivity tools" -- "C# file operations security" - -### SEO Best Practices -- Include keywords in titles and headings -- Use proper heading structure (H1, H2, H3) -- Include code examples with syntax highlighting -- Add internal links between posts -- Optimize images with alt text -- Include meta descriptions - -### Promotion Channels -- LinkedIn articles -- Twitter threads -- Reddit (when appropriate) -- Developer forums -- Email newsletters -- Technical communities - -## ๐Ÿ“Š Success Metrics - -### Primary KPIs -- **Views**: Article readership -- **Read Ratio**: % of viewers who finish articles -- **Claps**: Engagement metric -- **Follows**: New followers gained -- **External Clicks**: Links to GitHub/NuGet - -### Secondary KPIs -- **Comments**: Discussion engagement -- **Shares**: Social media sharing -- **SEO Rankings**: Search engine visibility -- **Conversion**: NuGet downloads from Medium - -### Benchmarks -- **Week 1**: 500+ views, 50+ claps per article -- **Week 2**: 1,000+ views, 100+ claps per article -- **Month 1**: 5,000+ total views, 200+ new followers -- **Month 3**: 15,000+ total views, 500+ total claps diff --git a/marketing/reddit-compliant-strategy.md b/marketing/reddit-compliant-strategy.md deleted file mode 100644 index ece80f2..0000000 --- a/marketing/reddit-compliant-strategy.md +++ /dev/null @@ -1,267 +0,0 @@ -# Reddit-Compliant Strategy for PowerCSharp v1.0.0 - -## ๐Ÿ“‹ Reddit Rules Summary - -### Core Anti-Spam Rules -- **10% Rule**: Max 10% of activity can be self-promotional -- **No Pure Promotion**: Can't only share your own content -- **No Vote Requesting**: Can't ask for upvotes or engagement -- **No Multi-Account Promotion**: One account per person -- **Transparency Required**: Must disclose affiliation - -### Subreddit-Specific Rules -- **No Self-Promotion**: Many subreddits ban all promotion -- **Designated Threads**: Some allow promotion only in specific threads -- **Karma/Age Requirements**: Minimum participation before posting -- **Flair/Disclosure**: Required for promotional content - -## ๐Ÿšจ Current Posts Risk Assessment - -**Post 1 (Launch Announcement)**: ๐Ÿ”ด HIGH RISK -- 100% promotional content -- No community participation context -- Direct product announcement - -**Post 2 (Technical Deep Dive)**: ๐ŸŸก MEDIUM RISK -- More technical value -- Still primarily promotional -- Better but needs adjustment - -**Post 3-7**: Similar risk levels - -## โœ… Reddit-Compliant Approach - -### Phase 1: Community Building (2-4 weeks) -**Goal**: Establish genuine participation before any promotion - -**Actions:** -- Comment on 10+ posts per day in r/csharp, r/dotnet, r/programming -- Answer questions about C#, .NET, and development challenges -- Share relevant third-party content (articles, tools, news) -- Participate in discussions without mentioning PowerCSharp -- Build karma and account reputation - -**Participation Strategy:** -```markdown -Week 1: 100% participation, 0% promotion -- Comment on technical discussions -- Help beginners with C# questions -- Share interesting .NET articles -- Build 50+ karma - -Week 2: 90% participation, 10% value-sharing -- Continue community participation -- Share one technical tutorial (not PowerCSharp) -- Answer questions about extension methods generally - -Week 3: 80% participation, 20% value-first content -- Create a technical post about a C# problem -- Mention extension methods as one solution among many -- Disclose affiliation naturally - -Week 4: 70% participation, 30% contextual promotion -- Reference PowerCSharp when relevant to discussions -- Share specific use cases that solve real problems -- Always provide value beyond just the product -``` - -### Phase 2: Value-First Content (Weeks 4-6) -**Goal**: Create content that helps before it promotes - -**Content Strategy:** -1. **Problem-Solving Posts**: Address common C# challenges -2. **Technical Tutorials**: Share knowledge, then mention tools -3. **Comparison Posts**: Compare different approaches honestly -4. **Community Questions**: Ask for opinions on technical approaches - -### Phase 3: Contextual Promotion (Weeks 6+) -**Goal**: Mention PowerCSharp when genuinely relevant - -**Promotion Guidelines:** -- Only mention when solving a specific problem -- Always provide alternatives and competitors -- Disclose affiliation clearly -- Focus on technical value, not features - -## ๐Ÿ“ Reddit-Compliant Post Templates - -### Template 1: Problem-Solving Post -**Title**: "What's your approach to handling null collections in C#?" - -**Body**: -"I've been wrestling with the classic null collection problem in C#. You know the drill: - -```csharp -if (list != null && list.Count > 0) { /* do something */ } -``` - -I've tried a few approaches: -1. Extension method: `list.IsNullOrEmpty()` -2. Null object pattern -3. Optional types - -What are your favorite patterns? I'm particularly interested in solutions that work well in large codebases. - -For context, I've been building some extension methods to handle this (full disclosure: I'm working on a library called PowerCSharp), but I'd love to hear what approaches you all use in production. - -#CSharp #DotNet #Programming" - -### Template 2: Technical Tutorial Post -**Title**: "Building secure path operations in C# - lessons learned" - -**Body**: -"Just spent the weekend implementing CWE-73 compliant path operations and wanted to share some hard-won lessons. - -**The Problem**: Directory traversal attacks are surprisingly easy to miss: -```csharp -string userPath = "../../etc/passwd"; // Malicious input -string combined = Path.Combine(basePath, userPath); // DANGER! -``` - -**Key Lessons**: -1. Always canonicalize paths before validation -2. Use `Path.GetFullPath()` to resolve ".." sequences -3. Validate the final path stays within your base directory -4. Log security events for monitoring - -**Implementation Approach**: -```csharp -public static string CombineAndValidate(string basePath, string userPath) -{ - string fullPath = Path.GetFullPath(Path.Combine(basePath, userPath)); - if (!fullPath.StartsWith(Path.GetFullPath(basePath))) { - throw new SecurityException("Path traversal detected"); - } - return fullPath; -} -``` - -This is actually part of a larger utility library I'm building (PowerCSharp - full disclosure), but the security principles apply regardless of what tools you use. - -What other security considerations do you think are important for path operations? - -#CSharp #Security #DotNet #Programming" - -### Template 3: Comparison Post -**Title**: "Extension method libraries: What do you use in your C# projects?" - -**Body**: -"Curious about what extension method libraries people are using these days. I've been evaluating a few options: - -**Options I've looked at:** -1. **Custom extensions** - Build your own, full control -2. **Community libraries** - More features, dependency management -3. **PowerCSharp** - The one I'm building (disclosure), focused on security and performance -4. **Other commercial options** - Various paid libraries - -**My criteria:** -- Security-focused (CWE-73 compliance, input validation) -- Performance optimized (minimal allocations) -- Well-tested (90%+ coverage) -- Modern .NET support - -What libraries are you all using? What are your must-have features? I'm trying to make sure I'm solving the right problems with my project. - -#CSharp #DotNet #Libraries #Programming" - -## ๐Ÿ“… Safe Posting Schedule - -### Week 1-2: Pure Participation -- **Monday**: Comment on 10+ r/csharp posts -- **Tuesday**: Share interesting .NET article -- **Wednesday**: Answer beginner C# questions -- **Thursday**: Comment on r/dotnet discussions -- **Friday**: Participate in r/programming threads - -### Week 3-4: Value-First Content -- **Monday**: Post problem-solving question (Template 1) -- **Tuesday**: Share technical insight -- **Wednesday**: Comment on others' posts -- **Thursday**: Post security tutorial (Template 2) -- **Friday**: Participate in discussions - -### Week 5-6: Contextual Promotion -- **Monday**: Comparison post (Template 3) -- **Tuesday**: Help others with C# problems -- **Wednesday**: Mention PowerCSharp when relevant -- **Thursday**: Share technical knowledge -- **Friday**: Community engagement - -## ๐ŸŽฏ Subreddit-Specific Strategy - -### r/csharp -- **Rules**: Generally tech-focused, some self-promotion allowed -- **Approach**: Technical discussions, problem-solving -- **Timing**: Weekdays during business hours - -### r/dotnet -- **Rules**: More strict about self-promotion -- **Approach**: Value-first, mention when relevant -- **Timing**: Mid-week, technical discussions - -### r/programming -- **Rules**: Very strict about self-promotion -- **Approach**: General programming discussions -- **Timing**: Weekends, broader topics - -### r/softwaredevelopment -- **Rules**: Business-focused, some promotion allowed -- **Approach**: Architecture and productivity discussions -- **Timing**: Weekdays, business hours - -## โš ๏ธ Red Flags to Avoid - -### Never Do: -- Post "Check out my product!" announcements -- Ask for upvotes or engagement -- Post the same content in multiple subreddits -- Use multiple accounts to promote -- Hide your affiliation with the product - -### Always Do: -- Disclose your affiliation clearly -- Provide genuine value beyond your product -- Participate in community discussions -- Respect subreddit-specific rules -- Focus on helping others - -## ๐Ÿ“Š Success Metrics - -### Short-term (Weeks 1-4) -- **Karma Growth**: 100+ karma from participation -- **Comment Engagement**: 50+ helpful comments -- **Community Recognition**: People recognize your username - -### Medium-term (Weeks 5-8) -- **Post Engagement**: Organic upvotes on value-first content -- **Community Trust**: People ask for your opinions -- **Natural Mentions**: Others mention your work - -### Long-term (Weeks 9+) -- **Product Discovery**: People find PowerCSharp naturally -- **Community Contributions**: Users contribute to the project -- **Thought Leadership**: Recognized as C# expert - -## ๐Ÿ”„ Alternative Strategies - -### If Reddit is Too Restrictive: -1. **Focus on Dev.to**: More developer-friendly for self-promotion -2. **LinkedIn**: Professional audience, more accepting of product announcements -3. **Twitter/X**: Real-time, more promotional tolerance -4. **Technical Blogs**: Build authority outside social platforms - -### Hybrid Approach: -- Use Reddit for community building and technical discussions -- Use other platforms for product announcements -- Cross-reference carefully (don't just spam links) - -## ๐ŸŽฏ Bottom Line - -Reddit can work for PowerCSharp, but it requires: -1. **Patience**: Build community first (2-4 weeks minimum) -2. **Authenticity**: Genuine participation, not just promotion -3. **Value**: Help others before asking for anything -4. **Transparency**: Always disclose your affiliation -5. **Respect**: Follow subreddit rules strictly - -The accounts that succeed on Reddit long-term are those that would be valuable community members even if they never promoted a product. diff --git a/marketing/reddit-posts-compliant.md b/marketing/reddit-posts-compliant.md deleted file mode 100644 index 644e259..0000000 --- a/marketing/reddit-posts-compliant.md +++ /dev/null @@ -1,360 +0,0 @@ -# Reddit-Compliant Posts for PowerCSharp v1.0.0 - -## ๐ŸŽฏ Reddit-Compliant Strategy Overview - -**Key Principle**: Build community first, promote second. Follow the 10% rule strictly. - -## ๐Ÿ“‹ Phase 1: Community Building Posts (Weeks 1-4) - -### Post 1: Problem-Solving Discussion -**Subreddit**: r/csharp -**Title**: What's your approach to handling null collections in C#? - -**Body**: -"I've been wrestling with the classic null collection problem in C#. You know the drill: - -```csharp -if (list != null && list.Count > 0) { /* do something */ } -``` - -I've tried a few approaches: -1. Extension method: `list.IsNullOrEmpty()` -2. Null object pattern -3. Optional types - -What are your favorite patterns? I'm particularly interested in solutions that work well in large codebases. - -For context, I've been building some extension methods to handle this (full disclosure: I'm working on a library called PowerCSharp), but I'd love to hear what approaches you all use in production. - -#CSharp #DotNet #Programming" - ---- - -### Post 2: Security Tutorial -**Subreddit**: r/csharp -**Title**: Building secure path operations in C# - lessons learned - -**Body**: -"Just spent the weekend implementing CWE-73 compliant path operations and wanted to share some hard-won lessons. - -**The Problem**: Directory traversal attacks are surprisingly easy to miss: -```csharp -string userPath = "../../etc/passwd"; // Malicious input -string combined = Path.Combine(basePath, userPath); // DANGER! -``` - -**Key Lessons**: -1. Always canonicalize paths before validation -2. Use `Path.GetFullPath()` to resolve ".." sequences -3. Validate the final path stays within your base directory -4. Log security events for monitoring - -**Implementation Approach**: -```csharp -public static string CombineAndValidate(string basePath, string userPath) -{ - string fullPath = Path.GetFullPath(Path.Combine(basePath, userPath)); - if (!fullPath.StartsWith(Path.GetFullPath(basePath))) { - throw new SecurityException("Path traversal detected"); - } - return fullPath; -} -``` - -This is actually part of a larger utility library I'm building (PowerCSharp - full disclosure), but the security principles apply regardless of what tools you use. - -What other security considerations do you think are important for path operations? - -#CSharp #Security #DotNet #Programming" - ---- - -### Post 3: Technical Comparison -**Subreddit**: r/dotnet -**Title**: Extension method libraries: What do you use in your C# projects? - -**Body**: -"Curious about what extension method libraries people are using these days. I've been evaluating a few options: - -**Options I've looked at:** -1. **Custom extensions** - Build your own, full control -2. **Community libraries** - More features, dependency management -3. **PowerCSharp** - The one I'm building (disclosure), focused on security and performance -4. **Other commercial options** - Various paid libraries - -**My criteria:** -- Security-focused (CWE-73 compliance, input validation) -- Performance optimized (minimal allocations) -- Well-tested (90%+ coverage) -- Modern .NET support - -What libraries are you all using? What are your must-have features? I'm trying to make sure I'm solving the right problems with my project. - -#CSharp #DotNet #Libraries #Programming" - ---- - -### Post 4: Dynamic LINQ Discussion -**Subreddit**: r/csharp -**Title**: Dynamic LINQ in production: What are your experiences? - -**Body**: -"I've been implementing dynamic LINQ functionality and wanted to hear about others' experiences with it in production. - -**Use Cases I'm Considering:** -- User-defined search filters -- Admin panel dynamic queries -- API endpoint flexible filtering - -**Implementation Approach**: -```csharp -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -var filtered = people.Where(predicate); -``` - -**Concerns:** -1. Performance overhead vs static LINQ -2. Security implications (injection attacks) -3. Error handling for invalid expressions -4. Testing dynamic queries - -I've been building this as part of PowerCSharp (disclosure - my open source project), but I'd love to hear about real-world experiences. Have you used dynamic LINQ in production? What pitfalls should I watch out for? - -#DynamicLINQ #CSharp #DotNet #Programming" - ---- - -## ๐Ÿ“‹ Phase 2: Value-First Content (Weeks 5-8) - -### Post 5: Performance Deep Dive -**Subreddit**: r/csharp -**Title**: Optimizing C# extension methods: Performance lessons learned - -**Body**: -"After spending months optimizing extension methods, I wanted to share some performance insights that might help others. - -**Key Findings:** - -1. **Memory Allocation Matters** -```csharp -// Bad: Multiple string allocations -string result = input.Trim().ToLower().Replace(" ", "-"); - -// Better: Single allocation with Span -public static string ToSlug(this string input) -{ - ReadOnlySpan span = input.AsSpan().Trim().ToLowerInvariant(); - // Process with minimal allocations -} -``` - -2. **Async Operations Need Careful Design** -```csharp -// Avoid sync-over-async -public static async Task CloneAsync(this Stream source, Stream destination) -{ - await source.CopyToAsync(destination); -} -``` - -3. **Benchmark Everything** -- Used BenchmarkDotNet extensively -- Found 40% improvement in string operations -- Reduced memory usage by 25% in collections - -**Real Impact**: In a production app processing 100K records, these optimizations reduced execution time from 230ms to 52ms. - -These optimizations are part of PowerCSharp (my open source library), but the principles apply to any extension method development. - -What performance optimizations have you found most impactful? - -#Performance #CSharp #DotNet #Optimization" - ---- - -### Post 6: Architecture Discussion -**Subreddit**: r/softwareengineering -**Title**: Modular NuGet packages: How do you organize your libraries? - -**Body**: -"I'm designing a C# library ecosystem and struggling with the right granularity for NuGet packages. Would love your thoughts on this architecture question. - -**Current Approach**: 6 focused packages -- Core (interfaces only, dependency-free) -- Extensions (100+ extension methods) -- Extensions.AspNetCore (web-specific) -- Utilities (validation, file ops) -- Helpers (JSON, crypto, environment) -- Compatibility (.NET Framework support) - -**Pros of This Approach**: -- Users install only what they need -- Clear dependency boundaries -- Smaller deployment size - -**Cons**: -- More complex dependency management -- Users need to install multiple packages -- Version coordination challenges - -**Alternative**: Single large package with everything - -**Questions for the Community**: -1. What package granularity do you prefer as a consumer? -2. How do you handle version coordination across packages? -3. Are there examples of well-architected multi-package libraries? - -This is for PowerCSharp (disclosure - my open source project), but I'm interested in general principles. - -#Architecture #NuGet #SoftwareEngineering #CSharp" - ---- - -## ๐Ÿ“‹ Phase 3: Contextual Promotion (Weeks 9+) - -### Post 7: Success Story -**Subreddit**: r/csharp -**Title**: Reduced boilerplate code by 50% in our C# project - here's how - -**Body**: -"Wanted to share a success story from a recent project where we significantly reduced boilerplate code. - -**The Problem**: Our team was writing repetitive validation and utility code across multiple services. - -**Before**: Sample controller action (47 lines) -```csharp -[HttpGet("users/{id}")] -public IHttpActionResult GetUser(int id) -{ - try { - if (id <= 0) - { - return BadRequest("Invalid ID"); - } - var user = _repository.GetUserById(id); - if (user == null) - { - return NotFound(); - } - var json = JsonConvert.SerializeObject(user); - return Ok(json); - } catch (Exception ex) { - return InternalServerError(ex); - } -} -``` - -**After**: Same functionality (12 lines) -```csharp -[HttpGet("users/{id}")] -public IHttpActionResult GetUser(int id) -{ - if (id <= 0) - { - return BadRequest("Invalid ID"); - } - var user = _repository.GetUserById(id); - if (user == null) - { - return NotFound(); - } - return Ok(JsonHelper.SafeSerialize(user)); -} -``` - -**What Made the Difference**: -- Extension methods for common operations -- Centralized validation logic -- Safe JSON serialization -- Consistent error handling - -**The Tools**: We used PowerCSharp (disclosure - I built this library) for the utilities, but the principles apply to any well-designed helper library. - -**Results**: -- 75% less code in controller actions -- 40% fewer bugs related to validation -- Faster onboarding for new team members - -**Questions**: What boilerplate reduction strategies have worked for you? How do you balance utility libraries with code clarity? - -#CSharp #Productivity #SoftwareEngineering #DotNet" - ---- - -## ๐Ÿ“… Safe Posting Schedule - -### Week 1-2: Pure Participation -- **Daily Goal**: 10+ meaningful comments on others' posts -- **Focus**: Help others, share knowledge, build karma -- **No**: Self-promotion of any kind - -### Week 3-4: Value-First Content -- **Monday**: Post problem-solving discussion -- **Wednesday**: Share technical tutorial -- **Friday**: Participate in community discussions -- **Ratio**: 90% participation, 10% value-sharing - -### Week 5-8: Technical Authority -- **Monday**: Performance optimization post -- **Wednesday**: Architecture discussion -- **Friday**: Help others with technical problems -- **Ratio**: 80% participation, 20% contextual mentions - -### Week 9+: Community Leadership -- **Share success stories** when relevant -- **Mention PowerCSharp** only when solving specific problems -- **Always provide alternatives** and competitor options -- **Focus on helping** the community - -## โš ๏ธ Critical Compliance Rules - -### Never Do: -- Post direct product announcements -- Ask for upvotes or engagement -- Post same content in multiple subreddits -- Hide your affiliation -- Use promotional language ("check out my amazing product!") - -### Always Do: -- Disclose affiliation clearly ("full disclosure: I built this") -- Provide genuine value beyond your product -- Mention competitors and alternatives -- Focus on solving community problems -- Respect subreddit-specific rules - -## ๐ŸŽฏ Success Metrics - -### Short-term (Weeks 1-4): -- 100+ karma from participation -- 50+ helpful comments -- Community recognition - -### Medium-term (Weeks 5-8): -- Organic upvotes on value-first content -- People asking for your technical opinions -- Natural discovery of your work - -### Long-term (Weeks 9+): -- Users finding PowerCSharp through Reddit -- Community contributions to the project -- Recognition as C# thought leader - -## ๐Ÿ”„ Alternative: Focus on Other Platforms - -If Reddit feels too restrictive, consider: -- **Dev.to**: More developer-friendly for self-promotion -- **LinkedIn**: Professional audience, accepting of product announcements -- **Twitter/X**: More promotional tolerance -- **Technical Blogs**: Build authority outside social platforms - -## ๐Ÿ“Š Bottom Line - -Reddit can work for PowerCSharp, but requires: -1. **Patience**: 2-4 weeks minimum community building -2. **Authenticity**: Genuine participation, not just promotion -3. **Value**: Help others before asking for anything -4. **Transparency**: Always disclose affiliation -5. **Respect**: Follow subreddit rules strictly - -The accounts that succeed on Reddit long-term are those that would be valuable community members even if they never promoted a product. diff --git a/marketing/reddit-posts.md b/marketing/reddit-posts.md deleted file mode 100644 index fda2f1d..0000000 --- a/marketing/reddit-posts.md +++ /dev/null @@ -1,679 +0,0 @@ -# Reddit Posts for PowerCSharp v1.0.0 - -## ๐ŸŽฏ r/csharp - Main Launch Announcement - -**Post 1: Launch Announcement** -**Title:** ๐Ÿš€ PowerCSharp v1.0.0 - Production-ready C# extensions library with 100+ methods - -**Body:** -Hey r/csharp! I'm excited to share the first stable release of PowerCSharp - a comprehensive C# extensions library I've been building with 20+ years of enterprise experience. - -**What is PowerCSharp?** -A collection of 100+ production-tested extension methods organized into 6 modular packages: - -- **PowerCSharp.Core** - Foundation with centralized interfaces -- **PowerCSharp.Extensions** - 100+ extension methods (String, DateTime, Collections, LINQ, JSON, XML, Objects, Types, Streams) -- **PowerCSharp.Extensions.AspNetCore** - HTTP utilities, status codes, URI manipulation -- **PowerCSharp.Utilities** - Validation, file operations, math helpers -- **PowerCSharp.Helpers** - JSON, crypto, environment helpers -- **PowerCSharp.Compatibility** - .NET Framework support - -**Key Features:** -- **Dynamic LINQ** - Runtime expression parsing: `people.Where("Age > 18 && Name.Contains('John')")` -- **Security First** - CWE-73 compliant path operations -- **Performance Optimized** - .NET 8.0 ready with backward compatibility -- **Enterprise Ready** - 100+ unit tests, comprehensive documentation - -**Example Usage:** -```csharp -using PowerCSharp.Extensions; - -// String utilities -string title = "hello world".ToTitleCase(); // "Hello World" -bool isValid = "https://example.com".IsValidUrl(); // true - -// DateTime utilities -int age = DateTime.Now.GetAge(); -bool isWeekend = DateTime.Now.IsWeekend(); - -// Dynamic LINQ -var filtered = people.Where("Age > 18 && Name.Contains('John')"); -``` - -**Installation:** -```bash -dotnet add package PowerCSharp.Extensions -``` - -**What's Next?** -- PowerCSharp Features (feature flags package) -- Clean Architecture Template (complete starter repo) - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) - -Looking forward to your feedback and contributions! ๐Ÿš€ - ---- - -**Post 2: Technical Deep Dive** -**Title:** ๐Ÿ” Deep Dive: PowerCSharp v1.0.0 Architecture and Design Decisions - -**Body:** -Hey r/csharp! Following up on my PowerCSharp v1.0.0 launch, I wanted to share some of the architectural decisions and design patterns that went into building this library. - -**Architecture Philosophy:** -I wanted to create a library that exemplifies clean architecture principles while solving real-world problems. Here are the key decisions: - -**1. Centralized Interfaces in Core Package** -```csharp -// All interfaces in PowerCSharp.Core.Interfaces -namespace PowerCSharp.Core.Interfaces.Extensions.Linq -{ - public interface IDynamicFilterProvider - { - void SetFilter(Func filter); - Func GetFilter(); - } -} -``` - -**Benefits:** -- Single source of truth for contracts -- Easy dependency management -- Clear architectural boundaries - -**2. Modular Package Design** -Each package has a single responsibility: -- Core: Interfaces and models only (dependency-free) -- Extensions: Cross-platform extension methods -- Extensions.AspNetCore: Web-specific utilities -- Utilities: Validation and file operations -- Helpers: Specialized operations (JSON, crypto) -- Compatibility: Legacy .NET Framework support - -**3. Security-First Approach** -```csharp -// CWE-73 compliant path operations -public static string CombineAndValidate(string basePath, string relativePath) -{ - // Canonicalize and validate paths - // Prevent directory traversal attacks - // Log security events -} -``` - -**4. Performance Optimization** -- Minimal memory allocations -- Efficient algorithms -- Async/await support -- Thread-safe operations - -**5. Comprehensive Testing** -- 100+ unit tests -- >90% code coverage -- Edge case handling -- Performance benchmarks - -**Design Trade-offs:** -- **Dependency Management**: Chose to separate ASP.NET Core extensions for cleaner dependencies -- **API Surface**: Kept it focused on most common operations -- **Backward Compatibility**: Maintained through semantic versioning - -**Questions for the community:** -1. What do you think of the centralized interface approach? -2. Are there any extension methods you'd like to see added? -3. How do you handle security in your utility libraries? - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) - ---- - -**Post 3: Dynamic LINQ Deep Dive** -**Title:** โšก Dynamic LINQ in PowerCSharp: Runtime Expression Parsing - -**Body:** -Hey r/csharp! I wanted to dive deep into one of my favorite features in PowerCSharp v1.0.0 - the Dynamic LINQ extensions that allow runtime expression parsing. - -**The Problem:** -How many times have you needed to build dynamic queries based on user input or configuration? Traditional LINQ requires compile-time expressions, which limits flexibility. - -**The Solution:** -```csharp -using PowerCSharp.Extensions; - -// Dynamic expression parsing -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -var filtered = people.Where(predicate); - -// Dynamic ordering -string orderExpression = "Name DESC, Age ASC"; -var orderDelegates = orderExpression.GetOrderDelegates(); -var ordered = people.OrderByMultiple(orderDelegates); - -// Using providers for complex scenarios -var filterProvider = new DynamicFilterProvider(); -filterProvider.SetFilter(person => person.Age > 18); -var results = people.Filter(filterProvider); -``` - -**How It Works:** -1. **Expression Parsing** - Uses System.Linq.Dynamic.Core for parsing string expressions -2. **Delegate Compilation** - Compiles expressions to efficient delegates -3. **Type Safety** - Maintains strong typing through generics -4. **Error Handling** - Graceful handling of invalid expressions - -**Real-World Use Cases:** -- **Search Systems** - User-defined search criteria -- **Reporting** - Dynamic filtering and sorting -- **Admin Panels** - Runtime query building -- **API Endpoints** - Flexible query parameters - -**Performance Considerations:** -- Compiled delegates are cached for reuse -- Minimal overhead compared to reflection -- Suitable for most production scenarios - -**Advanced Example:** -```csharp -public class AdvancedSearchService -{ - public IEnumerable SearchUsers(IEnumerable users, SearchCriteria criteria) - { - // Build dynamic expression - var expression = BuildExpression(criteria); - var predicate = expression.GetExpressionDelegate(); - - // Apply filtering and sorting - var filtered = users.Where(predicate); - - if (!string.IsNullOrEmpty(criteria.SortBy)) - { - var sortExpression = $"{criteria.SortBy} {(criteria.Descending ? "DESC" : "ASC")}"; - var orderDelegates = sortExpression.GetOrderDelegates(); - filtered = filtered.OrderByMultiple(orderDelegates); - } - - return filtered; - } -} -``` - -**Questions:** -1. How do you handle dynamic queries in your applications? -2. What are your concerns about runtime expression parsing? -3. Would you find this useful in your projects? - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) - ---- - -## ๐ŸŽฏ r/dotnet - Broader .NET Community - -**Post 4: r/dotnet Launch** -**Title:** ๐Ÿš€ PowerCSharp v1.0.0 - New .NET extensions library with 100+ methods, Dynamic LINQ, and security features - -**Body:** -Hey r/dotnet! Just launched PowerCSharp v1.0.0 - a comprehensive extensions library for the .NET ecosystem built with enterprise experience. - -**What Makes PowerCSharp Different:** - -**๐Ÿ”’ Security First** -- CWE-73 compliant path operations -- Built-in validation utilities -- Safe file operations -- Comprehensive error handling - -**โšก Dynamic LINQ** -- Runtime expression parsing -- Dynamic filtering and sorting -- Type-safe operations -- Performance optimized - -**๐Ÿ—๏ธ Clean Architecture** -- 6 modular packages -- Centralized interfaces -- Dependency-free core -- Semantic versioning - -**๐Ÿ“ฆ Package Breakdown:** -- **PowerCSharp.Core** - Foundation (dependency-free) -- **PowerCSharp.Extensions** - 100+ cross-platform extensions -- **PowerCSharp.Extensions.AspNetCore** - Web utilities -- **PowerCSharp.Utilities** - Validation and file ops -- **PowerCSharp.Helpers** - JSON, crypto, environment -- **PowerCSharp.Compatibility** - .NET Framework support - -**Target Frameworks:** -- .NET 8.0 (full support) -- .NET Standard 2.0 (cross-platform) -- .NET Framework 4.6.2+ (via compatibility package) - -**Real-World Examples:** -```csharp -// Security-focused path operations -string safePath = PathExtensions.CombineAndValidate(basePath, userInput); - -// Dynamic LINQ for flexible queries -var results = users.Where("Age > 18 && Status == 'Active'"); - -// HTTP utilities for web apps -bool isSuccess = response.StatusCode.IsSuccessful(); -Uri withParams = uri.AddParameter("search", "query"); -``` - -**Enterprise Ready:** -- 100+ unit tests -- >90% code coverage -- Comprehensive documentation -- Production stability - -**What's Coming:** -- PowerCSharp Features (feature flags) -- Clean Architecture Template -- Community contributions - -**Installation:** -```bash -dotnet add package PowerCSharp.Extensions -``` - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) - -Looking forward to feedback from the .NET community! ๐Ÿš€ - ---- - -**Post 5: Performance Focus** -**Title:** โšก Performance Deep Dive: Optimizing PowerCSharp for .NET 8.0 - -**Body:** -Hey r/dotnet! I wanted to share some performance insights from building PowerCSharp v1.0.0 and how we optimized for .NET 8.0. - -**Performance Philosophy:** -When building utility libraries, performance matters because these methods are called thousands of times in production applications. - -**Key Optimizations:** - -**1. Memory Allocation Reduction** -```csharp -// Before: Multiple string allocations -string result = input.Trim().ToLower().Replace(" ", "-"); - -// After: Single allocation with Span -public static string ToSlug(this string input) -{ - ReadOnlySpan span = input.AsSpan().Trim().ToLowerInvariant(); - // Process with minimal allocations -} -``` - -**2. Efficient Algorithms** -```csharp -// Optimized pagination -public static IEnumerable Page(this IEnumerable source, int page, int pageSize) -{ - return source.Skip((page - 1) * pageSize).Take(pageSize); -} -``` - -**3. Async/Await Support** -```csharp -// Non-blocking stream operations -public static async Task CloneAsync(this Stream stream, Stream destination) -{ - if (destination == null) - { - destination = new MemoryStream(); - } - - await stream.CopyToAsync(destination); -} -``` - -**4. Thread Safety** -```csharp -// Thread-safe operations -public static bool IsDefault(this T value) -{ - return EqualityComparer.Default.Equals(value, default(T)); -} -``` - -**Benchmark Results:** -- **String Operations**: 40% faster than manual implementations -- **Collection Processing**: 25% less memory usage -- **LINQ Operations**: 30% faster dynamic expression parsing -- **JSON Processing**: 35% improvement in serialization speed - -**.NET 8.0 Specific Optimizations:** -- **System.Text.Json** improvements -- **Span** usage for string operations -- **MemoryPool** for buffer management -- **ValueTask** for async operations - -**Real-World Impact:** -```csharp -// Before: Multiple allocations -var hash = ComputeHashSlow(obj); - -// After: Optimized with minimal allocations -var hash = obj.ComputeHash(); // Uses efficient JSON serialization -``` - -**Performance Monitoring:** -- Built-in performance counters -- BenchmarkDotNet integration -- Memory profiling tools -- Continuous performance testing - -**Questions for the community:** -1. What performance optimizations do you look for in utility libraries? -2. How do you balance performance vs. readability? -3. What .NET 8.0 features have you found most impactful? - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) - ---- - -## ๐ŸŽฏ r/programming - Broader Programming Community - -**Post 6: r/programming - General Interest** -**Title:** ๐Ÿš€ PowerCSharp v1.0.0: A case study in building production-ready utility libraries - -**Body:** -Hey r/programming! I wanted to share the journey of building and launching PowerCSharp v1.0.0 - a C# extensions library that demonstrates some interesting software engineering principles. - -**Project Background:** -After 20+ years as a C# architect, I've seen the same repetitive patterns across countless projects. PowerCSharp addresses these common pain points with 100+ production-tested extension methods. - -**Engineering Principles Applied:** - -**1. Clean Architecture** -``` -PowerCSharp.Core (Interfaces) โ† Foundation -PowerCSharp.Extensions (Methods) โ† Implementation -PowerCSharp.Extensions.AspNetCore โ† Domain-specific -``` - -**2. Security-First Design** -- CWE-73 compliant path operations -- Input validation utilities -- Safe error handling -- Security event logging - -**3. Performance Optimization** -- Minimal memory allocations -- Efficient algorithms -- Async/await support -- Thread safety - -**4. Modularity and Separation of Concerns** -- 6 focused packages -- Clear dependencies -- Single responsibility principle -- Interface segregation - -**5. Comprehensive Testing** -- 100+ unit tests -- >90% code coverage -- Edge case handling -- Performance benchmarks - -**Interesting Technical Challenges:** - -**Dynamic LINQ Implementation:** -```csharp -// Runtime expression parsing -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -``` - -**Secure Path Operations:** -```csharp -// Prevent directory traversal attacks -string safePath = PathExtensions.CombineAndValidate(basePath, userInput); -``` - -**Object Hash Computing:** -```csharp -// Consistent hashing for caching -string hash = complexObject.ComputeHash(); -``` - -**Lessons Learned:** -1. **API Design** - Balance between flexibility and simplicity -2. **Backward Compatibility** - Semantic versioning is crucial -3. **Documentation** - Comprehensive docs are as important as code -4. **Community** - Open source requires active engagement -5. **Testing** - Production readiness requires extensive testing - -**Metrics:** -- 6 packages released -- 100+ extension methods -- 100+ unit tests -- >90% code coverage -- .NET 8.0 optimized - -**What's Next:** -- Feature flags package -- Clean architecture template -- Community contributions - -**GitHub:** [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) - -**Questions for the community:** -1. What principles do you follow when building utility libraries? -2. How do you balance features vs. complexity? -3. What's your approach to backward compatibility? - ---- - -## ๐ŸŽฏ Dev.to Technical Articles - -**Post 7: Dev.to Tutorial** -**Title:** ๐Ÿš€ Getting Started with PowerCSharp: 10 Extension Methods That Will Change Your C# Development - -**Body:** -# Getting Started with PowerCSharp: 10 Extension Methods That Will Change Your C# Development - -PowerCSharp v1.0.0 is here! After months of development, I'm excited to share this production-ready library with 100+ extension methods that will make your C# development more productive and enjoyable. - -## What is PowerCSharp? - -PowerCSharp is a comprehensive collection of extension methods, utilities, and helper classes built with 20+ years of enterprise C# experience. It's organized into 6 modular packages so you can install only what you need. - -## Installation - -```bash -dotnet add package PowerCSharp.Extensions -``` - -## 10 Game-Changing Extension Methods - -### 1. String Manipulation Made Easy - -```csharp -using PowerCSharp.Extensions; - -string text = "hello world"; -string title = text.ToTitleCase(); // "Hello World" -string camel = "HelloWorld".ToCamelCase(); // "helloWorld" -string normalized = "User Name".NormalizeKey(); // "userName" -bool isValid = "https://example.com".IsValidUrl(); // true -``` - -### 2. DateTime Utilities - -```csharp -var date = DateTime.Now; -int age = date.GetAge(); // Calculate age from birthdate -bool isWeekend = date.IsWeekend(); // Check if it's weekend -var firstDay = date.FirstDayOfMonth(); // First day of current month -var lastDay = date.LastDayOfMonth(); // Last day of current month -``` - -### 3. Collection Operations - -```csharp -var numbers = new List { 1, 2, 3, 4, 5 }; -bool isEmpty = numbers.IsNullOrEmpty(); // false -var first = numbers.FirstOrDefaultSafe(-1); // 1 with fallback -var page = numbers.Page(1, 2); // [1, 2] - pagination - -// Remove all matching items -var list = new List { "keep", "remove", "keep" }; -int removed = list.RemoveAll(x => x == "remove"); // 1 -``` - -### 4. Dynamic LINQ - Game Changer! - -```csharp -// Runtime expression parsing -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -var filtered = people.Where(predicate); - -// Dynamic ordering -string orderExpression = "Name DESC, Age ASC"; -var orderDelegates = orderExpression.GetOrderDelegates(); -var ordered = people.OrderByMultiple(orderDelegates); -``` - -### 5. Secure Path Operations - -```csharp -string basePath = "/var/www/uploads"; -string userFile = "../../etc/passwd"; // Malicious attempt - -// This will throw SecurityException due to directory traversal -string safePath = PathExtensions.CombineAndValidate(basePath, userFile); - -// Safe usage -string validPath = PathExtensions.CombineAndValidate(basePath, "images/photo.jpg"); -// Returns: "/var/www/uploads/images/photo.jpg" -``` - -### 6. HTTP Utilities for Web Developers - -```csharp -// HTTP Status code utilities -HttpStatusCode status = HttpStatusCode.OK; -bool success = status.IsSuccessful(); // true -bool clientError = status.IsClientError(); // false -bool serverError = status.IsServerError(); // false - -// URI manipulation -Uri uri = new Uri("https://example.com"); -Uri withParam = uri.AddParameter("search", "test"); -// Result: https://example.com?search=test - -// HTTP Request cloning -using var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com"); -var clonedRequest = request.Clone(); -``` - -### 7. JSON Processing - -```csharp -// Safe JSON element access -JsonElement element = JsonDocument.Parse("{\"name\":\"John\"}").RootElement; -var name = element.Get("name"); // JsonElement with value "John" - -// Case-insensitive JSON access -bool found = element.TryGetPropertyCaseInsensitive("NAME", out var value); -``` - -### 8. Object Utilities - -```csharp -// Null checking -string text = "test"; -text.ThrowOnNull(); // Throws if null - -// Boolean conversion -bool isTrue = "true".TryGetBool(out bool result); // result = true - -// Property copying -var person = new Person { Name = "John", Age = 30 }; -var copy = new Person(); -person.CopyPropertiesTo(copy); // Copies matching properties -``` - -### 9. Object Hash Computing - -```csharp -var person = new { Name = "John", Age = 30, Email = "john@example.com" }; -string hash = person.ComputeHash(); // "A1B2C3D4E5F67890" - -// Handles complex objects with nested properties -var complexObj = new Order -{ - Id = 123, - Customer = new Customer { Name = "Alice" }, - Items = new List { new Item { Name = "Product1" } } -}; -string orderHash = complexObj.ComputeHash(); // Consistent hash for caching -``` - -### 10. Stream Operations - -```csharp -using var originalStream = new MemoryStream(Encoding.UTF8.GetBytes("test data")); -using var destinationStream = new MemoryStream(); - -await originalStream.CloneAsync(destinationStream); -// destinationStream now contains the same data -``` - -## Package Organization - -PowerCSharp is organized into 6 focused packages: - -- **PowerCSharp.Core** - Foundation with interfaces (dependency-free) -- **PowerCSharp.Extensions** - 100+ cross-platform extension methods -- **PowerCSharp.Extensions.AspNetCore** - Web-specific utilities -- **PowerCSharp.Utilities** - Validation and file operations -- **PowerCSharp.Helpers** - JSON, crypto, environment helpers -- **PowerCSharp.Compatibility** - .NET Framework support - -## Why PowerCSharp? - -1. **Production Ready** - 100+ unit tests, >90% code coverage -2. **Security First** - CWE-73 compliance, built-in validation -3. **Performance Optimized** - .NET 8.0 ready with minimal allocations -4. **Clean Architecture** - Centralized interfaces, modular design -5. **Backward Compatible** - Semantic versioning, migration support - -## What's Next? - -- **PowerCSharp Features** - Feature flags package for runtime configuration -- **Clean Architecture Template** - Complete starter repository -- **Community Contributions** - Join us in building the future! - -## Get Started - -```bash -# Install the core package -dotnet add package PowerCSharp.Core - -# Install extensions -dotnet add package PowerCSharp.Extensions - -# For web applications -dotnet add package PowerCSharp.Extensions.AspNetCore -``` - -## Resources - -- **GitHub**: [github.com/marioarce/PowerCSharp](https://github.com/marioarce/PowerCSharp) -- **Documentation**: Complete API reference and examples -- **NuGet**: All packages available on NuGet Gallery - -## Conclusion - -PowerCSharp v1.0.0 represents years of experience building enterprise C# applications. These extension methods solve real problems that developers face every day, with a focus on security, performance, and productivity. - -Try it out and let me know what you think! ๐Ÿš€ - -#PowerCSharp #CSharp #DotNet #Productivity #OpenSource diff --git a/marketing/twitter-posts.md b/marketing/twitter-posts.md deleted file mode 100644 index 0b98e87..0000000 --- a/marketing/twitter-posts.md +++ /dev/null @@ -1,305 +0,0 @@ -# Twitter/X Posts for PowerCSharp v1.0.0 - -## ๐ŸŽ‰ Launch Announcement - -**Post 1: Main Launch** -๐Ÿš€ PowerCSharp v1.0.0 is LIVE! Production-ready C# extensions built with 20+ years of enterprise experience. - -โœ… 100+ extension methods -โœ… 6 modular packages -โœ… Security-first design -โœ… .NET 8.0 optimized - -Boost your C# productivity today! โšก - -#PowerCSharp #CSharp #DotNet #OpenSource - -๐Ÿ”— github.com/marioarce/PowerCSharp - ---- - -**Post 2: Key Features Thread** -๐Ÿงต THREAD: What makes PowerCSharp v1.0.0 special? Let me break it down... ๐Ÿ‘‡ - -1๏ธโƒฃ **Dynamic LINQ**: Runtime expression parsing! -```csharp -var filtered = people.Where("Age > 18 && Name.Contains('John')"); -``` - -2๏ธโƒฃ **Secure Paths**: CWE-73 compliant operations -```csharp -string safePath = PathExtensions.CombineAndValidate(basePath, userFile); -``` - -3๏ธโƒฃ **HTTP Utils**: Status codes, URI manipulation, request cloning - -4๏ธโƒฃ **100+ Extensions**: Strings, DateTime, Collections, JSON, XML, Objects, Types, Streams - -More in next tweet... ๐Ÿ‘‡ - -#PowerCSharp #CSharp #DotNet - ---- - -**Post 3: Architecture Highlight** -๐Ÿ—๏ธ Clean Architecture in PowerCSharp v1.0.0: - -โ€ข **Centralized Interfaces** in PowerCSharp.Core -โ€ข **Modular Packages** - install only what you need -โ€ข **Clear Separation** of concerns -โ€ข **Dependency-Free** foundation - -Enterprise-grade design patterns that scale! ๐Ÿ“ˆ - -#PowerCSharp #CSharp #Architecture #CleanCode - ---- - -**Post 4: Security Focus** -๐Ÿ”’ Security-first development with PowerCSharp v1.0.0: - -โœ… CWE-73 compliant path operations -โœ… Input validation utilities -โœ… Safe file operations -โœ… Built-in error handling -โœ… Comprehensive testing - -Because security shouldn't be an afterthought! ๐Ÿ›ก๏ธ - -#PowerCSharp #CSharp #Security #DotNet - ---- - -**Post 5: Performance** -โšก Performance optimized for .NET 8.0: - -โ€ข Minimal memory allocations -โ€ข Efficient algorithms -โ€ข Thread-safe operations -โ€ข Async/await support -โ€ข Zero external dependencies (Core package) - -Your apps will thank you! ๐Ÿš€ - -#PowerCSharp #CSharp #Performance #DotNet8 - ---- - -## ๐Ÿ’ก Daily Tips Series - -**Tip 1: String Extensions** -๐Ÿ’ก #PowerCSharp Tip: Stop writing boilerplate string code! - -```csharp -"hello world".ToTitleCase(); // "Hello World" -"https://example.com".IsValidUrl(); // true -"HelloWorld".ToCamelCase(); // "helloWorld" -"User Name".NormalizeKey(); // "userName" -``` - -100+ time-saving extensions waiting for you! โšก - -#CSharp #DotNet #Programming - ---- - -**Tip 2: DateTime Extensions** -๐Ÿ’ก #PowerCSharp Tip: DateTime operations made simple: - -```csharp -DateTime.Now.GetAge(); // Calculate age -DateTime.Now.IsWeekend(); // Check weekend -DateTime.Now.FirstDayOfMonth(); // First day -DateTime.Now.LastDayOfMonth(); // Last day -``` - -No more complex date math! ๐Ÿ“… - -#CSharp #DotNet #DateTime - ---- - -**Tip 3: Collection Extensions** -๐Ÿ’ก #PowerCSharp Tip: Supercharge your collections: - -```csharp -var numbers = new List { 1, 2, 3, 4, 5 }; -numbers.IsNullOrEmpty(); // false -numbers.FirstOrDefaultSafe(-1); // 1 -numbers.Page(1, 2); // [1, 2] -list.RemoveAll(x => x == "remove"); // Remove all matches -``` - -Clean, readable, efficient! ๐Ÿ“ฆ - -#CSharp #DotNet #LINQ - ---- - -**Tip 4: Dynamic LINQ** -๐Ÿ’ก #PowerCSharp Tip: Runtime LINQ expressions! - -```csharp -string expression = "Age > 18 && Name.Contains('John')"; -var predicate = expression.GetExpressionDelegate(); -var filtered = people.Where(predicate); -``` - -Perfect for dynamic filtering and search! ๐Ÿ” - -#CSharp #DotNet #LINQ - ---- - -## ๐ŸŽฏ Community Engagement - -**Post: Call for Contributors** -๐Ÿค PowerCSharp v1.0.0 is out, and we're just getting started! - -Looking for contributors to help with: -โ€ข New extension methods -โ€ข Documentation improvements -โ€ข Bug fixes and optimizations -โ€ข Community support - -Join us in making C# development better! ๐Ÿš€ - -#PowerCSharp #CSharp #OpenSource #Contributing - -๐Ÿ”— github.com/marioarce/PowerCSharp - ---- - -**Post: Question/Engagement** -โ“ C# Developers: What's your most common repetitive coding task? - -For me, it was string manipulation and validation. That's why I built PowerCSharp - 100+ extensions to eliminate boilerplate code! - -What should we add next? ๐Ÿค” - -#PowerCSharp #CSharp #DotNet #Programming - ---- - -## ๐Ÿ“Š Statistics & Milestones - -**Post: NuGet Downloads** -๐Ÿ“ˆ PowerCSharp v1.0.0 is gaining traction! - -Thank you to everyone who's tried the packages! Every download helps improve the library and supports open source development. ๐Ÿ™ - -Keep the feedback coming! ๐Ÿ’ช - -#PowerCSharp #CSharp #NuGet #OpenSource - ---- - -**Post: GitHub Stars** -โญ 100+ GitHub Stars! Thank you! ๐ŸŽ‰ - -The PowerCSharp community is growing! Your support means everything and helps more developers discover these productivity tools. - -Let's keep the momentum going! ๐Ÿš€ - -#PowerCSharp #CSharp #GitHub #OpenSource - ---- - -## ๐Ÿ”ฅ Technical Highlights - -**Post: Object Hash Computing** -๐Ÿ’ก #PowerCSharp Feature: Consistent object hashing! - -```csharp -var person = new { Name = "John", Age = 30 }; -string hash = person.ComputeHash(); // "A1B2C3D4E5F67890" -``` - -Perfect for caching, deduplication, and change tracking! ๐Ÿ”„ - -#CSharp #DotNet #Hashing - ---- - -**Post: HTTP Utilities** -๐Ÿ’ก #PowerCSharp Feature: HTTP utilities for web devs! - -```csharp -HttpStatusCode.OK.IsSuccessful(); // true -HttpStatusCode.NotFound.IsClientError(); // true -uri.AddParameter("search", "test"); // Add query params -request.Clone(); // Clone HTTP requests -``` - -Web development made easier! ๐ŸŒ - -#CSharp #DotNet #AspNetCore - ---- - -## ๐Ÿš€ Future Teasers - -**Post: Features Package Coming** -๐Ÿ”œ Coming Soon: PowerCSharp Features! - -Feature flags for easy package management: -โ€ข Enable/disable functionality at runtime -โ€ข Configuration-driven features -โ€ข Modular architecture -โ€ข Zero-downtime feature toggles - -The future of modular C# development! ๐Ÿš€ - -#PowerCSharp #CSharp #FeatureFlags - ---- - -**Post: Clean Architecture Template** -๐Ÿ—๏ธ Coming Soon: Clean Architecture Template! - -Complete .NET 8.0 repository with: -โ€ข PowerCSharp integration -โ€ข Enterprise-grade patterns -โ€ข Production-ready setup -โ€ข Best practices built-in - -Jumpstart your next project! โšก - -#PowerCSharp #CSharp #CleanArchitecture #DotNet - ---- - -## ๐Ÿ“ Call to Actions - -**Post: Try It Now** -๐Ÿš€ Ready to boost your C# productivity? - -PowerCSharp v1.0.0 is production-ready with: -โ€ข 6 modular packages -โ€ข 100+ extensions -โ€ข Enterprise stability -โ€ข Comprehensive docs - -Install today and see the difference! โšก - -```bash -dotnet add package PowerCSharp.Extensions -``` - -#PowerCSharp #CSharp #DotNet #Productivity - ---- - -**Post: Documentation** -๐Ÿ“š Complete documentation available! - -โ€ข API reference for all 100+ methods -โ€ข Usage examples and best practices -โ€ข Migration guides -โ€ข Contributing guidelines - -Everything you need to master PowerCSharp! ๐Ÿ“– - -#PowerCSharp #CSharp #Documentation - -๐Ÿ”— github.com/marioarce/PowerCSharp From f6bb77f4c6bf79892238a878e3baab7f201f63e8 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Thu, 9 Jul 2026 16:53:09 -0600 Subject: [PATCH 02/63] docs: standardize banner image references to absolute GitHub URLs - Updated all README files to use absolute GitHub URLs for PowerCSharp banner images - Changed from relative paths to raw.githubusercontent.com URLs for consistent rendering - Added banner images to Features package READMEs that were missing them - Updated Core and Extensions package READMEs to use absolute URLs instead of relative paths - Ensures banner images display correctly across all documentation platforms --- README.md | 2 +- src/Features/PowerCSharp.BuiltInFeatures/README.md | 2 ++ src/Features/PowerCSharp.Feature.Cache.Abstractions/README.md | 2 ++ src/Features/PowerCSharp.Feature.Cache.BitFaster/README.md | 2 ++ src/Features/PowerCSharp.Feature.Cache.Disk/README.md | 2 ++ src/Features/PowerCSharp.Feature.Cache/README.md | 2 ++ src/Features/PowerCSharp.Features.Abstractions/README.md | 2 ++ src/Features/PowerCSharp.Features/README.md | 2 ++ src/PowerCSharp.Compatibility/README.md | 2 +- src/PowerCSharp.Core/README.md | 2 +- src/PowerCSharp.Extensions.AspNetCore/README.md | 2 +- src/PowerCSharp.Extensions/README.md | 2 +- src/PowerCSharp.Helpers/README.md | 2 +- src/PowerCSharp.Utilities/README.md | 2 +- 14 files changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 60fdc94..b43d325 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # PowerCSharp -![PowerCSharp Banner](docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp](https://img.shields.io/badge/PowerCSharp-v2.0.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/src/Features/PowerCSharp.BuiltInFeatures/README.md b/src/Features/PowerCSharp.BuiltInFeatures/README.md index 4efb480..8dc58f5 100644 --- a/src/Features/PowerCSharp.BuiltInFeatures/README.md +++ b/src/Features/PowerCSharp.BuiltInFeatures/README.md @@ -1,5 +1,7 @@ # PowerCSharp.BuiltInFeatures +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + Bundle of lightweight, runtime-flag-toggled ASP.NET Core capabilities. Toggle each via `PowerFeatures::Enabled`. Any built-in can be disabled and replaced by a custom Pluggable Feature. diff --git a/src/Features/PowerCSharp.Feature.Cache.Abstractions/README.md b/src/Features/PowerCSharp.Feature.Cache.Abstractions/README.md index 5d4bee9..68e2a3b 100644 --- a/src/Features/PowerCSharp.Feature.Cache.Abstractions/README.md +++ b/src/Features/PowerCSharp.Feature.Cache.Abstractions/README.md @@ -1,5 +1,7 @@ # PowerCSharp.Feature.Cache.Abstractions +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + Framework-agnostic contracts and safe-off NoOp implementations for the PowerCSharp Cache feature. - Targets `netstandard2.0` and `net8.0`, so cache providers can run on **.NET Framework** and **.NET Core**. diff --git a/src/Features/PowerCSharp.Feature.Cache.BitFaster/README.md b/src/Features/PowerCSharp.Feature.Cache.BitFaster/README.md index b82b466..1411f1c 100644 --- a/src/Features/PowerCSharp.Feature.Cache.BitFaster/README.md +++ b/src/Features/PowerCSharp.Feature.Cache.BitFaster/README.md @@ -1,5 +1,7 @@ # PowerCSharp.Feature.Cache.BitFaster +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + BitFaster-backed implementation of the PowerCSharp Cache feature. References `BitFaster.Caching`; this dependency is isolated here and never enters apps that don't reference this package. diff --git a/src/Features/PowerCSharp.Feature.Cache.Disk/README.md b/src/Features/PowerCSharp.Feature.Cache.Disk/README.md index 1d44eb7..649f3fa 100644 --- a/src/Features/PowerCSharp.Feature.Cache.Disk/README.md +++ b/src/Features/PowerCSharp.Feature.Cache.Disk/README.md @@ -1,5 +1,7 @@ # PowerCSharp.Feature.Cache.Disk +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + Disk-backed LRU cache implementation for the PowerCSharp Cache feature. - Targets `net8.0`, so it runs on **.NET 8.0 and later**. diff --git a/src/Features/PowerCSharp.Feature.Cache/README.md b/src/Features/PowerCSharp.Feature.Cache/README.md index 12eb56d..65bccad 100644 --- a/src/Features/PowerCSharp.Feature.Cache/README.md +++ b/src/Features/PowerCSharp.Feature.Cache/README.md @@ -1,5 +1,7 @@ # PowerCSharp.Feature.Cache +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + Cache feature module, options, and ASP.NET Core wiring โ€” **no third-party dependencies**. Pair this package with `PowerCSharp.Feature.Cache.Abstractions` (contracts + NoOp) and a provider package (e.g. `PowerCSharp.Feature.Cache.BitFaster`) to choose a backend. diff --git a/src/Features/PowerCSharp.Features.Abstractions/README.md b/src/Features/PowerCSharp.Features.Abstractions/README.md index a84142a..a236122 100644 --- a/src/Features/PowerCSharp.Features.Abstractions/README.md +++ b/src/Features/PowerCSharp.Features.Abstractions/README.md @@ -1,5 +1,7 @@ # PowerCSharp.Features.Abstractions +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + Contracts for the PowerCSharp Features system. Zero third-party dependencies so any feature can reference it cheaply (it relies only on the shared ASP.NET Core framework). diff --git a/src/Features/PowerCSharp.Features/README.md b/src/Features/PowerCSharp.Features/README.md index 3d799b3..6cdcdeb 100644 --- a/src/Features/PowerCSharp.Features/README.md +++ b/src/Features/PowerCSharp.Features/README.md @@ -1,5 +1,7 @@ # PowerCSharp.Features +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) + The Features engine: feature discovery (hybrid auto-scan + explicit), composite flag resolution, DI orchestration, a feature registry, and diagnostics. diff --git a/src/PowerCSharp.Compatibility/README.md b/src/PowerCSharp.Compatibility/README.md index 45f3978..461cdaf 100644 --- a/src/PowerCSharp.Compatibility/README.md +++ b/src/PowerCSharp.Compatibility/README.md @@ -1,6 +1,6 @@ # PowerCSharp.Compatibility -![PowerCSharp Banner](../../docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp.Compatibility](https://img.shields.io/badge/PowerCSharp.Compatibility-v0.1.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/src/PowerCSharp.Core/README.md b/src/PowerCSharp.Core/README.md index e3ca22e..24556fd 100644 --- a/src/PowerCSharp.Core/README.md +++ b/src/PowerCSharp.Core/README.md @@ -1,6 +1,6 @@ # PowerCSharp.Core -![PowerCSharp Banner](../docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp.Core](https://img.shields.io/badge/PowerCSharp.Core-v0.3.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/src/PowerCSharp.Extensions.AspNetCore/README.md b/src/PowerCSharp.Extensions.AspNetCore/README.md index ff4d8a8..7c41467 100644 --- a/src/PowerCSharp.Extensions.AspNetCore/README.md +++ b/src/PowerCSharp.Extensions.AspNetCore/README.md @@ -1,6 +1,6 @@ # PowerCSharp.Extensions.AspNetCore -![PowerCSharp Banner](../../docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp.Extensions.AspNetCore](https://img.shields.io/badge/PowerCSharp.Extensions.AspNetCore-v0.3.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/src/PowerCSharp.Extensions/README.md b/src/PowerCSharp.Extensions/README.md index 84da19d..7f7d4b5 100644 --- a/src/PowerCSharp.Extensions/README.md +++ b/src/PowerCSharp.Extensions/README.md @@ -1,6 +1,6 @@ # PowerCSharp.Extensions -![PowerCSharp Banner](../docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp.Extensions](https://img.shields.io/badge/PowerCSharp.Extensions-v0.3.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/src/PowerCSharp.Helpers/README.md b/src/PowerCSharp.Helpers/README.md index dbf7b25..e2483e6 100644 --- a/src/PowerCSharp.Helpers/README.md +++ b/src/PowerCSharp.Helpers/README.md @@ -1,6 +1,6 @@ # PowerCSharp.Helpers -![PowerCSharp Banner](../docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp.Helpers](https://img.shields.io/badge/PowerCSharp.Helpers-v0.2.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) diff --git a/src/PowerCSharp.Utilities/README.md b/src/PowerCSharp.Utilities/README.md index 90e4f98..39d6f51 100644 --- a/src/PowerCSharp.Utilities/README.md +++ b/src/PowerCSharp.Utilities/README.md @@ -1,6 +1,6 @@ # PowerCSharp.Utilities -![PowerCSharp Banner](../docs/images/PowerCSharp_Banner.png) +![PowerCSharp Banner](https://raw.githubusercontent.com/marioarce/PowerCSharp/0191ee12092c28ccf5a578e59977583117a3ff00/docs/images/PowerCSharp_Banner.png) [![PowerCSharp.Utilities](https://img.shields.io/badge/PowerCSharp.Utilities-v0.2.0-blue.svg)](https://github.com/marioarce/PowerCSharp) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) From 3a2a11030482f4e81c80cd8dcc473143e0946a87 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:17:34 -0600 Subject: [PATCH 03/63] test: remove outdated and placeholder test files - Deleted UnitTest1.cs from PowerCSharp.Core.Tests containing StringExtensionsTests - Deleted UnitTest1.cs from PowerCSharp.Utilities.Tests containing placeholder test - Cleaned up test projects by removing obsolete test files --- tests/PowerCSharp.Core.Tests/UnitTest1.cs | 190 ------------------ .../PowerCSharp.Utilities.Tests/UnitTest1.cs | 10 - 2 files changed, 200 deletions(-) delete mode 100644 tests/PowerCSharp.Core.Tests/UnitTest1.cs delete mode 100644 tests/PowerCSharp.Utilities.Tests/UnitTest1.cs diff --git a/tests/PowerCSharp.Core.Tests/UnitTest1.cs b/tests/PowerCSharp.Core.Tests/UnitTest1.cs deleted file mode 100644 index 7108a56..0000000 --- a/tests/PowerCSharp.Core.Tests/UnitTest1.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using PowerCSharp.Core; -using Xunit; - -namespace PowerCSharp.Core.Tests; - -public class StringExtensionsTests -{ - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnTrueForNull() - { - // Arrange - string? nullString = null; - - // Act - bool result = nullString.IsNullOrWhiteSpace(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnTrueForEmptyString() - { - // Arrange - string emptyString = ""; - - // Act - bool result = emptyString.IsNullOrWhiteSpace(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnTrueForWhitespace() - { - // Arrange - string whitespaceString = " \t\n "; - - // Act - bool result = whitespaceString.IsNullOrWhiteSpace(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnFalseForValidString() - { - // Arrange - string validString = "Hello World"; - - // Act - bool result = validString.IsNullOrWhiteSpace(); - - // Assert - Assert.False(result); - } - - [Fact] - public void SafeSubstring_ShouldReturnSubstringForValidParameters() - { - // Arrange - string text = "Hello World"; - - // Act - string result = text.SafeSubstring(0, 5); - - // Assert - Assert.Equal("Hello", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnEmptyForInvalidStartIndex() - { - // Arrange - string text = "Hello World"; - - // Act - string result = text.SafeSubstring(20, 5); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnEmptyForInvalidLength() - { - // Arrange - string text = "Hello World"; - - // Act - string result = text.SafeSubstring(0, -1); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnPartialForLengthBeyondBounds() - { - // Arrange - string text = "Hello"; - - // Act - string result = text.SafeSubstring(2, 10); - - // Assert - Assert.Equal("llo", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnEmptyForNullString() - { - // Arrange - string? nullString = null; - - // Act - string result = nullString.SafeSubstring(0, 5); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void ToTitleCase_ShouldConvertToTitleCase() - { - // Arrange - string text = "hello world from powercsharp"; - - // Act - string result = text.ToTitleCase(); - - // Assert - Assert.Equal("Hello World From Powercsharp", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleEmptyString() - { - // Arrange - string emptyString = ""; - - // Act - string result = emptyString.ToTitleCase(); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleNullString() - { - // Arrange - string? nullString = null; - - // Act - string result = nullString.ToTitleCase(); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleSingleWord() - { - // Arrange - string singleWord = "hello"; - - // Act - string result = singleWord.ToTitleCase(); - - // Assert - Assert.Equal("Hello", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleMultipleSpaces() - { - // Arrange - string multipleSpaces = "hello world"; - - // Act - string result = multipleSpaces.ToTitleCase(); - - // Assert - Assert.Equal("Hello World", result); - } -} \ No newline at end of file diff --git a/tests/PowerCSharp.Utilities.Tests/UnitTest1.cs b/tests/PowerCSharp.Utilities.Tests/UnitTest1.cs deleted file mode 100644 index 790fccf..0000000 --- a/tests/PowerCSharp.Utilities.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PowerCSharp.Utilities.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file From edf1dc61239a0559e1041c9b07f2e254e2245cd6 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:18:19 -0600 Subject: [PATCH 04/63] feat: add DistinctBy method to ListExtensions - Added DistinctBy method that removes duplicates based on a key selector function - Maintained null safety and improved code readability - Added comprehensive XML documentation for the new DistinctBy method --- .../Collections/ListExtensions.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/PowerCSharp.Core/Collections/ListExtensions.cs b/src/PowerCSharp.Core/Collections/ListExtensions.cs index 39c6888..88058eb 100644 --- a/src/PowerCSharp.Core/Collections/ListExtensions.cs +++ b/src/PowerCSharp.Core/Collections/ListExtensions.cs @@ -38,12 +38,39 @@ public static class ListExtensions var clonedList = new List(sourceList.Count); foreach (var item in sourceList) { - if (item != null) + clonedList.Add(item != null ? (T)item.Clone() : default); + } + + return clonedList; + } + + /// + /// Returns a new list containing only the first occurrence of each unique item based on the specified key selector. + /// + /// The type of elements in the list. + /// The type of the key to compare for uniqueness. + /// The source list to deduplicate. + /// A function to extract the key for each element. + /// A new list with duplicates removed based on the specified key, or null if the input is null. + public static List? DistinctBy(this List? sourceList, Func keySelector) + { + if (sourceList == null) + { + return null; + } + + var seenKeys = new HashSet(); + var result = new List(); + + foreach (var item in sourceList) + { + var key = keySelector(item); + if (seenKeys.Add(key)) { - clonedList.Add((T)item.Clone()); + result.Add(item); } } - return clonedList; + return result; } } From 1d1d91b9fc2b3efa8f08c8aecd2bc1eea4ab8bb5 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:18:59 -0600 Subject: [PATCH 05/63] test: add comprehensive unit tests for ListExtensions - Created ListExtensionsTests.cs with full test coverage for ListExtensions methods - Added 8 tests for DistinctBy method covering null, empty, duplicates, complex types, and order preservation - Added 5 tests for DeepClone method covering null, empty, cloneable items, null items, and modification independence - Included test helper classes TestPerson and TestCloneable to support test scenarios - Ensures robust validation of ListExtensions functionality --- .../ListExtensionsTests.cs | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs diff --git a/tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs b/tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs new file mode 100644 index 0000000..2de7850 --- /dev/null +++ b/tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs @@ -0,0 +1,267 @@ +using PowerCSharp.Core.Collections; +using Xunit; + +namespace PowerCSharp.Core.Tests; + +public class ListExtensionsTests +{ + #region DistinctBy Tests + + [Fact] + public void DistinctBy_WithNullList_ShouldReturnNull() + { + // Arrange + List? nullList = null; + + // Act + var result = nullList.DistinctBy(x => x); + + // Assert + Assert.Null(result); + } + + [Fact] + public void DistinctBy_WithEmptyList_ShouldReturnEmptyList() + { + // Arrange + var emptyList = new List(); + + // Act + var result = emptyList.DistinctBy(x => x); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public void DistinctBy_WithNoDuplicates_ShouldReturnAllItems() + { + // Arrange + var list = new List { 1, 2, 3, 4, 5 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Equal(5, result.Count); + Assert.Equal(list, result); + } + + [Fact] + public void DistinctBy_WithDuplicates_ShouldRemoveDuplicates() + { + // Arrange + var list = new List { 1, 2, 2, 3, 4, 4, 5 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Equal(5, result.Count); + Assert.Equal(1, result[0]); + Assert.Equal(2, result[1]); + Assert.Equal(3, result[2]); + Assert.Equal(4, result[3]); + Assert.Equal(5, result[4]); + } + + [Fact] + public void DistinctBy_WithComplexType_ShouldDedulicateBySpecifiedField() + { + // Arrange + var list = new List + { + new TestPerson { Id = 1, Name = "John" }, + new TestPerson { Id = 2, Name = "Jane" }, + new TestPerson { Id = 1, Name = "John Duplicate" }, + new TestPerson { Id = 3, Name = "Bob" }, + new TestPerson { Id = 2, Name = "Jane Duplicate" } + }; + + // Act + var result = list.DistinctBy(x => x.Id); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal(1, result[0].Id); + Assert.Equal("John", result[0].Name); // First occurrence kept + Assert.Equal(2, result[1].Id); + Assert.Equal("Jane", result[1].Name); // First occurrence kept + Assert.Equal(3, result[2].Id); + Assert.Equal("Bob", result[2].Name); + } + + [Fact] + public void DistinctBy_WithStringKey_ShouldDedulicateByStringField() + { + // Arrange + var list = new List + { + new TestPerson { Id = 1, Name = "John" }, + new TestPerson { Id = 2, Name = "Jane" }, + new TestPerson { Id = 3, Name = "John" }, + new TestPerson { Id = 4, Name = "Bob" } + }; + + // Act + var result = list.DistinctBy(x => x.Name); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal("John", result[0].Name); + Assert.Equal("Jane", result[1].Name); + Assert.Equal("Bob", result[2].Name); + } + + [Fact] + public void DistinctBy_WithAllDuplicates_ShouldReturnSingleItem() + { + // Arrange + var list = new List { 5, 5, 5, 5, 5 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Single(result); + Assert.Equal(5, result[0]); + } + + [Fact] + public void DistinctBy_ShouldPreserveOrderOfFirstOccurrences() + { + // Arrange + var list = new List { 3, 1, 2, 1, 3, 2 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal(3, result[0]); // First occurrence of 3 + Assert.Equal(1, result[1]); // First occurrence of 1 + Assert.Equal(2, result[2]); // First occurrence of 2 + } + + #endregion + + #region DeepClone Tests + + [Fact] + public void DeepClone_WithNullList_ShouldReturnNull() + { + // Arrange + List? nullList = null; + + // Act + var result = nullList.DeepClone(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void DeepClone_WithEmptyList_ShouldReturnEmptyList() + { + // Arrange + var emptyList = new List(); + + // Act + var result = emptyList.DeepClone(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + Assert.NotSame(emptyList, result); + } + + [Fact] + public void DeepClone_WithCloneableItems_ShouldCreateDeepCopy() + { + // Arrange + var list = new List + { + new TestCloneable { Value = 1 }, + new TestCloneable { Value = 2 }, + new TestCloneable { Value = 3 } + }; + + // Act + var result = list.DeepClone(); + + // Assert + Assert.NotNull(result); + Assert.Equal(list.Count, result.Count); + Assert.NotSame(list, result); + + for (int i = 0; i < list.Count; i++) + { + Assert.NotSame(list[i], result[i]); + Assert.Equal(list[i].Value, result[i].Value); + } + } + + [Fact] + public void DeepClone_WithNullItems_ShouldHandleNullItems() + { + // Arrange + var list = new List + { + new TestCloneable { Value = 1 }, + null, + new TestCloneable { Value = 3 } + }; + + // Act + var result = list.DeepClone(); + + // Assert + Assert.NotNull(result); + Assert.Equal(list.Count, result.Count); + Assert.NotNull(result[0]); + Assert.Null(result[1]); + Assert.NotNull(result[2]); + } + + [Fact] + public void DeepClone_ModifyingCloneShouldNotAffectOriginal() + { + // Arrange + var list = new List + { + new TestCloneable { Value = 10 }, + new TestCloneable { Value = 20 } + }; + + // Act + var result = list.DeepClone(); + result[0].Value = 999; + + // Assert + Assert.Equal(10, list[0].Value); + Assert.Equal(999, result[0].Value); + } + + #endregion + + #region Test Helper Classes + + private class TestPerson + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + } + + private class TestCloneable : ICloneable + { + public int Value { get; set; } + + public object Clone() + { + return new TestCloneable { Value = this.Value }; + } + } + + #endregion +} From c8d99e3fe110a28db9b8c42cee4b8e6911a0e1d4 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:19:19 -0600 Subject: [PATCH 06/63] test: add comprehensive unit tests for StringExtensions - Created StringExtensionsTests.cs with full test coverage for string extension methods - Added 4 tests for IsNullOrWhiteSpace covering null, empty, whitespace, and valid strings - Added 5 tests for SafeSubstring covering valid parameters, invalid indices, null strings, and boundary conditions - Added 5 tests for ToTitleCase covering normal conversion, empty strings, null strings, single words, and multiple spaces - Ensures robust validation of string extension functionality --- .../StringExtensionsTests.cs | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs diff --git a/tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs b/tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs new file mode 100644 index 0000000..5a3775f --- /dev/null +++ b/tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs @@ -0,0 +1,191 @@ +using System; +using PowerCSharp.Core; +using PowerCSharp.Extensions.Strings; +using Xunit; + +namespace PowerCSharp.Core.Tests; + +public class StringExtensionsTests +{ + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnTrueForNull() + { + // Arrange + string? nullString = null; + + // Act + bool result = nullString.IsNullOrWhiteSpace(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnTrueForEmptyString() + { + // Arrange + string emptyString = ""; + + // Act + bool result = emptyString.IsNullOrWhiteSpace(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnTrueForWhitespace() + { + // Arrange + string whitespaceString = " \t\n "; + + // Act + bool result = whitespaceString.IsNullOrWhiteSpace(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnFalseForValidString() + { + // Arrange + string validString = "Hello World"; + + // Act + bool result = validString.IsNullOrWhiteSpace(); + + // Assert + Assert.False(result); + } + + [Fact] + public void SafeSubstring_ShouldReturnSubstringForValidParameters() + { + // Arrange + string text = "Hello World"; + + // Act + string result = text.SafeSubstring(0, 5); + + // Assert + Assert.Equal("Hello", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnEmptyForInvalidStartIndex() + { + // Arrange + string text = "Hello World"; + + // Act + string result = text.SafeSubstring(20, 5); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnEmptyForInvalidLength() + { + // Arrange + string text = "Hello World"; + + // Act + string result = text.SafeSubstring(0, -1); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnPartialForLengthBeyondBounds() + { + // Arrange + string text = "Hello"; + + // Act + string result = text.SafeSubstring(2, 10); + + // Assert + Assert.Equal("llo", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnEmptyForNullString() + { + // Arrange + string? nullString = null; + + // Act + string result = nullString.SafeSubstring(0, 5); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void ToTitleCase_ShouldConvertToTitleCase() + { + // Arrange + string text = "hello world from powercsharp"; + + // Act + string result = text.ToTitleCase(); + + // Assert + Assert.Equal("Hello World From Powercsharp", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleEmptyString() + { + // Arrange + string emptyString = ""; + + // Act + string result = emptyString.ToTitleCase(); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleNullString() + { + // Arrange + string? nullString = null; + + // Act + string result = nullString.ToTitleCase(); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleSingleWord() + { + // Arrange + string singleWord = "hello"; + + // Act + string result = singleWord.ToTitleCase(); + + // Assert + Assert.Equal("Hello", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleMultipleSpaces() + { + // Arrange + string multipleSpaces = "hello world"; + + // Act + string result = multipleSpaces.ToTitleCase(); + + // Assert + Assert.Equal("Hello World", result); + } +} \ No newline at end of file From 7fc8457e40e5e372d5095c932a2e1acd723ef8e9 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:19:40 -0600 Subject: [PATCH 07/63] test: fix exception type in TryAdd null dictionary test - Changed expected exception from ArgumentNullException to NullReferenceException - Corrected test to properly validate null reference behavior when calling TryAdd on null dictionary - Aligns test expectations with actual runtime behavior of null reference operations --- tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs b/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs index 9a368bd..bbaf674 100644 --- a/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs +++ b/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs @@ -120,7 +120,7 @@ public void TryAdd_WithNullDictionary_ShouldThrowArgumentNullException() Dictionary? nullDictionary = null; // Act & Assert - Assert.Throws(() => nullDictionary!.TryAdd("key", 1)); + Assert.Throws(() => nullDictionary!.TryAdd("key", 1)); } [Fact] From cfd491361d84f20bf60bb2fbb829a72319d5fdf9 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:20:07 -0600 Subject: [PATCH 08/63] test: update MathHelper tests to use MathUtility class name - Updated all test method calls from MathHelper to MathUtility - Changed Clamp, IsInRange, Percentage, ToRadians, ToDegrees, IsEven, and IsOdd method references - Maintains all existing test logic while using the corrected class name - Aligns tests with the actual class name in the codebase --- .../MathHelperTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs b/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs index 308770b..d8e930b 100644 --- a/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs +++ b/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs @@ -14,7 +14,7 @@ public void Clamp_ShouldReturnMinWhenValueIsBelowMin() int max = 20; // Act - int result = MathHelper.Clamp(value, min, max); + int result = MathUtility.Clamp(value, min, max); // Assert Assert.Equal(min, result); @@ -29,7 +29,7 @@ public void Clamp_ShouldReturnMaxWhenValueIsAboveMax() int max = 20; // Act - int result = MathHelper.Clamp(value, min, max); + int result = MathUtility.Clamp(value, min, max); // Assert Assert.Equal(max, result); @@ -44,7 +44,7 @@ public void Clamp_ShouldReturnValueWhenInRange() int max = 20; // Act - int result = MathHelper.Clamp(value, min, max); + int result = MathUtility.Clamp(value, min, max); // Assert Assert.Equal(value, result); @@ -59,7 +59,7 @@ public void IsInRange_ShouldReturnTrueWhenValueInRange() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.True(result); @@ -74,7 +74,7 @@ public void IsInRange_ShouldReturnTrueWhenValueEqualsMin() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.True(result); @@ -89,7 +89,7 @@ public void IsInRange_ShouldReturnTrueWhenValueEqualsMax() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.True(result); @@ -104,7 +104,7 @@ public void IsInRange_ShouldReturnFalseWhenValueBelowMin() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.False(result); @@ -119,7 +119,7 @@ public void IsInRange_ShouldReturnFalseWhenValueAboveMax() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.False(result); @@ -133,7 +133,7 @@ public void Percentage_ShouldCalculateCorrectPercentage() double total = 100; // Act - double result = MathHelper.Percentage(part, total); + double result = MathUtility.Percentage(part, total); // Assert Assert.Equal(25.0, result); @@ -147,7 +147,7 @@ public void Percentage_ShouldReturnZeroWhenTotalIsZero() double total = 0; // Act - double result = MathHelper.Percentage(part, total); + double result = MathUtility.Percentage(part, total); // Assert Assert.Equal(0.0, result); @@ -160,7 +160,7 @@ public void ToRadians_ShouldConvertDegreesToRadians() double degrees = 180; // Act - double result = MathHelper.ToRadians(degrees); + double result = MathUtility.ToRadians(degrees); // Assert Assert.Equal(Math.PI, result, 5); @@ -173,7 +173,7 @@ public void ToDegrees_ShouldConvertRadiansToDegrees() double radians = Math.PI; // Act - double result = MathHelper.ToDegrees(radians); + double result = MathUtility.ToDegrees(radians); // Assert Assert.Equal(180.0, result, 5); @@ -186,7 +186,7 @@ public void IsEven_ShouldReturnTrueForEvenNumber() int number = 4; // Act - bool result = MathHelper.IsEven(number); + bool result = MathUtility.IsEven(number); // Assert Assert.True(result); @@ -199,7 +199,7 @@ public void IsEven_ShouldReturnFalseForOddNumber() int number = 3; // Act - bool result = MathHelper.IsEven(number); + bool result = MathUtility.IsEven(number); // Assert Assert.False(result); @@ -212,7 +212,7 @@ public void IsOdd_ShouldReturnTrueForOddNumber() int number = 3; // Act - bool result = MathHelper.IsOdd(number); + bool result = MathUtility.IsOdd(number); // Assert Assert.True(result); @@ -225,7 +225,7 @@ public void IsOdd_ShouldReturnFalseForEvenNumber() int number = 4; // Act - bool result = MathHelper.IsOdd(number); + bool result = MathUtility.IsOdd(number); // Assert Assert.False(result); From 46b95969bc261a78924117442a74b0bf13d045ac Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:20:25 -0600 Subject: [PATCH 09/63] test: update ValidationUtility tests to use correct class name - Changed ValidationHelper references to ValidationUtility in test methods - Updated IsNumeric method calls to use the correct class name - Maintains all existing test logic while using the proper class name - Aligns tests with the actual ValidationUtility class in the codebase --- tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs b/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs index 1b10792..ef4ad71 100644 --- a/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs +++ b/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs @@ -90,7 +90,7 @@ public void IsNumeric_ShouldReturnFalseForNonNumericString() string text = "abc123"; // Act - bool result = ValidationHelper.IsNumeric(text); + bool result = ValidationUtility.IsNumeric(text); // Assert Assert.False(result); @@ -103,7 +103,7 @@ public void IsNumeric_ShouldReturnFalseForNullString() string? value = null; // Act - bool result = ValidationHelper.IsNumeric(value); + bool result = ValidationUtility.IsNumeric(value); // Assert Assert.False(result); @@ -116,7 +116,7 @@ public void IsNumeric_ShouldReturnFalseForEmptyString() string value = ""; // Act - bool result = ValidationHelper.IsNumeric(value); + bool result = ValidationUtility.IsNumeric(value); // Assert Assert.False(result); From 4fabf950f5ab1af96555d046001ac67341891253 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:20:46 -0600 Subject: [PATCH 10/63] build: add Extensions reference to Core.Tests and format project files - Added PowerCSharp.Extensions project reference to PowerCSharp.Core.Tests.csproj - Reformatted ItemGroup tags in BuiltInFeatures.Tests.csproj for consistency - Reformatted ItemGroup tags in Features.Tests.csproj for consistency - Reformatted ItemGroup tags in Utilities.Tests.csproj for consistency - Ensures Core.Tests can access extension methods from PowerCSharp.Extensions --- .../PowerCSharp.BuiltInFeatures.Tests.csproj | 6 +++--- tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj | 5 +++-- .../PowerCSharp.Features.Tests.csproj | 6 +++--- .../PowerCSharp.Utilities.Tests.csproj | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj b/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj index 62e1870..94d18e5 100644 --- a/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj +++ b/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj b/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj index 5c6f25b..9463f5d 100644 --- a/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj +++ b/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj @@ -20,8 +20,9 @@ - - + + + diff --git a/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj b/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj index 8972f8f..3b4e1d1 100644 --- a/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj +++ b/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj b/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj index a5ee7d8..9b49ac0 100644 --- a/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj +++ b/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj @@ -20,8 +20,8 @@ - - + + From fb75d91194bc614f6bdbe801d16f0d1c52cac4d6 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:21:09 -0600 Subject: [PATCH 11/63] build: add test projects to solution file - Added PowerCSharp.Core.Tests project to solution with Debug and Release configurations - Added PowerCSharp.Compatibility.Extensions.Tests project to solution with Debug and Release configurations - Added PowerCSharp.Utilities.Tests project to solution with Debug and Release configurations - Configured nested project structure to place all three test projects under Tests folder - Ensures test projects are included in solution build and navigation --- PowerCSharp.sln | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/PowerCSharp.sln b/PowerCSharp.sln index 561d853..291e5c9 100644 --- a/PowerCSharp.sln +++ b/PowerCSharp.sln @@ -47,6 +47,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Cache.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Cache.Disk", "src\Features\PowerCSharp.Feature.Cache.Disk\PowerCSharp.Feature.Cache.Disk.csproj", "{137976EF-A677-499E-9567-A8B7E3B6F1BE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Core.Tests", "tests\PowerCSharp.Core.Tests\PowerCSharp.Core.Tests.csproj", "{F5B563E8-E215-4350-9480-BBFCE47DCF34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Compatibility.Extensions.Tests", "tests\PowerCSharp.Compatibility.Extensions.Tests\PowerCSharp.Compatibility.Extensions.Tests.csproj", "{94E9EFB2-F912-49B7-93F2-E908BD689DAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Utilities.Tests", "tests\PowerCSharp.Utilities.Tests\PowerCSharp.Utilities.Tests.csproj", "{79C80B63-F711-4EC4-91F5-52D892E60DC6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -128,6 +134,18 @@ Global {137976EF-A677-499E-9567-A8B7E3B6F1BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {137976EF-A677-499E-9567-A8B7E3B6F1BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {137976EF-A677-499E-9567-A8B7E3B6F1BE}.Release|Any CPU.Build.0 = Release|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.Build.0 = Release|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.Build.0 = Release|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {73835311-BC0A-4107-9E6D-4950DF565C46} = {493BDE4C-2EBA-49EE-BF44-10AAB73634D6} @@ -149,5 +167,8 @@ Global {01C67F51-0FE0-4576-9507-7CE856370C14} = {8507D629-2649-468E-8345-212CFC547FA8} {E61A4C83-42A8-42A5-B1F1-868ED5DE762F} = {006F80FE-AC82-4752-972B-081BA6C6A651} {137976EF-A677-499E-9567-A8B7E3B6F1BE} = {006F80FE-AC82-4752-972B-081BA6C6A651} + {F5B563E8-E215-4350-9480-BBFCE47DCF34} = {8507D629-2649-468E-8345-212CFC547FA8} + {94E9EFB2-F912-49B7-93F2-E908BD689DAF} = {8507D629-2649-468E-8345-212CFC547FA8} + {79C80B63-F711-4EC4-91F5-52D892E60DC6} = {8507D629-2649-468E-8345-212CFC547FA8} EndGlobalSection EndGlobal From 0b80da03b5ab0f5a7796f3909589c4f649d55730 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:24:04 -0600 Subject: [PATCH 12/63] feat: add Coalesce method to StringExtensions - Added Coalesce extension method that returns fallback value when raw value is null or empty - Method takes nullable string rawValue and nullable string fallback parameters - Returns rawValue when it has content, otherwise returns fallback - Includes comprehensive XML documentation for the new method - Provides convenient null-coalescing behavior for string values --- .../Strings/StringExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/PowerCSharp.Extensions/Strings/StringExtensions.cs b/src/PowerCSharp.Extensions/Strings/StringExtensions.cs index 88aee76..a7d31a4 100644 --- a/src/PowerCSharp.Extensions/Strings/StringExtensions.cs +++ b/src/PowerCSharp.Extensions/Strings/StringExtensions.cs @@ -661,6 +661,17 @@ public static string Convert9DigitZipTo5Digit(this string zipCode) ?? zipCode; } + /// + /// Returns the raw value when it has content; otherwise the current fallback value. + /// + /// The candidate raw value. + /// The value to keep when the candidate is empty. + /// The overriding value or the existing fallback. + public static string? Coalesce(this string? rawValue, string? fallback) + { + return string.IsNullOrEmpty(rawValue) ? fallback : rawValue; + } + /// /// Returns a copy of the string after removing Html Tags and Entities like ® or  . /// From 294dc24008dd97891c475ffc7e5fce83fd7214cc Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:24:38 -0600 Subject: [PATCH 13/63] test: add comprehensive unit tests for Coalesce method - Created StringExtensionsTests.cs with full test coverage for the new Coalesce method - Added 7 tests covering null, empty, whitespace, and various null/empty combinations - Tests verify that Coalesce returns rawValue when it has content and fallback when rawValue is null or empty - Includes edge case tests for null fallback, both null, and both empty scenarios - Ensures robust validation of the Coalesce string extension method --- .../StringExtensionsTests.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs diff --git a/tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs b/tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs new file mode 100644 index 0000000..86eb6ab --- /dev/null +++ b/tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs @@ -0,0 +1,109 @@ +using PowerCSharp.Extensions.Strings; +using Xunit; + +namespace PowerCSharp.Extensions.Tests; + +public class StringExtensionsTests +{ + #region Coalesce Tests + + [Fact] + public void Coalesce_WithNonNullRawValue_ShouldReturnRawValue() + { + // Arrange + string rawValue = "hello"; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(rawValue, result); + } + + [Fact] + public void Coalesce_WithEmptyRawValue_ShouldReturnFallback() + { + // Arrange + string rawValue = ""; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(fallback, result); + } + + [Fact] + public void Coalesce_WithNullRawValue_ShouldReturnFallback() + { + // Arrange + string? rawValue = null; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(fallback, result); + } + + [Fact] + public void Coalesce_WithWhitespaceRawValue_ShouldReturnRawValue() + { + // Arrange + string rawValue = " "; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(rawValue, result); + } + + [Fact] + public void Coalesce_WithNullFallback_ShouldReturnFallback() + { + // Arrange + string rawValue = ""; + string? fallback = null; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Coalesce_WithBothNull_ShouldReturnNull() + { + // Arrange + string? rawValue = null; + string? fallback = null; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Coalesce_WithBothEmpty_ShouldReturnFallback() + { + // Arrange + string rawValue = ""; + string fallback = ""; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(fallback, result); + } + + #endregion +} From ae56ed6fc06ddcaf4d667dc6c7a7a09cbe43667f Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:42:49 -0600 Subject: [PATCH 14/63] ci: add Windows to CI test matrix for cross-platform validation - Added matrix strategy with ubuntu-latest and windows-latest operating systems - Changed runs-on to use matrix.os instead of hardcoded ubuntu-latest - Enables cross-platform testing for both Linux and Windows environments - Ensures code compatibility across different operating systems --- .github/workflows/ci-cd.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 658c963..1e81389 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -37,7 +37,10 @@ env: jobs: build-and-test: name: Build and Test - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] steps: - name: Checkout From 86464f7fa3409f8a21ecb845bc6b034a5d1e4bac Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:48:15 -0600 Subject: [PATCH 15/63] ci: revert CI to single Ubuntu runner and remove Compatibility.Extensions.Tests from solution - Reverted build-and-test job to use single ubuntu-latest runner instead of matrix strategy - Removed PowerCSharp.Compatibility.Extensions.Tests project from solution file - Cleaned up build configurations for the removed test project - Simplified CI configuration to use single platform for testing --- .github/workflows/ci-cd.yml | 5 +---- PowerCSharp.sln | 7 ------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1e81389..658c963 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -37,10 +37,7 @@ env: jobs: build-and-test: name: Build and Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] + runs-on: ubuntu-latest steps: - name: Checkout diff --git a/PowerCSharp.sln b/PowerCSharp.sln index 291e5c9..c7a1d94 100644 --- a/PowerCSharp.sln +++ b/PowerCSharp.sln @@ -49,8 +49,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Cache.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Core.Tests", "tests\PowerCSharp.Core.Tests\PowerCSharp.Core.Tests.csproj", "{F5B563E8-E215-4350-9480-BBFCE47DCF34}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Compatibility.Extensions.Tests", "tests\PowerCSharp.Compatibility.Extensions.Tests\PowerCSharp.Compatibility.Extensions.Tests.csproj", "{94E9EFB2-F912-49B7-93F2-E908BD689DAF}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Utilities.Tests", "tests\PowerCSharp.Utilities.Tests\PowerCSharp.Utilities.Tests.csproj", "{79C80B63-F711-4EC4-91F5-52D892E60DC6}" EndProject Global @@ -138,10 +136,6 @@ Global {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.Build.0 = Release|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.Build.0 = Release|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.Build.0 = Debug|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -168,7 +162,6 @@ Global {E61A4C83-42A8-42A5-B1F1-868ED5DE762F} = {006F80FE-AC82-4752-972B-081BA6C6A651} {137976EF-A677-499E-9567-A8B7E3B6F1BE} = {006F80FE-AC82-4752-972B-081BA6C6A651} {F5B563E8-E215-4350-9480-BBFCE47DCF34} = {8507D629-2649-468E-8345-212CFC547FA8} - {94E9EFB2-F912-49B7-93F2-E908BD689DAF} = {8507D629-2649-468E-8345-212CFC547FA8} {79C80B63-F711-4EC4-91F5-52D892E60DC6} = {8507D629-2649-468E-8345-212CFC547FA8} EndGlobalSection EndGlobal From 393b2fc9c4763b741216000b77e7081006cc63d5 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 15:09:40 -0600 Subject: [PATCH 16/63] docs(agents): add repository-wide CLAUDE operating guidelines - Define non-negotiable architectural and workflow rules for AI coding agents - Document package topology, dependency-isolation constraints, and version-family boundaries - Establish structured response formatting, communication standards, and build/test/pack commands - Add index of nested project guidance for sensitive and feature-specific areas --- CLAUDE.md | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..22b1e98 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,201 @@ +# CLAUDE.md + +> Guidance for Claude โ€” and any AI coding agent โ€” operating in the **PowerCSharp** repository. +> Read this file in full before touching any code, documentation, or CI/CD configuration. +> Nested `CLAUDE.md` files in specific project folders take precedence over this file for +> anything specific to that project; this file is the baseline that always applies. + +--- + +## 1. Role + +You are acting as a **Senior .NET Architect and AI Workflow Engineer** embedded in the PowerCSharp +codebase โ€” a modular, independently-versioned suite of C# libraries (`Core`, `Extensions`, +`Utilities`, `Helpers`, `Compatibility`, `Extensions.AspNetCore`) plus a pluggable **Features +Framework** (`Features` engine, `BuiltInFeatures`, and the `Feature.*` family: `Cache` shipped +today, `Sitecore` and others on the roadmap). + +Your job is not to produce code that merely compiles. It is to produce code and documentation +that: + +- Fits the existing architectural seams instead of inventing new ones. +- Respects package boundaries and dependency-isolation rules (Section 3). +- Is reviewable by a human maintainer in one pass โ€” clear diffs, clear rationale, no surprises. +- Ships with tests, XML doc comments, and README/CHANGELOG updates where convention demands it. + +## 2. Operating Principles (non-negotiable) + +1. **Understand before you edit.** Read the relevant `src/**`, `tests/**`, and `docs/**` files for + the area you're touching before writing a line of code. Do not assume a class, interface, or + convention exists โ€” verify it by reading the source. +2. **Ask, don't guess, on architectural forks.** If a change could reasonably go two ways (new + package vs. extend an existing one; new version family vs. reuse one; new abstraction vs. reuse + an existing contract), stop and ask the maintainer rather than picking one silently. +3. **Never commit or push without explicit human approval.** Prepare changes on a branch and stop. + Only stage/commit/push when the user has explicitly said to do so in the current turn. +4. **Never work directly on `main` or `develop`.** Always branch from `develop` using + `feature/` (or `release/` for release prep), per `docs/WORKFLOW.md`. If you lack + the git permissions to create or push a branch, say so explicitly and ask for them rather than + working uncommitted on a protected branch. +5. **Preserve dependency isolation.** A package must never leak a third-party dependency into + consumers who didn't ask for it (Section 3.2). This is the single most important architectural + invariant in this repository โ€” violating it is a design bug, not a style nit. +6. **Respect version-family boundaries.** Every change belongs to exactly one version family + (Section 4), and that determines where the version bump happens. Do not bump a family you + didn't touch. +7. **Escalate before refactoring sensitive zones.** `src/Features/PowerCSharp.Features` + (assembly discovery + composite flag resolution) and + `src/Features/PowerCSharp.Feature.Cache.Disk` (cross-process file locking) are structurally + sensitive โ€” see their nested `CLAUDE.md` files. Scope changes narrowly; do not "clean up" these + areas opportunistically. +8. **`PowerCSharp.Compatibility` is a live production dependency** for real .NET Framework + consumers, not a legacy afterthought. Treat every change to it as shipping immediately โ€” see + `src/PowerCSharp.Compatibility/CLAUDE.md`. +9. **Public API changes are breaking-change events.** Any signature, namespace, or behavioral + change to a public type in a shipped package (anything with a NuGet badge in `README.md`) + requires an explicit note in the response and, where applicable, a `CHANGELOG.md` entry. + +## 3. Repository Architecture + +### 3.1 Package topology + +```text +PowerCSharp.Core zero-dependency foundation (interfaces, models) + โ””โ”€ PowerCSharp.Extensions cross-platform extension methods (net8.0 + netstandard2.0) + โ””โ”€ PowerCSharp.Extensions.AspNetCore ASP.NET Coreโ€“specific extensions (net8.0) + โ””โ”€ PowerCSharp.Utilities validation, file, math utilities (net8.0 + netstandard2.0) + โ””โ”€ PowerCSharp.Helpers JSON, crypto, environment helpers (net8.0 + netstandard2.0) + +PowerCSharp.Compatibility standalone .NET Framework layer (net462/net472/net48) + NOT part of the Core dependency graph โ€” own CLAUDE.md + +--- Features Framework (independent versioning family) --- +PowerCSharp.Features.Abstractions pure contracts, zero third-party deps + โ””โ”€ PowerCSharp.Features engine: discovery, flag resolution, DI orchestration + โ””โ”€ PowerCSharp.BuiltInFeatures Group 1 bundle (CORS today; runtime-flag toggled only) + +--- Cache Feature Family (independent versioning family) --- +PowerCSharp.Feature.Cache.Abstractions cache contracts + NoOp (netstandard2.0 + net8.0) + โ””โ”€ PowerCSharp.Feature.Cache module + options + AddCacheFeature() wiring + โ”œโ”€ PowerCSharp.Feature.Cache.BitFaster BitFaster-backed LRU (isolates BitFaster.Caching) + โ””โ”€ PowerCSharp.Feature.Cache.Disk disk-backed LRU (cross-process locking) โ€” own CLAUDE.md + +--- Roadmapped, confirmed in scope (see src/Features/CLAUDE.md) --- +PowerCSharp.Feature.Sitecore third-party GraphQL/Sitecore integration โ€” not started +``` + +### 3.2 Dependency-isolation rule + +Third-party NuGet packages (`BitFaster.Caching`, a future Sitecore SDK, etc.) are referenced +**only** by the leaf package that needs them โ€” never by an `*.Abstractions` package or the +engine. An application that does not reference `PowerCSharp.Feature.Cache.BitFaster` must never +pull `BitFaster.Caching` in transitively. Apply the identical rule to any new pluggable feature, +including the future `PowerCSharp.Feature.Sitecore`. + +### 3.3 Solutions + +- `PowerCSharp.sln` โ€” the main solution; drives `dotnet build` / `test` / `pack` in CI + (`.github/workflows/ci-cd.yml`). +- `PowerCSharp-Compatibility.sln` โ€” isolated solution for the .NET Framework layer. **Not + currently wired into CI** โ€” see `src/PowerCSharp.Compatibility/CLAUDE.md` for the implication + before touching that package. + +## 4. Versioning Model + +Centrally managed in `Directory.Build.props` as independently-bumped "families": + +| MSBuild property | Covers | Bumped via | +|---|---|---| +| `PowerCSharpVersion` | Core, Extensions, Extensions.AspNetCore, Utilities, Helpers | `workflow_dispatch` โ†’ `package_family: core` | +| `PowerCSharpCompatibilityVersion` | Compatibility | manual edit in `Directory.Build.props` | +| `PowerCSharpFeaturesVersion` | Features.Abstractions, Features, BuiltInFeatures | `workflow_dispatch` โ†’ `package_family: features` | +| `PowerCSharpFeatureCacheVersion` | Feature.Cache.Abstractions, Feature.Cache, Feature.Cache.BitFaster, Feature.Cache.Disk | `workflow_dispatch` โ†’ `package_family: cache` | + +If you are adding to an existing package, bump its existing family. If you are standing up a new +pluggable `Feature.` family (e.g. `Feature.Sitecore`), it earns its own +`PowerCSharpFeatureVersion` property and its own `workflow_dispatch` choice โ€” mirroring the +Cache precedent (`docs/PowerCSharp.Features.PackageLayout.md` ยง2 already anticipates +`PowerCSharpFeatureSitecoreVersion`). Never fold a new feature family into +`PowerCSharpFeaturesVersion`; that property is reserved for the framework trio itself. + +## 5. Formatting Constraints for Structured Output + +When a response involves anything beyond a short prose answer โ€” a plan, a code review, a proposed +change set, a risk assessment โ€” structure it with the XML tags below so it can be parsed and +reviewed reliably. Use only the tags relevant to the response; do not force all of them into a +trivial answer (a one-line fix does not need a `` block). + +```xml + + What you found reading the code. Cite file paths and line numbers. State assumptions explicitly + rather than silently relying on them. + + + + Ordered, numbered steps. Each step names the file(s) it touches and why. + + + + Anything touching a sensitive zone (Section 2.7), a version-family boundary, a public API + surface, or CI/CD. State the blast radius. If there is no material risk, say so explicitly + rather than omitting the tag. + + + + + One-line description of the change. + + + + + Tests added/updated, and what was manually verified (build output, dotnet test results, etc.). + + + + Anything left for the human reviewer to decide before merge. + +``` + +Code blocks always carry a language tag (` ```csharp `, ` ```xml `, ` ```yaml `). Commit message +suggestions follow Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`), +per `docs/WORKFLOW.md`. + +## 6. Tone & Communication Guidelines + +- Write like a senior architect reviewing a colleague's PR: direct, specific, evidence-based. Cite + the file and line you're referring to instead of describing code in the abstract. +- Prefer precision over hedging. If something is a hard rule (Section 2), say so plainly. If it's + a judgment call, say that too, and give the reasoning, not just the conclusion. +- No filler, no restating the request back, no preambles. +- Surface risk and disagreement early and explicitly rather than burying it at the end of a long + explanation. If a request conflicts with an architectural invariant in this file, name which one + and why, then propose the compliant alternative โ€” do not silently comply or silently refuse. +- Keep the length of a response proportional to the size of the change. A one-line fix gets a + one-line summary. A new package gets a structured `` / `` writeup. + +## 7. How to Build, Test, and Pack + +```bash +dotnet restore PowerCSharp.sln +dotnet build PowerCSharp.sln --configuration Release +dotnet test PowerCSharp.sln --configuration Release --collect:"XPlat Code Coverage" +dotnet pack PowerCSharp.sln --configuration Release --output ./packages +``` + +The .NET Framework compatibility layer is **not** covered by the commands above โ€” see +`src/PowerCSharp.Compatibility/CLAUDE.md`. + +## 8. Nested Documentation Index + +- `Workflows.md` โ€” step-by-step SOPs for recurring tasks (extending a core library, building a new + pluggable Feature package, cutting a release). +- `src/Features/CLAUDE.md` โ€” Features Framework internals, extension points, Sitecore roadmap. +- `src/Features/PowerCSharp.Feature.Cache.Disk/CLAUDE.md` โ€” disk-cache locking model and hazards. +- `src/PowerCSharp.Compatibility/CLAUDE.md` โ€” .NET Framework layer constraints and the CI gap. +- `docs/WORKFLOW.md` โ€” GitFlow branching and CI/CD pipeline reference (human-facing, complements + this file). +- `docs/PowerCSharp.Features.Architecture.md` and + `docs/PowerCSharp.Features.Authoring-Guide.md` โ€” required reading before building any new + Feature package, pluggable or built-in. +- `docs/EDGE_CASES_AND_SECURITY.md` โ€” per-API edge-case and security notes; consult before + changing the behavior of any existing public extension/utility method. From 002cee94dd6f85a39ecdf61828aea46a24c4a0bc Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 15:10:09 -0600 Subject: [PATCH 17/63] docs(workflows): add SOP guide for recurring repository tasks - Add standardized procedures for extending core libraries with tests, docs, and changelog updates - Document end-to-end workflow for creating new pluggable feature packages with dependency isolation and version-family setup - Define release preparation and publishing checklist, including branching, versioning, CI/CD dispatch, and post-release verification --- Workflows.md | 149 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 Workflows.md diff --git a/Workflows.md b/Workflows.md new file mode 100644 index 0000000..605dfd2 --- /dev/null +++ b/Workflows.md @@ -0,0 +1,149 @@ +# Workflows.md + +> Standard Operating Procedures for recurring tasks in the PowerCSharp repository. These SOPs are +> the concrete, step-by-step companion to `CLAUDE.md`. Follow them in order; do not skip +> verification steps to save time. + +--- + +## SOP-01 โ€” Adding a New Extension / Utility / Helper Method + +**When to use:** adding a method to an existing `PowerCSharp.Extensions`, `PowerCSharp.Utilities`, +`PowerCSharp.Helpers`, or `PowerCSharp.Extensions.AspNetCore` class (or a new class within one of +those packages). This is the `PowerCSharpVersion` family. + +1. **Locate the right home.** Match the method to an existing namespace/class by convention + (e.g. string helpers โ†’ `PowerCSharp.Extensions` `StringExtensions`; validation โ†’ + `PowerCSharp.Utilities` `ValidationUtility`). Read the target file fully before adding to it โ€” + check `docs/PowerCSharp..md` for the documented surface and + `docs/EDGE_CASES_AND_SECURITY.md` for existing null/edge-case conventions in that area. +2. **Match existing signatures and null-handling style.** This codebase favors "safe" variants + that don't throw (`SafeSubstring`, `FirstOrDefaultSafe`) โ€” follow the established pattern for + the class you're extending rather than introducing a new error-handling style. +3. **Target frameworks.** Confirm the package's `` in its `.csproj` + (`net8.0;netstandard2.0` for most Core-family packages). If the API you need doesn't exist on + `netstandard2.0`, either polyfill it or guard with `#if NET8_0_OR_GREATER` โ€” do not silently + drop a target framework. +4. **Nullable + EditorConfig.** `Nullable` is `enable` repo-wide (`Directory.Build.props`). Follow + `.editorconfig` member-ordering and style rules; run `dotnet format` if unsure. +5. **Write the method with full XML doc comments** (``, ``, ``, + `` where relevant) โ€” these packages ship with `GenerateDocumentationFile` and their + docs pages are hand-maintained from these comments. +6. **Add tests** in the matching `tests/PowerCSharp..Tests` project (xunit). Cover: happy + path, null/empty input, and at least one boundary case, mirroring the style already used for + sibling methods in the same test file. +7. **Update package docs.** Add a short usage example to `docs/PowerCSharp..md` and, if + the method is a headline addition, to the "Usage Examples" section of the root `README.md`. +8. **Update `CHANGELOG.md`** under an `[Unreleased]` or next-version heading, following the + Keep a Changelog format already in use. +9. **Verify locally:** + ```bash + dotnet build PowerCSharp.sln --configuration Release + dotnet test tests/PowerCSharp..Tests --configuration Release + ``` +10. **Version family:** this change belongs to `PowerCSharpVersion` (or + `PowerCSharpCompatibilityVersion` if the target is `PowerCSharp.Compatibility` โ€” see that + package's own `CLAUDE.md` first). Do not bump the version yourself; that happens in SOP-03 at + release time via `workflow_dispatch`. Note the intended family in your summary to the reviewer. +11. **Branch and hand off.** Work on `feature/` branched from `develop`. Do not + merge or push without explicit approval โ€” present the diff for peer review per the working + agreement in place for this repository. + +--- + +## SOP-02 โ€” Building a New Pluggable Feature Package (`PowerCSharp.Feature.`) + +**When to use:** standing up a new feature under the Features Framework โ€” for example, the +roadmapped `PowerCSharp.Feature.Sitecore`. Read `docs/PowerCSharp.Features.Architecture.md` and +`docs/PowerCSharp.Features.Authoring-Guide.md` in full before starting; this SOP sequences that +guidance into an actionable checklist. Do not begin writing code before both documents have been +read for this session. + +1. **Classify the tier.** Run the decision tree in the Authoring Guide ยง1. A feature that pulls a + third-party SDK (a Sitecore GraphQL/Content Serialization client, for example) or carries + non-trivial implementation is **always Pluggable (Group 2)** โ€” it never belongs in + `PowerCSharp.BuiltInFeatures`. +2. **Decide single package vs. family.** If there is exactly one implementation, use a single + `PowerCSharp.Feature.` package. If there are or will be swappable backends (as with + Cache: BitFaster vs. Disk), split into `PowerCSharp.Feature.` (contracts + module) plus + one `PowerCSharp.Feature..` package per backend. **Ask the maintainer if this is + ambiguous** โ€” do not guess for a feature with real architectural weight like Sitecore. +3. **Scaffold the project(s)** under `src/Features/PowerCSharp.Feature.[.{Provider}]/`, + matching `docs/PowerCSharp.Features.PackageLayout.md` ยง1 folder conventions: + - Contracts-only packages target `netstandard2.0;net8.0` and take **zero** third-party + dependencies beyond `Microsoft.Extensions.Logging.Abstractions`. + - Implementation packages target `net8.0` and isolate their third-party SDK dependency + entirely within that package (Section 3.2 of the root `CLAUDE.md`). +4. **Define the anatomy** (Authoring Guide ยง2), all four/five pieces: + - a stable `FeatureKey` string (e.g. `"Sitecore"`), used in config as `PowerFeatures:`; + - the contracts other code will depend on; + - a `FeatureOptionsBase` subclass bound from `PowerFeatures:`; + - an `IFeatureModule` implementation for auto-discovery, plus (optionally) an explicit + `AddFeature()` extension method for opt-in registration; + - a safe-off (NoOp) behavior for when the flag is disabled โ€” dependents must still resolve. +5. **Wire `ConfigureServices`** using the Cache module (`CacheFeatureModule`, in + `src/Features/PowerCSharp.Feature.Cache/`) as the literal template: check + `context.Flags.IsEnabled(FeatureKey)` first, register the NoOp implementation and return early + if disabled, otherwise register the real implementation. Add `ConfigurePipeline` only if the + feature contributes middleware. +6. **Add the project(s) to `PowerCSharp.sln`** under the existing `Features` solution folder, plus + a matching `tests/PowerCSharp.Feature..Tests` project referencing `xunit` + + `Microsoft.NET.Test.Sdk` at the versions already used by sibling test projects. +7. **Add a new version-family property** to `Directory.Build.props` + (`PowerCSharpFeatureVersion`, e.g. `PowerCSharpFeatureSitecoreVersion`), starting at + `1.0.0`. Set every new package's `` to that property. Add the family to the + `package_family` choice list in `.github/workflows/ci-cd.yml`'s `workflow_dispatch` inputs. +8. **Write tests** covering: flag-off โ†’ NoOp is resolved; flag-on โ†’ real implementation is + resolved; option binding; and the feature's own core logic. Mirror + `tests/PowerCSharp.Feature.Cache.Tests` structure. +9. **Document it**: a package `README.md` (packed via `PackageReadmeFile`, see the Cache `.csproj` + for the pattern), an entry in the root `README.md` package table, and a `docs/PowerCSharp.Feature..md` + page following the existing per-package doc format. +10. **Verify dependency isolation manually.** Reference only the new package from a scratch console + app and confirm the third-party SDK does *not* appear in `dotnet list package --include-transitive` + output unless that specific package was referenced. +11. **Branch and hand off.** Work on `feature/-feature` from `develop`. Present the plan + (tier decision, package layout, version-family name) as a `` block per `CLAUDE.md` + Section 5 before writing substantial code, since this is an architectural addition โ€” confirm + with the maintainer before scaffolding if any step above required a judgment call. + +--- + +## SOP-03 โ€” Preparing and Publishing a Release + +**When to use:** a `develop`-branch feature set is ready to ship to NuGet.org and GitHub Packages +for one or more version families. Full reference: `docs/WORKFLOW.md`. + +1. **Confirm `develop` is green.** `dotnet build PowerCSharp.sln --configuration Release` and + `dotnet test PowerCSharp.sln --configuration Release` must both pass locally before starting; + CI will re-verify on push but don't rely on CI to catch avoidable failures. +2. **Identify which version family(ies) changed** since the last release: `core` + (`PowerCSharpVersion`), `features` (`PowerCSharpFeaturesVersion`), `cache` + (`PowerCSharpFeatureCacheVersion`), or a newly-introduced family from SOP-02. Cross-check + against `CHANGELOG.md`'s `[Unreleased]` section. +3. **Create the release branch from `develop`:** + ```bash + git checkout develop + git pull origin develop + git checkout -b release/v + ``` +4. **Update documentation and changelog** on the release branch: move `[Unreleased]` entries in + `CHANGELOG.md` under the new version heading with today's date, and confirm the root + `README.md` NuGet badge table doesn't need a new row (new package) or wording updates. +5. **Bump the version(s).** Preferred path is the `workflow_dispatch` trigger on + `.github/workflows/ci-cd.yml` (`release_type`: patch/minor/major, `package_family`: + core/features/cache/\) โ€” this both edits `Directory.Build.props` and creates the + `v` / `features-v` / `cache-v` tag automatically. If multiple families + changed, run it once per family. Manual edits to `Directory.Build.props` are the fallback only + if `workflow_dispatch` is unavailable. +6. **Open the PR:** `release/v` โ†’ `main`, including release notes, a summary of testing + performed, and any breaking changes called out explicitly (per `CLAUDE.md` Section 2.9). +7. **On merge to `main`**, the pipeline automatically: builds and tests, packs (`dotnet pack + PowerCSharp.sln --configuration Release --output ./packages`), publishes to NuGet.org and + GitHub Packages, and deletes the `release/*` branch. Confirm the packages appear on + nuget.org/GitHub Packages after the workflow completes โ€” do not assume a green pipeline run + equals a successful publish without checking. +8. **Verify Codecov** picked up the coverage report for the release build (badge in `README.md`). +9. **Do not commit, tag, or push any step above without explicit sign-off** unless the user has + directed you to execute the release directly โ€” treat release actions as high-blast-radius by + default and confirm before executing steps 3, 5, 6, and 7. From d40a91cb5f8c2fc98526d045f598f16d940a21e6 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 15:10:30 -0600 Subject: [PATCH 18/63] docs(features): add framework-specific CLAUDE guidance for feature architecture - Define high-sensitivity operating constraints for the Features engine and module lifecycle - Document two-tier feature model and strict dependency-direction rules across abstractions, engine, built-in, and pluggable packages - Capture engine internals and invariants for module discovery, flag provider precedence, and module self-gating behavior - Add implementation guardrails for future pluggable features and clarify current Sitecore roadmap status --- src/Features/CLAUDE.md | 96 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/Features/CLAUDE.md diff --git a/src/Features/CLAUDE.md b/src/Features/CLAUDE.md new file mode 100644 index 0000000..66b0d12 --- /dev/null +++ b/src/Features/CLAUDE.md @@ -0,0 +1,96 @@ +# CLAUDE.md โ€” Features Framework (`src/Features/`) + +> Scope: `PowerCSharp.Features.Abstractions`, `PowerCSharp.Features`, `PowerCSharp.BuiltInFeatures`, +> and the `PowerCSharp.Feature.*` family. Read this alongside the root `CLAUDE.md` and, before any +> substantial change here, `docs/PowerCSharp.Features.Architecture.md` and +> `docs/PowerCSharp.Features.Authoring-Guide.md` in full. + +## Sensitivity level: high + +The engine (`PowerCSharp.Features`) is load-bearing for every feature package that exists or will +exist, including the roadmapped `PowerCSharp.Feature.Sitecore`. A subtle change to discovery or +flag-resolution order silently changes behavior for every consumer, in every host app, without a +compile error. Treat changes here as you would changes to a dependency-injection container: +narrowly scoped, heavily tested, and called out explicitly as `` in your response. + +## 1. The Two-Tier Model + +| Tier | Package | Gating | Third-party deps | +|---|---|---|---| +| Group 1 โ€” Built-in | `PowerCSharp.BuiltInFeatures` | Runtime flag only | None isolated (framework/ASP.NET Core only) | +| Group 2 โ€” Pluggable | `PowerCSharp.Feature.[.Provider]` | Package reference **and** runtime flag | Isolated per package | + +Any Built-in Feature can be disabled and replaced by a custom Pluggable Feature. Do not add a +third-party dependency to `PowerCSharp.BuiltInFeatures` โ€” that would violate the tier's contract; +a feature needing one belongs in Group 2. + +## 2. Dependency Direction (do not invert) + +```text +PowerCSharp.Features.Abstractions (contracts, zero deps) + โ–ฒ โ–ฒ + โ”‚ โ”‚ +PowerCSharp.Features PowerCSharp.Feature. (module/options) + (engine) โ–ฒ + โ–ฒ โ”‚ + โ”‚ PowerCSharp.Feature.. (isolates 3rd-party SDK) +PowerCSharp.BuiltInFeatures +``` + +`Features.Abstractions` must never gain a dependency on `Features` (the engine) or on any +`Feature.*` package โ€” it is the floor everything else builds on. If you find yourself wanting to +reference the engine from Abstractions, the abstraction belongs somewhere else. + +## 3. Engine Internals โ€” What Each Piece Does + +Grounded in the current implementation (`src/Features/PowerCSharp.Features/`): + +- **`PowerFeaturesServiceCollectionExtensions.AddPowerFeatures`** โ€” the single entry point. It (in + order): builds the flag-provider chain, discovers modules, and for **every** discovered module โ€” + enabled or not โ€” invokes `ConfigureServices`. Modules self-gate internally (Model A: "always + invoke `ConfigureServices`; the module decides active vs. NoOp"). Do not change this to + conditionally skip disabled modules' `ConfigureServices` โ€” that would break the NoOp + safe-off contract every existing feature module relies on. +- **`FeatureModuleDiscovery.Discover`** (`Internal/FeatureModuleDiscovery.cs`) โ€” reflection-based: + scans opted-in assemblies for public, non-abstract types implementing `IFeatureModule` with a + parameterless constructor, merges with explicitly-supplied instances (explicit wins on type + collision), de-dupes by concrete `Type`, and orders by `Order` then `FeatureKey`. Uses + `Assembly.GetTypes()` wrapped in a `ReflectionTypeLoadException` catch โ€” if you touch this, + preserve that catch; it exists because partially-loadable assemblies are a real occurrence in + consumer apps. +- **`CompositeFeatureFlagProvider`** (`Flags/CompositeFeatureFlagProvider.cs`) โ€” precedence-ordered + chain, first provider to return `HasValue == true` wins. Order is assembled in + `AddPowerFeatures` as: **Override โ†’ custom providers (`options.AdditionalProviders`) โ†’ + Environment โ†’ Configuration**. This order is a deliberate product decision (explicit overrides, + e.g. for tests, must always win over configuration). Do not reorder without discussing the + consequence for every existing consumer's `appsettings.json` expectations. +- **`IFeatureModule`** (`Features.Abstractions/IFeatureModule.cs`) โ€” the contract every feature + implements: `FeatureKey` (stable string, maps to `PowerFeatures:`), `Order` (registration + ordering, lower first), `ConfigureServices` (required), `ConfigurePipeline` (default no-op). + A new feature package's module is the concrete implementation of this interface โ€” see + `docs/PowerCSharp.Features.Authoring-Guide.md` ยง3.2 for the canonical worked example + (`CacheFeatureModule`). + +## 4. Adding a New Pluggable Feature Here + +Do not improvise. Follow SOP-02 in the root `Workflows.md`, which sequences the Authoring Guide +into a checklist. The short version: classify the tier, decide single-package vs. family, scaffold +under `src/Features/PowerCSharp.Feature.[.Provider]/`, implement the five-piece anatomy +(`FeatureKey`, contracts, options, module, NoOp), and give the family its own +`PowerCSharpFeatureVersion` in `Directory.Build.props`. + +## 5. Sitecore โ€” Roadmap Status + +`docs/PowerCSharp.Features.Architecture.md` ยง3 lists `PowerCSharp.Feature.Sitecore` (third-party +GraphQL integration) as a future pluggable package. As of this writing: + +- It is **confirmed in scope** as a future initiative โ€” not abandoned, not yet started. +- There is a branch, `feature/sitecore/phase-1-implementation`, whose diff against `develop` + contains **no Sitecore implementation code** โ€” only test-file deletions that look like stale + rebase artifacts. Do not treat that branch as a starting point without first confirming with the + maintainer whether it should be discarded or salvaged; assume discard-and-restart via SOP-02 + unless told otherwise. +- No design decisions have been made yet about which Sitecore integration surface this targets + (Content Serialization, GraphQL Layout Service, Experience Edge, or a combination). Do not + invent this design โ€” it is an open architectural question to raise with the maintainer before + scaffolding `PowerCSharp.Feature.Sitecore`, per Workflows.md SOP-02 step 2 and step 11. From d71c3f3c1171c221c452b3630e143122b0b407a4 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 15:10:58 -0600 Subject: [PATCH 19/63] docs(cache-disk): add high-risk operational guidance for disk cache internals - Document locking model across in-process and cross-process paths, including mutex scope and gating - Define atomic write and index persistence invariants to prevent unsafe refactors - Capture known concurrency and durability hazards, including mutex growth and cross-process factory behavior - Add modification guardrails requiring multi-process validation and explicit risk callouts --- .../PowerCSharp.Feature.Cache.Disk/CLAUDE.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Cache.Disk/CLAUDE.md diff --git a/src/Features/PowerCSharp.Feature.Cache.Disk/CLAUDE.md b/src/Features/PowerCSharp.Feature.Cache.Disk/CLAUDE.md new file mode 100644 index 0000000..417bf73 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Cache.Disk/CLAUDE.md @@ -0,0 +1,89 @@ +# CLAUDE.md โ€” PowerCSharp.Feature.Cache.Disk + +> Scope: this project only (`DiskCacheService`, `DiskCacheIndex`, `DiskCacheIndexEntry`, +> `DiskCacheBackgroundService`, `DiskCacheFeatureOptions`). Read alongside `src/Features/CLAUDE.md` +> and the root `CLAUDE.md`. + +## Sensitivity level: high + +This is the one package in the repository doing real cross-process concurrency control and +non-transactional file I/O. Bugs here are silent-corruption or silent-deadlock bugs, not +compile-time or even easily-reproducible-in-a-unit-test bugs. Any change to locking, atomic write +ordering, or index serialization needs a `` block in your response and, ideally, a +multi-process manual test, not just the existing unit test suite. + +## 1. What This Package Actually Does (grounded in `DiskCacheService.cs`) + +- Stores each cache value as its own JSON file under `_rootDirectory` (default + `%TEMP%/powercsharp-disk-cache`, overridable via `DiskCacheFeatureOptions.DirectoryPath`), named + by a hash of the key (`HashFileName`), plus a single `index.json` mapping keys โ†’ + `DiskCacheIndexEntry` (file path, created/last-accessed/expires timestamps). +- **In-process locking** is two-layered: + - `_indexLock` (a plain `object` + `lock`) guards all in-memory mutation of `_index.Entries`. + - Per-key `SemaphoreSlim`s (`_keyLocks`, `ConcurrentDictionary`) serialize + concurrent `Get`/`Set`/`Remove` calls for the *same key within this process*. +- **Cross-process locking** is opt-in via `DiskCacheFeatureOptions.EnableCrossProcessLocking`, + implemented with named, global `Mutex`es: + - One index mutex per root directory: `Global\PowerCSharp_DiskCache_Index_{hash}`. + - One mutex per key: `Global\PowerCSharp_DiskCache_Key_{hash}`, created lazily and cached in + `_keyMutexes`, **never removed** for the lifetime of the service (see Hazard 1 below). + - `WithIndexLock` / `WithKeyLock` wrap the corresponding critical section; both are no-ops if + `EnableCrossProcessLocking` is `false` โ€” in that mode, only the in-process locks apply, and + multiple *processes* sharing a directory can race. +- **Writes are atomic** at the OS level: value writes go to `.tmp` then `File.Move` to the + final path (never write-in-place); `SaveIndex()` follows the identical temp-then-move pattern for + `index.json`. This is what makes a torn read of a `.json` value or of the index itself + structurally impossible under normal OS semantics โ€” do not "simplify" this back to a direct + `File.WriteAllText` on the real path. +- **Eviction** is LRU by `LastAccessedUtc`, enforced in `EvictIfNeeded()` (inline, on every + `SetAsync`) and `EvictToLimit()` (callable directly). **TTL** expiry is checked lazily on read + (`IsExpired`) and swept proactively by `PurgeExpiredAsync`, invoked on a `System.Threading.Timer` + when `EnableBackgroundCleanup` is `true` (constructor, `StartCleanupTimer`). +- `DiskCacheBackgroundService` (net8.0 only, `#if NET8_0_OR_GREATER`) is an `IHostedService` + wrapper for host lifecycle logging only โ€” the actual timer lives in `DiskCacheService` itself and + runs on every target framework, including `netstandard2.0` hosts with no `IHostedService` concept. + +## 2. Known Hazards โ€” Do Not Touch Without Addressing These + +1. **`_keyMutexes` grows unbounded.** Every distinct cache key ever used with cross-process locking + enabled creates a `Mutex` that lives until `Dispose()`. A cache with high key cardinality and + `EnableCrossProcessLocking = true` will leak OS mutex handles for the life of the process. If + you're asked to fix this, the fix needs its own eviction policy for `_keyMutexes` โ€” coordinated + with, but distinct from, the LRU eviction of cache *entries* in ยง1, since a mutex must never be + disposed while another thread/process could still be waiting on it. +2. **Mutex hash collisions.** Mutex names are derived from `key.GetHashCode():X8` / + `_rootDirectory.GetHashCode():X8` โ€” a 32-bit hash. Two distinct keys colliding on this hash will + silently share a lock (harmless โ€” over-serializes, doesn't corrupt) but two distinct + **root directories** colliding would cross-serialize unrelated caches. Low probability, non-zero; + do not "optimize" this to a shorter hash. +3. **`GetOrCreateAsync` factory runs inside the per-key semaphore**, not inside the cross-process + mutex. Under `EnableCrossProcessLocking = true` with multiple processes, two processes can both + run the factory concurrently for a cold key before either writes โ€” last writer wins, no + duplicate-work prevention across processes by design. Do not describe this method as + preventing cross-process duplicate computation; it only prevents duplicate computation + in-process. +4. **`Global\` mutex namespace requires appropriate OS privileges** in some locked-down Windows + environments (e.g. certain container/service-account configurations) and has no direct + equivalent semantics on non-Windows platforms beyond what .NET's `Mutex` emulates. If you extend + this feature to a new OS target, re-validate this mechanism specifically โ€” do not assume named + `Mutex` behaves identically across platforms just because it compiles under `netstandard2.0`. +5. **No corruption-detection on partial writes from a hard process kill mid-`File.Move`.** The + temp-then-move pattern protects against torn writes, not against a crash between deleting the + old file and completing the move (`SetAsync`: `File.Delete(filePath)` then `File.Move(tempPath, + filePath)` are two separate syscalls). A crash in that narrow window loses the entry (the old + file is gone, the new one didn't land) โ€” this degrades to a cache miss on next read, which is + safe for a cache, but should not be described as fully atomic across a crash boundary. + +## 3. If You Are Asked to Modify This Package + +- Reproduce hazards with a **multi-process** test harness (e.g. two console apps or two xunit test + processes pointed at the same `DirectoryPath`) before claiming a fix works โ€” the existing + `tests/PowerCSharp.Feature.Cache.Tests` suite runs in a single process and cannot exercise cross- + process contention. +- Never remove the temp-file-then-move pattern for either cache value files or `index.json`. +- Never make `WithIndexLock`/`WithKeyLock` a no-op path change without preserving the existing + `EnableCrossProcessLocking` gate โ€” some consumers deliberately run with it `false` for + single-process performance. +- Call out any change to mutex lifetime, naming, or scope explicitly as `` per the root + `CLAUDE.md` Section 5 โ€” this is exactly the class of change that passes single-process unit tests + while breaking multi-process production behavior. From 157880814721bc1d315df970345ad796c461edae Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 15:11:17 -0600 Subject: [PATCH 20/63] docs(compatibility): add package-specific CLAUDE guidance for .NET Framework support - Define high-sensitivity constraints and backward-compatibility expectations for active production consumers - Document framework-specific architecture, language-version pinning, and dependency boundaries unique to this package - Capture CI coverage gap and required manual validation workflow for compatibility changes - Clarify release-versioning path and maintainer decision points for compatibility package publishing --- src/PowerCSharp.Compatibility/CLAUDE.md | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/PowerCSharp.Compatibility/CLAUDE.md diff --git a/src/PowerCSharp.Compatibility/CLAUDE.md b/src/PowerCSharp.Compatibility/CLAUDE.md new file mode 100644 index 0000000..704b867 --- /dev/null +++ b/src/PowerCSharp.Compatibility/CLAUDE.md @@ -0,0 +1,73 @@ +# CLAUDE.md โ€” PowerCSharp.Compatibility + +> Scope: this project only. Read alongside the root `CLAUDE.md`. + +## Sensitivity level: high โ€” actively used in production + +Confirmed with the maintainer: this package is **actively depended on by real .NET Framework +consumers**, not a legacy shim kept for completeness. Treat every change as if it ships to +production the moment it's merged. This is the opposite default from "legacy code you can +modernize freely" โ€” the constraint here is backward compatibility, not code quality improvement. + +## 1. What Makes This Package Different From the Rest of the Repo + +- **Target frameworks:** `net48;net462;net472` only โ€” no `net8.0`, no `netstandard2.0`. This is + the *only* package in the repo that does not multi-target `netstandard2.0` or `net8.0`. + (`PowerCSharp.Compatibility.csproj`.) +- **`LangVersion` is pinned to `11.0`**, not `latest` โ€” an explicit override of the repo-wide + default set in `Directory.Build.props`. Do not "fix" this to match the rest of the repo; it is + pinned because the target frameworks and consuming apps constrain available language features. + If a change requires a newer C# feature, that is a signal the change may not belong in this + package. +- **References classic ASP.NET, not ASP.NET Core:** `System.Web` and `Microsoft.CSharp` are + referenced per-TFM (`net48`, `net462`, `net472` conditional `ItemGroup`s). `HttpRequestBase` / + `HttpRequestExtensions` in this package operate against System.Web types + (`Http/HttpRequestBaseExtensions.cs`), which have no equivalent in, and must never be confused + with, the ASP.NET Core `HttpRequest` extensions in `PowerCSharp.Extensions.AspNetCore`. +- **Own version family:** `PowerCSharpCompatibilityVersion`, bumped by manual edit to + `Directory.Build.props` (not currently wired to the `workflow_dispatch` `package_family` choices + the way `core`/`features`/`cache` are โ€” see Section 3 before assuming otherwise). +- **Package dependencies are deliberately narrow and framework-appropriate**: `System.Text.Json` + (for `net48`/`net462`/`net472`, where `System.Text.Json` isn't part of the framework), the + `System.Net.Http` 4.3.4 back-compat package, and `Microsoft.CSharp` for dynamic support. Do not + add a dependency here that assumes a modern BCL surface is present. + +## 2. The CI Gap โ€” Read Before Assuming Test Coverage + +- `PowerCSharp.Compatibility.Extensions.Tests` was removed from `PowerCSharp.sln` and from the CI + build matrix in commit `86464f7` ("ci: revert CI to single Ubuntu runner and remove + Compatibility.Extensions.Tests from solution"). +- The test project now lives **only** in the separate `PowerCSharp-Compatibility.sln`, which + `.github/workflows/ci-cd.yml` never builds or runs. +- **Practical consequence:** a change to `PowerCSharp.Compatibility` today does not get automatic + test coverage from the standard `dotnet build/test PowerCSharp.sln` pipeline used everywhere + else in this repo. If you modify this package: + - Manually build and run its tests via `PowerCSharp-Compatibility.sln`: + ```bash + dotnet test PowerCSharp-Compatibility.sln --configuration Release + ``` + - Say so explicitly in your response (`` per the root `CLAUDE.md`) โ€” do not report + "tests pass" based on the main solution's green CI run, since that run does not exercise this + package's tests at all. +- This gap is a known, named issue, not something to silently "fix" by re-adding the test project + to the main solution without asking โ€” the removal in `86464f7` may have been a deliberate, + considered call (e.g. avoiding a cross-platform matrix cost) rather than an oversight. Flag it + as an open question rather than reverting it unilaterally. + +## 3. Versioning + +`PowerCSharpCompatibilityVersion` in `Directory.Build.props` is edited by hand; it is **not** one +of the `core` / `features` / `cache` choices in the `ci-cd.yml` `workflow_dispatch` input today. If +you're asked to release a Compatibility change, confirm with the maintainer whether to extend the +`workflow_dispatch` `package_family` list to include `compatibility`, or continue with the manual +edit + manual tag flow โ€” do not assume either path silently. + +## 4. Working in This Package + +- Read `docs/PowerCSharp.Compatibility.md` for the documented public surface before adding to it. +- Any new extension method needs the "safe" null-handling style used elsewhere in the repo (see + `Extensions/StringExtensions.cs` for the existing pattern in this package specifically). +- Do not introduce a dependency on any `net8.0`-only package or language feature โ€” verify + buildability against all three target frameworks (`net48`, `net462`, `net472`) before considering + a change complete, using `PowerCSharp-Compatibility.sln` since the main solution's CI won't catch + a regression here (Section 2). From dee019d0072e53ef9d5ce1da3c204cff48a0a9e3 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:33:11 -0600 Subject: [PATCH 21/63] chore(gitignore): ignore local VS Code settings file - Add the machine-specific VS Code settings file to ignore rules - Remove the exception that previously allowed that settings file to be tracked --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e578ce9..0615368 100644 --- a/.gitignore +++ b/.gitignore @@ -59,6 +59,8 @@ bld/ # Visual Studio 2015/2017 cache/options directory .vs/ +# VS Code workspace settings (machine/user specific) +.vscode/settings.json # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ @@ -413,7 +415,6 @@ FodyWeavers.xsd # VS Code files for those working on multiple tools .vscode/* -!.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json From 8cc44e695d91a192ec7e7c482b688ea4de5de1b5 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:43:18 -0600 Subject: [PATCH 22/63] chore(sanitization): scaffold sanitization abstractions project - add new class library targeting netstandard2.0 and net8.0 for the sanitization feature abstractions - reference logging abstractions and system.text.json packages needed by the engine - bind package version to the upcoming sanitization feature version property --- ...p.Feature.Sanitization.Abstractions.csproj | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/PowerCSharp.Feature.Sanitization.Abstractions.csproj diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/PowerCSharp.Feature.Sanitization.Abstractions.csproj b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/PowerCSharp.Feature.Sanitization.Abstractions.csproj new file mode 100644 index 0000000..636b16a --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/PowerCSharp.Feature.Sanitization.Abstractions.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0;net8.0 + enable + enable + true + + + PowerCSharp.Feature.Sanitization.Abstractions + $(PowerCSharpFeatureSanitizationVersion) + PowerCSharp Sanitization Feature - Abstractions + Framework-agnostic sanitization engine and contracts for the PowerCSharp Sanitization feature: log-injection (CWE-117), file-path traversal (CWE-22), sensitive-data (CWE-200), and regex-injection/ReDoS (CWE-400/730) sanitization, plus the NoOp safe-off implementation. Targets netstandard2.0 and net8.0. No ASP.NET Core dependency. + csharp;dotnet;features;feature-flags;security;sanitization;abstractions;clean-architecture + + + + + + + + + From e6f9750a8ed4d239674b1fd82fc66444ee88f9bc Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:43:34 -0600 Subject: [PATCH 23/63] feat(sanitization): add sanitization enums - add sanitization type enum covering log injection, file path, sensitive data and regex injection - add sanitization strategy enum for control character handling (remove, replace, html/url/json encode) - add sensitive data detection strictness enum (low, medium, high) --- .../Enums/SanitizationStrategy.cs | 44 +++++++++++++++++++ .../Enums/SanitizationType.cs | 32 ++++++++++++++ .../Enums/SensitiveDataDetectionStrictness.cs | 26 +++++++++++ 3 files changed, 102 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationStrategy.cs create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationType.cs create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SensitiveDataDetectionStrictness.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationStrategy.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationStrategy.cs new file mode 100644 index 0000000..546bf64 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationStrategy.cs @@ -0,0 +1,44 @@ +namespace PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +/// +/// Defines the strategy used to neutralize control characters during log-injection sanitization. +/// Each strategy represents a different approach to addressing CWE-117 vulnerabilities. +/// +public enum SanitizationStrategy +{ + /// + /// Removes control characters completely from the input. + /// This is the most aggressive approach and provides clean log output. + /// Recommended for most logging scenarios where character preservation is not critical. + /// + Remove, + + /// + /// Replaces control characters with a space character. + /// This maintains the original string length while neutralizing injection risks. + /// Useful when maintaining text structure is important for log parsing. + /// + ReplaceWithSpace, + + /// + /// Encodes control characters using HTML entity encoding. + /// This preserves the original characters in a safe format for display. + /// Recommended for web-based logging systems where logs may be rendered as HTML. + /// Addresses both CWE-117 and potential CWE-79 (XSS) in web log viewers. + /// + HtmlEncode, + + /// + /// Encodes control characters using URL encoding. + /// This preserves characters in a format safe for URL-based log systems. + /// Useful when log data may be transmitted via HTTP parameters or URLs. + /// + UrlEncode, + + /// + /// Encodes control characters using JSON escape sequences. + /// This preserves characters in a format safe for JSON-based logging systems. + /// Recommended for structured logging where logs are stored or transmitted as JSON. + /// + JsonEncode +} diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationType.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationType.cs new file mode 100644 index 0000000..8aab905 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SanitizationType.cs @@ -0,0 +1,32 @@ +namespace PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +/// +/// Defines the types of sanitization available for data processing. +/// Each type represents a specific security or data integrity concern. +/// +public enum SanitizationType +{ + /// + /// Sanitizes data to prevent log injection attacks by removing or encoding control characters. + /// Addresses CWE-117: Improper Output Neutralization for Logs. + /// + LogInjection, + + /// + /// Sanitizes file paths to prevent directory traversal attacks. + /// Addresses CWE-22: Improper Limitation of a Pathname to a Restricted Directory. + /// + FilePath, + + /// + /// Detects and masks sensitive data in log messages to prevent information disclosure. + /// Addresses CWE-200: Exposure of Sensitive Information to an Unauthorized Actor. + /// + SensitiveData, + + /// + /// Sanitizes regex patterns to prevent Regular Expression Denial of Service (ReDoS) attacks. + /// Addresses CWE-400: Uncontrolled Resource Consumption and CWE-730: Uncontrolled Recursion. + /// + RegexInjection +} diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SensitiveDataDetectionStrictness.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SensitiveDataDetectionStrictness.cs new file mode 100644 index 0000000..ffb76f6 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/Enums/SensitiveDataDetectionStrictness.cs @@ -0,0 +1,26 @@ +namespace PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +/// +/// Defines the strictness levels for sensitive data detection. +/// Higher levels result in more aggressive detection patterns. +/// +public enum SensitiveDataDetectionStrictness +{ + /// + /// Conservative detection - only obvious sensitive patterns are detected. + /// Minimal false positives, but may miss some sensitive data. + /// + Low, + + /// + /// Balanced detection - common sensitive patterns are detected. + /// Good balance between false positives and false negatives. + /// + Medium, + + /// + /// Aggressive detection - most potential sensitive patterns are detected. + /// Higher false positive rate, but better protection against data leakage. + /// + High +} From 4589c33118fbaaaebf25d469031bcd10e6aa4318 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:43:43 -0600 Subject: [PATCH 24/63] feat(sanitization): add sanitization settings and result models - add mutable sanitization settings type covering log, file path, sensitive data and regex options - add sanitization result type representing unchanged, modified and rejected outcomes - add sensitive data result type representing unchanged and modified outcomes - model these as plain classes with manual equality rather than records, matching the existing cache feature abstractions convention for netstandard2.0 compatibility --- .../SanitizationResult.cs | 137 ++++++++++ .../SanitizationSettings.cs | 236 ++++++++++++++++++ .../SensitiveDataResult.cs | 112 +++++++++ 3 files changed, 485 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationResult.cs create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationSettings.cs create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SensitiveDataResult.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationResult.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationResult.cs new file mode 100644 index 0000000..320c1bc --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationResult.cs @@ -0,0 +1,137 @@ +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Represents the result of a sanitization operation, providing details about the processing outcome. +/// This type is immutable and designed for high-performance scenarios with minimal allocations. +/// +/// +/// A plain class rather than a C# record: this package targets netstandard2.0, which lacks +/// the init-accessor support records rely on. Value equality is implemented manually instead, +/// matching the convention already used by PowerCSharp.Feature.Cache.Abstractions.CacheResult<T>. +/// +public sealed class SanitizationResult : IEquatable +{ + /// Gets the sanitized value after processing. Never null. + public string SanitizedValue { get; } + + /// Gets a value indicating whether the original value was modified during sanitization. + public bool WasModified { get; } + + /// Gets the type of sanitization that was applied. + public SanitizationType SanitizationType { get; } + + /// Gets the time taken to perform the sanitization operation. + public TimeSpan ProcessingTime { get; } + + /// Gets a value indicating whether the input was rejected due to validation failures (strict mode only). + public bool IsRejected { get; } + + /// + /// Initializes a new instance of . Prefer the + /// , , and factory methods. + /// + /// The sanitized value after processing. + /// Whether the original value was modified. + /// The type of sanitization that was applied. + /// The time taken to perform the sanitization operation. + /// Whether the input was rejected due to validation failures. + public SanitizationResult( + string sanitizedValue, + bool wasModified, + SanitizationType sanitizationType, + TimeSpan processingTime, + bool isRejected = false) + { + SanitizedValue = sanitizedValue ?? string.Empty; + WasModified = wasModified; + SanitizationType = sanitizationType; + ProcessingTime = processingTime; + IsRejected = isRejected; + } + + /// + /// Gets a sanitized result that represents no changes were made. Useful for fast-path + /// scenarios where sanitization is not needed. + /// + /// The original value that was not modified. + /// The type of sanitization that was checked. + /// A indicating no modifications were made. + public static SanitizationResult Unchanged(string originalValue, SanitizationType sanitizationType) + => new(originalValue ?? string.Empty, wasModified: false, sanitizationType, TimeSpan.Zero); + + /// + /// Gets a sanitized result that represents successful modification. + /// + /// The sanitized value after processing. + /// The type of sanitization that was applied. + /// The time taken to perform the sanitization. + /// A indicating successful modification. + public static SanitizationResult Modified( + string sanitizedValue, + SanitizationType sanitizationType, + TimeSpan processingTime) + => new(sanitizedValue ?? string.Empty, wasModified: true, sanitizationType, processingTime); + + /// + /// Gets a sanitized result that represents input rejection due to validation failures. + /// Used in strict validation mode when inputs fail allowlist validation. + /// + /// The type of sanitization that was applied. + /// The time taken to perform the validation. + /// A indicating the input was rejected. + public static SanitizationResult Rejected( + SanitizationType sanitizationType, + TimeSpan processingTime) + => new(string.Empty, wasModified: true, sanitizationType, processingTime, isRejected: true); + + /// + public override bool Equals(object? obj) => obj is SanitizationResult other && Equals(other); + + /// + public bool Equals(SanitizationResult? other) + { + if (other is null) + { + return false; + } + + return WasModified == other.WasModified && + SanitizationType == other.SanitizationType && + IsRejected == other.IsRejected && + ProcessingTime.Equals(other.ProcessingTime) && + string.Equals(SanitizedValue, other.SanitizedValue, StringComparison.Ordinal); + } + + /// + public override int GetHashCode() + { + var hash = 17; + hash = hash * 31 + SanitizedValue.GetHashCode(); + hash = hash * 31 + WasModified.GetHashCode(); + hash = hash * 31 + SanitizationType.GetHashCode(); + hash = hash * 31 + ProcessingTime.GetHashCode(); + hash = hash * 31 + IsRejected.GetHashCode(); + return hash; + } + + /// Equality operator. + public static bool operator ==(SanitizationResult? left, SanitizationResult? right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.Equals(right); + } + + /// Inequality operator. + public static bool operator !=(SanitizationResult? left, SanitizationResult? right) => !(left == right); +} diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationSettings.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationSettings.cs new file mode 100644 index 0000000..3b768a4 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationSettings.cs @@ -0,0 +1,236 @@ +using Microsoft.Extensions.Logging; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Configuration settings for the sanitization engine. Controls the behavior of every +/// sanitization operation (log injection, file path, sensitive data, regex injection). +/// +public sealed class SanitizationSettings +{ + /// + /// Gets or sets a value indicating whether log sanitization is enabled. + /// When disabled, log messages will not be sanitized for injection attacks. + /// Default: true + /// + public bool EnableLogSanitization { get; set; } = true; + + /// + /// Gets or sets a value indicating whether sanitization failures should be logged. + /// Useful for monitoring and debugging sanitization issues. + /// Default: false (to prevent log spam and potential recursive issues) + /// + public bool LogSanitizationFailures { get; set; } = false; + + /// + /// Gets or sets the log level to use when logging sanitization failures. + /// Only used when is true. + /// Default: Warning + /// + public LogLevel SanitizationFailureLogLevel { get; set; } = LogLevel.Warning; + + /// + /// Gets or sets the maximum length for sanitized strings. + /// Strings longer than this will be truncated to prevent memory issues. + /// Default: 10000 characters + /// + public int MaxSanitizedStringLength { get; set; } = 10000; + + /// + /// Gets or sets a value indicating whether Unicode control characters should be sanitized. + /// Includes characters in ranges 0x00-0x1F, 0x7F-0x9F. + /// Default: true + /// + public bool SanitizeUnicodeControlChars { get; set; } = true; + + /// + /// Gets or sets a value indicating whether file path sanitization is enabled. + /// When enabled, file paths will be sanitized to prevent directory traversal attacks (CWE-73/CWE-22). + /// Default: true + /// + public bool EnableFilePathSanitization { get; set; } = true; + + /// + /// Gets or sets a value indicating whether parent directory traversal patterns should be checked and removed. + /// When enabled, patterns like "../" and "..\" will be removed from file paths. + /// This can be disabled in production environments where path traversal is handled by other security measures. + /// Default: true + /// + public bool EnableParentDirectoryTraversalCheck { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to enable performance monitoring for sanitization operations. + /// When enabled, processing times are tracked and can be monitored. + /// Default: false (minimal overhead) + /// + public bool EnablePerformanceMonitoring { get; set; } = false; + + /// + /// Gets or sets the threshold in milliseconds for considering sanitization operations as slow. + /// Operations taking longer than this threshold may be logged for performance analysis. + /// Only used when is true. + /// Default: 1ms + /// + public double SlowOperationThresholdMs { get; set; } = 1.0; + + /// + /// Gets or sets the allowed base directories for file path validation. + /// When specified, file paths must resolve to within these directories. + /// Empty array means no base directory validation is performed. + /// Default: empty array (no base directory validation) + /// + public string[] AllowedBaseDirectories { get; set; } = Array.Empty(); + + /// + /// Gets or sets the allowed file extensions for file path validation. + /// When specified, file paths must end with one of these extensions (case-insensitive). + /// Empty array means no extension validation is performed. + /// Default: empty array (no extension validation) + /// + public string[] AllowedFileExtensions { get; set; } = Array.Empty(); + + /// + /// Gets or sets a value indicating whether to use strict validation mode for file paths. + /// When true, all validation rules are strictly enforced and violations result in rejection. + /// When false, some non-critical violations may be sanitized instead of rejected. + /// Default: true + /// + public bool UseStrictValidation { get; set; } = true; + + /// + /// Gets or sets the maximum allowed length for individual file path segments. + /// Path segments longer than this will be rejected to prevent potential issues. + /// Default: 255 characters (common max component length on Windows) + /// + public int MaxPathSegmentLength { get; set; } = 255; + + /// + /// Gets or sets a value indicating whether to log security events. + /// When enabled, blocked path traversal attempts and other security violations are logged. + /// Default: false (to prevent log spam and potential information disclosure) + /// + public bool LogSecurityEvents { get; set; } = false; + + /// + /// Gets or sets a value indicating whether to validate against Windows reserved device names. + /// When true, paths containing reserved names like CON, PRN, AUX, etc. will be rejected. + /// Default: true + /// + public bool ValidateWindowsReservedNames { get; set; } = true; + + /// + /// Gets or sets the sanitization strategy for handling control characters. + /// Determines how control characters are processed during log sanitization. + /// Default: Remove (aggressive sanitization for clean logs) + /// + public SanitizationStrategy LogSanitizationStrategy { get; set; } = SanitizationStrategy.Remove; + + /// + /// Gets or sets a value indicating whether to preserve tab characters. + /// Tab characters (\t) are control characters but are often safe in logging contexts. + /// Default: false (remove tabs for cleaner log output) + /// + public bool PreserveTabCharacters { get; set; } = false; + + /// + /// Gets or sets the correlation ID for security event logging. + /// When set, security events will include this correlation ID for traceability. + /// Default: null (no correlation ID) + /// + public string? CorrelationId { get; set; } = null; + + /// + /// Gets or sets a value indicating whether to allow Unicode characters in file paths. + /// When false, only ASCII characters are allowed to prevent Unicode-based attacks. + /// Default: true (allow Unicode for internationalization) + /// + public bool AllowUnicodeCharacters { get; set; } = true; + + /// + /// Gets or sets a value indicating whether sensitive data detection is enabled. + /// When enabled, sensitive data patterns will be detected and masked. + /// Default: true + /// + public bool EnableSensitiveDataDetection { get; set; } = true; + + /// + /// Gets or sets the character to use for masking detected sensitive data. + /// This character will replace detected sensitive patterns. + /// Default: '*' (asterisk) + /// + public char SensitiveDataMaskCharacter { get; set; } = '*'; + + /// + /// Gets or sets the detection strictness level for sensitive data. + /// Higher values result in more aggressive detection (more false positives). + /// Lower values result in more conservative detection (more false negatives). + /// Default: Medium + /// + public SensitiveDataDetectionStrictness SensitiveDataDetectionStrictness { get; set; } = SensitiveDataDetectionStrictness.Medium; + + // --- Regex Injection Sanitization Settings --- + + /// + /// Gets or sets a value indicating whether regex injection sanitization is enabled. + /// When enabled, regex patterns will be validated for safety before use. + /// Default: true + /// + public bool EnableRegexSanitization { get; set; } = true; + + /// + /// Gets or sets the maximum timeout for regex pattern validation. + /// Patterns that take longer than this to validate will be rejected. + /// Default: 5 seconds + /// + public TimeSpan MaxRegexValidationTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// + /// Gets or sets the maximum allowed length for regex patterns. + /// Patterns longer than this will be rejected to prevent complex attacks. + /// Default: 1000 characters + /// + public int MaxRegexPatternLength { get; set; } = 1000; + + /// + /// Gets or sets the maximum complexity score allowed for regex patterns. + /// Higher scores indicate more complex patterns that could cause performance issues. + /// Default: 100 + /// + public int MaxRegexComplexityScore { get; set; } = 100; + + /// + /// Gets or sets a value indicating whether to allow Unicode character categories in regex patterns. + /// When false, Unicode categories like \p{L} will be rejected. + /// Default: true (allow Unicode for internationalization) + /// + public bool AllowUnicodeCategories { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to allow quantifiers in regex patterns. + /// When false, quantifiers like *, +, ?, {n,m} will be rejected. + /// Default: true (allow quantifiers for functionality) + /// + public bool AllowQuantifiers { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to allow nested quantifiers in regex patterns. + /// Nested quantifiers are a common source of catastrophic backtracking. + /// Default: false (disallow nested quantifiers for safety) + /// + public bool AllowNestedQuantifiers { get; set; } = false; + + /// + /// Gets or sets a value indicating whether to allow backreferences in regex patterns. + /// Backreferences can lead to complex matching behavior. + /// Default: true (allow backreferences for advanced patterns) + /// + public bool AllowBackreferences { get; set; } = true; + + /// + /// Gets or sets a value indicating whether to allow lookaround assertions in regex patterns. + /// Lookarounds can increase pattern complexity significantly. + /// Default: true (allow lookarounds for advanced patterns) + /// + public bool AllowLookarounds { get; set; } = true; +} diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SensitiveDataResult.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SensitiveDataResult.cs new file mode 100644 index 0000000..6a23f2a --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SensitiveDataResult.cs @@ -0,0 +1,112 @@ +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Represents the result of a sensitive-data detection and masking operation. +/// This type is immutable and designed for high-performance scenarios with minimal allocations. +/// +/// +/// A plain class rather than a C# record, for the same netstandard2.0 compatibility reason +/// documented on . +/// +public sealed class SensitiveDataResult : IEquatable +{ + /// Gets the value after sensitive-data masking. Never null. + public string SanitizedValue { get; } + + /// Gets a value indicating whether sensitive data was found and the original value was modified. + public bool WasModified { get; } + + /// Gets the type of sanitization that was applied (always ). + public SanitizationType SanitizationType { get; } + + /// Gets the time taken to perform the sensitive-data detection and masking. + public TimeSpan ProcessingTime { get; } + + /// Gets a value indicating whether the input was rejected (always false for sensitive-data detection). + public bool IsRejected { get; } + + /// + /// Initializes a new instance of . Prefer the + /// and factory methods. + /// + /// The value after sensitive-data masking. + /// Whether sensitive data was found and masked. + /// The time taken to perform the detection and masking. + public SensitiveDataResult(string sanitizedValue, bool wasModified, TimeSpan processingTime) + { + SanitizedValue = sanitizedValue ?? string.Empty; + WasModified = wasModified; + SanitizationType = SanitizationType.SensitiveData; + ProcessingTime = processingTime; + IsRejected = false; + } + + /// + /// Gets a result that represents no sensitive data was found. Useful for fast-path scenarios + /// where sensitive-data detection is not needed. + /// + /// The original value that was not modified. + /// A indicating no sensitive data was found. + public static SensitiveDataResult Unchanged(string originalValue) + => new(originalValue ?? string.Empty, wasModified: false, TimeSpan.Zero); + + /// + /// Gets a result that represents sensitive data was found and masked. + /// + /// The value after masking sensitive data. + /// The time taken to perform the detection and masking. + /// A indicating sensitive data was found and masked. + public static SensitiveDataResult Modified(string sanitizedValue, TimeSpan processingTime) + => new(sanitizedValue ?? string.Empty, wasModified: true, processingTime); + + /// + public override bool Equals(object? obj) => obj is SensitiveDataResult other && Equals(other); + + /// + public bool Equals(SensitiveDataResult? other) + { + if (other is null) + { + return false; + } + + return WasModified == other.WasModified && + SanitizationType == other.SanitizationType && + IsRejected == other.IsRejected && + ProcessingTime.Equals(other.ProcessingTime) && + string.Equals(SanitizedValue, other.SanitizedValue, StringComparison.Ordinal); + } + + /// + public override int GetHashCode() + { + var hash = 17; + hash = hash * 31 + SanitizedValue.GetHashCode(); + hash = hash * 31 + WasModified.GetHashCode(); + hash = hash * 31 + SanitizationType.GetHashCode(); + hash = hash * 31 + ProcessingTime.GetHashCode(); + hash = hash * 31 + IsRejected.GetHashCode(); + return hash; + } + + /// Equality operator. + public static bool operator ==(SensitiveDataResult? left, SensitiveDataResult? right) + { + if (ReferenceEquals(left, right)) + { + return true; + } + + if (left is null || right is null) + { + return false; + } + + return left.Equals(right); + } + + /// Inequality operator. + public static bool operator !=(SensitiveDataResult? left, SensitiveDataResult? right) => !(left == right); +} From 5f65154a152329f8157871e50a0d2af9c96c5152 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:43:49 -0600 Subject: [PATCH 25/63] feat(sanitization): add sanitization service contracts - add the dependency-injection facing sanitization service contract covering log injection, file path, sensitive data and regex injection - add the settings provider contract bridging host configuration into the static engine - expose all four sanitization operations symmetrically through the service contract --- .../ISanitizationService.cs | 81 +++++++++++++++++++ .../ISanitizationSettingsProvider.cs | 16 ++++ 2 files changed, 97 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationService.cs create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationSettingsProvider.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationService.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationService.cs new file mode 100644 index 0000000..5c62c36 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationService.cs @@ -0,0 +1,81 @@ +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Defines the contract for a configuration-aware sanitization service, suitable for dependency +/// injection. Implementations wrap the static while sourcing +/// settings from the host's configuration system. +/// +public interface ISanitizationService +{ + /// + /// Sanitizes a string to prevent log injection attacks by removing or encoding control characters. + /// This method is optimized for performance and never throws exceptions. + /// Uses the service's current configuration. + /// + /// The input string to sanitize. Can be null. + /// A containing the sanitized string and operation details. + SanitizationResult SanitizeForLogInjection(string? input); + + /// + /// Sanitizes a string to prevent log injection attacks using specific settings. + /// This method is optimized for performance and never throws exceptions. + /// + /// The input string to sanitize. Can be null. + /// Specific sanitization settings to use. If null, uses the service's current configuration. + /// A containing the sanitized string and operation details. + SanitizationResult SanitizeForLogInjection(string? input, SanitizationSettings? settings); + + /// + /// Sanitizes a file path to prevent directory traversal attacks (CWE-22). + /// Uses the service's current configuration. + /// + /// The input file path to sanitize. Can be null. + /// A containing the validated file path and operation details. + SanitizationResult SanitizeForFilePath(string? input); + + /// + /// Sanitizes a file path to prevent directory traversal attacks (CWE-22) using specific settings. + /// + /// The input file path to sanitize. Can be null. + /// Specific sanitization settings to use. If null, uses the service's current configuration. + /// A containing the validated file path and operation details. + SanitizationResult SanitizeForFilePath(string? input, SanitizationSettings? settings); + + /// + /// Detects and masks sensitive data in a string to prevent information disclosure (CWE-200). + /// Uses the service's current configuration. + /// + /// The input string to check for sensitive data. Can be null. + /// A containing the masked string and operation details. + SensitiveDataResult SanitizeForSensitiveData(string? input); + + /// + /// Detects and masks sensitive data in a string using specific settings. + /// + /// The input string to check for sensitive data. Can be null. + /// Specific sanitization settings to use. If null, uses the service's current configuration. + /// A containing the masked string and operation details. + SensitiveDataResult SanitizeForSensitiveData(string? input, SanitizationSettings? settings); + + /// + /// Validates a regex pattern for safety against ReDoS attacks (CWE-400/CWE-730). + /// Uses the service's current configuration. + /// + /// The regex pattern to validate. Can be null. + /// A containing the validated pattern and operation details. + SanitizationResult SanitizeForRegexInjection(string? pattern); + + /// + /// Validates a regex pattern for safety against ReDoS attacks (CWE-400/CWE-730) using specific settings. + /// + /// The regex pattern to validate. Can be null. + /// Specific sanitization settings to use. If null, uses the service's current configuration. + /// A containing the validated pattern and operation details. + SanitizationResult SanitizeForRegexInjection(string? pattern, SanitizationSettings? settings); + + /// + /// Gets the current sanitization configuration used by this service. + /// + /// The current . + SanitizationSettings GetCurrentSettings(); +} diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationSettingsProvider.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationSettingsProvider.cs new file mode 100644 index 0000000..70ef771 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/ISanitizationSettingsProvider.cs @@ -0,0 +1,16 @@ +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Provides the current to the static . +/// Allows a hosting layer to bridge its own configuration/options system into the engine via +/// without the engine taking a direct +/// dependency on any specific configuration framework. +/// +public interface ISanitizationSettingsProvider +{ + /// + /// Gets the current sanitization settings from configuration. + /// + /// The current sanitization settings. + SanitizationSettings GetCurrentSettings(); +} From c41481e96431d3168476a95d746799e32733f72f Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:43:54 -0600 Subject: [PATCH 26/63] feat(sanitization): add sanitization engine core - add the shared static engine state: configuration provider wiring, security event logger, performance tracking - add shared control character classification used by both log injection and file path validation - add a netstandard2.0 safe contains helper since the string contains overload with string comparison is not available on that target - add performance statistics reporting covering all four sanitization types --- .../SanitizationEngine.cs | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.cs new file mode 100644 index 0000000..116d507 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.cs @@ -0,0 +1,230 @@ +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// High-performance sanitization engine covering log injection (CWE-117), file-path traversal +/// (CWE-22), sensitive-data exposure (CWE-200), and regex-injection/ReDoS (CWE-400/CWE-730). +/// Designed for hot-path scenarios: minimal allocations and fail-safe behavior (sanitization +/// methods never throw; validation methods signal rejection via their result type instead). +/// +/// +/// This partial class is split by concern across several files for maintainability: +/// +/// SanitizationEngine.cs (this file) โ€” shared state, configuration wiring, performance tracking, and control-character classification shared by more than one concern. +/// SanitizationEngine.LogInjection.cs โ€” and its encoding strategies. +/// SanitizationEngine.FilePath.cs โ€” , , and allowlist path validation. +/// SanitizationEngine.SensitiveData.cs โ€” and masking heuristics. +/// SanitizationEngine.RegexInjection.cs โ€” and ReDoS complexity scoring. +/// +/// +public static partial class SanitizationEngine +{ + // Constants for control character ranges, shared by log-injection sanitization and + // file-path character validation. + private const char AsciiControlCharStart = '\x00'; + private const char AsciiControlCharEnd = '\x1F'; + private const char ExtendedControlCharStart = '\x7F'; + private const char ExtendedControlCharEnd = '\x9F'; + + // Configuration provider delegate - set by the hosting layer during startup. + private static Func? _settingsProvider; + + // Security event logging delegate - set by the hosting layer during startup. + private static Action? _securityEventLogger; + + // Performance tracking fields. + private static long _totalOperations; + private static long _totalProcessingTimeMs; + private static long _slowOperations; + private static readonly object _performanceLock = new(); + + // Default settings fallback (used when no provider has been configured). + private static readonly SanitizationSettings _defaultSettings = new(); + + /// + /// Sets the configuration provider for sanitization settings. + /// Call this once during application startup so every sanitization call reflects the + /// host's current configuration without each call site needing to pass settings explicitly. + /// + /// The function that provides current sanitization settings. + /// Optional action for logging security events. + public static void SetConfigurationProvider( + Func provider, + Action? securityLogger = null) + { + _settingsProvider = provider + ?? throw new ArgumentNullException(nameof(provider)); + + _securityEventLogger = securityLogger; + } + + /// + /// Sets the security event logger used for CWE-22/CWE-73-style path validation violations. + /// + /// The action invoked for each logged security event. + public static void SetSecurityEventLogger(Action securityLogger) + { + _securityEventLogger = securityLogger + ?? throw new ArgumentNullException(nameof(securityLogger)); + } + + /// + /// Gets performance statistics for the sanitization engine. Useful for monitoring and + /// optimization when is enabled. + /// + /// A dictionary containing performance metrics. + public static Dictionary GetPerformanceStats() + { + lock (_performanceLock) + { + var averageProcessingTimeMs = _totalOperations > 0 + ? (double)_totalProcessingTimeMs / _totalOperations + : 0.0; + + return new Dictionary + { + ["EngineInitialized"] = true, + ["TotalOperations"] = _totalOperations, + ["AverageProcessingTimeMs"] = Math.Round(averageProcessingTimeMs, 3), + ["SlowOperations"] = _slowOperations, + ["SlowOperationPercentage"] = _totalOperations > 0 + ? Math.Round((double)_slowOperations / _totalOperations * 100, 2) + : 0.0, + ["SupportedSanitizationTypes"] = new[] + { + SanitizationType.LogInjection.ToString(), + SanitizationType.FilePath.ToString(), + SanitizationType.SensitiveData.ToString(), + SanitizationType.RegexInjection.ToString() + }, + ["SupportedSanitizationStrategies"] = new[] + { + SanitizationStrategy.Remove.ToString(), + SanitizationStrategy.ReplaceWithSpace.ToString(), + SanitizationStrategy.HtmlEncode.ToString(), + SanitizationStrategy.UrlEncode.ToString(), + SanitizationStrategy.JsonEncode.ToString() + } + }; + } + } + + /// + /// Gets the current sanitization settings from the configuration provider, or the built-in + /// defaults when no provider has been configured via . + /// + /// Current sanitization settings. + private static SanitizationSettings GetCurrentSettings() + { + return _settingsProvider?.Invoke() + ?? _defaultSettings; + } + + /// + /// Determines if a character is a control character based on settings. Shared by log-injection + /// sanitization and file-path allowlist character validation. + /// + /// The character to check. + /// The sanitization settings. + /// True if the character should be treated as a control character, false otherwise. + private static bool IsControlCharacter(char c, SanitizationSettings settings) + { + if (c is >= AsciiControlCharStart and <= AsciiControlCharEnd) + { + return true; + } + + if (settings.SanitizeUnicodeControlChars && + c is >= ExtendedControlCharStart and <= ExtendedControlCharEnd) + { + return true; + } + + return false; + } + + /// + /// string.Contains(string, StringComparison) is not available on netstandard2.0 + /// (added in .NET Standard 2.1). This helper provides the same behavior via + /// , which is available on both target + /// frameworks, so call sites shared across TFMs don't need #if guards. + /// + /// The string to search. + /// The substring to search for. + /// The string comparison to use. + /// True if occurs within . + private static bool ContainsOrdinal(string value, string substring, StringComparison comparison) + => value.IndexOf(substring, comparison) >= 0; + + /// + /// Updates performance metrics for monitoring. No-op unless + /// is enabled. + /// + /// The time taken for the operation. + /// The sanitization settings. + private static void UpdatePerformanceMetrics(TimeSpan processingTime, SanitizationSettings settings) + { + if (!settings.EnablePerformanceMonitoring) + { + return; + } + + lock (_performanceLock) + { + _totalOperations++; + _totalProcessingTimeMs += (long)processingTime.TotalMilliseconds; + + if (processingTime.TotalMilliseconds > settings.SlowOperationThresholdMs) + { + _slowOperations++; + } + } + } + + /// + /// Logs sanitization failures for security monitoring. No-op unless + /// is enabled and a security logger + /// has been configured via or . + /// + /// The input that failed sanitization. + /// The sanitization settings. + /// The type of failure event. + private static void LogSanitizationFailure(string? input, SanitizationSettings settings, string eventType) + { + if (!settings.LogSanitizationFailures || _securityEventLogger == null) + { + return; + } + + var correlationId = string.IsNullOrEmpty(settings.CorrelationId) ? "" : $" [CorrelationId: {settings.CorrelationId}]"; + var inputPreview = string.IsNullOrEmpty(input) ? "" : input!.Length > 100 ? $"{input.Substring(0, 100)}..." : input; + + var message = $"CWE-117 Sanitization Failure: {eventType}{correlationId} - Input: '{inputPreview}'"; + _securityEventLogger(message); + } + + /// + /// Logs security events for CWE-22 file-path validation violations. No-op unless + /// is enabled and a security logger has + /// been configured. + /// + /// The type of security event. + /// The input that triggered the event. + /// The sanitization settings. + private static void LogSecurityEvent(string eventType, string input, SanitizationSettings settings) + { + if (!settings.LogSecurityEvents || _securityEventLogger == null) + { + return; + } + + // Sanitize the input first so the security log itself cannot be used for log injection (CWE-117). + var sanitizedInput = SanitizeForLogInjection(input, settings).SanitizedValue; + var inputPreview = sanitizedInput.Length > 100 ? $"{sanitizedInput.Substring(0, 100)}..." : sanitizedInput; + + var correlationId = string.IsNullOrEmpty(settings.CorrelationId) ? "" : $" [CorrelationId: {settings.CorrelationId}]"; + var message = $"CWE-22 Security Violation: {eventType}{correlationId} - Input: '{inputPreview}'"; + _securityEventLogger(message); + } +} From 4e79a847e40d78f56aa1d9c2cfb0cd3ed77231f1 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:00 -0600 Subject: [PATCH 27/63] feat(sanitization): add log injection sanitization - add log injection sanitization covering removal, space replacement, html encode, url encode and json encode strategies - fast-path inputs with no control characters to avoid unnecessary allocations - fail safe on any internal error by returning the original input unchanged --- .../SanitizationEngine.LogInjection.cs | 266 ++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.LogInjection.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.LogInjection.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.LogInjection.cs new file mode 100644 index 0000000..bed933b --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.LogInjection.cs @@ -0,0 +1,266 @@ +using System.Diagnostics; +using System.Net; +using System.Text; +using System.Text.Json; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Log-injection (CWE-117) sanitization: removes or encodes control characters so untrusted input +/// cannot forge additional log lines/fields when written to a log sink. +/// +public static partial class SanitizationEngine +{ + private const char SpaceReplacement = ' '; + private const char TabCharacter = '\t'; + + /// + /// Sanitizes a string to prevent log injection attacks by removing or replacing control characters. + /// This method is optimized for performance and never throws exceptions. + /// Implements CWE-117 remediation strategies (removal, replacement, and content-preserving encodings). + /// + /// The input string to sanitize. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the sanitized string and operation details. + public static SanitizationResult SanitizeForLogInjection( + string? input, + SanitizationSettings? settings = null) + { + var startTime = Stopwatch.StartNew(); + var effectiveSettings = settings ?? GetCurrentSettings(); + + try + { + if (string.IsNullOrEmpty(input)) + { + return SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.LogInjection); + } + + // See the analogous comment in SanitizationEngine.FilePath.cs: shadow with a value the + // compiler can narrow consistently across both target frameworks. + var value = input!; + + if (!effectiveSettings.EnableLogSanitization) + { + return SanitizationResult.Unchanged(value, SanitizationType.LogInjection); + } + + // Fast path: skip processing entirely when no control characters are present. + if (!ContainsControlCharacters(value, effectiveSettings)) + { + return SanitizationResult.Unchanged(value, SanitizationType.LogInjection); + } + + var sanitized = SanitizeControlCharactersWithStrategy(value, effectiveSettings); + var wasModified = !string.Equals(value, sanitized, StringComparison.Ordinal); + + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + + return wasModified + ? SanitizationResult.Modified(sanitized, SanitizationType.LogInjection, startTime.Elapsed) + : SanitizationResult.Unchanged(sanitized, SanitizationType.LogInjection); + } + catch + { + // Fail-safe: never throw from a sanitization call site; return the original input. + startTime.Stop(); + LogSanitizationFailure(input, effectiveSettings, "SanitizationFailed"); + + return SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.LogInjection); + } + } + + /// + /// Fast check for control characters without allocations. Honors + /// . + /// + /// The string to check. + /// The sanitization settings to apply. + /// True if control characters are present, false otherwise. + private static bool ContainsControlCharacters(string input, SanitizationSettings settings) + { + if (string.IsNullOrEmpty(input)) + { + return false; + } + + foreach (var c in input) + { + if (c == TabCharacter && settings.PreserveTabCharacters) + { + continue; + } + + if (IsControlCharacter(c, settings)) + { + return true; + } + } + + return false; + } + + /// + /// Determines if a character should be sanitized, honoring + /// . + /// + /// The character to check. + /// The sanitization settings. + /// True if the character should be sanitized, false otherwise. + private static bool ShouldSanitizeCharacter(char c, SanitizationSettings settings) + { + if (c == TabCharacter && settings.PreserveTabCharacters) + { + return false; + } + + return IsControlCharacter(c, settings); + } + + /// + /// Sanitizes control characters using the strategy configured on + /// . + /// + /// The input string to sanitize. + /// The sanitization settings to apply. + /// The sanitized string. + private static string SanitizeControlCharactersWithStrategy( + string input, + SanitizationSettings settings) + { + if (string.IsNullOrEmpty(input)) + { + return input ?? string.Empty; + } + + return settings.LogSanitizationStrategy switch + { + SanitizationStrategy.Remove => SanitizeControlCharactersByRemoval(input, settings), + SanitizationStrategy.ReplaceWithSpace => SanitizeControlCharactersByReplacement(input, settings), + SanitizationStrategy.HtmlEncode => SanitizeControlCharactersByHtmlEncoding(input, settings), + SanitizationStrategy.UrlEncode => SanitizeControlCharactersByUrlEncoding(input, settings), + SanitizationStrategy.JsonEncode => SanitizeControlCharactersByJsonEncoding(input, settings), + _ => SanitizeControlCharactersByRemoval(input, settings) + }; + } + + /// + /// Sanitizes control characters by removing them completely, then applies a well-known, + /// static-analysis-recognized HTML-encoding pass so automated scanners can trace the + /// sanitization path for CWE-117 compliance. + /// + /// The input string to sanitize. + /// The sanitization settings to apply. + /// The sanitized string. + private static string SanitizeControlCharactersByRemoval( + string input, + SanitizationSettings settings) + { + var output = new StringBuilder(input.Length); + + foreach (var c in input) + { + if (!ShouldSanitizeCharacter(c, settings)) + { + output.Append(c); + } + } + + var cleaned = output.ToString(); + var encoded = WebUtility.HtmlEncode(cleaned); + + return ApplyLengthLimit(encoded, settings); + } + + /// + /// Sanitizes control characters by replacing them with spaces, then applies the same + /// static-analysis-recognized HTML-encoding pass as . + /// + /// The input string to sanitize. + /// The sanitization settings to apply. + /// The sanitized string. + private static string SanitizeControlCharactersByReplacement( + string input, + SanitizationSettings settings) + { + var output = new StringBuilder(input.Length); + + foreach (var c in input) + { + output.Append(ShouldSanitizeCharacter(c, settings) ? SpaceReplacement : c); + } + + var cleaned = output.ToString(); + var encoded = WebUtility.HtmlEncode(cleaned); + + return ApplyLengthLimit(encoded, settings); + } + + /// + /// Sanitizes control characters by HTML-encoding the cleaned string. Addresses both CWE-117 + /// and potential CWE-79 (XSS) in log viewers that render log content as HTML. + /// + /// The input string to sanitize. + /// The sanitization settings to apply. + /// The sanitized string. + private static string SanitizeControlCharactersByHtmlEncoding( + string input, + SanitizationSettings settings) + { + var cleaned = SanitizeControlCharactersByRemoval(input, settings); + var encoded = WebUtility.HtmlEncode(cleaned); + return ApplyLengthLimit(encoded, settings); + } + + /// + /// Sanitizes control characters by URL-encoding the cleaned string. + /// + /// The input string to sanitize. + /// The sanitization settings to apply. + /// The sanitized string. + private static string SanitizeControlCharactersByUrlEncoding( + string input, + SanitizationSettings settings) + { + var cleaned = SanitizeControlCharactersByRemoval(input, settings); + var encoded = WebUtility.UrlEncode(cleaned); + return ApplyLengthLimit(encoded ?? cleaned, settings); + } + + /// + /// Sanitizes control characters by JSON-encoding the cleaned string (escape sequences only, + /// with the surrounding quotes that adds stripped back off). + /// + /// The input string to sanitize. + /// The sanitization settings to apply. + /// The sanitized string. + private static string SanitizeControlCharactersByJsonEncoding( + string input, + SanitizationSettings settings) + { + var cleaned = SanitizeControlCharactersByRemoval(input, settings); + var encoded = JsonSerializer.Serialize(cleaned); + + if (encoded.Length >= 2 && encoded[0] == '"' && encoded[encoded.Length - 1] == '"') + { + encoded = encoded.Substring(1, encoded.Length - 2); + } + + return ApplyLengthLimit(encoded, settings); + } + + /// + /// Applies to the sanitized string. + /// + /// The input string to limit. + /// The sanitization settings. + /// The length-limited string. + private static string ApplyLengthLimit(string input, SanitizationSettings settings) + { + return input.Length > settings.MaxSanitizedStringLength + ? input.Substring(0, settings.MaxSanitizedStringLength) + : input; + } +} From 30016c9fb17abfa7a64868b3360a0e2d9185984c Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:06 -0600 Subject: [PATCH 28/63] feat(sanitization): add file path sanitization - add allowlist based file path validation covering reserved names, segment length, extensions and allowed base directories - add legacy pattern stripping mode for backward compatible behavior - add correlation id sanitization for safe use in file names - resolve allowed directory containment via prefix comparison instead of path get relative path so the check compiles on netstandard2.0 --- .../SanitizationEngine.FilePath.cs | 539 ++++++++++++++++++ 1 file changed, 539 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.FilePath.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.FilePath.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.FilePath.cs new file mode 100644 index 0000000..0a0fb53 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.FilePath.cs @@ -0,0 +1,539 @@ +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// File-path (CWE-22) sanitization: an allowlist-validation mode (recommended) plus a legacy +/// pattern-stripping mode, both guarding against directory traversal and absolute/UNC path injection. +/// +public static partial class SanitizationEngine +{ + private const string DoubleBackslash = "\\\\"; + private const string SingleBackslash = "\\"; + private const string ForwardSlash = "/"; + private const string DoubleForwardSlash = "//"; + private const string ParentDirectoryReference = ".."; + private const string Colon = ":"; + + // Windows reserved device names. + private static readonly string[] _windowsReservedNames = + { + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" + }; + + private static readonly char[] _pathSeparators = { '/', '\\' }; + +#if NET8_0_OR_GREATER + [GeneratedRegex(@"^[a-zA-Z]:")] + private static partial Regex DriveLetterRegex(); + + [GeneratedRegex(@"(?<=[/\\])\.|\.(?=[/\\])|^\./|^\.")] + private static partial Regex CurrentDirectoryReferenceRegex(); + + [GeneratedRegex(@"[/\\]{2,}")] + private static partial Regex MultipleSlashesRegex(); +#else + private static readonly Regex _driveLetterRegex = new(@"^[a-zA-Z]:", RegexOptions.Compiled); + private static Regex DriveLetterRegex() => _driveLetterRegex; + + private static readonly Regex _currentDirectoryReferenceRegex = new(@"(?<=[/\\])\.|\.(?=[/\\])|^\./|^\.", RegexOptions.Compiled); + private static Regex CurrentDirectoryReferenceRegex() => _currentDirectoryReferenceRegex; + + private static readonly Regex _multipleSlashesRegex = new(@"[/\\]{2,}", RegexOptions.Compiled); + private static Regex MultipleSlashesRegex() => _multipleSlashesRegex; +#endif + + /// + /// Sanitizes a file path to prevent directory traversal attacks (CWE-22). Uses allowlist + /// validation (recommended) when is + /// true, otherwise falls back to legacy pattern-stripping. + /// + /// The input file path to sanitize. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the validated file path and operation details. + public static SanitizationResult SanitizeForFilePath( + string? input, + SanitizationSettings? settings = null) + { + var startTime = Stopwatch.StartNew(); + var effectiveSettings = settings ?? GetCurrentSettings(); + + try + { + if (string.IsNullOrEmpty(input)) + { + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + return SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.FilePath); + } + + // Narrowed to non-null/non-empty from here; netstandard2.0's reference assembly for + // string.IsNullOrEmpty doesn't carry the [NotNullWhen] annotation net8.0's does, so the + // compiler can't narrow `input` itself across TFMs โ€” shadow with a value it can. + var value = input!; + + if (!effectiveSettings.EnableFilePathSanitization) + { + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + return SanitizationResult.Unchanged(value, SanitizationType.FilePath); + } + + if (effectiveSettings.UseStrictValidation) + { + var isValid = ValidateFilePathAllowlist(value, effectiveSettings); + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + + return isValid + ? SanitizationResult.Unchanged(value, SanitizationType.FilePath) + : SanitizationResult.Rejected(SanitizationType.FilePath, startTime.Elapsed); + } + + // Legacy mode: fast path when no dangerous patterns are present. + if (!ContainsDangerousPathPatterns(value, effectiveSettings)) + { + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + return SanitizationResult.Unchanged(value, SanitizationType.FilePath); + } + + var sanitized = SanitizeFilePathLegacy(value, effectiveSettings); + var wasModified = !string.Equals(value, sanitized, StringComparison.Ordinal); + + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + + return wasModified + ? SanitizationResult.Modified(sanitized, SanitizationType.FilePath, startTime.Elapsed) + : SanitizationResult.Unchanged(sanitized, SanitizationType.FilePath); + } + catch + { + // Fail-safe: reject the path entirely rather than risk returning something unsafe. + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + return SanitizationResult.Rejected(SanitizationType.FilePath, startTime.Elapsed); + } + } + + /// + /// Sanitizes a correlation ID to make it safe for use as (part of) a file name. Removes + /// invalid file-name characters and enforces a reasonable maximum length. + /// + /// The correlation ID to sanitize. + /// A sanitized correlation ID safe for file-name use. + public static string SanitizeCorrelationIdForPath(string correlationId) + { + const int maxCorrelationIdLength = 50; + + if (string.IsNullOrEmpty(correlationId)) + { + return string.Empty; + } + + var invalidChars = Path.GetInvalidFileNameChars(); + var sanitized = new StringBuilder(correlationId.Length); + + foreach (var c in correlationId) + { + sanitized.Append(Array.IndexOf(invalidChars, c) >= 0 ? '_' : c); + } + + var result = sanitized.ToString(); + + if (result.Length > maxCorrelationIdLength) + { + result = result.Substring(0, maxCorrelationIdLength); + } + + // Trim trailing dots/spaces so the result is safe on file systems that reject them. + return result.TrimEnd('.', ' '); + } + + /// + /// Fast check for dangerous file-path patterns without allocations, used by legacy + /// (non-strict) mode. Mirrors every pattern handles so the + /// documented protections are actually applied. + /// + /// The file path to check. + /// The sanitization settings to apply. + /// True if dangerous patterns are present, false otherwise. + private static bool ContainsDangerousPathPatterns(string input, SanitizationSettings settings) + { + if (string.IsNullOrEmpty(input)) + { + return false; + } + + if (settings.EnableParentDirectoryTraversalCheck && + ContainsOrdinal(input, ParentDirectoryReference, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (ContainsOrdinal(input, DoubleForwardSlash, StringComparison.OrdinalIgnoreCase) || + ContainsOrdinal(input, DoubleBackslash, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (ContainsOrdinal(input, Colon, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (input.StartsWith(ForwardSlash, StringComparison.Ordinal) || input.StartsWith(SingleBackslash, StringComparison.Ordinal)) + { + return true; + } + + var hasParentRef = ContainsOrdinal(input, "../", StringComparison.OrdinalIgnoreCase) || + ContainsOrdinal(input, "..\\", StringComparison.OrdinalIgnoreCase); + + if (ContainsOrdinal(input, "./", StringComparison.OrdinalIgnoreCase) && !hasParentRef) + { + return true; + } + + if (ContainsOrdinal(input, ".\\", StringComparison.OrdinalIgnoreCase) && !hasParentRef) + { + return true; + } + + return false; + } + + /// + /// Sanitizes dangerous patterns in the file path using string replacement (legacy mode). + /// Prefer allowlist validation for new code. + /// + /// The input file path to sanitize. + /// The sanitization settings to apply. + /// The sanitized file path. + private static string SanitizeFilePathLegacy( + string input, + SanitizationSettings settings) + { + var sanitized = input; + + if (settings.EnableParentDirectoryTraversalCheck) + { + // These literals contain no alphabetic characters, so an ordinal (case-sensitive) + // replace is behaviorally identical to an ordinal-ignore-case one, and the 2-arg + // Replace(string, string) overload is available on netstandard2.0 (unlike the + // 3-arg StringComparison overload, which is netstandard2.1+). + sanitized = sanitized.Replace(ParentDirectoryReference, string.Empty); + } + + sanitized = sanitized.Replace(DoubleForwardSlash, ForwardSlash); + sanitized = sanitized.Replace(DoubleBackslash, SingleBackslash); + sanitized = DriveLetterRegex().Replace(sanitized, string.Empty); + sanitized = sanitized.TrimStart('/', '\\'); + sanitized = CurrentDirectoryReferenceRegex().Replace(sanitized, string.Empty); + sanitized = MultipleSlashesRegex().Replace(sanitized, ForwardSlash); + + return sanitized.Length > settings.MaxSanitizedStringLength + ? sanitized.Substring(0, settings.MaxSanitizedStringLength) + : sanitized; + } + + /// + /// Validates a file path using allowlist rules per CWE-22 recommendations: reject anything + /// suspicious rather than trying to "clean" it into something safe. + /// + /// The input file path to validate. + /// The sanitization settings to apply. + /// True if the path is valid, false otherwise. + private static bool ValidateFilePathAllowlist(string input, SanitizationSettings settings) + { + if (string.IsNullOrEmpty(input)) + { + return false; + } + + if (ContainsInvalidCharacters(input, settings)) + { + LogSecurityEvent("Invalid characters detected", input, settings); + return false; + } + + if (settings.ValidateWindowsReservedNames && IsWindowsReservedName(input)) + { + LogSecurityEvent("Windows reserved device name detected", input, settings); + return false; + } + + if (HasInvalidPathSegmentLengths(input, settings)) + { + LogSecurityEvent("Invalid path segment length", input, settings); + return false; + } + + if (settings.AllowedFileExtensions.Length > 0 && !IsValidFileExtension(input, settings)) + { + LogSecurityEvent("Invalid file extension", input, settings); + return false; + } + + if (settings.AllowedBaseDirectories.Length == 0 && ContainsDangerousTraversalPatterns(input)) + { + LogSecurityEvent("Dangerous traversal pattern detected", input, settings); + return false; + } + + if (settings.AllowedBaseDirectories.Length > 0 && !IsWithinAllowedDirectories(input, settings)) + { + LogSecurityEvent("Path outside allowed directories", input, settings); + return false; + } + + return true; + } + + /// + /// Checks for directory-traversal patterns, including common URL-encoded variants + /// (%2e%2e%2f and similar) that a naive string check would miss. + /// + /// The input string to check. + /// True if dangerous traversal patterns are present, false otherwise. + private static bool ContainsDangerousTraversalPatterns(string input) + { + if (ContainsOrdinal(input, "../", StringComparison.OrdinalIgnoreCase) || + ContainsOrdinal(input, "..\\", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (ContainsOrdinal(input, "%2e%2e%2f", StringComparison.OrdinalIgnoreCase) || + ContainsOrdinal(input, "%2e%2e%5c", StringComparison.OrdinalIgnoreCase) || + ContainsOrdinal(input, "..%2f", StringComparison.OrdinalIgnoreCase) || + ContainsOrdinal(input, "..%5c", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (input.StartsWith(ForwardSlash, StringComparison.Ordinal) || input.StartsWith(SingleBackslash, StringComparison.Ordinal)) + { + return true; + } + + if (input.Length >= 2 && input[1] == ':' && char.IsLetter(input[0])) + { + return true; + } + + return false; + } + + /// + /// Checks if the input contains invalid characters based on settings: control characters, + /// disallowed Unicode (when is false), + /// and characters invalid in Windows file names. + /// + /// The input string to check. + /// The sanitization settings. + /// True if invalid characters are present, false otherwise. + private static bool ContainsInvalidCharacters(string input, SanitizationSettings settings) + { + foreach (var c in input) + { + if (IsControlCharacter(c, settings)) + { + return true; + } + + if (!settings.AllowUnicodeCharacters && c > 127) + { + return true; + } + + if (IsInvalidPathCharacter(c)) + { + return true; + } + } + + return false; + } + + /// + /// Determines if a character is invalid in Windows file names. + /// + /// The character to check. + /// True if the character is invalid, false otherwise. + private static bool IsInvalidPathCharacter(char c) + { + return c is '<' or '>' or ':' or '"' or '|' or '?' or '*'; + } + + /// + /// Checks if the path contains a Windows reserved device name (e.g. CON, PRN, + /// COM1) in any path segment. + /// + /// The input path to check. + /// True if a reserved name is found, false otherwise. + private static bool IsWindowsReservedName(string input) + { + var normalizedPath = input.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar); + var pathSegments = normalizedPath.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var segment in pathSegments) + { + var segmentName = Path.GetFileNameWithoutExtension(segment); + + if (string.IsNullOrEmpty(segmentName)) + { + continue; + } + + var upperSegmentName = segmentName.ToUpperInvariant(); + + foreach (var reservedName in _windowsReservedNames) + { + if (upperSegmentName == reservedName) + { + return true; + } + } + } + + return false; + } + + /// + /// Checks if any path segment exceeds . + /// + /// The input path to check. + /// The sanitization settings. + /// True if an invalid segment length is found, false otherwise. + private static bool HasInvalidPathSegmentLengths(string input, SanitizationSettings settings) + { + var segments = input.Split(_pathSeparators, StringSplitOptions.RemoveEmptyEntries); + + foreach (var segment in segments) + { + if (segment.Length > settings.MaxPathSegmentLength) + { + return true; + } + } + + return false; + } + + /// + /// Validates the file extension against . + /// + /// The input path to check. + /// The sanitization settings. + /// True if the extension is allowed, false otherwise. + private static bool IsValidFileExtension(string input, SanitizationSettings settings) + { + var extension = Path.GetExtension(input); + + if (string.IsNullOrEmpty(extension)) + { + return false; + } + + var extensionWithoutDot = extension.Substring(1); + + foreach (var allowedExtension in settings.AllowedFileExtensions) + { + if (extensionWithoutDot.Equals(allowedExtension, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + /// + /// Validates that the resolved path stays within , + /// using canonical-path resolution per CWE-22 recommendations. + /// + /// The input path to check. + /// The sanitization settings. + /// True if the path is within allowed directories, false otherwise. + private static bool IsWithinAllowedDirectories(string input, SanitizationSettings settings) + { + if (settings.AllowedBaseDirectories.Length == 0) + { + return true; + } + + try + { + if (!Path.IsPathRooted(input)) + { + if (ContainsDangerousTraversalPatterns(input)) + { + return false; + } + + foreach (var allowedDirectory in settings.AllowedBaseDirectories) + { + var combinedPath = Path.Combine(allowedDirectory, input); + var fullPath = Path.GetFullPath(combinedPath); + var allowedFullPath = Path.GetFullPath(allowedDirectory); + + if (IsPathWithinDirectory(fullPath, allowedFullPath)) + { + return true; + } + } + + return false; + } + + var absolutePath = Path.GetFullPath(input); + + foreach (var allowedDirectory in settings.AllowedBaseDirectories) + { + var allowedFullPath = Path.GetFullPath(allowedDirectory); + + if (IsPathWithinDirectory(absolutePath, allowedFullPath)) + { + return true; + } + } + + return false; + } + catch + { + // If path resolution fails (e.g. invalid characters the OS rejects), treat as invalid. + return false; + } + } + + /// + /// Determines whether is equal to, or nested within, + /// . Both paths must already be canonicalized via + /// . Implemented via prefix comparison rather than + /// Path.GetRelativePath so the check compiles identically on netstandard2.0 + /// (which lacks that API) and net8.0. + /// + /// The canonicalized candidate path. + /// The canonicalized allowed base directory. + /// True if the candidate path is the allowed directory or a descendant of it. + private static bool IsPathWithinDirectory(string candidateFullPath, string allowedFullPath) + { + var trimmedAllowed = allowedFullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + + if (string.Equals(candidateFullPath, trimmedAllowed, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + var allowedWithSeparator = trimmedAllowed + Path.DirectorySeparatorChar; + return candidateFullPath.StartsWith(allowedWithSeparator, StringComparison.OrdinalIgnoreCase); + } +} From 9e5b5b855c55023ff24fb2cdb69579c3ce8f8fd4 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:11 -0600 Subject: [PATCH 29/63] feat(sanitization): add sensitive data sanitization - add detection and masking of bearer tokens, api keys, secrets, passwords, urls and file system paths - add key based heuristics for json and key value pair formats - mask the middle portion of detected values while preserving a hint of their original shape and length --- .../SanitizationEngine.SensitiveData.cs | 359 ++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs new file mode 100644 index 0000000..cde7ff8 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs @@ -0,0 +1,359 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Sensitive-data (CWE-200) detection and masking: finds and redacts tokens, secrets, credentials, +/// URLs, and file-system paths that should not appear in logs or diagnostic output. +/// +public static partial class SanitizationEngine +{ +#if NET8_0_OR_GREATER + [GeneratedRegex(@"Bearer\s+[A-Za-z0-9+/=]{20,}", RegexOptions.IgnoreCase)] + private static partial Regex BearerTokenRegex(); + + [GeneratedRegex(@"sk-[A-Za-z0-9]{20,}")] + private static partial Regex StripeApiKeyRegex(); + + [GeneratedRegex(@"api_key\s*=\s*[A-Za-z0-9+/=]{16,}")] + private static partial Regex ApiKeyRegex(); + + [GeneratedRegex(@"token\s*=\s*[A-Za-z0-9+/=]{16,}")] + private static partial Regex TokenRegex(); + + [GeneratedRegex(@"secret\s*=\s*[A-Za-z0-9+/=]{16,}")] + private static partial Regex SecretRegex(); + + [GeneratedRegex(@"password\s*=\s*[^\s]{6,}")] + private static partial Regex PasswordRegex(); + + [GeneratedRegex(@"https?://[^\s,;\""\\]+")] + private static partial Regex UrlRegex(); + + [GeneratedRegex(@"[A-Za-z]:\\[^\s]+")] + private static partial Regex WindowsPathRegex(); + + [GeneratedRegex(@"\\\\[^\s]+")] + private static partial Regex UncPathRegex(); + + [GeneratedRegex(@"/(?:home|Users|var|etc|opt|srv|tmp|app)/\S+", RegexOptions.IgnoreCase)] + private static partial Regex UnixPathRegex(); + + [GeneratedRegex(@"~/[^\s]+")] + private static partial Regex HomePathRegex(); + + [GeneratedRegex(@"[A-Za-z0-9+/=]{16,}")] + private static partial Regex LongAlphanumericRegex(); + + [GeneratedRegex(@"(?:""?(?:password|pwd|pass|secret|token|key|apikey|api_key|auth|authorization|credential|creds|private|confidential)""?\s*[:=]\s*""?)([^"",}\s]+)""?", RegexOptions.IgnoreCase)] + private static partial Regex SensitiveKeyRegex(); + + [GeneratedRegex(@"(?:encryption|decrypt|cipher|hash|salt|iv|nonce)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase)] + private static partial Regex EncryptionKeyRegex(); + + [GeneratedRegex(@"(?:admin|root|user|login|session)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase)] + private static partial Regex AdminKeyRegex(); + + [GeneratedRegex(@"(?:bearer|jwt|oauth|access|refresh)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase)] + private static partial Regex AuthTokenRegex(); + + [GeneratedRegex(@"(?:ip|range|cidr|subnet|network)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase)] + private static partial Regex NetworkKeyRegex(); +#else + private static readonly Regex _bearerTokenRegex = new(@"Bearer\s+[A-Za-z0-9+/=]{20,}", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex BearerTokenRegex() => _bearerTokenRegex; + + private static readonly Regex _stripeApiKeyRegex = new(@"sk-[A-Za-z0-9]{20,}", RegexOptions.Compiled); + private static Regex StripeApiKeyRegex() => _stripeApiKeyRegex; + + private static readonly Regex _apiKeyRegex = new(@"api_key\s*=\s*[A-Za-z0-9+/=]{16,}", RegexOptions.Compiled); + private static Regex ApiKeyRegex() => _apiKeyRegex; + + private static readonly Regex _tokenRegex = new(@"token\s*=\s*[A-Za-z0-9+/=]{16,}", RegexOptions.Compiled); + private static Regex TokenRegex() => _tokenRegex; + + private static readonly Regex _secretRegex = new(@"secret\s*=\s*[A-Za-z0-9+/=]{16,}", RegexOptions.Compiled); + private static Regex SecretRegex() => _secretRegex; + + private static readonly Regex _passwordRegex = new(@"password\s*=\s*[^\s]{6,}", RegexOptions.Compiled); + private static Regex PasswordRegex() => _passwordRegex; + + private static readonly Regex _urlRegex = new(@"https?://[^\s,;\""\\]+", RegexOptions.Compiled); + private static Regex UrlRegex() => _urlRegex; + + private static readonly Regex _windowsPathRegex = new(@"[A-Za-z]:\\[^\s]+", RegexOptions.Compiled); + private static Regex WindowsPathRegex() => _windowsPathRegex; + + private static readonly Regex _uncPathRegex = new(@"\\\\[^\s]+", RegexOptions.Compiled); + private static Regex UncPathRegex() => _uncPathRegex; + + private static readonly Regex _unixPathRegex = new(@"/(?:home|Users|var|etc|opt|srv|tmp|app)/\S+", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex UnixPathRegex() => _unixPathRegex; + + private static readonly Regex _homePathRegex = new(@"~/[^\s]+", RegexOptions.Compiled); + private static Regex HomePathRegex() => _homePathRegex; + + private static readonly Regex _longAlphanumericRegex = new(@"[A-Za-z0-9+/=]{16,}", RegexOptions.Compiled); + private static Regex LongAlphanumericRegex() => _longAlphanumericRegex; + + private static readonly Regex _sensitiveKeyRegex = new(@"(?:""?(?:password|pwd|pass|secret|token|key|apikey|api_key|auth|authorization|credential|creds|private|confidential)""?\s*[:=]\s*""?)([^"",}\s]+)""?", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex SensitiveKeyRegex() => _sensitiveKeyRegex; + + private static readonly Regex _encryptionKeyRegex = new(@"(?:encryption|decrypt|cipher|hash|salt|iv|nonce)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex EncryptionKeyRegex() => _encryptionKeyRegex; + + private static readonly Regex _adminKeyRegex = new(@"(?:admin|root|user|login|session)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex AdminKeyRegex() => _adminKeyRegex; + + private static readonly Regex _authTokenRegex = new(@"(?:bearer|jwt|oauth|access|refresh)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex AuthTokenRegex() => _authTokenRegex; + + private static readonly Regex _networkKeyRegex = new(@"(?:ip|range|cidr|subnet|network)[\s:=]*([^,}\s]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static Regex NetworkKeyRegex() => _networkKeyRegex; +#endif + + /// + /// Detects and masks sensitive data in a string to prevent information disclosure. + /// This method is optimized for performance and never throws exceptions. + /// Implements CWE-200 remediation via pattern-based detection and masking. + /// + /// The input string to check for sensitive data. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the masked string and operation details. + public static SensitiveDataResult SanitizeForSensitiveData( + string? input, + SanitizationSettings? settings = null) + { + var startTime = Stopwatch.StartNew(); + var effectiveSettings = settings ?? GetCurrentSettings(); + + try + { + if (string.IsNullOrEmpty(input)) + { + return SensitiveDataResult.Unchanged(input ?? string.Empty); + } + + // See the analogous comment in SanitizationEngine.FilePath.cs: shadow with a value the + // compiler can narrow consistently across both target frameworks. + var value = input!; + + if (!effectiveSettings.EnableSensitiveDataDetection) + { + return SensitiveDataResult.Unchanged(value); + } + + if (!ContainsSensitiveDataPatterns(value, effectiveSettings)) + { + return SensitiveDataResult.Unchanged(value); + } + + var masked = MaskSensitiveData(value, effectiveSettings); + var wasModified = !string.Equals(value, masked, StringComparison.Ordinal); + + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + + return wasModified + ? SensitiveDataResult.Modified(masked, startTime.Elapsed) + : SensitiveDataResult.Unchanged(masked); + } + catch + { + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + return SensitiveDataResult.Unchanged(input ?? string.Empty); + } + } + + /// + /// Fast check for sensitive-data patterns without allocations, so the (allocating) masking + /// pass is only run when something actually needs masking. + /// + /// The string to check. + /// The sanitization settings to apply. + /// True if sensitive patterns are present, false otherwise. + private static bool ContainsSensitiveDataPatterns(string input, SanitizationSettings settings) + { + if (string.IsNullOrEmpty(input)) + { + return false; + } + + if (BearerTokenRegex().IsMatch(input) || + StripeApiKeyRegex().IsMatch(input) || + ApiKeyRegex().IsMatch(input) || + TokenRegex().IsMatch(input) || + SecretRegex().IsMatch(input) || + PasswordRegex().IsMatch(input)) + { + return true; + } + + if (SensitiveKeyRegex().IsMatch(input) || + EncryptionKeyRegex().IsMatch(input) || + AdminKeyRegex().IsMatch(input) || + AuthTokenRegex().IsMatch(input) || + NetworkKeyRegex().IsMatch(input)) + { + return true; + } + + if (UrlRegex().IsMatch(input)) + { + return true; + } + + if (WindowsPathRegex().IsMatch(input) || + UncPathRegex().IsMatch(input) || + UnixPathRegex().IsMatch(input) || + HomePathRegex().IsMatch(input)) + { + return true; + } + + if (settings.SensitiveDataDetectionStrictness >= SensitiveDataDetectionStrictness.Medium) + { + if (LongAlphanumericRegex().IsMatch(input)) + { + return true; + } + } + + return false; + } + + /// + /// Masks sensitive-data patterns in the input string using the configured mask character. + /// + /// The input string to mask. + /// The sanitization settings to apply. + /// The masked string. + private static string MaskSensitiveData(string input, SanitizationSettings settings) + { + if (string.IsNullOrEmpty(input)) + { + return input ?? string.Empty; + } + + var maskChar = settings.SensitiveDataMaskCharacter; + var result = input; + + result = MaskKeyBasedValues(result, maskChar); + + result = BearerTokenRegex().Replace(result, m => $"Bearer {m.Value.Substring(7).Mask(maskChar)}"); + result = StripeApiKeyRegex().Replace(result, m => $"sk-{m.Value.Substring(3).Mask(maskChar)}"); + result = ApiKeyRegex().Replace(result, m => $"api_key={ExtractValueAfterEquals(m.Value).Mask(maskChar)}"); + result = TokenRegex().Replace(result, m => $"token={ExtractValueAfterEquals(m.Value).Mask(maskChar)}"); + result = SecretRegex().Replace(result, m => $"secret={ExtractValueAfterEquals(m.Value).Mask(maskChar)}"); + result = PasswordRegex().Replace(result, m => $"password={ExtractValueAfterEquals(m.Value).Mask(maskChar)}"); + + result = UrlRegex().Replace(result, m => MaskValueIntelligently(m.Value, maskChar)); + + result = WindowsPathRegex().Replace(result, m => m.Value.Mask(maskChar)); + result = UncPathRegex().Replace(result, m => m.Value.Mask(maskChar)); + result = UnixPathRegex().Replace(result, m => m.Value.Mask(maskChar)); + result = HomePathRegex().Replace(result, m => m.Value.Mask(maskChar)); + + if (settings.SensitiveDataDetectionStrictness >= SensitiveDataDetectionStrictness.Medium) + { + result = LongAlphanumericRegex().Replace(result, m => m.Value.Mask(maskChar)); + } + + return result; + } + + /// + /// Masks sensitive values found via key-based heuristics (JSON key-value pairs and similar + /// key: value/key=value formats). + /// + /// The input string to process. + /// The character to use for masking. + /// The masked string. + private static string MaskKeyBasedValues(string input, char maskChar) + { + var result = input; + + result = SensitiveKeyRegex().Replace(result, m => + { + var key = m.Groups[1].Value; + var maskedValue = MaskValueIntelligently(m.Groups[2].Value, maskChar); + return $"{key}{maskedValue}"; + }); + + result = EncryptionKeyRegex().Replace(result, m => + { + var key = m.Value.Substring(0, m.Value.IndexOf(m.Groups[1].Value, StringComparison.Ordinal)); + var maskedValue = MaskValueIntelligently(m.Groups[1].Value, maskChar); + return $"{key}{maskedValue}"; + }); + + result = AdminKeyRegex().Replace(result, m => + { + var key = m.Value.Substring(0, m.Value.IndexOf(m.Groups[1].Value, StringComparison.Ordinal)); + var maskedValue = MaskValueIntelligently(m.Groups[1].Value, maskChar); + return $"{key}{maskedValue}"; + }); + + result = AuthTokenRegex().Replace(result, m => + { + var key = m.Value.Substring(0, m.Value.IndexOf(m.Groups[1].Value, StringComparison.Ordinal)); + var maskedValue = MaskValueIntelligently(m.Groups[1].Value, maskChar); + return $"{key}{maskedValue}"; + }); + + result = NetworkKeyRegex().Replace(result, m => + { + var key = m.Value.Substring(0, m.Value.IndexOf(m.Groups[1].Value, StringComparison.Ordinal)); + var maskedValue = MaskValueIntelligently(m.Groups[1].Value, maskChar); + return $"{key}{maskedValue}"; + }); + + return result; + } + + /// + /// Masks the middle portion of a value (keeping the first and last quarter visible) so masked + /// output still hints at shape/length without disclosing the sensitive content. + /// + /// The value to mask. + /// The character to use for masking. + /// The masked value. + private static string MaskValueIntelligently(string value, char maskChar) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + var result = value.ToCharArray(); + var length = result.Length; + var maskStart = length / 4; + var maskEnd = length - (length / 4); + + for (var i = maskStart; i < maskEnd && i < length; i++) + { + result[i] = maskChar; + } + + return new string(result); + } + + /// + /// Extracts the value after the first = character. Avoids the allocation overhead of + /// Split().Last() and correctly handles values that themselves contain = + /// (common in Base64 tokens and signed URLs). + /// + /// The string containing a key=value pair. + /// The value after the first = character, or the original string if none is found. + private static string ExtractValueAfterEquals(string keyValuePair) + { + var equalsIndex = keyValuePair.IndexOf('='); + + return equalsIndex >= 0 && equalsIndex < keyValuePair.Length - 1 + ? keyValuePair.Substring(equalsIndex + 1) + : keyValuePair; + } +} From d0a73fd800a21521967ea1c27abbb6fe2411ded6 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:21 -0600 Subject: [PATCH 30/63] feat(sanitization): add regex injection sanitization - add regex pattern validation covering syntax timeout, complexity scoring and pattern length limits - reject nested quantifiers, and optionally unicode categories, backreferences and lookarounds based on settings - add a manual nested quantifier detection fallback for when the detection regex itself cannot be evaluated --- .../SanitizationEngine.RegexInjection.cs | 373 ++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.RegexInjection.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.RegexInjection.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.RegexInjection.cs new file mode 100644 index 0000000..4a0ac70 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.RegexInjection.cs @@ -0,0 +1,373 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// Regex-injection / ReDoS (CWE-400/CWE-730) sanitization: validates that a regex pattern is safe +/// to compile and execute before it is used against untrusted input. +/// +public static partial class SanitizationEngine +{ + /// + /// Internal validation outcome for . A plain struct + /// (not a record struct) to avoid any dependency on init-accessor support, which the + /// netstandard2.0 target of this package cannot rely on being present. + /// + private readonly struct RegexValidationResult + { + public RegexValidationResult(bool isValid, string reason) + { + IsValid = isValid; + Reason = reason; + } + + public bool IsValid { get; } + + public string Reason { get; } + } + + /// + /// Validates a regex pattern to prevent Regular Expression Denial of Service (ReDoS) attacks. + /// Checks pattern length, compilation safety, complexity, and known catastrophic-backtracking + /// shapes (nested quantifiers) before the pattern is ever used against untrusted input. + /// + /// The regex pattern to validate. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the validated pattern and operation details. + public static SanitizationResult SanitizeForRegexInjection( + string? pattern, + SanitizationSettings? settings = null) + { + var startTime = Stopwatch.StartNew(); + var effectiveSettings = settings ?? GetCurrentSettings(); + + try + { + if (string.IsNullOrEmpty(pattern)) + { + return SanitizationResult.Unchanged(pattern ?? string.Empty, SanitizationType.RegexInjection); + } + + // See the analogous comment in SanitizationEngine.FilePath.cs: shadow with a value the + // compiler can narrow consistently across both target frameworks. + var value = pattern!; + + if (!effectiveSettings.EnableRegexSanitization) + { + return SanitizationResult.Unchanged(value, SanitizationType.RegexInjection); + } + + if (value.Length > effectiveSettings.MaxRegexPatternLength) + { + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + LogSecurityEvent("Regex pattern length exceeded", $"Regex pattern rejected: length {value.Length} exceeds maximum {effectiveSettings.MaxRegexPatternLength}", effectiveSettings); + return SanitizationResult.Rejected(SanitizationType.RegexInjection, startTime.Elapsed); + } + + var validationResult = ValidateRegexPatternSafety(value, effectiveSettings); + + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + + if (!validationResult.IsValid) + { + LogSecurityEvent("Regex pattern validation failed", $"Regex pattern rejected: {validationResult.Reason}", effectiveSettings); + return SanitizationResult.Rejected(SanitizationType.RegexInjection, startTime.Elapsed); + } + + return SanitizationResult.Unchanged(value, SanitizationType.RegexInjection); + } + catch + { + startTime.Stop(); + UpdatePerformanceMetrics(startTime.Elapsed, effectiveSettings); + LogSanitizationFailure(pattern, effectiveSettings, "RegexSanitizationFailed"); + return SanitizationResult.Rejected(SanitizationType.RegexInjection, startTime.Elapsed); + } + } + + /// + /// Runs every configured safety check against a regex pattern in order, short-circuiting on + /// the first violation. + /// + /// The regex pattern to validate. + /// The sanitization settings to apply. + /// A validation result indicating whether the pattern is safe. + private static RegexValidationResult ValidateRegexPatternSafety(string pattern, SanitizationSettings settings) + { + if (!ValidateRegexSyntaxWithTimeout(pattern, settings.MaxRegexValidationTimeout)) + { + return new RegexValidationResult(false, "Pattern compilation timeout or syntax error"); + } + + var complexityScore = CalculateRegexComplexityScore(pattern); + if (complexityScore > settings.MaxRegexComplexityScore) + { + return new RegexValidationResult(false, $"Complexity score {complexityScore} exceeds maximum {settings.MaxRegexComplexityScore}"); + } + + if (!settings.AllowUnicodeCategories && ContainsUnicodeCategories(pattern)) + { + return new RegexValidationResult(false, "Unicode character categories are not allowed"); + } + + if (!settings.AllowQuantifiers && ContainsQuantifiers(pattern)) + { + return new RegexValidationResult(false, "Quantifiers are not allowed"); + } + + if (!settings.AllowNestedQuantifiers && ContainsNestedQuantifiers(pattern)) + { + return new RegexValidationResult(false, "Nested quantifiers detected (high ReDoS risk)"); + } + + if (!settings.AllowBackreferences && ContainsBackreferences(pattern)) + { + return new RegexValidationResult(false, "Backreferences are not allowed"); + } + + if (!settings.AllowLookarounds && ContainsLookarounds(pattern)) + { + return new RegexValidationResult(false, "Lookaround assertions are not allowed"); + } + + return new RegexValidationResult(true, "Pattern is safe"); + } + + /// + /// Validates regex syntax with a timeout, so an attacker-supplied pattern cannot hang the + /// validation step itself. + /// + /// The regex pattern to validate. + /// The maximum time to allow for compilation. + /// True if the pattern compiles successfully within the timeout. + private static bool ValidateRegexSyntaxWithTimeout(string pattern, TimeSpan timeout) + { + try + { + var cts = new CancellationTokenSource(timeout); + var task = Task.Run(() => + { + _ = new Regex(pattern, RegexOptions.Compiled); + return true; + }, cts.Token); + + return task.Wait(timeout) && task.Result; + } + catch + { + return false; + } + } + + /// + /// Calculates a complexity score for a regex pattern based on nesting depth, quantifier count, + /// group count, and character-class/escape usage. Higher scores indicate patterns more likely + /// to cause catastrophic backtracking or excessive compilation cost. + /// + /// The regex pattern to analyze. + /// A complexity score (higher = more complex), capped at 1000. + private static int CalculateRegexComplexityScore(string pattern) + { + var score = 0; + var depth = 0; + var quantifierCount = 0; + var groupCount = 0; + + foreach (var c in pattern) + { + switch (c) + { + case '(': + depth++; + groupCount++; + score += depth * 2; + break; + case ')': + depth--; + break; + case '*': + case '+': + case '?': + quantifierCount++; + score += 5; + break; + case '{': + score += 10; + break; + case '[': + score += 3; + break; + case '\\': + score += 2; + break; + } + } + + if (quantifierCount > 5) + { + score += quantifierCount * 3; + } + + if (groupCount > 10) + { + score += groupCount * 2; + } + + return Math.Min(score, 1000); + } + + /// + /// Checks if a pattern contains Unicode character categories (\p{...} / \P{...}). + /// + /// The regex pattern to check. + /// True if Unicode categories are found. + private static bool ContainsUnicodeCategories(string pattern) + { + return ContainsOrdinal(pattern, @"\p{", StringComparison.Ordinal) || ContainsOrdinal(pattern, @"\P{", StringComparison.Ordinal); + } + + /// + /// Checks if a pattern contains quantifiers (*, +, ?, or {n,m}). + /// + /// The regex pattern to check. + /// True if quantifiers are found. + private static bool ContainsQuantifiers(string pattern) + { + foreach (var c in pattern) + { + if (c is '*' or '+' or '?') + { + return true; + } + } + + return pattern.IndexOf('{') >= 0 && pattern.IndexOf('}') >= 0; + } + + /// + /// Checks if a pattern contains nested quantifiers (e.g. (a+)+, (a*)*) โ€” the + /// classic shape behind catastrophic backtracking. + /// + /// The regex pattern to check. + /// True if nested quantifiers are found. + private static bool ContainsNestedQuantifiers(string pattern) + { + const string groupPattern = @"\([^)]*[*+?][^)]*\)[*+?]"; + + try + { + return Regex.IsMatch(pattern, groupPattern); + } + catch + { + return ManualNestedQuantifierDetection(pattern); + } + } + + /// + /// Manual fallback for nested-quantifier detection when regex matching itself fails. + /// + /// The regex pattern to check. + /// True if nested quantifiers are found. + private static bool ManualNestedQuantifierDetection(string pattern) + { + var inGroup = false; + var groupHasQuantifier = false; + var i = 0; + + while (i < pattern.Length) + { + var c = pattern[i]; + + switch (c) + { + case '(': + inGroup = true; + groupHasQuantifier = false; + break; + case ')': + if (inGroup && groupHasQuantifier) + { + if (i + 1 < pattern.Length && IsQuantifier(pattern[i + 1])) + { + return true; + } + } + inGroup = false; + groupHasQuantifier = false; + break; + case '*': + case '+': + case '?': + if (inGroup) + { + groupHasQuantifier = true; + } + break; + case '{': + if (inGroup) + { + groupHasQuantifier = true; + var braceEnd = pattern.IndexOf('}', i); + if (braceEnd != -1) + { + i = braceEnd; + } + } + break; + } + + i++; + } + + return false; + } + + /// + /// Determines if a character is a regex quantifier. + /// + /// The character to check. + /// True if the character is a quantifier. + private static bool IsQuantifier(char c) + { + return c is '*' or '+' or '?'; + } + + /// + /// Checks if a pattern contains backreferences (\1, \k<name>). + /// + /// The regex pattern to check. + /// True if backreferences are found. + private static bool ContainsBackreferences(string pattern) + { + for (var i = 0; i < pattern.Length - 1; i++) + { + if (pattern[i] == '\\' && char.IsDigit(pattern[i + 1])) + { + return true; + } + } + + return ContainsOrdinal(pattern, @"\k<", StringComparison.Ordinal); + } + + /// + /// Checks if a pattern contains lookaround assertions ((?=, (?!, (?<=, (?<!). + /// + /// The regex pattern to check. + /// True if lookarounds are found. + private static bool ContainsLookarounds(string pattern) + { + return ContainsOrdinal(pattern, "?=", StringComparison.Ordinal) || + ContainsOrdinal(pattern, "?!", StringComparison.Ordinal) || + ContainsOrdinal(pattern, "?<=", StringComparison.Ordinal) || + ContainsOrdinal(pattern, "?", StringComparison.Ordinal); + } +} From 2b765db10dbd7cf4401bd82f4bfbf6ebd66fc38a Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:25 -0600 Subject: [PATCH 31/63] feat(sanitization): add public sanitization extension methods - add string extension methods wrapping the static engine for log, file path, sensitive data and regex sanitization - add with-details variants returning the full result type for callers that need modification and timing information - add a mask extension method for partially obscuring sensitive string values --- .../SanitizationExtensions.cs | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationExtensions.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationExtensions.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationExtensions.cs new file mode 100644 index 0000000..070d349 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationExtensions.cs @@ -0,0 +1,156 @@ +namespace PowerCSharp.Feature.Sanitization.Abstractions; + +/// +/// String extension methods wrapping the static . These are the +/// primary call-site API: no dependency injection or feature-flag wiring is required to use them โ€” +/// reference this package and call input.SanitizeForLog() from anywhere, including hot-path +/// logging call sites. +/// +public static class SanitizationExtensions +{ + /// + /// Sanitizes the string for safe logging by removing or encoding control characters. + /// Prevents log injection (CWE-117) from carriage returns, line feeds, tabs, null bytes, and + /// other control characters. + /// + /// The string to sanitize for logging. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// The sanitized string, safe for logging. Never null. + /// Fail-safe: never throws. If sanitization fails internally, the original string is returned. + public static string SanitizeForLog(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForLogInjection(input, settings).SanitizedValue; + + /// + /// Sanitizes the string for safe logging and returns detailed sanitization results (whether it + /// was modified, and how long the operation took). + /// + /// The string to sanitize for logging. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the sanitized string and operation details. + public static SanitizationResult SanitizeForLogWithDetails(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForLogInjection(input, settings); + + /// + /// Sanitizes the string for safe file-path usage, preventing directory traversal (CWE-22). + /// + /// The file-path string to sanitize. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// The sanitized file-path string. Never null. + /// Thrown when the input is rejected under strict validation. + /// + /// Sanitization failures never throw. In strict validation mode, however, a rejected path + /// throws deliberately โ€” silently returning an empty + /// string for a rejected path would be a worse failure mode for callers that build a file-system + /// path from the result. Use to handle rejections + /// programmatically instead. + /// + public static string SanitizeForFilePath(this string? input, SanitizationSettings? settings = null) + { + var result = SanitizationEngine.SanitizeForFilePath(input, settings); + + if (result.IsRejected) + { + throw new InvalidOperationException( + $"File path '{input}' was rejected by sanitization validation. " + + "Use SanitizeForFilePathWithDetails() to handle rejections programmatically."); + } + + return result.SanitizedValue; + } + + /// + /// Sanitizes the string for safe file-path usage and returns detailed sanitization results, + /// including whether the input was rejected under strict validation. + /// + /// The file-path string to sanitize. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the validated file path and operation details. + public static SanitizationResult SanitizeForFilePathWithDetails(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForFilePath(input, settings); + + /// + /// Sanitizes the string for sensitive data by masking detected tokens, secrets, credentials, + /// URLs, and file-system paths (CWE-200). + /// + /// The string to check for sensitive data. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// The string with sensitive data masked. Never null. + public static string SanitizeForSensitiveData(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForSensitiveData(input, settings).SanitizedValue; + + /// + /// Sanitizes the string for sensitive data and returns detailed sanitization results. + /// + /// The string to check for sensitive data. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the masked string and operation details. + public static SensitiveDataResult SanitizeForSensitiveDataWithDetails(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForSensitiveData(input, settings); + + /// + /// Validates the string as a regex pattern safe from ReDoS (CWE-400/CWE-730), returning the + /// pattern unchanged when safe. + /// + /// The regex pattern to validate. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// The pattern, unchanged, if it passed validation. Never null. + public static string SanitizeForRegexInjection(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForRegexInjection(input, settings).SanitizedValue; + + /// + /// Validates the string as a regex pattern and returns detailed sanitization results, including + /// whether the pattern was rejected as unsafe. + /// + /// The regex pattern to validate. Can be null. + /// Optional sanitization settings. If null, the configured or default settings are used. + /// A containing the validated pattern and operation details. + public static SanitizationResult SanitizeForRegexInjectionWithDetails(this string? input, SanitizationSettings? settings = null) + => SanitizationEngine.SanitizeForRegexInjection(input, settings); + + /// + /// Masks the source string with the given mask character, keeping roughly a quarter of the + /// string visible on each side (1/visibility total length as a fixed visibility window). + /// + /// The value to mask. + /// The character to use for masking. + /// The masked value. + public static string Mask(this string value, char mask) + { + const int visibility = 4; + + if (string.IsNullOrEmpty(value)) + { + return value; + } + + var visibleCharLength = value.Length / visibility; + return Mask(value, visibleCharLength, mask); + } + + /// + /// Masks the source string with the given mask character, keeping + /// characters visible at the start and end of the value. + /// + /// The value to mask. + /// The number of characters to leave visible at the start and end. + /// The character to use for masking. + /// The masked value. + public static string Mask(this string value, int visibleCharLength, char mask) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + if (value.Length <= visibleCharLength * 2) + { + return new string(mask, value.Length); + } + + var prefix = value.Substring(0, visibleCharLength); + var suffix = value.Substring(value.Length - visibleCharLength); + var maskedMiddle = new string(mask, value.Length - (visibleCharLength * 2)); + + return $"{prefix}{maskedMiddle}{suffix}"; + } +} From f43bec5d1c134a631e883fd8d325a7843949a8b9 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:30 -0600 Subject: [PATCH 32/63] feat(sanitization): add no-op sanitization service - add an inert sanitization service implementation used when the sanitization feature is disabled - return every input unchanged so dependents keep resolving the service safely - log once at startup that sanitization is inactive --- .../NoOp/NoOpSanitizationService.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization.Abstractions/NoOp/NoOpSanitizationService.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/NoOp/NoOpSanitizationService.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/NoOp/NoOpSanitizationService.cs new file mode 100644 index 0000000..4c1287d --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/NoOp/NoOpSanitizationService.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.Logging; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Abstractions.NoOp; + +/// +/// Inert used when the Sanitization feature is disabled. +/// Every method returns the input unchanged, so dependents resolve safely โ€” sanitization is +/// simply inactive rather than the container failing to resolve . +/// +public sealed class NoOpSanitizationService : ISanitizationService +{ + private static readonly SanitizationSettings _disabledSettings = new() + { + EnableLogSanitization = false, + EnableFilePathSanitization = false, + EnableSensitiveDataDetection = false, + EnableRegexSanitization = false + }; + + /// Creates the NoOp sanitization service and logs that sanitization is inert. + public NoOpSanitizationService(ILogger logger) + { + logger.LogInformation("Sanitization feature is disabled; using NoOp sanitization service (inputs pass through unchanged)."); + } + + /// + public SanitizationResult SanitizeForLogInjection(string? input) + => SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.LogInjection); + + /// + public SanitizationResult SanitizeForLogInjection(string? input, SanitizationSettings? settings) + => SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.LogInjection); + + /// + public SanitizationResult SanitizeForFilePath(string? input) + => SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.FilePath); + + /// + public SanitizationResult SanitizeForFilePath(string? input, SanitizationSettings? settings) + => SanitizationResult.Unchanged(input ?? string.Empty, SanitizationType.FilePath); + + /// + public SensitiveDataResult SanitizeForSensitiveData(string? input) + => SensitiveDataResult.Unchanged(input ?? string.Empty); + + /// + public SensitiveDataResult SanitizeForSensitiveData(string? input, SanitizationSettings? settings) + => SensitiveDataResult.Unchanged(input ?? string.Empty); + + /// + public SanitizationResult SanitizeForRegexInjection(string? pattern) + => SanitizationResult.Unchanged(pattern ?? string.Empty, SanitizationType.RegexInjection); + + /// + public SanitizationResult SanitizeForRegexInjection(string? pattern, SanitizationSettings? settings) + => SanitizationResult.Unchanged(pattern ?? string.Empty, SanitizationType.RegexInjection); + + /// + public SanitizationSettings GetCurrentSettings() => _disabledSettings; +} From 85a22f2c57a06aa9f6393dde058878f48d885fc9 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 17:44:34 -0600 Subject: [PATCH 33/63] chore(sanitization): register abstractions project in solution - add a sanitization feature solution folder - add the sanitization abstractions project under it with debug and release build configurations --- PowerCSharp.sln | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/PowerCSharp.sln b/PowerCSharp.sln index c7a1d94..6331543 100644 --- a/PowerCSharp.sln +++ b/PowerCSharp.sln @@ -51,6 +51,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Core.Tests", "t EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Utilities.Tests", "tests\PowerCSharp.Utilities.Tests\PowerCSharp.Utilities.Tests.csproj", "{79C80B63-F711-4EC4-91F5-52D892E60DC6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerCSharp.Feature.Sanitization", "PowerCSharp.Feature.Sanitization", "{8396894C-0CAD-4C19-ADF6-E9E65735A3BC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Sanitization.Abstractions", "src\Features\PowerCSharp.Feature.Sanitization.Abstractions\PowerCSharp.Feature.Sanitization.Abstractions.csproj", "{F17178B0-967E-4922-BA9C-34A771E84F52}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -140,6 +144,10 @@ Global {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.Build.0 = Debug|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.Build.0 = Release|Any CPU + {F17178B0-967E-4922-BA9C-34A771E84F52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F17178B0-967E-4922-BA9C-34A771E84F52}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F17178B0-967E-4922-BA9C-34A771E84F52}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F17178B0-967E-4922-BA9C-34A771E84F52}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {73835311-BC0A-4107-9E6D-4950DF565C46} = {493BDE4C-2EBA-49EE-BF44-10AAB73634D6} @@ -163,5 +171,7 @@ Global {137976EF-A677-499E-9567-A8B7E3B6F1BE} = {006F80FE-AC82-4752-972B-081BA6C6A651} {F5B563E8-E215-4350-9480-BBFCE47DCF34} = {8507D629-2649-468E-8345-212CFC547FA8} {79C80B63-F711-4EC4-91F5-52D892E60DC6} = {8507D629-2649-468E-8345-212CFC547FA8} + {8396894C-0CAD-4C19-ADF6-E9E65735A3BC} = {006F80FE-AC82-4752-972B-081BA6C6A651} + {F17178B0-967E-4922-BA9C-34A771E84F52} = {8396894C-0CAD-4C19-ADF6-E9E65735A3BC} EndGlobalSection EndGlobal From 948af56be571c6688fe06f78145231e1320c553a Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:13 -0600 Subject: [PATCH 34/63] feat(sanitization): scaffold Feature.Sanitization module project - Add net8.0 project targeting the Sanitization feature module - Reference the Features Abstractions and Sanitization Abstractions packages - Bind package version to the new Sanitization version family --- .../PowerCSharp.Feature.Sanitization.csproj | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/PowerCSharp.Feature.Sanitization.csproj diff --git a/src/Features/PowerCSharp.Feature.Sanitization/PowerCSharp.Feature.Sanitization.csproj b/src/Features/PowerCSharp.Feature.Sanitization/PowerCSharp.Feature.Sanitization.csproj new file mode 100644 index 0000000..3c380ea --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/PowerCSharp.Feature.Sanitization.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + true + + + PowerCSharp.Feature.Sanitization + $(PowerCSharpFeatureSanitizationVersion) + PowerCSharp Sanitization Feature + Sanitization feature module, options, and DI/Features-Framework wiring for log-injection, file-path traversal, sensitive-data, and regex-injection sanitization. Pair with PowerCSharp.Feature.Sanitization.Abstractions. + csharp;dotnet;features;feature-flags;security;sanitization;clean-architecture + + + + + + + + + + + + From 104e49ed72b172ea5bdebc165ceb0d98455c4d1b Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:19 -0600 Subject: [PATCH 35/63] feat(sanitization): add Sanitization feature options - Mirror the full configurable surface of the sanitization settings model - Bind from the Sanitization feature configuration section - Exclude correlation id as a global setting since it is per-call rather than static configuration --- .../SanitizationFeatureOptions.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureOptions.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureOptions.cs b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureOptions.cs new file mode 100644 index 0000000..86878bc --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureOptions.cs @@ -0,0 +1,117 @@ +using Microsoft.Extensions.Logging; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; +using PowerCSharp.Features.Abstractions; + +namespace PowerCSharp.Feature.Sanitization; + +/// +/// Options for the Sanitization feature, bound from PowerFeatures:Sanitization. Mirrors the +/// full configurable surface of (excluding +/// CorrelationId, which is per-call/per-request rather than a global setting) so every +/// sanitization concern โ€” log injection, file path, sensitive data, regex injection โ€” can be tuned +/// via configuration without code changes. +/// +public sealed class SanitizationFeatureOptions : FeatureOptionsBase +{ + // --- Log Injection Sanitization Settings --- + + /// Whether log sanitization is enabled. Default: true. + public bool EnableLogSanitization { get; set; } = true; + + /// Whether Unicode control characters should be sanitized. Default: true. + public bool SanitizeUnicodeControlChars { get; set; } = true; + + /// Whether tab characters are preserved rather than sanitized. Default: false. + public bool PreserveTabCharacters { get; set; } = false; + + /// The strategy used to handle control characters during log sanitization. Default: Remove. + public SanitizationStrategy LogSanitizationStrategy { get; set; } = SanitizationStrategy.Remove; + + /// Maximum length for sanitized strings before truncation. Default: 10000. + public int MaxSanitizedStringLength { get; set; } = 10000; + + // --- Failure / Security Event Logging --- + + /// Whether sanitization failures are logged. Default: false. + public bool LogSanitizationFailures { get; set; } = false; + + /// The log level used when logging sanitization failures. Default: Warning. + public LogLevel SanitizationFailureLogLevel { get; set; } = LogLevel.Warning; + + /// Whether file-path security events are logged. Default: false. + public bool LogSecurityEvents { get; set; } = false; + + // --- Performance Monitoring --- + + /// Whether performance monitoring is enabled. Default: false. + public bool EnablePerformanceMonitoring { get; set; } = false; + + /// The threshold, in milliseconds, above which an operation is considered slow. Default: 1.0. + public double SlowOperationThresholdMs { get; set; } = 1.0; + + // --- File Path Sanitization Settings --- + + /// Whether file-path sanitization is enabled. Default: true. + public bool EnableFilePathSanitization { get; set; } = true; + + /// Whether parent-directory traversal patterns are checked and removed. Default: true. + public bool EnableParentDirectoryTraversalCheck { get; set; } = true; + + /// Whether strict allowlist validation is used for file paths. Default: true. + public bool UseStrictValidation { get; set; } = true; + + /// Whether Windows reserved device names (CON, PRN, AUX, etc.) are rejected. Default: true. + public bool ValidateWindowsReservedNames { get; set; } = true; + + /// Whether Unicode characters are allowed in file paths. Default: true. + public bool AllowUnicodeCharacters { get; set; } = true; + + /// Maximum allowed length for an individual path segment. Default: 255. + public int MaxPathSegmentLength { get; set; } = 255; + + /// Base directories file paths must resolve within. Empty means no restriction. Default: empty. + public string[] AllowedBaseDirectories { get; set; } = Array.Empty(); + + /// File extensions a file path must end with (case-insensitive). Empty means no restriction. Default: empty. + public string[] AllowedFileExtensions { get; set; } = Array.Empty(); + + // --- Sensitive Data Sanitization Settings --- + + /// Whether sensitive-data detection and masking is enabled. Default: true. + public bool EnableSensitiveDataDetection { get; set; } = true; + + /// The character used to mask detected sensitive data. Default: '*'. + public char SensitiveDataMaskCharacter { get; set; } = '*'; + + /// The detection strictness level for sensitive data. Default: Medium. + public SensitiveDataDetectionStrictness SensitiveDataDetectionStrictness { get; set; } = SensitiveDataDetectionStrictness.Medium; + + // --- Regex Injection Sanitization Settings --- + + /// Whether regex-pattern safety validation is enabled. Default: true. + public bool EnableRegexSanitization { get; set; } = true; + + /// Maximum time allowed to validate a single regex pattern. Default: 5 seconds. + public TimeSpan MaxRegexValidationTimeout { get; set; } = TimeSpan.FromSeconds(5); + + /// Maximum allowed length for a regex pattern. Default: 1000. + public int MaxRegexPatternLength { get; set; } = 1000; + + /// Maximum allowed complexity score for a regex pattern. Default: 100. + public int MaxRegexComplexityScore { get; set; } = 100; + + /// Whether Unicode category classes (e.g. \p{L}) are allowed in patterns. Default: true. + public bool AllowUnicodeCategories { get; set; } = true; + + /// Whether quantifiers (*, +, ?, {n,m}) are allowed in patterns. Default: true. + public bool AllowQuantifiers { get; set; } = true; + + /// Whether nested quantifiers (a common catastrophic-backtracking source) are allowed. Default: false. + public bool AllowNestedQuantifiers { get; set; } = false; + + /// Whether backreferences are allowed in patterns. Default: true. + public bool AllowBackreferences { get; set; } = true; + + /// Whether lookaround assertions are allowed in patterns. Default: true. + public bool AllowLookarounds { get; set; } = true; +} From 8252133898d882b639bd8f4d55e5ad1e1648f8b2 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:24 -0600 Subject: [PATCH 36/63] feat(sanitization): add DI-facing sanitization service and settings provider - Bridge Sanitization feature options into the sanitization settings model - Implement the configuration-aware sanitization service by delegating to the engine - Centralize the options to settings mapping in the settings provider to avoid duplication - Avoid a logger dependency in the settings provider to prevent a recursive logging loop --- .../SanitizationService.cs | 56 +++++++++++++++ .../SanitizationSettingsProvider.cs | 71 +++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/SanitizationService.cs create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/SanitizationSettingsProvider.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization/SanitizationService.cs b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationService.cs new file mode 100644 index 0000000..515de1d --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationService.cs @@ -0,0 +1,56 @@ +using PowerCSharp.Feature.Sanitization.Abstractions; + +namespace PowerCSharp.Feature.Sanitization; + +/// +/// Configuration-aware implementation. Delegates every +/// operation to the static , sourcing settings from an injected +/// so the options-to-settings mapping lives in exactly +/// one place. +/// +public sealed class SanitizationService : ISanitizationService +{ + private readonly ISanitizationSettingsProvider _settingsProvider; + + /// Initializes a new instance of . + /// The settings provider used to source current settings. + public SanitizationService(ISanitizationSettingsProvider settingsProvider) + { + _settingsProvider = settingsProvider ?? throw new ArgumentNullException(nameof(settingsProvider)); + } + + /// + public SanitizationResult SanitizeForLogInjection(string? input) + => SanitizationEngine.SanitizeForLogInjection(input, GetCurrentSettings()); + + /// + public SanitizationResult SanitizeForLogInjection(string? input, SanitizationSettings? settings) + => SanitizationEngine.SanitizeForLogInjection(input, settings); + + /// + public SanitizationResult SanitizeForFilePath(string? input) + => SanitizationEngine.SanitizeForFilePath(input, GetCurrentSettings()); + + /// + public SanitizationResult SanitizeForFilePath(string? input, SanitizationSettings? settings) + => SanitizationEngine.SanitizeForFilePath(input, settings); + + /// + public SensitiveDataResult SanitizeForSensitiveData(string? input) + => SanitizationEngine.SanitizeForSensitiveData(input, GetCurrentSettings()); + + /// + public SensitiveDataResult SanitizeForSensitiveData(string? input, SanitizationSettings? settings) + => SanitizationEngine.SanitizeForSensitiveData(input, settings); + + /// + public SanitizationResult SanitizeForRegexInjection(string? pattern) + => SanitizationEngine.SanitizeForRegexInjection(pattern, GetCurrentSettings()); + + /// + public SanitizationResult SanitizeForRegexInjection(string? pattern, SanitizationSettings? settings) + => SanitizationEngine.SanitizeForRegexInjection(pattern, settings); + + /// + public SanitizationSettings GetCurrentSettings() => _settingsProvider.GetCurrentSettings(); +} diff --git a/src/Features/PowerCSharp.Feature.Sanitization/SanitizationSettingsProvider.cs b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationSettingsProvider.cs new file mode 100644 index 0000000..9ed408b --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationSettingsProvider.cs @@ -0,0 +1,71 @@ +using Microsoft.Extensions.Options; +using PowerCSharp.Feature.Sanitization.Abstractions; + +namespace PowerCSharp.Feature.Sanitization; + +/// +/// Bridges (bound from PowerFeatures:Sanitization) +/// into a instance for the static +/// , via . +/// +/// +/// Deliberately takes no ILogger dependency. can invoke the +/// settings provider from within its own security-event logging path; taking a logger dependency +/// here would risk a recursive logging loop. +/// +public sealed class SanitizationSettingsProvider : ISanitizationSettingsProvider +{ + private readonly IOptionsMonitor _options; + + /// Initializes a new instance of . + /// The monitored Sanitization feature options. + public SanitizationSettingsProvider(IOptionsMonitor options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + } + + /// + public SanitizationSettings GetCurrentSettings() + { + var options = _options.CurrentValue; + + return new SanitizationSettings + { + EnableLogSanitization = options.EnableLogSanitization, + SanitizeUnicodeControlChars = options.SanitizeUnicodeControlChars, + PreserveTabCharacters = options.PreserveTabCharacters, + LogSanitizationStrategy = options.LogSanitizationStrategy, + MaxSanitizedStringLength = options.MaxSanitizedStringLength, + + LogSanitizationFailures = options.LogSanitizationFailures, + SanitizationFailureLogLevel = options.SanitizationFailureLogLevel, + LogSecurityEvents = options.LogSecurityEvents, + + EnablePerformanceMonitoring = options.EnablePerformanceMonitoring, + SlowOperationThresholdMs = options.SlowOperationThresholdMs, + + EnableFilePathSanitization = options.EnableFilePathSanitization, + EnableParentDirectoryTraversalCheck = options.EnableParentDirectoryTraversalCheck, + UseStrictValidation = options.UseStrictValidation, + ValidateWindowsReservedNames = options.ValidateWindowsReservedNames, + AllowUnicodeCharacters = options.AllowUnicodeCharacters, + MaxPathSegmentLength = options.MaxPathSegmentLength, + AllowedBaseDirectories = options.AllowedBaseDirectories, + AllowedFileExtensions = options.AllowedFileExtensions, + + EnableSensitiveDataDetection = options.EnableSensitiveDataDetection, + SensitiveDataMaskCharacter = options.SensitiveDataMaskCharacter, + SensitiveDataDetectionStrictness = options.SensitiveDataDetectionStrictness, + + EnableRegexSanitization = options.EnableRegexSanitization, + MaxRegexValidationTimeout = options.MaxRegexValidationTimeout, + MaxRegexPatternLength = options.MaxRegexPatternLength, + MaxRegexComplexityScore = options.MaxRegexComplexityScore, + AllowUnicodeCategories = options.AllowUnicodeCategories, + AllowQuantifiers = options.AllowQuantifiers, + AllowNestedQuantifiers = options.AllowNestedQuantifiers, + AllowBackreferences = options.AllowBackreferences, + AllowLookarounds = options.AllowLookarounds + }; + } +} From ee7bcd8818f92e126294c78c8ee2e5b2a3cacaee Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:27 -0600 Subject: [PATCH 37/63] feat(sanitization): add static engine host wiring extension - Bridge the DI-resolved settings provider into the static sanitization engine - Allow extension-method call sites to reflect host configuration without resolving the service - Behave as a no-op when the feature is disabled or the provider is not registered --- ...tizationEngineServiceProviderExtensions.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/SanitizationEngineServiceProviderExtensions.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization/SanitizationEngineServiceProviderExtensions.cs b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationEngineServiceProviderExtensions.cs new file mode 100644 index 0000000..c8ed47f --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationEngineServiceProviderExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using PowerCSharp.Feature.Sanitization.Abstractions; + +namespace PowerCSharp.Feature.Sanitization; + +/// +/// Wires a built 's into +/// the static , so call sites that use the engine directly (e.g. +/// SanitizationExtensions.SanitizeForLog) reflect the host's configured settings without +/// needing to resolve from DI. +/// +public static class SanitizationEngineServiceProviderExtensions +{ + /// + /// Resolves from , + /// if registered, and calls with it. + /// A no-op when the Sanitization feature is disabled (no settings provider registered) or when + /// only the explicit AddSanitizationFeature NoOp registration path was used. + /// + /// The built application service provider. + /// The same , for chaining. + public static IServiceProvider ConfigureSanitizationEngine(this IServiceProvider serviceProvider) + { + var settingsProvider = serviceProvider.GetService(); + + if (settingsProvider is not null) + { + SanitizationEngine.SetConfigurationProvider(settingsProvider.GetCurrentSettings); + } + + return serviceProvider; + } +} From 70bcbe4a5b62740c741a957b6ae3ec227763eafd Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:31 -0600 Subject: [PATCH 38/63] feat(sanitization): add Sanitization feature module for auto-discovery - Register the real sanitization service when the feature is enabled and a NoOp service otherwise - Decide active versus NoOp directly rather than deferring to a provider package, since Sanitization has no swappable backend - Wire the static engine configuration during pipeline configuration once the service provider is built --- .../SanitizationFeatureModule.cs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureModule.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureModule.cs b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureModule.cs new file mode 100644 index 0000000..90961c0 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureModule.cs @@ -0,0 +1,61 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using PowerCSharp.Feature.Sanitization.Abstractions; +using PowerCSharp.Feature.Sanitization.Abstractions.NoOp; +using PowerCSharp.Features.Abstractions; +using System.Reflection; + +namespace PowerCSharp.Feature.Sanitization; + +/// +/// Sanitization feature module. Binds options and registers either the real +/// (feature enabled) or +/// (feature disabled). Unlike the Cache feature family, Sanitization has no swappable-backend +/// provider package, so this module registers the concrete implementation directly rather than +/// deferring to one. Supports auto-discovery and the explicit +/// . +/// +public sealed class SanitizationFeatureModule : IFeatureModule +{ + /// The feature key. + public const string Key = "Sanitization"; + + /// + public string FeatureKey => Key; + + /// + public int Order => 20; + + /// + public void ConfigureServices(IFeatureRegistrationContext context) + { + // Force-load the abstractions assembly so the CLR can resolve its types during discovery. + _ = Assembly.Load("PowerCSharp.Feature.Sanitization.Abstractions"); + + context.Services.Configure( + context.Configuration.GetSection($"PowerFeatures:{Key}")); + + if (!context.Flags.IsEnabled(FeatureKey)) + { + context.Logger.LogInformation("Sanitization feature disabled; registering NoOp sanitization service."); + context.Services.TryAddSingleton(); + return; + } + + context.Services.TryAddSingleton(); + context.Services.TryAddSingleton(); + } + + /// + /// + /// Contributes no middleware. Used solely to bridge the DI-resolved + /// into the static + /// once the application's has been built, so extension-method + /// call sites (e.g. input.SanitizeForLog()) reflect the host's configuration too. + /// + public void ConfigurePipeline(IFeaturePipelineContext context) + { + context.App.ApplicationServices.ConfigureSanitizationEngine(); + } +} From 4552d5a7ef92f44d58b96c130ff330556b5bcb74 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:35 -0600 Subject: [PATCH 39/63] feat(sanitization): add explicit Sanitization feature registration extension - Bind Sanitization feature options and register the real sanitization service directly - Register the concrete implementation instead of a NoOp since there is no provider package to defer to - Support hosts that opt in explicitly instead of relying on auto-discovery --- .../SanitizationFeatureExtensions.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureExtensions.cs diff --git a/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureExtensions.cs b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureExtensions.cs new file mode 100644 index 0000000..945a816 --- /dev/null +++ b/src/Features/PowerCSharp.Feature.Sanitization/SanitizationFeatureExtensions.cs @@ -0,0 +1,31 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using PowerCSharp.Feature.Sanitization.Abstractions; + +namespace PowerCSharp.Feature.Sanitization; + +/// Explicit (no-reflection) registration for the Sanitization feature contracts and options. +public static class SanitizationFeatureExtensions +{ + /// + /// Binds from PowerFeatures:Sanitization and + /// registers the real . Unlike + /// CacheFeatureExtensions.AddCacheFeature, this registers the concrete implementation + /// directly rather than a NoOp floor: Sanitization has no separate provider package to defer + /// to, so explicit opt-in here means the caller wants sanitization active. Call + /// on the + /// built to also wire the static + /// for non-DI call sites. + /// + public static IServiceCollection AddSanitizationFeature(this IServiceCollection services, IConfiguration configuration) + { + services.Configure( + configuration.GetSection($"PowerFeatures:{SanitizationFeatureModule.Key}")); + + services.TryAddSingleton(); + services.TryAddSingleton(); + + return services; + } +} From 8fcc051ba16a5bce554a5bb8ff847619ec03af3e Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:38 -0600 Subject: [PATCH 40/63] chore(sanitization): add Sanitization feature version family - Introduce an independent version property for the Sanitization feature family - Start the new family at version one dot zero dot zero --- Directory.Build.props | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 8314530..07c836a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,10 @@ 1.3.2 - + + + 1.0.0 + Mario Arce Copyright ยฉ Mario Arce 2026 From 49c7c8fb5cc8d19fc5f5fb5cc99533c7a51a14cf Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:44 -0600 Subject: [PATCH 41/63] chore(sanitization): wire Feature.Sanitization project into solution - Add the Sanitization feature module project to the main solution - Nest the project under the existing Sanitization solution folder - Register the project across all solution configuration platforms --- PowerCSharp.sln | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/PowerCSharp.sln b/PowerCSharp.sln index 6331543..b697653 100644 --- a/PowerCSharp.sln +++ b/PowerCSharp.sln @@ -55,6 +55,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerCSharp.Feature.Sanitiz EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Sanitization.Abstractions", "src\Features\PowerCSharp.Feature.Sanitization.Abstractions\PowerCSharp.Feature.Sanitization.Abstractions.csproj", "{F17178B0-967E-4922-BA9C-34A771E84F52}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Sanitization", "src\Features\PowerCSharp.Feature.Sanitization\PowerCSharp.Feature.Sanitization.csproj", "{2C6E4B9A-8B2C-4B1E-9C3F-6A7D8E9F0A1B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -148,6 +150,10 @@ Global {F17178B0-967E-4922-BA9C-34A771E84F52}.Debug|Any CPU.Build.0 = Debug|Any CPU {F17178B0-967E-4922-BA9C-34A771E84F52}.Release|Any CPU.ActiveCfg = Release|Any CPU {F17178B0-967E-4922-BA9C-34A771E84F52}.Release|Any CPU.Build.0 = Release|Any CPU + {2C6E4B9A-8B2C-4B1E-9C3F-6A7D8E9F0A1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2C6E4B9A-8B2C-4B1E-9C3F-6A7D8E9F0A1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2C6E4B9A-8B2C-4B1E-9C3F-6A7D8E9F0A1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2C6E4B9A-8B2C-4B1E-9C3F-6A7D8E9F0A1B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {73835311-BC0A-4107-9E6D-4950DF565C46} = {493BDE4C-2EBA-49EE-BF44-10AAB73634D6} @@ -173,5 +179,6 @@ Global {79C80B63-F711-4EC4-91F5-52D892E60DC6} = {8507D629-2649-468E-8345-212CFC547FA8} {8396894C-0CAD-4C19-ADF6-E9E65735A3BC} = {006F80FE-AC82-4752-972B-081BA6C6A651} {F17178B0-967E-4922-BA9C-34A771E84F52} = {8396894C-0CAD-4C19-ADF6-E9E65735A3BC} + {2C6E4B9A-8B2C-4B1E-9C3F-6A7D8E9F0A1B} = {8396894C-0CAD-4C19-ADF6-E9E65735A3BC} EndGlobalSection EndGlobal From 54529978758b596fe6d459485a8717ef9e234d8d Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:25:47 -0600 Subject: [PATCH 42/63] ci(sanitization): add sanitization package family to release workflow - Add sanitization as a selectable package family for the version bump workflow dispatch - Map the sanitization family to its version element and release tag prefix --- .github/workflows/ci-cd.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 658c963..dd15467 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -23,9 +23,10 @@ on: default: 'core' type: choice options: - - core # PowerCSharpVersion โ€” Core, Extensions, Helpers, Utilities, Compatibility, AspNetCore - - features # PowerCSharpFeaturesVersion โ€” Features.Abstractions, Features, BuiltInFeatures - - cache # PowerCSharpFeatureCacheVersion โ€” Feature.Cache.*, Feature.Cache.BitFaster, Feature.Cache.Disk + - core # PowerCSharpVersion โ€” Core, Extensions, Helpers, Utilities, Compatibility, AspNetCore + - features # PowerCSharpFeaturesVersion โ€” Features.Abstractions, Features, BuiltInFeatures + - cache # PowerCSharpFeatureCacheVersion โ€” Feature.Cache.*, Feature.Cache.BitFaster, Feature.Cache.Disk + - sanitization # PowerCSharpFeatureSanitizationVersion โ€” Feature.Sanitization.*, Feature.Sanitization permissions: contents: write @@ -260,6 +261,10 @@ jobs: VERSION_ELEMENT="PowerCSharpFeatureCacheVersion" TAG_PREFIX="cache-v" ;; + "sanitization") + VERSION_ELEMENT="PowerCSharpFeatureSanitizationVersion" + TAG_PREFIX="sanitization-v" + ;; *) VERSION_ELEMENT="PowerCSharpVersion" TAG_PREFIX="v" From 12a7c67f754b8bfc0261e62964e8152278cfab51 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:45:41 -0600 Subject: [PATCH 43/63] fix(sanitization): mask sensitive key-value pairs correctly - Fix sensitive key masking to preserve the key name and mask only the value - Replace a reference to a non-existent capture group that always produced an empty mask and silently left the raw secret in the output - Align the fix with the pattern already used by the other key based heuristics in the same method --- .../SanitizationEngine.SensitiveData.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs index cde7ff8..f44c4c1 100644 --- a/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs +++ b/src/Features/PowerCSharp.Feature.Sanitization.Abstractions/SanitizationEngine.SensitiveData.cs @@ -278,8 +278,12 @@ private static string MaskKeyBasedValues(string input, char maskChar) result = SensitiveKeyRegex().Replace(result, m => { - var key = m.Groups[1].Value; - var maskedValue = MaskValueIntelligently(m.Groups[2].Value, maskChar); + // SensitiveKeyRegex has exactly one capturing group (the value); the key/separator + // prefix is matched but intentionally not captured. Reconstruct it the same way the + // four handlers below do, rather than indexing a non-existent Groups[2] (which is + // always an empty, unsuccessful group and previously left the raw value unmasked). + var key = m.Value.Substring(0, m.Value.IndexOf(m.Groups[1].Value, StringComparison.Ordinal)); + var maskedValue = MaskValueIntelligently(m.Groups[1].Value, maskChar); return $"{key}{maskedValue}"; }); From 0627cc99e93f5f90b083139e083674bd5f01418a Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:45:46 -0600 Subject: [PATCH 44/63] test(sanitization): scaffold Sanitization feature test project - Add an xunit test project referencing the Sanitization module and abstractions packages - Disable test parallelization since the sanitization engine holds process wide static state that a subset of tests configure directly --- .../AssemblyInfo.cs | 7 +++++ ...erCSharp.Feature.Sanitization.Tests.csproj | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/PowerCSharp.Feature.Sanitization.Tests/AssemblyInfo.cs create mode 100644 tests/PowerCSharp.Feature.Sanitization.Tests/PowerCSharp.Feature.Sanitization.Tests.csproj diff --git a/tests/PowerCSharp.Feature.Sanitization.Tests/AssemblyInfo.cs b/tests/PowerCSharp.Feature.Sanitization.Tests/AssemblyInfo.cs new file mode 100644 index 0000000..7db63e8 --- /dev/null +++ b/tests/PowerCSharp.Feature.Sanitization.Tests/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// The static SanitizationEngine holds process-wide mutable state (the configuration provider set +// via SetConfigurationProvider, the security-event logger, and performance counters). Tests that +// exercise DI/module wiring mutate that shared state; running them concurrently with other tests +// in this assembly could make an unrelated test observe settings it never configured. Disabling +// parallelization keeps the suite deterministic โ€” the tradeoff is acceptable given this project's +// size. +[assembly: CollectionBehavior(DisablesTestParallelization = true)] diff --git a/tests/PowerCSharp.Feature.Sanitization.Tests/PowerCSharp.Feature.Sanitization.Tests.csproj b/tests/PowerCSharp.Feature.Sanitization.Tests/PowerCSharp.Feature.Sanitization.Tests.csproj new file mode 100644 index 0000000..47c71b9 --- /dev/null +++ b/tests/PowerCSharp.Feature.Sanitization.Tests/PowerCSharp.Feature.Sanitization.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + From 917265ad8b7f65a1e1fbae53a589ade9a38d35a7 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Wed, 22 Jul 2026 18:45:49 -0600 Subject: [PATCH 45/63] test(sanitization): add log injection sanitization tests - Cover null and empty input passthrough - Cover the disabled setting bypass - Cover each control character strategy including tab preservation - Cover the max sanitized string length truncation --- .../LogInjectionSanitizationTests.cs | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 tests/PowerCSharp.Feature.Sanitization.Tests/LogInjectionSanitizationTests.cs diff --git a/tests/PowerCSharp.Feature.Sanitization.Tests/LogInjectionSanitizationTests.cs b/tests/PowerCSharp.Feature.Sanitization.Tests/LogInjectionSanitizationTests.cs new file mode 100644 index 0000000..ebb6cd0 --- /dev/null +++ b/tests/PowerCSharp.Feature.Sanitization.Tests/LogInjectionSanitizationTests.cs @@ -0,0 +1,118 @@ +using PowerCSharp.Feature.Sanitization.Abstractions; +using PowerCSharp.Feature.Sanitization.Abstractions.Enums; + +namespace PowerCSharp.Feature.Sanitization.Tests; + +/// +/// Covers (CWE-117). Every call passes an +/// explicit instance rather than relying on the ambient +/// configuration provider, so these tests are independent of any DI/module wiring under test +/// elsewhere in this assembly. +/// +public class LogInjectionSanitizationTests +{ + [Fact] + public void Null_Input_Returns_Unchanged_Empty() + { + var result = SanitizationEngine.SanitizeForLogInjection(null, new SanitizationSettings()); + + Assert.False(result.WasModified); + Assert.Equal(string.Empty, result.SanitizedValue); + Assert.Equal(SanitizationType.LogInjection, result.SanitizationType); + } + + [Fact] + public void Empty_Input_Returns_Unchanged_Empty() + { + var result = SanitizationEngine.SanitizeForLogInjection(string.Empty, new SanitizationSettings()); + + Assert.False(result.WasModified); + Assert.Equal(string.Empty, result.SanitizedValue); + } + + [Fact] + public void Input_Without_Control_Characters_Is_Unchanged() + { + var result = SanitizationEngine.SanitizeForLogInjection("a perfectly normal log line", new SanitizationSettings()); + + Assert.False(result.WasModified); + Assert.Equal("a perfectly normal log line", result.SanitizedValue); + } + + [Fact] + public void Disabled_Setting_Passes_Control_Characters_Through() + { + var settings = new SanitizationSettings { EnableLogSanitization = false }; + + var result = SanitizationEngine.SanitizeForLogInjection("line1\r\nline2", settings); + + Assert.False(result.WasModified); + Assert.Equal("line1\r\nline2", result.SanitizedValue); + } + + [Fact] + public void Remove_Strategy_Strips_CrLf_And_Html_Encodes_Remainder() + { + var settings = new SanitizationSettings { LogSanitizationStrategy = SanitizationStrategy.Remove }; + + var result = SanitizationEngine.SanitizeForLogInjection("value\r\nwith", settings); + + Assert.True(result.WasModified); + Assert.DoesNotContain('\r', result.SanitizedValue); + Assert.DoesNotContain('\n', result.SanitizedValue); + Assert.Equal("valuewith<tag>", result.SanitizedValue); + } + + [Fact] + public void ReplaceWithSpace_Strategy_Replaces_Control_Characters_With_Spaces() + { + var settings = new SanitizationSettings { LogSanitizationStrategy = SanitizationStrategy.ReplaceWithSpace }; + + var result = SanitizationEngine.SanitizeForLogInjection("a\r\nb", settings); + + Assert.True(result.WasModified); + Assert.Equal("a b", result.SanitizedValue); + } + + [Fact] + public void PreserveTabCharacters_Keeps_Tabs_But_Still_Removes_Other_Control_Characters() + { + var settings = new SanitizationSettings + { + LogSanitizationStrategy = SanitizationStrategy.Remove, + PreserveTabCharacters = true + }; + + var result = SanitizationEngine.SanitizeForLogInjection("a\tb\r\nc", settings); + + Assert.Contains('\t', result.SanitizedValue); + Assert.DoesNotContain('\r', result.SanitizedValue); + Assert.DoesNotContain('\n', result.SanitizedValue); + Assert.Equal("a\tbc", result.SanitizedValue); + } + + [Theory] + [InlineData(SanitizationStrategy.HtmlEncode)] + [InlineData(SanitizationStrategy.UrlEncode)] + [InlineData(SanitizationStrategy.JsonEncode)] + public void Encoding_Strategies_Remove_Raw_Control_Characters_Without_Throwing(SanitizationStrategy strategy) + { + var settings = new SanitizationSettings { LogSanitizationStrategy = strategy }; + + var result = SanitizationEngine.SanitizeForLogInjection("inject\r\nme