CodeMatrix.AspNetCore.Utilities.Database 1.0.23

dotnet add package CodeMatrix.AspNetCore.Utilities.Database --version 1.0.23
                    
NuGet\Install-Package CodeMatrix.AspNetCore.Utilities.Database -Version 1.0.23
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Database" Version="1.0.23" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CodeMatrix.AspNetCore.Utilities.Database" Version="1.0.23" />
                    
Directory.Packages.props
<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Database" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CodeMatrix.AspNetCore.Utilities.Database --version 1.0.23
                    
#r "nuget: CodeMatrix.AspNetCore.Utilities.Database, 1.0.23"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CodeMatrix.AspNetCore.Utilities.Database@1.0.23
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CodeMatrix.AspNetCore.Utilities.Database&version=1.0.23
                    
Install as a Cake Addin
#tool nuget:?package=CodeMatrix.AspNetCore.Utilities.Database&version=1.0.23
                    
Install as a Cake Tool

CodeMatrix.AspNetCore.Utilities

Description

Utilities for Dot Net Core Applications

CodeMatrix.AspNetCore.Utilities is a comprehensive library that provides a collection of tools, extensions, and helpers designed to simplify common tasks in ASP.NET Core applications. This package includes utilities for JSON serialization/deserialization, date and time formatting, database operations, HTTP client interactions, model state validation, and data conversion.

Key features include:

  • Custom JSON converters for handling special data types
  • Extensive set of extension methods for common .NET types
  • Database operation helpers with automatic mapping capabilities
  • Performance-optimized Dapper integration with analytics and caching
  • Model state validation filters for API controllers
  • Configurable HTTP client with logging support
  • API result wrapper for consistent response formatting
  • Attribute-based database parameter mapping

Whether you're building a web API, a data-intensive application, or just need to streamline common operations, this library aims to reduce boilerplate code and enhance productivity.

Installation

Package Variants

To reduce dependency conflicts and allow consumers to choose only the features they need, this package is available in multiple variants:

🔹 Core Variant (CodeMatrix.AspNetCore.Utilities)

Minimal dependencies - Only essential utilities

  • Dependencies: Microsoft.Extensions.Caching.Abstractions, Microsoft.Extensions.Http, System.Text.Json
  • Features: Basic extensions, converters, filters, model validation
  • Best for: Projects that want minimal dependency footprint
<PackageReference Include="CodeMatrix.AspNetCore.Utilities" Version="1.0.0" />
🔹 Database Variant (CodeMatrix.AspNetCore.Utilities.Database)

Core + Database features

  • Additional Dependencies: Dapper, Microsoft.Data.SqlClient
  • Features: Core features + DapperHelper, database utilities
  • Best for: Projects using SQL Server and Dapper ORM
<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Database" Version="1.0.0" />
🔹 Http Variant (CodeMatrix.AspNetCore.Utilities.Http)

Core + HTTP Client features

  • Additional Dependencies: Microsoft.Extensions.Http.Polly, Polly
  • Features: Core features + Enhanced HTTP client with retry policies
  • Best for: Projects making HTTP calls that need resilience
<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Http" Version="1.0.0" />
🔹 Azure Variant (CodeMatrix.AspNetCore.Utilities.Azure)

Core + Azure integration

  • Additional Dependencies: Azure.Core, Azure.Identity, Microsoft.Identity.Client
  • Features: Core features + Azure authentication and integration
  • Best for: Projects deploying to Azure or using Azure services
<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Azure" Version="1.0.0" />
🔹 Full Variant (CodeMatrix.AspNetCore.Utilities.Full)

All features included

  • Dependencies: All dependencies from above variants
  • Features: Complete feature set
  • Best for: Projects that need all features and don't mind the dependency footprint
<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Full" Version="1.0.0" />

Consumer Project Examples

Example 1: Web API with Minimal Dependencies (Core Variant)
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
	<TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
	
	<PackageReference Include="CodeMatrix.AspNetCore.Utilities" Version="1.0.0" />
  </ItemGroup>
</Project>

Available features: Extensions, Converters, Filters, Basic HTTP utilities

Example 2: Data-Heavy Application (Database Variant)
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
	<TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
	
	<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Database" Version="1.0.0" />
  </ItemGroup>
</Project>

Available features: Core features + DapperHelper, DBHelpers, Database utilities

Example 3: Microservice with HTTP Calls (Http Variant)
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
	<TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
	
	<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Http" Version="1.0.0" />
  </ItemGroup>
</Project>

Available features: Core features + Enhanced HTTP client with retry policies

Example 4: Azure-Hosted Application (Azure Variant)
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
	<TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
	
	<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Azure" Version="1.0.0" />
  </ItemGroup>
</Project>

Available features: Core features + Azure.Identity, Azure.Core integration

Example 5: Complex Application (Full Variant)
<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
	<TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  
  <ItemGroup>
	
	<PackageReference Include="CodeMatrix.AspNetCore.Utilities.Full" Version="1.0.0" />
  </ItemGroup>
</Project>

Available features: All features included

Dependency Comparison

Variant Package Size Key Dependencies Use Case
Core ~67 KB System.Text.Json, Extensions.Http Minimal web APIs
Database ~85 KB Core + Dapper, SqlClient Data applications
Http ~90 KB Core + Polly Microservices
Azure ~120 KB Core + Azure.* packages Azure applications
Full ~150 KB All dependencies Complex applications

Choose the smallest variant that meets your needs to minimize dependency conflicts.

Migration Guide

If you're currently using the main package and experiencing dependency conflicts:

  1. Identify which features you actually use in your project
  2. Choose the appropriate variant based on your needs
  3. Replace the package reference with the specific variant
  4. Test your application to ensure all required features are available

Getting Started

Configuring JSON Options

Add the default JSON options to your service collection in Program.cs or Startup.cs:

using CodeMatrix.AspNetCore.Utilities.DependencyInjection;

// Add default JSON serialization options
services.AddDefaultJsonOptions();

// Or with custom configuration
services.AddCustomJsonOptions(options => 
{
	options.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
	options.PropertyNameCaseInsensitive = true;
});

Setting Up HTTP Client Helper

Configure HTTP client helper for making API requests with structured HttpResult<T> responses:

using CodeMatrix.AspNetCore.Utilities.DependencyInjection;
using CodeMatrix.AspNetCore.Utilities.Interfaces;
using CodeMatrix.AspNetCore.Utilities.Models;

// Add HTTP client helper with default options
// Default: 5-minute timeout, compression enabled, SSL verification enabled
services.AddHttpClientHelper();

// Or with custom configuration
services.AddHttpClientHelper(options => 
{
	// Timeout configuration
	options.DefaultTimeoutMinutes = 3;
	
	// Compression - automatically decompress GZIP, Deflate, and Brotli responses
	options.EnableCompression = true;  // Default: true (recommended)
	
	// SSL certificate verification - set to false only for development/testing
	options.VerifySsl = true;          // Default: true (ALWAYS true in production)
	
	// Retry configuration (requires Polly integration)
	options.MaxRetryAttempts = 3;      // Retry on transient failures (5xx errors)
	options.RetryDelaySeconds = 1;     // Initial delay with exponential backoff
});

// Inject and use in your services
public class MyService 
{
	private readonly IHttpClientHelper _httpClient;
	
	public MyService(IHttpClientHelper httpClient)
	{
		_httpClient = httpClient;
	}
	
	public async Task<ApiResult<MyData>> GetDataAsync()
	{
		// All methods now return HttpResult<T> with status codes and structured responses
		HttpResult<string> result = await _httpClient.InvokeGetAsync("https://api.example.com/data");
		
		if (result.IsSuccessful)
		{
			return new ApiResult<MyData>
			{
				IsSuccessful = true,
				Data = JsonSerializer.Deserialize<MyData>(result.Data),
				Message = $"Data retrieved successfully (HTTP {result.StatusCode})"
			};
		}
		else
		{
			return new ApiResult<MyData>
			{
				IsSuccessful = false,
				Message = $"Failed to retrieve data: {result.Message} (HTTP {result.StatusCode})"
			};
		}
	}
	
	public async Task<ApiResult<string>> PostDataAsync(MyData data)
	{
		string jsonBody = JsonSerializer.Serialize(data);
		var headers = new Dictionary<string, string>
		{
			{ "X-API-Key", "your-api-key" }
		};
		
		// HttpResult includes status code and error handling without exceptions
		HttpResult<string> result = await _httpClient.InvokePostAsync(
			"https://api.example.com/data", 
			jsonBody, 
			headers, 
			timeoutInMinutes: 5
		);
		
		return new ApiResult<string>
		{
			IsSuccessful = result.IsSuccessful,
			Data = result.Data,
			Message = result.Message
		};
	}
}

Database Abstraction Layer

Version 1.0.24 introduces a powerful database abstraction layer that decouples your application code from specific ORM implementations. Choose between Dapper, Entity Framework Core, or ADO.NET without changing your business logic.

Why Use the Abstraction Layer?
  • 🔄 ORM Agnostic: Switch between Dapper, EF Core, or ADO.NET without code changes
  • 🧩 Dependency Injection Ready: Seamless integration with ASP.NET Core DI
  • 🎯 Clean Architecture: Separate data access concerns from business logic
  • ✅ Testable: Mock implementations for unit testing
  • 📦 No Lock-In: Start with one ORM, migrate to another later if needed
Core Abstractions

The abstraction layer consists of four main interfaces:

// 1. Execute SQL commands and queries
public interface IDbCommandExecutor
{
    Task<IEnumerable<T>> QueryAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
    Task<T?> QuerySingleOrDefaultAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
    Task<int> ExecuteAsync(string sql, object? param = null, CancellationToken cancellationToken = default);
    Task<T?> ExecuteScalarAsync<T>(string sql, object? param = null, CancellationToken cancellationToken = default);
}

// 2. Execute stored procedures
public interface IStoredProcedureExecutor
{
    Task<IEnumerable<T>> ExecuteProcedureAsync<T>(string procedureName, object? param = null, CancellationToken cancellationToken = default);
    Task<T?> ExecuteProcedureSingleOrDefaultAsync<T>(string procedureName, object? param = null, CancellationToken cancellationToken = default);
    Task<T?> ExecuteScalarAsync<T>(string procedureName, object? param = null, CancellationToken cancellationToken = default);
    Task ExecuteNonQueryAsync(string procedureName, object? param = null, CancellationToken cancellationToken = default);
}

// 3. Execute async IQueryable operations (EF Core only)
public interface IAsyncQueryExecutor
{
    Task<int> CountAsync<T>(IQueryable<T> query, CancellationToken cancellationToken = default);
    Task<List<T>> ToListAsync<T>(IQueryable<T> query, CancellationToken cancellationToken = default);
    Task<T?> FirstOrDefaultAsync<T>(IQueryable<T> query, CancellationToken cancellationToken = default);
    Task<bool> AnyAsync<T>(IQueryable<T> query, CancellationToken cancellationToken = default);
}

// 4. Create database connections
public interface IDbConnectionFactory
{
    IDbConnection CreateConnection();
    string ConnectionString { get; }
}
Setup: Choose Your Provider
Option 1: Dapper (Lightweight, Fast)
using CodeMatrix.AspNetCore.Utilities.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register Dapper as the database provider
builder.Services.AddDapperDatabase(
    builder.Configuration.GetConnectionString("DefaultConnection"));

var app = builder.Build();
Option 2: Entity Framework Core (Full ORM)
using CodeMatrix.AspNetCore.Utilities.DependencyInjection;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// Register your DbContext as usual
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

// Register EF Core query executor
builder.Services.AddEntityFrameworkQueryExecutor();

var app = builder.Build();
Option 3: ADO.NET (Maximum Control)
using CodeMatrix.AspNetCore.Utilities.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

// Register ADO.NET as the database provider
builder.Services.AddAdoNetDatabase(
    builder.Configuration.GetConnectionString("DefaultConnection"));

var app = builder.Build();
Using the Abstraction in Your Code

Once configured, inject and use the abstractions in your services:

Example 1: Basic Queries
using CodeMatrix.AspNetCore.Utilities.Abstractions;

public class ProductRepository
{
    private readonly IDbCommandExecutor _db;

    public ProductRepository(IDbCommandExecutor db)
    {
        _db = db;
    }

    // Works with Dapper, ADO.NET, or any provider implementing IDbCommandExecutor
    public async Task<IEnumerable<Product>> GetAllProductsAsync()
    {
        return await _db.QueryAsync<Product>("SELECT * FROM Products");
    }

    public async Task<Product?> GetProductByIdAsync(int id)
    {
        return await _db.QuerySingleOrDefaultAsync<Product>(
            "SELECT * FROM Products WHERE Id = @Id",
            new { Id = id });
    }

    public async Task<int> CreateProductAsync(Product product)
    {
        return await _db.ExecuteAsync(
            "INSERT INTO Products (Name, Price) VALUES (@Name, @Price)",
            product);
    }
}
Example 2: Stored Procedures
public class OrderService
{
    private readonly IStoredProcedureExecutor _sp;

    public OrderService(IStoredProcedureExecutor sp)
    {
        _sp = sp;
    }

    // Execute stored procedure - works with any provider
    public async Task<IEnumerable<Order>> GetOrdersByCustomerAsync(int customerId)
    {
        return await _sp.ExecuteProcedureAsync<Order>(
            "GetOrdersByCustomer",
            new { CustomerId = customerId });
    }

    public async Task<int> GetTotalOrdersAsync(int customerId)
    {
        return await _sp.ExecuteScalarAsync<int>(
            "GetTotalOrderCount",
            new { CustomerId = customerId }) ?? 0;
    }

    public async Task ProcessOrderAsync(int orderId)
    {
        await _sp.ExecuteNonQueryAsync(
            "ProcessOrder",
            new { OrderId = orderId });
    }
}
Example 3: Pagination with EF Core
using CodeMatrix.AspNetCore.Utilities.Abstractions;
using CodeMatrix.AspNetCore.Utilities.Extensions;
using CodeMatrix.AspNetCore.Utilities.Models;
using Microsoft.EntityFrameworkCore;

public class UserRepository
{
    private readonly MyDbContext _context;
    private readonly IAsyncQueryExecutor _queryExecutor;

    public UserRepository(MyDbContext context, IAsyncQueryExecutor queryExecutor)
    {
        _context = context;
        _queryExecutor = queryExecutor;
    }

    // Pagination abstraction - no direct EF Core dependency
    public async Task<PagedResult<User>> GetUsersAsync(int page, int pageSize)
    {
        var query = _context.Users
            .Where(u => u.IsActive)
            .OrderBy(u => u.Name);

        return await query.ToPagedResultAsync(_queryExecutor, page, pageSize);
    }
}
Advanced: Custom Connection Factory

Create a custom connection factory for other databases (MySQL, PostgreSQL, etc.):

using CodeMatrix.AspNetCore.Utilities.Abstractions;
using System.Data;
using Npgsql; // PostgreSQL example

public class PostgreSqlConnectionFactory : IDbConnectionFactory
{
    public string ConnectionString { get; }

    public PostgreSqlConnectionFactory(string connectionString)
    {
        ConnectionString = connectionString;
    }

    public IDbConnection CreateConnection()
    {
        return new NpgsqlConnection(ConnectionString);
    }
}

// Register in DI
services.AddDatabaseConnectionFactory(
    new PostgreSqlConnectionFactory(connectionString));
Migration Guide

If you're currently using Dapper or EF Core directly, here's how to migrate:

Before (Direct Dapper Usage)
using Dapper;
using Microsoft.Data.SqlClient;

public class ProductRepository
{
    private readonly string _connectionString;

    public ProductRepository(IConfiguration configuration)
    {
        _connectionString = configuration.GetConnectionString("DefaultConnection");
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        using var connection = new SqlConnection(_connectionString);
        return await connection.QueryAsync<Product>("SELECT * FROM Products");
    }
}
After (Using Abstraction)
using CodeMatrix.AspNetCore.Utilities.Abstractions;

public class ProductRepository
{
    private readonly IDbCommandExecutor _db;

    public ProductRepository(IDbCommandExecutor db)
    {
        _db = db;
    }

    public async Task<IEnumerable<Product>> GetAllAsync()
    {
        return await _db.QueryAsync<Product>("SELECT * FROM Products");
    }
}

Benefits:

  • ✅ No connection management
  • ✅ Easily switch to ADO.NET or EF Core later
  • ✅ Mock IDbCommandExecutor for unit tests
  • ✅ Single responsibility - repository doesn't care about infrastructure
Testing with Mocks

The abstraction layer makes unit testing trivial:

using Moq;
using Xunit;

public class ProductRepositoryTests
{
    [Fact]
    public async Task GetAllProducts_ReturnsProducts()
    {
        // Arrange
        var mockDb = new Mock<IDbCommandExecutor>();
        mockDb.Setup(x => x.QueryAsync<Product>(It.IsAny<string>(), null, default))
              .ReturnsAsync(new[] { new Product { Id = 1, Name = "Test" } });

        var repository = new ProductRepository(mockDb.Object);

        // Act
        var products = await repository.GetAllProductsAsync();

        // Assert
        Assert.Single(products);
        Assert.Equal("Test", products.First().Name);
    }
}

Target Framework and Dependencies

  • Target Frameworks: .NET 8.0, .NET 9.0, .NET 10.0
  • Package Version Management: Central Package Management via Directory.Packages.props
  • Key Dependencies:
    • Dapper 2.1.79
    • Microsoft.Data.SqlClient 7.0.2
    • Microsoft.EntityFrameworkCore 8.0.11 / 9.0.1 / 10.0.9 (by target framework)
    • Microsoft.Extensions.Http.Polly 10.0.9
    • Polly 8.7.0

For the complete list of dependencies, check the .csproj file.

Dependency Update Policy

To reduce package churn and noisy transitive outdated warnings for consumers, this package follows a patch-cadence dependency policy:

  • Cadence: Weekly dependency review with grouped updates.
  • Update Scope: Patch updates are prioritized and automated; minor and major updates are reviewed manually.
  • Central Management: Direct dependency versions are maintained in Directory.Packages.props.
  • Consumer Override Friendly: Consumers can still override transitive versions in their application when needed.
  • Security Visibility: Automated outdated and vulnerability reports run via CI workflow.

What This Means for Consumers

  • You should get stable behavior with timely security and patch updates.
  • You may still see newer transitive versions in your app over time, but these are typically patch-level and safe to adopt independently.
  • If your organization pins dependency versions globally, you can override package transitives without waiting for a package release.

Table of Contents

Attributes

DbParamAttribute

Description: Represents an attribute that specifies the name of a database parameter.

Namespace: CodeMatrix.AspNetCore.Utilities.Attributes

Usage:

The DbParamAttribute can be applied to properties or fields to specify the name of the corresponding database parameter. This can be useful in scenarios where you need to map class properties to database parameters for data access operations.

Signature:

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] 

public class DbParamAttribute : Attribute  
{  
	public string Name { get; set; }
	public DbParamAttribute(string name);
}

Example:

using CodeMatrix.AspNetCore.Utilities.Attributes;

public class User { 

	[DbParam("user_id")] 
	public int Id { get; set; }

	[DbParam("user_name")]
	public string Name { get; set; }

	[DbParam("user_email")]
	public string Email { get; set; }
}

In this example, the DbParamAttribute is used to specify the database parameter names for the Id, Name, and Email properties of the User class. When performing database operations, these attributes can be used to map the class properties to the corresponding database parameters.

Converters

BooleanJsonConverter

Description: Converts a nullable boolean value to and from JSON.

Namespace: CodeMatrix.AspNetCore.Utilities.Converters

Usage:

The BooleanJsonConverter can be used to handle nullable boolean values when serializing and deserializing JSON. This converter supports various representations of boolean values, such as "true", "yes", "y", "1" for true and "false", "no", "n", "0" for false.

Signature:

public class BooleanJsonConverter : JsonConverter<bool?> { 
	public override bool? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); 
	public override void Write(Utf8JsonWriter writer, bool? value, JsonSerializerOptions options); 
}

Example:

using System.Text.Json; 
using System.Text.Json.Serialization; 
using CodeMatrix.AspNetCore.Utilities.Converters;

public class Example { 
	[JsonConverter(typeof(BooleanJsonConverter))] 
	public bool? IsActive { get; set; } 
}

public class Program { 
	public static void Main() { 
		var json = "{\"IsActive\":\"yes\"}"; 
		var options = new JsonSerializerOptions { Converters = { new BooleanJsonConverter() } };

		var example = JsonSerializer.Deserialize<Example>(json, options);
		Console.WriteLine(example?.IsActive); // Output: True

		var serializedJson = JsonSerializer.Serialize(example, options);
		Console.WriteLine(serializedJson); // Output: {"IsActive":true}
	}
}

In this example, the BooleanJsonConverter is used to handle the IsActive property of the Example class. The converter allows the property to be deserialized from various string representations of boolean values and serialized back to JSON.

DateTimeJsonConverter

Description: Converts a nullable DateTime to and from JSON using configurable date formats.

Namespace: CodeMatrix.AspNetCore.Utilities.Converters

Usage:

The DateTimeJsonConverter can be used to handle nullable DateTime values when serializing and deserializing JSON. This converter supports various input date formats and a configurable output date format. It has enhanced handling for fractional seconds with support for different precision levels (1-7 digits) and automatically normalizes them to millisecond precision.

Signature:

public class DateTimeJsonConverter : JsonConverter<DateTime?> { 
	public DateTimeJsonConverter(); 
	public DateTimeJsonConverter(string outputFormat, bool useUtc = false); 
	public DateTimeJsonConverter(string[] inputFormats, string outputFormat, bool useUtc = false); 
	public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); 
	public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options); 
}

Examples:

Below are comprehensive examples demonstrating the various ways to use DateTimeJsonConverter:

Example 1: Default Usage (Property Attribute)
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using CodeMatrix.AspNetCore.Utilities.Converters;

public class Example
{
	// Uses default format (dd/MM/yyyy HH:mm:ss)
	[JsonConverter(typeof(DateTimeJsonConverter))]
	public DateTime? EventDate { get; set; }

	// Using a custom format
	[JsonConverter(typeof(DateTimeJsonConverter), "yyyy-MM-dd")]
	public DateTime? DateOnly { get; set; }
}

public class Program
{
	public static void Main()
	{
		// Deserializing with various date formats
		var json = "{\"EventDate\":\"2022-01-01T12:00:00\",\"DateOnly\":\"2022-05-15\"}";
		var example = JsonSerializer.Deserialize<Example>(json);
		
		Console.WriteLine(example?.EventDate); // Output: 01/01/2022 12:00:00
		Console.WriteLine(example?.DateOnly); // Output: 15/05/2022 00:00:00

		// Serializing back to JSON
		var serializedJson = JsonSerializer.Serialize(example);
		Console.WriteLine(serializedJson); 
		// Output: {"EventDate":"01/01/2022 12:00:00","DateOnly":"2022-05-15"}
	}
}
Example 2: Using JsonSerializerOptions
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using CodeMatrix.AspNetCore.Utilities.Converters;

public class Event
{
	public DateTime? StartDate { get; set; }
	public DateTime? EndDate { get; set; }
}

public class Program
{
	public static void Main()
	{
		// JSON with different date formats
		var json = @"{
			""StartDate"": ""2022-06-01T09:00:00"",
			""EndDate"": ""01/07/2022 17:30:00""
		}";

		// Setup options with default converter
		var defaultOptions = new JsonSerializerOptions { 
			Converters = { new DateTimeJsonConverter() }
		};
		
		// Setup options with custom format
		var customOptions = new JsonSerializerOptions { 
			Converters = { new DateTimeJsonConverter("MM/dd/yyyy") }
		};

		// Setup options with UTC conversion
		var utcOptions = new JsonSerializerOptions { 
			Converters = { new DateTimeJsonConverter("yyyy-MM-dd'T'HH:mm:ss'Z'", true) }
		};

		// Deserialize with default options
		var eventObj = JsonSerializer.Deserialize<Event>(json, defaultOptions);
		Console.WriteLine($"Default format - Start: {eventObj?.StartDate}, End: {eventObj?.EndDate}");
		// Output: Default format - Start: 01/06/2022 09:00:00, End: 01/07/2022 17:30:00

		// Serialize with different formats
		Console.WriteLine(JsonSerializer.Serialize(eventObj, defaultOptions));
		// Output: {"StartDate":"01/06/2022 09:00:00","EndDate":"01/07/2022 17:30:00"}

		Console.WriteLine(JsonSerializer.Serialize(eventObj, customOptions));
		// Output: {"StartDate":"06/01/2022","EndDate":"07/01/2022"}

		Console.WriteLine(JsonSerializer.Serialize(eventObj, utcOptions));
		// Output: {"StartDate":"2022-06-01T09:00:00Z","EndDate":"2022-07-01T17:30:00Z"}
	}
}
Example 3: Using Custom Input Formats
using System;
using System.Text.Json;
using CodeMatrix.AspNetCore.Utilities.Converters;

public class Meeting
{
	public DateTime? ScheduledTime { get; set; }
}

public class Program
{
	public static void Main()
	{
		// Define specific input formats you want to support
		string[] customFormats = new[] {
			"MM/dd/yy HH:mm", 
			"dd-MMM-yyyy HH:mm:ss",
			"yyyy.MM.dd"
		};

		// Create converter with custom input/output formats
		var converter = new DateTimeJsonConverter(
			inputFormats: customFormats,
			outputFormat: "dd MMM yyyy HH:mm:ss"
		);
		
		var options = new JsonSerializerOptions { 
			Converters = { converter }
		};

		// Parse various format examples
		string[] jsonExamples = {
			@"{""ScheduledTime"": ""06/17/25 14:30""}",
			@"{""ScheduledTime"": ""17-Jun-2025 14:30:00""}",
			@"{""ScheduledTime"": ""2025.06.17""}"
		};

		foreach (var jsonExample in jsonExamples)
		{
			var meeting = JsonSerializer.Deserialize<Meeting>(jsonExample, options);
			Console.WriteLine($"Parsed: {meeting?.ScheduledTime}");
			
			var serialized = JsonSerializer.Serialize(meeting, options);
			Console.WriteLine($"Serialized: {serialized}");
		}
		
		// Output:
		// Parsed: 17 Jun 2025 14:30:00
		// Serialized: {"ScheduledTime":"17 Jun 2025 14:30:00"}
		// Parsed: 17 Jun 2025 14:30:00
		// Serialized: {"ScheduledTime":"17 Jun 2025 14:30:00"}  
		// Parsed: 17 Jun 2025 00:00:00
		// Serialized: {"ScheduledTime":"17 Jun 2025 00:00:00"}
	}
}
Example 4: Common Format Handling

The DateTimeJsonConverter supports numerous common date formats out of the box, including flexible handling of fractional seconds. Here are some examples:

using System;
using System.Text.Json;
using CodeMatrix.AspNetCore.Utilities.Converters;

public class DateExample
{
	public string Format { get; set; }
	public DateTime? Date { get; set; }
}

public class Program
{
	public static void Main()
	{
		// Create options with the converter
		var options = new JsonSerializerOptions { 
			Converters = { new DateTimeJsonConverter() }
		};

		// Array of date formats with examples
		var dateExamples = new[] {
			// Basic date formats
			new { Format = "ISO 8601", Json = @"{""Date"": ""2025-06-17T14:30:00""}" },
			new { Format = "Short date", Json = @"{""Date"": ""17/06/2025""}" },
			new { Format = "US date", Json = @"{""Date"": ""06/17/2025""}" },
			new { Format = "Basic", Json = @"{""Date"": ""20250617""}" },
			
			// Date time formats
			new { Format = "Standard datetime", Json = @"{""Date"": ""17/06/2025 14:30:00""}" },
			new { Format = "With milliseconds", Json = @"{""Date"": ""2025-06-17 14:30:00.123""}" },
			
			// 12-hour formats
			new { Format = "12-hour format", Json = @"{""Date"": ""06/17/2025 2:30:00 PM""}" },
			
			// RFC formats
			new { Format = "RFC format", Json = @"{""Date"": ""Tue, 17 Jun 2025 14:30:00 GMT""}" }
		};

		foreach (var example in dateExamples)
		{
			try
			{
				var obj = JsonSerializer.Deserialize<DateExample>(
					$"{{\"Format\": \"{example.Format}\", {example.Json.Substring(1)}", 
					options
				);
				
				Console.WriteLine($"{obj.Format}: {obj.Date}");
			}
			catch (JsonException ex)
			{
				Console.WriteLine($"Error parsing {example.Format}: {ex.Message}");
			}
		}
	}
}
Flexible Fractional Seconds Handling

The DateTimeJsonConverter includes enhanced handling for fractional seconds, supporting various precision levels and automatically normalizing them to millisecond precision:

using System;
using System.Text.Json;
using CodeMatrix.AspNetCore.Utilities.Converters;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	private class TestClass
	{
		[JsonConverter(typeof(DateTimeJsonConverter))]
		public DateTime? Value { get; set; }
	}
	
	public static void Main()
	{
		// These examples demonstrate flexible fractional seconds handling
		var examples = new[]
		{
			"2023-06-19T12:30:45.1",      // 1 digit (parsed as 100ms)
			"2023-06-19T12:30:45.12",     // 2 digits (parsed as 120ms)
			"2023-06-19T12:30:45.123",    // 3 digits (parsed as 123ms)
			"2023-06-19T12:30:45.1234",   // 4 digits (truncated to 123ms)
			"2023-06-19T12:30:45.123Z",   // With Z timezone
			"2023-06-19T12:30:45.12+02:00" // With timezone offset
		};
		
		JsonSerializerOptions options = JsonOptionsProvider.GetJsonOptions();
		
		foreach (var dateString in examples)
		{
			string json = $@"{{ ""Value"": ""{dateString}"" }}";
			TestClass? result = JsonSerializer.Deserialize<TestClass>(json, options);
			
			Console.WriteLine($"Input: {dateString}");
			Console.WriteLine($"Parsed: {result?.Value?.ToString("yyyy-MM-dd HH:mm:ss.fff")}");
			Console.WriteLine();
		}
	}
}

These examples showcase the flexibility and power of the DateTimeJsonConverter in handling different date formats, customizing output, and working with UTC conversions. The converter can handle a wide range of date formats for deserialization while providing precise control over how dates are serialized to JSON.

Example 5: Combining Custom Formats with Default Formats
using System;
using System.Text.Json;
using CodeMatrix.AspNetCore.Utilities.Converters;

public class DateExample
{
	public string Format { get; set; }
	public DateTime? Date { get; set; }
}

public class Program
{
	public static void Main()
	{
		// Define your custom formats
		string[] myCustomFormats = new[] {
			"yyyy/dd/MM", // Year first, then day, then month (unusual format)
			"MMMM d, yyyy" // Month name, day, year (e.g., "June 17, 2025")
		};
		
		// Method 1: Default behavior now combines your formats with default formats
		// Your formats are given higher priority (checked first)
		var combinedConverter = new DateTimeJsonConverter(
			inputFormats: myCustomFormats,
			outputFormat: "yyyy-MM-dd HH:mm:ss"
		);
		
		// Method 2: Explicitly control whether to include default formats
		var customOnlyConverter = new DateTimeJsonConverter(
			inputFormats: myCustomFormats,
			outputFormat: "yyyy-MM-dd HH:mm:ss",
			includeDefaultFormats: false, // Only use the custom formats
			useUtc: false
		);
		
		var combinedOptions = new JsonSerializerOptions { 
			Converters = { combinedConverter }
		};
		
		var customOnlyOptions = new JsonSerializerOptions { 
			Converters = { customOnlyConverter }
		};
		
		// Examples using the different converters
		var customFormatJson = @"{""Format"": ""Custom format"", ""Date"": ""June 17, 2025""}";
		var standardFormatJson = @"{""Format"": ""Standard format"", ""Date"": ""2025-06-17T14:30:00""}";
		
		// Combined converter can handle both custom and standard formats
		var customDate = JsonSerializer.Deserialize<DateExample>(customFormatJson, combinedOptions);
		Console.WriteLine($"Combined converter with custom format: {customDate?.Date}");
		
		var standardDate = JsonSerializer.Deserialize<DateExample>(standardFormatJson, combinedOptions);  
		Console.WriteLine($"Combined converter with standard format: {standardDate?.Date}");
		
		// Custom-only converter can only handle the custom formats
		try {
			var customOnlyDate = JsonSerializer.Deserialize<DateExample>(customFormatJson, customOnlyOptions);
			Console.WriteLine($"Custom-only converter with custom format: {customOnlyDate?.Date}");
			
			var willFail = JsonSerializer.Deserialize<DateExample>(standardFormatJson, customOnlyOptions);
			Console.WriteLine("This line won't execute");
		}
		catch (JsonException) {
			Console.WriteLine("Custom-only converter failed with standard format (as expected)");
		}
	}
}

This example demonstrates how the DateTimeJsonConverter now automatically combines your custom formats with the default formats, giving priority to your custom formats. It also shows how to use the new constructor parameter includeDefaultFormats to explicitly control whether default formats should be included.

DateTimeOffsetJsonConverter

Description: Converts a nullable DateTimeOffset to and from JSON using configurable date formats.

Namespace: CodeMatrix.AspNetCore.Utilities.Converters

Usage:

The DateTimeOffsetJsonConverter can be used to handle nullable DateTimeOffset values when serializing and deserializing JSON. This converter supports various input date formats including timezone information and a configurable output date format.

Signature:

public class DateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset?> { 
	public DateTimeOffsetJsonConverter(); 
	public DateTimeOffsetJsonConverter(string outputFormat, bool preserveOffset = true); 
	public DateTimeOffsetJsonConverter(string[] inputFormats, string outputFormat, bool preserveOffset = true); 
	public override DateTimeOffset? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); 
	public override void Write(Utf8JsonWriter writer, DateTimeOffset? value, JsonSerializerOptions options); 
}

Example:

using System; 
using System.Text.Json; 
using System.Text.Json.Serialization; 
using CodeMatrix.AspNetCore.Utilities.Converters;

public class Example { 
	[JsonConverter(typeof(DateTimeOffsetJsonConverter))] 
	public DateTimeOffset? EventDate { get; set; } 
}

public class Program { 
	public static void Main() { 
		var json = "{\"EventDate\":\"2022-01-01T12:00:00+05:30\"}"; 
		var options = new JsonSerializerOptions { 
			Converters = { new DateTimeOffsetJsonConverter() } 
		};

		var example = JsonSerializer.Deserialize<Example>(json, options);
		Console.WriteLine(example?.EventDate); // Output: 01/01/2022 12:00:00 +05:30

		var serializedJson = JsonSerializer.Serialize(example, options);
		Console.WriteLine(serializedJson); // Output: {"EventDate":"2022-01-01T12:00:00+05:30"}
		
		// Use custom format and normalize to UTC
		var utcOptions = new JsonSerializerOptions { 
			Converters = { new DateTimeOffsetJsonConverter("yyyy-MM-dd'T'HH:mm:ss'Z'", false) } 
		};
		
		var serializedUtc = JsonSerializer.Serialize(example, utcOptions);
		Console.WriteLine(serializedUtc); // Output: {"EventDate":"2022-01-01T06:30:00Z"}
	}
}

In this example, the DateTimeOffsetJsonConverter is used to handle the EventDate property with timezone information. The converter preserves the original offset by default but can also normalize to UTC if needed.

NumberToStringJsonConverter

Description: Converts a number or string to a JSON string representation.

Namespace: CodeMatrix.AspNetCore.Utilities.Converters

Usage:

The NumberToStringJsonConverter can be used to handle numbers and strings when serializing and deserializing JSON. This converter ensures that numbers are converted to their string representation in JSON.

Signature:

public class NumberToStringJsonConverter : JsonConverter { 
	public override bool CanConvert(Type typeToConvert); 
	public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options); 
	public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options); 
}

Example:

using System; 
using System.Text.Json; 
using System.Text.Json.Serialization; 
using CodeMatrix.AspNetCore.Utilities.Converters;

public class Example { 
	[JsonConverter(typeof(NumberToStringJsonConverter))] 
	public string? Value { get; set; }
}
	public class Program { 
	public static void Main() { 
		var json = "{\"Value\":12345}"; 
		var options = new JsonSerializerOptions { Converters = { new NumberToStringJsonConverter() } };

		var example = JsonSerializer.Deserialize<Example>(json, options);
		Console.WriteLine(example?.Value); // Output: "12345"

		var serializedJson = JsonSerializer.Serialize(example, options);
		Console.WriteLine(serializedJson); // Output: {"Value":"12345"}
	}
}

In this example, the NumberToStringJsonConverter is used to handle the Value property of the Example class. The converter allows the property to be deserialized from a number and serialized back to JSON as a string.

Extensions

BoolExtension

Description: Provides extension methods for boolean values.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The ToYesNo extension method can be used to convert a boolean value to a "Yes" or "No" string representation.

Signature:

public static partial class Extensions { 
	public static string ToYesNo(this bool @this); 
}
Method: ToYesNo

Description: Converts a boolean value to a "Yes" or "No" string representation.

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		bool isActive = true; 
		string result = isActive.ToYesNo();
		Console.WriteLine(result); // Output: "Yes"

		isActive = false; 
		result = isActive.ToYesNo(); 
		Console.WriteLine(result); // Output: "No"
	}
}
Method: ToYesNo (bool?)

Description: Converts a nullable boolean value to a "Yes" or "No" string representation.

Signature:

public static string ToYesNo(this bool? @this);

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program
{
	public static void Main()
	{
		bool? isActive = true;
		string result = isActive.ToYesNo();
		Console.WriteLine(result); // Output: "Yes"

		isActive = false;
		result = isActive.ToYesNo();
		Console.WriteLine(result); // Output: "No"

		isActive = null;
		result = isActive.ToYesNo();
		Console.WriteLine(result); // Output: "No"
	}
}

CollectionExtension

Description: Provides extension methods for collections.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The CollectionExtension class provides methods to convert collections to dictionaries.

Methods:

ToDictionary (IFormCollection)

Description: Converts an IFormCollection to a dictionary.

Signature:

public static IDictionary<string, object> ToDictionary(this IFormCollection @this);

Example:

using System; 
using System.Collections.Generic; 
using Microsoft.AspNetCore.Http; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		IFormCollection formCollection = new FormCollection(new Dictionary<string, Microsoft.Extensions.Primitives.StringValues> { { "Key1", "Value1" }, { "Key2", "Value2" } });
		IDictionary<string, object> dictionary = formCollection.ToDictionary();
		foreach (var kvp in dictionary)
		{
			Console.WriteLine($"{kvp.Key}: {kvp.Value}");
		}
		// Output:
		// Key1: Value1
		// Key2: Value2
	}
}
ToDictionary (NameValueCollection)

Description: Converts a NameValueCollection to a dictionary.

Signature:

public static IDictionary<string, object> ToDictionary(this NameValueCollection @this);

Example:

using System; 
using System.Collections.Generic; 
using System.Collections.Specialized; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		NameValueCollection nameValueCollection = new NameValueCollection { { "Key1", "Value1" }, { "Key2", "Value2" } };
		IDictionary<string, object> dictionary = nameValueCollection.ToDictionary();
		foreach (var kvp in dictionary)
		{
			Console.WriteLine($"{kvp.Key}: {kvp.Value}");
		}
		// Output:
		// Key1: Value1
		// Key2: Value2
	}
}

DateTimeExtension

Description: Provides extension methods for DateTime.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The DateTimeExtension class provides methods to convert DateTime and nullable DateTime instances to formatted date strings.

Methods:

ToDateString (DateTime)

Description: Converts the DateTime to a formatted date string.

Signature:

public static string ToDateString(this DateTime @this);

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		DateTime now = DateTime.Now; 
		string formattedDate = now.ToDateString(); 
		Console.WriteLine(formattedDate); // Output: "2023-10-15 14:30:00.000" (example) 
	} 
}
ToDateString (DateTime?)

Description: Converts the nullable DateTime to a formatted date string.

Signature:

public static string? ToDateString(this DateTime? @this);

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		DateTime? now = DateTime.Now; 
		string? formattedDate = now.ToDateString(); 
		Console.WriteLine(formattedDate); // Output: "2023-10-15 14:30:00.000" (example)

		DateTime? nullDate = null; 
		formattedDate = nullDate.ToDateString(); 
		Console.WriteLine(formattedDate); // Output: (null)
	}
}
ToDateString (DateTime?, string)

Description: Converts the nullable DateTime to a formatted date string using the specified format.

Signature:

public static string ToDateString(this DateTime? @this, string dateFormat);

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		DateTime? now = DateTime.Now; 
		string formattedDate = now.ToDateString("MM/dd/yyyy"); 
		Console.WriteLine(formattedDate); // Output: "10/15/2023" (example)

		DateTime? nullDate = null; 
		formattedDate = nullDate.ToDateString("MM/dd/yyyy"); 
		Console.WriteLine(formattedDate); // Output: "" (empty string)
	}
}
ToShortDateString (DateTime)

Description: Converts the DateTime to a formatted short date string.

Signature:

public static string ToShortDateString(this DateTime @this);

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		DateTime now = DateTime.Now; 
		string shortDate = now.ToShortDateString(); 
		Console.WriteLine(shortDate); // Output: "2023-10-15" (example)
	}
}
ToShortDateString (DateTime?)

Description: Converts the nullable DateTime to a formatted short date string.

Signature:

public static string ToShortDateString(this DateTime? @this);

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		DateTime? now = DateTime.Now; 
		string shortDate = now.ToShortDateString(); 
		Console.WriteLine(shortDate); // Output: "2023-10-15" (example)

		DateTime? nullDate = null; 
		shortDate = nullDate.ToShortDateString(); 
		Console.WriteLine(shortDate); // Output: "" (empty string)
	}
}
ToIndianStandardDateTime (DateTime)

Description: Converts the specified DateTime to Indian Standard Time (IST).

Signature:

public static DateTime ToIndianStandardDateTime(this DateTime @this);

Example:

using System; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		DateTime utcNow = DateTime.UtcNow; 
		DateTime istNow = utcNow.ToIndianStandardDateTime(); 
		Console.WriteLine(istNow); // Output: IST time equivalent of UTC time
	}
}

ExceptionExtension

Description: Provides extension methods for handling exceptions.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The ExceptionExtension class provides a method to convert exceptions to API result objects.

Methods:

ToApiResult

Description: Converts an exception to an API result.

Signature:

public static ApiResult<T> ToApiResult<T>(this Exception ex, IHostEnvironment env);

Example:

using System; 
using Microsoft.Extensions.Hosting; 
using CodeMatrix.AspNetCore.Utilities.Extensions; 
using CodeMatrix.AspNetCore.Utilities.Models;

public class Program { 
	public static void Main() { 
		IHostEnvironment env = new HostEnvironment { EnvironmentName = Environments.Development }; 
		try { 
			// Simulate an exception
			throw new InvalidOperationException("An error occurred."); 
		} 
		catch (Exception ex) { 
			ApiResult<string> result = ex.ToApiResult<string>(env); 
			Console.WriteLine($"IsSuccessful: {result.IsSuccessful}"); 
			Console.WriteLine($"Message: {result.Message}"); 
		} 
	} 
}

HostEnvironmentExtension

Description: Provides extension methods for IHostEnvironment.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The HostEnvironmentExtension class provides a method to check if the current host environment is UAT (User Acceptance Testing).

Methods:

IsUAT

Description: Checks if the current host environment name is UAT.

Signature:

public static bool IsUAT(this IHostEnvironment hostEnvironment);

Example:

using System; 
using Microsoft.Extensions.Hosting; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		// Test with UAT environment
		IHostEnvironment env = new HostEnvironment { EnvironmentName = "UAT" }; 
		bool isUAT = env.IsUAT(); 
		Console.WriteLine($"Is UAT Environment: {isUAT}"); // Output: Is UAT Environment: True
		
		// Test with Production environment
		env.EnvironmentName = "Production";
		isUAT = env.IsUAT();
		Console.WriteLine($"Is UAT Environment: {isUAT}"); // Output: Is UAT Environment: False
		
		// Test with Development environment
		env.EnvironmentName = "Development";
		isUAT = env.IsUAT();
		Console.WriteLine($"Is UAT Environment: {isUAT}"); // Output: Is UAT Environment: False
	}
}

JsonExtension

Description: Provides extension methods for working with JsonElement.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The JsonExtension class provides methods to retrieve properties or elements from a JsonElement.

Methods:

GetByName

Description: Retrieves a property from the JsonElement by its name.

Signature:

public static JsonElement? GetByName(this JsonElement element, string name);

Example:

using System.Text.Json; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string json = "{\"name\":\"John\",\"age\":30}";
		JsonDocument doc = JsonDocument.Parse(json);
		JsonElement? nameElement = doc.RootElement.GetByName("name");
		Console.WriteLine(nameElement?.GetString()); // Output: John
	}
}
GetByIndex

Description: Retrieves an element from the JsonElement array by its index.

Signature:

public static JsonElement? GetByIndex(this JsonElement element, int index);

Example:

using System.Text.Json; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string json = "[\"apple\",\"banana\",\"cherry\"]";
		JsonDocument doc = JsonDocument.Parse(json);
		JsonElement? element = doc.RootElement.GetByIndex(1);
		Console.WriteLine(element?.GetString()); // Output: banana
	}
}
GetByPath

Description: Retrieves a nested property or element from the JsonElement by a dot-separated path.

Signature:

public static JsonElement? GetByPath(this JsonElement jsonElement, string path);

Example:

using System.Text.Json; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string json = "{\"person\":{\"name\":\"John\",\"age\":30}}";
		JsonDocument doc = JsonDocument.Parse(json);
		JsonElement? nameElement = doc.RootElement.GetByPath("person.name");
		Console.WriteLine(nameElement?.GetString()); // Output: John
	}
}

ModelStateExtension

Description: Provides extension methods for ModelStateDictionary.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The ModelStateExtension class provides a method to convert errors in the ModelStateDictionary to a string.

Methods:

ErrorsToString

Description: Converts the errors in the ModelStateDictionary to a string. Returns an empty string if there are no errors.

Signature:

public static string ErrorsToString(this ModelStateDictionary modelState);

Example:

using Microsoft.AspNetCore.Mvc.ModelBinding; 
using CodeMatrix.AspNetCore.Utilities.Extensions;
public class Program { 
	public static void Main() { 
		ModelStateDictionary modelState = new ModelStateDictionary();
		modelState.AddModelError("Name", "Name is required.");
		string errors = modelState.ErrorsToString();
		Console.WriteLine(errors); // Output: Name is required.
		
		// With no errors
		ModelStateDictionary validState = new ModelStateDictionary();
		string noErrorsResult = validState.ErrorsToString(); // Returns empty string instead of null
	}
}

ObjectExtension

Description: Provides extension methods for object.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The ObjectExtension class provides methods to serialize objects to JSON and retrieve properties as a dictionary.

Methods:

ToSafeString (object)

Description: Converts the object to a safe string representation.

Signature:

public static string? ToSafeString(this object @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		object obj = null;
		string? safeString = obj.ToSafeString();
		Console.WriteLine(safeString); // Output: (null)
	}
}
Serialize

Description: Serializes an object to a JSON string using the default JSON serializer options.

Signature:

public static string Serialize<T>(this T @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		var obj = new { Name = "John", Age = 30 };
		string json = obj.Serialize();
		Console.WriteLine(json); // Output: {"Name":"John","Age":30}
	}
}
GetPropertiesAsDictionary

Description: Gets the properties of an object as a dictionary.

Signature:

public static Dictionary<string, object?> GetPropertiesAsDictionary<T>(this T @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		var obj = new { Name = "John", Age = 30 };
		var properties = obj.GetPropertiesAsDictionary();
		foreach (var kvp in properties)
		{
			Console.WriteLine($"{kvp.Key}: {kvp.Value}");
		}
		// Output:
		// Name: John
		// Age: 30
	}
}

StreamExtension

Description: Provides extension methods for Stream.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The StreamExtension class provides a method to convert a stream to a base64 string.

Methods:

ConvertToBase64

Description: Converts the stream to a base64 string. The stream position is reset to the beginning after conversion.

Signature:

public static string ConvertToBase64(this Stream stream);

Example:

using System.IO; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		using (var stream = new MemoryStream(new byte[] { 1, 2, 3, 4 }))
		{
			string base64 = stream.ConvertToBase64();
			Console.WriteLine(base64); // Output: AQIDBA==
		}
	}
}

StringExtension

Description: Provides extension methods for string manipulation.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The StringExtension class provides methods to check for null or empty strings, convert strings, and deserialize JSON strings.

Method: IsNullOrEmpty

Description: Determines whether the specified string is null or empty.

Signature:

public static bool IsNullOrEmpty(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = null;
		bool isNullOrEmpty = str.IsNullOrEmpty();
		Console.WriteLine(isNullOrEmpty); // Output: True
	}
}
Method: IsNullOrEmptyToNull

Description: Converts a null or empty string to null.

Signature:

public static string? IsNullOrEmptyToNull(this string value);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "";
		string? result = str.IsNullOrEmptyToNull();
		Console.WriteLine(result == null); // Output: True
	}
}
Method: ToSafeString (string)

Description: Converts the string to a safe string representation (returns empty string if null).

Signature:

public static string ToSafeString(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = null;
		string safeString = str.ToSafeString();
		Console.WriteLine(safeString); // Output: ""
	}
}
Method: ToSafeInt

Description: Converts the string to a safe integer representation.

Signature:

public static int ToSafeInt(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "123";
		int result = str.ToSafeInt();
		Console.WriteLine(result); // Output: 123
	}
}
Method: Replace

Description: Replaces all occurrences of a specified string with a specified replacement value.

Signature:

public static string Replace(this string @this, string oldValue, object replacementValue);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "Hello, World!";
		string result = str.Replace("World", "C#");
		Console.WriteLine(result); // Output: Hello, C#!
	}
}
Method: RemoveUnicodeChars

Description: Removes Unicode characters from the string.

Signature:

public static string RemoveUnicodeChars(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "Hello \\u0041\\u0042\\u0043!";
		string result = str.RemoveUnicodeChars();
		Console.WriteLine(result); // Output: Hello ABC!
	}
}
Method: SplitProperCase

Description: Splits a PascalCase or camelCase string into separate words with proper casing.

Signature:

public static string SplitProperCase(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "PascalCaseExample";
		string result = str.SplitProperCase();
		Console.WriteLine(result); // Output: Pascal Case Example
	}
}
Method: ToDateTime

Description: Converts the string to a DateTime object using the date-time format - "yyyy-MM-dd HH:mm:ss".

Signature:

public static DateTime ToDateTime(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "2023-10-15 14:30:00";
		DateTime dateTime = str.ToDateTime();
		Console.WriteLine(dateTime); // Output: 10/15/2023 14:30:00
	}
}
Method: ToIndianStandardDateTime (string)

Description: Converts the string to a DateTime object in Indian Standard Time (IST).

Signature:

public static DateTime ToIndianStandardDateTime(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string str = "2023-10-15 14:30:00";
		DateTime istDateTime = str.ToIndianStandardDateTime();
		Console.WriteLine(istDateTime); // Output: IST equivalent of the given time
	}
}
Method: Deserialize

Description: Deserializes the JSON string to the specified type.

Signature:

public static T? Deserialize<T>(this string @this);

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;
public class Program { 
	public static void Main() { 
		string json = "{\"Name\":\"John\",\"Age\":30}";
		var obj = json.Deserialize<dynamic>();
		Console.WriteLine(obj.Name); // Output: John
	}
}

XmlExtension

Description: Provides extension methods for XML-related operations.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The XmlExtension class provides methods to deserialize XML nodes and convert between XDocument and XmlDocument.

Methods:

DeserializeXmlNode

Description: Deserializes the specified XmlNode into an object of type T.

Signature:

public static T? DeserializeXmlNode<T>(this XmlNode @this);

Example:

using System.Xml; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		string xml = "<Person><Name>John</Name><Age>30</Age></Person>";
		XmlDocument doc = new XmlDocument();
		doc.LoadXml(xml);
		var person = doc.DocumentElement.DeserializeXmlNode<dynamic>();
		Console.WriteLine(person.Name); // Output: John
	}
}
ToXmlDocument

Description: Converts the specified XDocument to an XmlDocument.

Signature:

public static XmlDocument ToXmlDocument(this XDocument xDocument);

Example:

using System.Xml; 
using System.Xml.Linq; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		XDocument xDoc = new XDocument(new XElement("Root", new XElement("Child", "Value")));
		XmlDocument xmlDoc = xDoc.ToXmlDocument();
		Console.WriteLine(xmlDoc.OuterXml); // Output: <Root><Child>Value</Child></Root>
	}
}
ToXDocument

Description: Converts the specified XmlDocument to an XDocument.

Signature:

public static XDocument ToXDocument(this XmlDocument xmlDocument);

Example:

using System.Xml; 
using System.Xml.Linq; 
using CodeMatrix.AspNetCore.Utilities.Extensions;

public class Program { 
	public static void Main() { 
		XmlDocument xmlDoc = new XmlDocument();
		xmlDoc.LoadXml("<Root><Child>Value</Child></Root>");
		XDocument xDoc = xmlDoc.ToXDocument();
		Console.WriteLine(xDoc); // Output: <Root><Child>Value</Child></Root>
	}
}

QueryableExtensions

Description: Provides extension methods for IQueryable to support pagination and simplify common query operations.

Namespace: CodeMatrix.AspNetCore.Utilities.Extensions

Usage:

The QueryableExtensions class provides methods to easily paginate database queries using Entity Framework Core or other IQueryable sources.

Methods:

ToPagedResultAsync<T>

public static Task<PagedResult<T>> ToPagedResultAsync<T>(
	this IQueryable<T> query,
	int page,
	int pageSize,
	CancellationToken cancellationToken = default)

Asynchronously converts an IQueryable to a PagedResult with metadata. Executes two queries: one for count, one for data.

ToPagedResult<T>

public static PagedResult<T> ToPagedResult<T>(
	this IQueryable<T> query,
	int page,
	int pageSize)

Synchronously converts an IQueryable to a PagedResult. Use ToPagedResultAsync for better database performance.

ToPagedResultAsync<T> (IEnumerable)

public static Task<PagedResult<T>> ToPagedResultAsync<T>(
	this IEnumerable<T> source,
	int page,
	int pageSize)

Converts an in-memory collection to a PagedResult. Warning: Loads entire collection into memory.

Paginate<T>

public static IQueryable<T> Paginate<T>(
	this IQueryable<T> query,
	int page,
	int pageSize)

Applies Skip/Take pagination to a query without executing it. Use when you need the query for further composition.

Example:

using CodeMatrix.AspNetCore.Utilities.Extensions;
using CodeMatrix.AspNetCore.Utilities.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
	private readonly ApplicationDbContext _context;
	
	public UsersController(ApplicationDbContext context)
	{
		_context = context;
	}
	
	// Example 1: Simple pagination with ToPagedResultAsync
	[HttpGet]
	public async Task<ActionResult<PagedResult<User>>> GetUsers(
		[FromQuery] int page = 1,
		[FromQuery] int pageSize = 10)
	{
		var result = await _context.Users
			.OrderBy(u => u.Name)
			.ToPagedResultAsync(page, pageSize);
		
		return Ok(result);
	}
	
	// Example 2: Pagination with filtering and sorting
	[HttpGet("active")]
	public async Task<ActionResult<PagedResult<UserDto>>> GetActiveUsers(
		[FromQuery] string? search = null,
		[FromQuery] string? sortBy = "name",
		[FromQuery] bool descending = false,
		[FromQuery] int page = 1,
		[FromQuery] int pageSize = 20)
	{
		var query = _context.Users
			.Where(u => u.IsActive);
		
		// Apply search filter
		if (!string.IsNullOrEmpty(search))
			query = query.Where(u => u.Name.Contains(search) || u.Email.Contains(search));
		
		// Apply sorting
		query = sortBy?.ToLower() switch
		{
			"email" => descending ? query.OrderByDescending(u => u.Email) : query.OrderBy(u => u.Email),
			"created" => descending ? query.OrderByDescending(u => u.CreatedAt) : query.OrderBy(u => u.CreatedAt),
			_ => descending ? query.OrderByDescending(u => u.Name) : query.OrderBy(u => u.Name)
		};
		
		// Apply pagination and project to DTO
		var result = await query
			.Select(u => new UserDto 
			{ 
				Id = u.Id, 
				Name = u.Name, 
				Email = u.Email 
			})
			.ToPagedResultAsync(page, pageSize);
		
		return Ok(result);
	}
	
	// Example 3: Using Paginate for complex scenarios
	[HttpGet("with-roles")]
	public async Task<ActionResult<PagedResult<UserWithRolesDto>>> GetUsersWithRoles(
		[FromQuery] int page = 1,
		[FromQuery] int pageSize = 10)
	{
		// Build base query with includes
		var baseQuery = _context.Users
			.Include(u => u.Roles)
			.OrderBy(u => u.Name);
		
		// Get total count before pagination
		var totalCount = await baseQuery.CountAsync();
		
		// Apply pagination
		var paginatedQuery = baseQuery.Paginate(page, pageSize);
		
		// Execute and project
		var items = await paginatedQuery
			.Select(u => new UserWithRolesDto
			{
				Id = u.Id,
				Name = u.Name,
				Roles = u.Roles.Select(r => r.Name).ToList()
			})
			.ToListAsync();
		
		// Create result manually
		var result = new PagedResult<UserWithRolesDto>(items, totalCount, page, pageSize);
		
		return Ok(result);
	}
	
	// Example 4: In-memory pagination (use sparingly)
	[HttpGet("cached")]
	public async Task<ActionResult<PagedResult<User>>> GetCachedUsers(
		[FromQuery] int page = 1,
		[FromQuery] int pageSize = 10)
	{
		// Get from cache (already in memory)
		var cachedUsers = await GetCachedUsersFromRedis();
		
		// Paginate in-memory collection
		var result = await cachedUsers.ToPagedResultAsync(page, pageSize);
		
		return Ok(result);
	}
}

Advanced: Pagination with Specification Pattern

using CodeMatrix.AspNetCore.Utilities.Extensions;
using CodeMatrix.AspNetCore.Utilities.Models;

public class UserService
{
	private readonly ApplicationDbContext _context;
	
	public async Task<PagedResult<User>> GetUsersAsync(
		ISpecification<User> specification,
		int page,
		int pageSize,
		CancellationToken ct = default)
	{
		var query = _context.Users.AsQueryable();
		
		// Apply specification
		if (specification.Criteria != null)
			query = query.Where(specification.Criteria);
		
		// Apply includes
		query = specification.Includes
			.Aggregate(query, (current, include) => current.Include(include));
		
		// Apply ordering
		if (specification.OrderBy != null)
			query = query.OrderBy(specification.OrderBy);
		
		// Apply pagination
		return await query.ToPagedResultAsync(page, pageSize, ct);
	}
}

Performance Notes:

  • ToPagedResultAsync executes 2 queries: COUNT(*) and SELECT with OFFSET/FETCH
  • Always apply filters BEFORE pagination to reduce count query cost
  • Use projections (Select) before pagination when possible to reduce data transfer
  • For very large tables, consider cursor-based pagination instead of offset-based

Filters

ModelStateValidationFilter

Description: Filter that validates the model state of an action before execution.

Namespace: CodeMatrix.AspNetCore.Utilities.Filters

Usage:

The ModelStateValidationFilter validates the model state before the action is executed. If the model state is invalid, it returns an OkObjectResult with an ApiResult<string> containing the error messages and prevents the action from executing.

Signature:

public class ModelStateValidationFilter : ActionFilterAttribute
{
	public override void OnActionExecuting(ActionExecutingContext context);
}

Example:

using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using CodeMatrix.AspNetCore.Utilities.Filters;

public class Program
{
	public static void Main()
	{
		var filter = new ModelStateValidationFilter();
		Console.WriteLine("ModelStateValidationFilter applied.");
	}
}

ModelStateValidationAsyncFilter

Description: Filter that validates the model state before and after the controller action execution.

Namespace: CodeMatrix.AspNetCore.Utilities.Filters

Usage:

The ModelStateValidationAsyncFilter validates the model state asynchronously before the action is executed. If the model state is invalid, it returns an OkObjectResult with an ApiResult<string> containing the error messages and prevents the action from executing.

Signature:

public class ModelStateValidationAsyncFilter : IAsyncActionFilter
{
	public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);
}

Example:

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using CodeMatrix.AspNetCore.Utilities.Filters;

public class Program
{
	public static async Task Main()
	{
		var filter = new ModelStateValidationAsyncFilter();
		Console.WriteLine("ModelStateValidationAsyncFilter applied.");
	}
}

Helpers

DbHelpers

Description: Provides helper methods for database operations.

Namespace: CodeMatrix.AspNetCore.Utilities.Helpers

Usage:

The DbHelpers class provides various methods to convert and map data from databases to strongly-typed objects.

Method: ConvertDataTable<T>

Description: Converts a DataTable to a list of objects.

Signature:

public static List<T> ConvertDataTable<T>(DataTable dt) where T : new()

Example:

using System;
using System.Data;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DataTable dt = new DataTable();
		// Add columns and rows to DataTable
		
		List<MyClass> list = DbHelpers.ConvertDataTable<MyClass>(dt);
		Console.WriteLine($"Converted {list.Count} rows to objects.");
	}
}
Method: ConvertDataTable<T>

Description: Converts a DataTable to a list of objects using a specified mapper type.

Signature:

public static List<T> ConvertDataTable<T>(DataTable dt, Type mapperType) where T : new()

Example:

using System;
using System.Data;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DataTable dt = new DataTable();
		// Add columns and rows to DataTable
		
		List<MyClass> list = DbHelpers.ConvertDataTable<MyClass>(dt, typeof(MyMapper));
		Console.WriteLine($"Converted {list.Count} rows to objects using custom mapper.");
	}
}
Method: MapToList<T>

Description: Maps a DataReader to a list of objects.

Signature:

public static List<T> MapToList<T>(IDataReader dr) where T : new()

Example:

using System;
using System.Data;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		// Assuming 'dr' is an IDataReader from a database query
		IDataReader dr = GetDataReader();
		
		List<MyClass> list = DbHelpers.MapToList<MyClass>(dr);
		Console.WriteLine($"Mapped {list.Count} rows to objects.");
	}
}
Method: MapToItem<T>

Description: Maps a DataReader to a single object.

Signature:

public static T MapToItem<T>(IDataReader dr) where T : new()

Example:

using System;
using System.Data;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		// Assuming 'dr' is an IDataReader from a database query
		IDataReader dr = GetDataReader();
		
		MyClass item = DbHelpers.MapToItem<MyClass>(dr);
		Console.WriteLine($"Mapped data to a single object.");
	}
}
Method: MapToItemUsingJson<T>

Description: Maps a DataReader to a single object using JSON serialization.

Signature:

public static T MapToItemUsingJson<T>(IDataReader dr)

Example:

using System;
using System.Data;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		// Assuming 'dr' is an IDataReader from a database query
		IDataReader dr = GetDataReader();
		
		MyClass item = DbHelpers.MapToItemUsingJson<MyClass>(dr);
		Console.WriteLine($"Mapped data to a single object using JSON.");
	}
}
Method: GetDbParamAttributeMappings<T>

Description: Gets database parameter mappings from attributes on a type.

Signature:

public static Dictionary<string, string> GetDbParamAttributeMappings<T>()

Example:

using System;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;
using CodeMatrix.AspNetCore.Utilities.Attributes;

public class MyModel
{
	[DbParam("param_id")]
	public int Id { get; set; }
	
	[DbParam("param_name")]
	public string Name { get; set; }
}

public class Program
{
	public static void Main()
	{
		Dictionary<string, string> mappings = DbHelpers.GetDbParamAttributeMappings<MyModel>();
		foreach (var mapping in mappings)
		{
			Console.WriteLine($"Property: {mapping.Key}, DB Param: {mapping.Value}");
		}
	}
}
Method: ChangeType<T>

Description: Converts a value to the specified type.

Signature:

public static T ChangeType<T>(object value)

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		object value = "123";
		int intValue = DbHelpers.ChangeType<int>(value);
		Console.WriteLine($"Converted value: {intValue}");
	}
}
Method: ChangeType

Description: Converts a value to the specified type.

Signature:

public static object ChangeType(object value, Type conversionType)

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		object value = "123";
		object intValue = DbHelpers.ChangeType(value, typeof(int));
		Console.WriteLine($"Converted value: {intValue}");
	}
}

DapperHelper

Description: Provides helper methods for Dapper ORM operations.

Namespace: CodeMatrix.AspNetCore.Utilities.Helpers

Integration guide: For a complete walkthrough on building a database context service with DapperHelper — including configuration, sync/async queries, bulk insert, output parameters, and multi-connection routing — see the DapperDBContextService integration guide.

DapperHelper Features
  • Easy-to-use wrapper for Dapper
  • Support for stored procedures
  • Parameter mapping from objects
  • Async operations
  • Query analytics
  • Retry policies for transient failures
  • Multiple result set support
DapperHelper Configuration

The DapperHelper class can be configured with connection strings and other options during initialization.

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		string connectionString = "Data Source=...;Initial Catalog=...;Integrated Security=True;";
		DapperHelper dapper = new DapperHelper(connectionString);
		Console.WriteLine("DapperHelper initialized.");
	}
}
Parameter Mapping

DapperHelper can map properties from objects to stored procedure parameters automatically.

Example:

using System;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;
using CodeMatrix.AspNetCore.Utilities.Attributes;

public class CustomerParam
{
	[DbParam("CustomerID")]
	public int Id { get; set; }
	
	[DbParam("CustomerName")]
	public string Name { get; set; }
}

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var param = new CustomerParam { Id = 1, Name = "Test Customer" };
		dapper.ExecuteProcedure<CustomerResult>("sp_GetCustomer", param);
	}
}
Method: ExecuteProcedure<T>

Description: Executes a stored procedure and returns a collection of results.

Signature:

public IEnumerable<T> ExecuteProcedure<T>(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var customers = dapper.ExecuteProcedure<Customer>("sp_GetCustomers");
		foreach (var customer in customers)
		{
			Console.WriteLine($"Customer: {customer.Name}");
		}
	}
}
Method: ExecuteProcedureAsync<T>

Description: Asynchronously executes a stored procedure and returns a collection of results.

Signature:

public Task<IEnumerable<T>> ExecuteProcedureAsync<T>(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var customers = await dapper.ExecuteProcedureAsync<Customer>("sp_GetCustomers");
		foreach (var customer in customers)
		{
			Console.WriteLine($"Customer: {customer.Name}");
		}
	}
}
Method: ExecuteScalar<T>

Description: Executes a stored procedure and returns a single value.

Signature:

public T ExecuteScalar<T>(string procedureName, object param = null)

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		int count = dapper.ExecuteScalar<int>("sp_GetCustomerCount");
		Console.WriteLine($"Customer count: {count}");
	}
}
Method: ExecuteScalarAsync<T>

Description: Asynchronously executes a stored procedure and returns a single value.

Signature:

public Task<T> ExecuteScalarAsync<T>(string procedureName, object param = null)

Example:

using System;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		int count = await dapper.ExecuteScalarAsync<int>("sp_GetCustomerCount");
		Console.WriteLine($"Customer count: {count}");
	}
}
Method: ExecuteNonQuery

Description: Executes a stored procedure that does not return any results.

Signature:

public void ExecuteNonQuery(string procedureName, object param = null)

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		dapper.ExecuteNonQuery("sp_UpdateCustomerStatus", new { CustomerID = 1, Status = "Active" });
		Console.WriteLine("Updated customer status.");
	}
}
Method: ExecuteNonQueryAsync

Description: Asynchronously executes a stored procedure that does not return any results.

Signature:

public Task ExecuteNonQueryAsync(string procedureName, object param = null)

Example:

using System;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		await dapper.ExecuteNonQueryAsync("sp_UpdateCustomerStatus", new { CustomerID = 1, Status = "Active" });
		Console.WriteLine("Updated customer status.");
	}
}
Method: BulkInsert<T>

Description: Performs a bulk insert operation.

Signature:

public void BulkInsert<T>(string tableName, IEnumerable<T> data)

Example:

using System;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		List<Customer> customers = new List<Customer>
		{
			new Customer { Name = "Customer 1" },
			new Customer { Name = "Customer 2" }
		};
		
		dapper.BulkInsert("Customers", customers);
		Console.WriteLine("Customers inserted.");
	}
}
Method: BulkInsertAsync<T>

Description: Asynchronously performs a bulk insert operation.

Signature:

public Task BulkInsertAsync<T>(string tableName, IEnumerable<T> data)

Example:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		List<Customer> customers = new List<Customer>
		{
			new Customer { Name = "Customer 1" },
			new Customer { Name = "Customer 2" }
		};
		
		await dapper.BulkInsertAsync("Customers", customers);
		Console.WriteLine("Customers inserted.");
	}
}
Method: ExecuteProcedureMultiple<TFirst, TSecond>

Description: Executes a stored procedure and returns multiple result sets as a tuple.

Signature:

public (IEnumerable<TFirst>, IEnumerable<TSecond>) ExecuteProcedureMultiple<TFirst, TSecond>(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var (customers, orders) = dapper.ExecuteProcedureMultiple<Customer, Order>("sp_GetCustomersAndOrders");
		
		Console.WriteLine($"Retrieved {customers.Count()} customers and {orders.Count()} orders.");
	}
}
Method: ExecuteProcedureMultiple<TFirst, TSecond, TThird>

Description: Executes a stored procedure and returns multiple result sets as a tuple with three types.

Signature:

public (IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>) ExecuteProcedureMultiple<TFirst, TSecond, TThird>(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var (customers, orders, products) = dapper.ExecuteProcedureMultiple<Customer, Order, Product>("sp_GetCustomersOrdersProducts");
		
		Console.WriteLine($"Retrieved {customers.Count()} customers, {orders.Count()} orders, and {products.Count()} products.");
	}
}
Method: ExecuteProcedureMultipleAsync<TFirst, TSecond>

Description: Asynchronously executes a stored procedure and returns multiple result sets as a tuple.

Signature:

public Task<(IEnumerable<TFirst>, IEnumerable<TSecond>)> ExecuteProcedureMultipleAsync<TFirst, TSecond>(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var (customers, orders) = await dapper.ExecuteProcedureMultipleAsync<Customer, Order>("sp_GetCustomersAndOrders");
		
		Console.WriteLine($"Retrieved {customers.Count()} customers and {orders.Count()} orders.");
	}
}
Method: ExecuteProcedureMultipleAsync<TFirst, TSecond, TThird>

Description: Asynchronously executes a stored procedure and returns multiple result sets as a tuple with three types.

Signature:

public Task<(IEnumerable<TFirst>, IEnumerable<TSecond>, IEnumerable<TThird>)> ExecuteProcedureMultipleAsync<TFirst, TSecond, TThird>(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var (customers, orders, products) = await dapper.ExecuteProcedureMultipleAsync<Customer, Order, Product>("sp_GetCustomersOrdersProducts");
		
		Console.WriteLine($"Retrieved {customers.Count()} customers, {orders.Count()} orders, and {products.Count()} products.");
	}
}
Method: ExecuteProcedureMultipleResult

Description: Executes a stored procedure and returns a GridReader for flexible result set processing.

Signature:

public (Dapper.SqlMapper.GridReader, IDbConnection) ExecuteProcedureMultipleResult(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using System.Data;
using Dapper;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var (grid, connection) = dapper.ExecuteProcedureMultipleResult("sp_GetMultipleResults");
		
		try
		{
			var customers = grid.Read<Customer>().ToList();
			var orders = grid.Read<Order>().ToList();
			var products = grid.Read<Product>().ToList();
			
			Console.WriteLine($"Retrieved {customers.Count} customers, {orders.Count} orders, and {products.Count} products.");
		}
		finally
		{
			// Important: Close the connection when done to avoid leaks
			connection.Close();
		}
	}
}
Method: ExecuteProcedureMultipleResultAsync

Description: Asynchronously executes a stored procedure and returns a GridReader for flexible result set processing.

Signature:

public Task<(Dapper.SqlMapper.GridReader, IDbConnection)> ExecuteProcedureMultipleResultAsync(string procedureName, object param = null)

Example:

using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Dapper;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static async Task Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		
		var (grid, connection) = await dapper.ExecuteProcedureMultipleResultAsync("sp_GetMultipleResults");
		
		try
		{
			var customers = grid.Read<Customer>().ToList();
			var orders = grid.Read<Order>().ToList();
			var products = grid.Read<Product>().ToList();
			
			Console.WriteLine($"Retrieved {customers.Count} customers, {orders.Count} orders, and {products.Count} products.");
		}
		finally
		{
			// Important: Close the connection when done to avoid leaks
			connection.Close();
		}
	}
}
Query Analytics

DapperHelper provides query analytics to help you understand the performance of your queries.

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string");
		dapper.EnableQueryAnalytics = true;
		
		var customers = dapper.ExecuteProcedure<Customer>("sp_GetCustomers");
		
		Console.WriteLine($"Query execution time: {dapper.LastQueryDuration.TotalMilliseconds} ms");
	}
}
Retry Policy

DapperHelper can be configured with retry policies to handle transient failures.

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		DapperHelper dapper = new DapperHelper("connection_string")
		{
			RetryCount = 3,
			RetryIntervalMilliseconds = 500
		};
		
		try
		{
			var result = dapper.ExecuteProcedure<Customer>("sp_GetCustomer", new { ID = 1 });
			Console.WriteLine("Query executed successfully with retry policy.");
		}
		catch (Exception ex)
		{
			Console.WriteLine($"Query failed after {dapper.RetryCount} attempts: {ex.Message}");
		}
	}




### HttpClientHelper
**Description:** Provides a wrapper around HttpClient with additional features.

**Namespace:** `CodeMatrix.AspNetCore.Utilities.Helpers`

#### HttpClientHelper Configuration
The `HttpClientHelper` can be configured with base address, timeout, and other options.

**Example:**
```csharp
using System;
using CodeMatrix.AspNetCore.Utilities.Helpers;
using CodeMatrix.AspNetCore.Utilities.Options;

public class Program
{
	public static void Main()
	{
		var options = new HttpClientOptions
		{
			BaseAddress = "https://api.example.com",
			DefaultTimeoutMinutes = 2
		};
		
		var httpClientHelper = new HttpClientHelper(options);
		Console.WriteLine("HttpClientHelper configured.");
	}
}
Authentication

HttpClientHelper can handle authentication automatically.

Example:

using System;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;
using CodeMatrix.AspNetCore.Utilities.Options;

public class Program
{
	public static async Task Main()
	{
		var options = new HttpClientOptions
		{
			BaseAddress = "https://api.example.com",
			DefaultTimeoutMinutes = 2
		};
		
		var httpClientHelper = new HttpClientHelper(options);
		httpClientHelper.SetBearerToken("your_token_here");
		
		var response = await httpClientHelper.GetAsync<MyResponseType>("https://api.example.com/protected-endpoint");
		Console.WriteLine($"Response received: {response.IsSuccess}");
	}
}
Custom Headers

You can add custom headers to requests easily.

Example:

using System;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Helpers;
using CodeMatrix.AspNetCore.Utilities.Options;

public class Program
{
	public static async Task Main()
	{
		var options = new HttpClientOptions
		{
			BaseAddress = "https://api.example.com",
			DefaultTimeoutMinutes = 2
		};
		
		var httpClientHelper = new HttpClientHelper(options);
		httpClientHelper.AddDefaultHeader("X-Custom-Header", "HeaderValue");
		
		var response = await httpClientHelper.GetAsync<MyResponseType>("https://api.example.com/endpoint-with-header");
		Console.WriteLine($"Response status: {response.IsSuccess}");
	}
}

JsonOptionsProvider

Description: Provides a centralized way to handle JSON serialization options.

Namespace: CodeMatrix.AspNetCore.Utilities.Helpers

Usage:

The JsonOptionsProvider provides consistent JSON serialization options across an application.

Example:

using System;
using System.Text.Json;
using CodeMatrix.AspNetCore.Utilities.Helpers;

public class Program
{
	public static void Main()
	{
		JsonSerializerOptions options = JsonOptionsProvider.DefaultOptions;
		
		string json = JsonSerializer.Serialize(new { Name = "Test" }, options);
		Console.WriteLine(json);
	}
}

Guard

Description: Provides guard clause methods for validating method arguments. Reduces boilerplate validation code and ensures consistent error handling.

Namespace: CodeMatrix.AspNetCore.Utilities.Helpers

Usage:

Guard clauses are used at the beginning of methods to validate inputs and fail fast with clear error messages. They use CallerArgumentExpression to automatically capture parameter names.

Methods:

NotNull<T>(T? value, string? paramName)

  • Ensures value is not null
  • Throws ArgumentNullException

NotNullOrEmpty(string? value, string? paramName)

  • Ensures string is not null or empty
  • Throws ArgumentException

NotNullOrWhiteSpace(string? value, string? paramName)

  • Ensures string is not null, empty, or whitespace
  • Throws ArgumentException

NotNullOrEmpty<T>(IEnumerable<T>? value, string? paramName)

  • Ensures collection is not null or empty
  • Throws ArgumentException

NotDefault<T>(T value, string? paramName)

  • Ensures value is not the default for its type
  • Throws ArgumentException

NotNegative(int/long/decimal value, string? paramName)

  • Ensures numeric value is not negative (>= 0)
  • Throws ArgumentOutOfRangeException

Positive(int/long value, string? paramName)

  • Ensures numeric value is positive (> 0)
  • Throws ArgumentOutOfRangeException

InRange(int/long value, min, max, string? paramName)

  • Ensures value is within specified range (inclusive)
  • Throws ArgumentOutOfRangeException

Against(bool condition, string message)

  • Ensures condition is false
  • Throws ArgumentException

Requires(bool condition, string paramName, string message)

  • Ensures condition is true
  • Throws ArgumentException

Example:

using CodeMatrix.AspNetCore.Utilities.Helpers;

public class UserService
{
	private readonly IUserRepository _repository;
	
	public UserService(IUserRepository repository)
	{
		_repository = Guard.NotNull(repository);
	}
	
	public User CreateUser(string email, string name, int age, List<string> roles)
	{
		// Guard clauses with automatic parameter name capture
		Guard.NotNullOrWhiteSpace(email);
		Guard.NotNullOrEmpty(name);
		Guard.InRange(age, 18, 120);
		Guard.NotNullOrEmpty(roles);
		
		// Custom validation
		Guard.Against(email.Contains(" "), "Email cannot contain spaces");
		Guard.Requires(age >= 18, nameof(age), "User must be 18 or older");
		
		var user = new User
		{
			Email = email,
			Name = name,
			Age = age,
			Roles = roles
		};
		
		return _repository.Add(user);
	}
	
	public void ProcessPayment(decimal amount)
	{
		Guard.Positive(amount); // Ensures amount > 0
		
		// Process payment logic...
	}
	
	public void UpdateUserAge(int userId, int newAge)
	{
		Guard.Positive(userId);
		Guard.NotNegative(newAge); // Allows 0, unlike Positive
		Guard.InRange(newAge, 0, 150);
		
		// Update logic...
	}
}

// Exception messages include actual parameter names:
// "Value cannot be null. (Parameter 'repository')"
// "Value cannot be null, empty, or whitespace. (Parameter 'email')"
// "Value must be between 18 and 120. (Parameter 'age')"

CacheService

Description: Default implementation of ICacheService supporting both in-memory and distributed caching with a unified API.

Namespace: CodeMatrix.AspNetCore.Utilities.Helpers

Usage:

CacheService provides a simple abstraction over IMemoryCache and IDistributedCache, allowing you to swap cache implementations without changing application code.

Constructors:

// For in-memory caching
public CacheService(IMemoryCache memoryCache, TimeSpan? defaultExpiration = null)

// For distributed caching (Redis, SQL Server, etc.)
public CacheService(IDistributedCache distributedCache, TimeSpan? defaultExpiration = null)

Methods:

  • GetOrSetAsync<T> - Gets cached value or sets it using factory function
  • GetAsync<T> - Gets a cached value
  • SetAsync<T> - Sets a cached value with optional expiration
  • RemoveAsync - Removes a cached value
  • ExistsAsync - Checks if a key exists
  • RefreshAsync - Refreshes expiration time (distributed cache only)
  • RemoveByPrefixAsync - Removes all keys with prefix (limited support)

Example:

using CodeMatrix.AspNetCore.Utilities.Helpers;
using CodeMatrix.AspNetCore.Utilities.Interfaces;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;

// Startup configuration
public void ConfigureServices(IServiceCollection services)
{
	// Option 1: In-memory caching
	services.AddMemoryCache();
	services.AddSingleton<ICacheService>(sp => 
		new CacheService(sp.GetRequiredService<IMemoryCache>(), TimeSpan.FromMinutes(30)));
	
	// Option 2: Distributed caching (Redis example)
	services.AddStackExchangeRedisCache(options =>
	{
		options.Configuration = "localhost:6379";
	});
	services.AddSingleton<ICacheService>(sp => 
		new CacheService(sp.GetRequiredService<IDistributedCache>(), TimeSpan.FromMinutes(30)));
}

// Usage in a service
public class ProductService
{
	private readonly ICacheService _cache;
	private readonly IProductRepository _repository;
	
	public ProductService(ICacheService cache, IProductRepository repository)
	{
		_cache = cache;
		_repository = repository;
	}
	
	public async Task<Product?> GetProductAsync(int id)
	{
		var cacheKey = $"product:{id}";
		
		// Get from cache or fetch from database
		return await _cache.GetOrSetAsync(
			key: cacheKey,
			factory: async () => await _repository.GetByIdAsync(id),
			expiration: TimeSpan.FromMinutes(10));
	}
	
	public async Task<List<Product>> GetFeaturedProductsAsync()
	{
		var cacheKey = "products:featured";
		
		// Check if cached
		var cached = await _cache.GetAsync<List<Product>>(cacheKey);
		if (cached != null)
			return cached;
		
		// Fetch and cache
		var products = await _repository.GetFeaturedAsync();
		await _cache.SetAsync(cacheKey, products, TimeSpan.FromHours(1));
		
		return products;
	}
	
	public async Task UpdateProductAsync(Product product)
	{
		await _repository.UpdateAsync(product);
		
		// Invalidate cache
		await _cache.RemoveAsync($"product:{product.Id}");
	}
	
	public async Task<bool> IsProductCachedAsync(int id)
	{
		return await _cache.ExistsAsync($"product:{id}");
	}
}

// Advanced: Cache decorator pattern
public class CachedProductService : IProductService
{
	private readonly IProductService _inner;
	private readonly ICacheService _cache;
	
	public CachedProductService(IProductService inner, ICacheService cache)
	{
		_inner = inner;
		_cache = cache;
	}
	
	public Task<Product?> GetByIdAsync(int id) =>
		_cache.GetOrSetAsync(
			$"product:{id}",
			() => _inner.GetByIdAsync(id),
			TimeSpan.FromMinutes(15));
}

Interfaces

IHttpClientHelper

Description: Interface defining the contract for HTTP client helpers.

Namespace: CodeMatrix.AspNetCore.Utilities.Interfaces

Usage:

The IHttpClientHelper interface defines methods for making HTTP requests. It can be used for dependency injection and testing.

Key Methods:

  • GetAsync<T>
  • PostAsync<T>
  • PutAsync<T>
  • DeleteAsync<T>

Example:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using CodeMatrix.AspNetCore.Utilities.Interfaces;

public class Program
{
	public static async Task Main()
	{
		var serviceProvider = new ServiceCollection()
			.AddHttpClientHelper()
			.BuildServiceProvider();
			
		var httpHelper = serviceProvider.GetRequiredService<IHttpClientHelper>();
		var response = await httpHelper.GetAsync<MyResponseType>("https://api.example.com/endpoint");
		
		Console.WriteLine($"Response received: {response.IsSuccess}");
	}
}

ICacheService

Description: Provides an abstraction for caching operations with support for multiple cache backends (in-memory, Redis, SQL Server, etc.).

Namespace: CodeMatrix.AspNetCore.Utilities.Interfaces

Usage:

The ICacheService interface defines a unified API for caching that works with any cache provider, making it easy to swap implementations without changing application code.

Methods:

GetOrSetAsync<T>

Task<T?> GetOrSetAsync<T>(string key, Func<Task<T>> factory, TimeSpan? expiration = null, CancellationToken cancellationToken = default)

Gets a value from cache, or generates and stores it using the factory function if not found.

GetAsync<T>

Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default)

Gets a value from the cache.

SetAsync<T>

Task SetAsync<T>(string key, T value, TimeSpan? expiration = null, CancellationToken cancellationToken = default)

Sets a value in the cache with optional expiration.

RemoveAsync

Task RemoveAsync(string key, CancellationToken cancellationToken = default)

Removes a value from the cache.

RemoveByPrefixAsync

Task RemoveByPrefixAsync(string prefix, CancellationToken cancellationToken = default)

Removes all cache entries with keys starting with the specified prefix. Note: Support varies by cache provider.

ExistsAsync

Task<bool> ExistsAsync(string key, CancellationToken cancellationToken = default)

Checks if a key exists in the cache.

RefreshAsync

Task RefreshAsync(string key, CancellationToken cancellationToken = default)

Refreshes the expiration time for a cached item (primarily for distributed caches).

Example:

using CodeMatrix.AspNetCore.Utilities.Interfaces;

public class WeatherService
{
	private readonly ICacheService _cache;
	private readonly IWeatherApi _weatherApi;
	
	public WeatherService(ICacheService cache, IWeatherApi weatherApi)
	{
		_cache = cache;
		_weatherApi = weatherApi;
	}
	
	public async Task<WeatherData> GetWeatherAsync(string city, CancellationToken ct = default)
	{
		var cacheKey = $"weather:{city.ToLower()}";
		
		// Cache for 15 minutes
		return await _cache.GetOrSetAsync(
			key: cacheKey,
			factory: () => _weatherApi.FetchWeatherAsync(city, ct),
			expiration: TimeSpan.FromMinutes(15),
			cancellationToken: ct);
	}
	
	public async Task ClearWeatherCacheAsync(string city)
	{
		await _cache.RemoveAsync($"weather:{city.ToLower()}");
	}
	
	public async Task ClearAllWeatherCacheAsync()
	{
		// Remove all weather-related cache entries
		await _cache.RemoveByPrefixAsync("weather:");
	}
}

Testing with ICacheService:

using Moq;
using Xunit;

public class WeatherServiceTests
{
	[Fact]
	public async Task GetWeatherAsync_ShouldUseCachedValue()
	{
		// Arrange
		var mockCache = new Mock<ICacheService>();
		var mockApi = new Mock<IWeatherApi>();
		
		var cachedWeather = new WeatherData { Temperature = 25 };
		mockCache.Setup(x => x.GetAsync<WeatherData>("weather:london", default))
			.ReturnsAsync(cachedWeather);
		
		var service = new WeatherService(mockCache.Object, mockApi.Object);
		
		// Act
		var result = await service.GetWeatherAsync("London");
		
		// Assert
		Assert.Equal(25, result.Temperature);
		mockApi.Verify(x => x.FetchWeatherAsync(It.IsAny<string>(), default), Times.Never);
	}
}

Models

ApiResult<T>

Description: Represents the result of an API operation.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Usage:

The ApiResult<T> class provides a standardized way to return results from API controllers.

Properties:

  • IsSuccess (bool)
  • Message (string)
  • Data (T)
  • StatusCode (int)
  • Exception (Exception)

Example:

using System;
using Microsoft.AspNetCore.Mvc;
using CodeMatrix.AspNetCore.Utilities.Models;

[ApiController]
[Route("api/[controller]")]
public class CustomersController : ControllerBase
{
	[HttpGet]
	public ActionResult<ApiResult<List<Customer>>> Get()
	{
		try
		{
			var customers = GetCustomers();
			return new ApiResult<List<Customer>>
			{
				IsSuccess = true,
				Data = customers,
				Message = "Customers retrieved successfully"
			};
		}
		catch (Exception ex)
		{
			return new ApiResult<List<Customer>>
			{
				IsSuccess = false,
				Message = "Failed to retrieve customers",
				Exception = ex
			};
		}
	}
}

HttpResult<T>

Description: Represents the result of an HTTP operation, extending ApiResult<T> with HTTP status code information.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Inheritance: Extends ApiResult<T>

Usage:

The HttpResult<T> class is specifically designed for HTTP client operations and includes detailed status code information along with response data.

Properties:

  • IsSuccessful (bool) - Indicates if the HTTP request was successful
  • Message (string) - Details about the operation result
  • Data (T) - The response data from the HTTP operation
  • StatusCode (int) - The HTTP status code returned by the server

Static Methods:

  • Success<T>(T data, string message, int statusCode) - Creates a successful result
  • Error<T>(T data, string message, int statusCode) - Creates an error result
  • Error<T>(string message) - Creates an error result with empty data

Example with HttpClientHelper:

using System;
using System.Threading.Tasks;
using CodeMatrix.AspNetCore.Utilities.Interfaces;
using CodeMatrix.AspNetCore.Utilities.Models;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class ProxyController : ControllerBase
{
	private readonly IHttpClientHelper _httpClient;
	
	public ProxyController(IHttpClientHelper httpClient)
	{
		_httpClient = httpClient;
	}
	
	[HttpGet("fetch-data")]
	public async Task<ActionResult<ApiResult<string>>> FetchData(string url)
	{
		try
		{
			// HttpClientHelper methods now return HttpResult<T>
			HttpResult<string> result = await _httpClient.InvokeGetAsync(
				url, 
				headers: null, 
				timeoutInMinutes: 5
			);
			
			if (result.IsSuccessful)
			{
				return Ok(new ApiResult<string>
				{
					IsSuccessful = true,
					Data = result.Data,
					Message = $"Data fetched successfully (HTTP {result.StatusCode})"
				});
			}
			else
			{
				return Ok(new ApiResult<string>
				{
					IsSuccessful = false,
					Data = result.Data,
					Message = $"Failed to fetch data: {result.Message} (HTTP {result.StatusCode})"
				});
			}
		}
		catch (Exception ex)
		{
			return Ok(new ApiResult<string>
			{
				IsSuccessful = false,
				Message = $"Error occurred: {ex.Message}"
			});
		}
	}
	
	[HttpPost("post-data")]
	public async Task<ActionResult<ApiResult<string>>> PostData(string url, [FromBody] string jsonBody)
	{
		try
		{
			var headers = new Dictionary<string, string>
			{
				{ "X-Custom-Header", "CustomValue" }
			};
			
			HttpResult<string> result = await _httpClient.InvokePostAsync(
				url, 
				jsonBody, 
				headers, 
				timeoutInMinutes: 5
			);
			
			return result.IsSuccessful 
				? Ok(new ApiResult<string>
				{
					IsSuccessful = true,
					Data = result.Data,
					Message = "Post successful"
				})
				: StatusCode(result.StatusCode, new ApiResult<string>
				{
					IsSuccessful = false,
					Data = result.Data,
					Message = result.Message
				});
		}
		catch (Exception ex)
		{
			return StatusCode(500, new ApiResult<string>
			{
				IsSuccessful = false,
				Message = $"Error occurred: {ex.Message}"
			});
		}
	}
	
	[HttpGet("fetch-bytes")]
	public async Task<ActionResult<ApiResult<byte[]>>> FetchBytes(string url)
	{
		try
		{
			HttpResult<byte[]> result = await _httpClient.InvokeGetBytesAsync(
				url, 
				queryParams: "", 
				timeoutInMinutes: 10
			);
			
			if (result.IsSuccessful)
			{
				return Ok(new ApiResult<byte[]>
				{
					IsSuccessful = true,
					Data = result.Data,
					Message = $"Binary data fetched successfully ({result.Data.Length} bytes)"
				});
			}
			else
			{
				return StatusCode(result.StatusCode, new ApiResult<byte[]>
				{
					IsSuccessful = false,
					Message = result.Message
				});
			}
		}
		catch (Exception ex)
		{
			return StatusCode(500, new ApiResult<byte[]>
			{
				IsSuccessful = false,
				Message = $"Error occurred: {ex.Message}"
			});
		}
	}
}

Available HttpClientHelper Methods Returning HttpResult:

All HttpClientHelper methods now return HttpResult<T> for better error handling and response details:

  1. InvokeGetAsync - Returns HttpResult<string> for GET requests
  2. InvokeGetBytesAsync - Returns HttpResult<byte[]> for binary GET requests
  3. InvokePostAsync (JSON) - Returns HttpResult<string> for JSON POST requests
  4. InvokePostAsync (MultipartFormDataContent) - Returns HttpResult<string> for multipart POST requests
  5. InvokePostAsync (byte array) - Returns HttpResult<string> for byte array POST requests
  6. InvokePostBytesAsync - Returns HttpResult<byte[]> for POST requests returning binary data
  7. InvokeXmlPostAsync - Returns HttpResult<string> for XML POST requests
  8. InvokeSendAsync - Returns HttpResult<Stream> for stream-based POST requests

Each method includes:

  • Comprehensive error handling without throwing exceptions
  • Detailed HTTP status codes for all responses
  • Meaningful error messages for failures
  • Structured response data in the Data property
  • Success flag (IsSuccessful) for easy result checking

HttpClientOptions

Description: Configuration options for HTTP client behavior including compression, SSL verification, timeouts, and retry policies.

Namespace: CodeMatrix.AspNetCore.Utilities.Options

Properties:

Property Type Default Description
DefaultTimeoutMinutes int 5 Request timeout in minutes. Set to 0 or negative to disable timeout.
EnableCompression bool true Automatically decompress GZIP, Deflate, and Brotli responses for better performance.
VerifySsl bool true Verify SSL/TLS certificates. ALWAYS true in production. Only set false for dev/test with self-signed certs.
MaxRetryAttempts int 3 Number of retry attempts for transient failures (requires Polly integration).
RetryDelaySeconds int 1 Initial retry delay with exponential backoff (requires Polly integration).
ClientNames List<string> ["Default", "XML"] Named client configurations registered with IHttpClientFactory.

Example with All Features:

using CodeMatrix.AspNetCore.Utilities.DependencyInjection;
using CodeMatrix.AspNetCore.Utilities.Options;

var services = new ServiceCollection();

services.AddHttpClientHelper(options =>
{
	// Network Configuration
	options.DefaultTimeoutMinutes = 5;      // 5-minute timeout for all requests
	
	// Performance
	options.EnableCompression = true;       // Enable automatic decompression
	
	// Security
	options.VerifySsl = true;               // Always verify SSL in production
	
	// Resilience (when Polly is available)
	options.MaxRetryAttempts = 3;           // Retry transient failures up to 3 times
	options.RetryDelaySeconds = 1;          // Start with 1 second, use exponential backoff
	
	// Registered client names - each gets own HttpClient configuration
	// Default clients include "Default" and "XML"
});

// For development with self-signed certificates only:
if (IsDevelopment)
{
	services.AddHttpClientHelper(options =>
	{
		options.VerifySsl = false;  // ONLY for dev/test environments!
		options.EnableCompression = true;
	});
}

Compression Details:

  • When EnableCompression = true, the handler automatically decompresses responses
  • Supported compression algorithms: GZIP, Deflate, Brotli
  • Improves performance for large responses by reducing bandwidth
  • Compatible with most modern APIs

SSL Verification Details:

  • VerifySsl = true (default): Validates server SSL certificates (production recommended)
  • VerifySsl = false: Accepts all certificates (dev/test only with self-signed certs)
  • Warning: Disabling SSL verification in production creates security vulnerabilities

Retry Policy Details (Polly integration):

  • Retries on HTTP 5xx errors (server errors)
  • Retries on connection failures (HttpRequestException)
  • Uses exponential backoff: delay = retryDelaySeconds ^ retryAttempt
  • Example: With 1s delay, retry delays are: 1s, 2s, 4s, 8s...

AppConstants

Description: Provides application-wide constants.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Usage:

The AppConstants class contains constants used throughout the application.

Example:

using System;
using CodeMatrix.AspNetCore.Utilities.Models;

public class Program
{
	public static void Main()
	{
		Console.WriteLine($"Date format: {AppConstants.DefaultDateFormat}");
	}
}

RegexConstants

Description: Provides commonly used regular expression patterns.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Usage:

The RegexConstants class contains predefined regular expression patterns for common validation scenarios.

Example:

using System;
using System.Text.RegularExpressions;
using CodeMatrix.AspNetCore.Utilities.Models;

public class Program
{
	public static void Main()
	{
		string email = "test@example.com";
		bool isValid = Regex.IsMatch(email, RegexConstants.EmailPattern);
		
		Console.WriteLine($"Is valid email: {isValid}");
	}
}

Error

Description: Represents an error with code, message, and optional details. Provides factory methods for common error types.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Usage:

The Error class is used as part of the Result pattern for functional error handling without exceptions.

Properties:

  • Code (string) - The error code for categorization
  • Message (string) - The error message describing what went wrong
  • Details (string?) - Optional additional details about the error

Static Properties:

  • None - Represents no error (success state)
  • NullValue - Represents a null value error

Factory Methods:

// Create custom error
var error = Error.Create("Custom.Code", "Custom message", "Optional details");

// Create validation error
var validationError = Error.Validation("Name is required");

// Create not found error
var notFoundError = Error.NotFound("User not found");

// Create unauthorized error
var unauthorizedError = Error.Unauthorized("Access denied");

// Create conflict error
var conflictError = Error.Conflict("Email already exists");

// Create failure error
var failureError = Error.Failure("Operation failed");

Example:

using CodeMatrix.AspNetCore.Utilities.Models;

public class UserService
{
	public Result<User> GetUser(int id)
	{
		var user = _repository.FindById(id);
		
		if (user == null)
			return Result<User>.Failure(Error.NotFound($"User with ID {id} not found"));
		
		return Result<User>.Success(user);
	}
	
	public Result<User> CreateUser(CreateUserRequest request)
	{
		if (string.IsNullOrEmpty(request.Email))
			return Result<User>.Failure(Error.Validation("Email is required"));
		
		if (_repository.EmailExists(request.Email))
			return Result<User>.Failure(Error.Conflict("Email already exists"));
		
		var user = new User { Email = request.Email };
		_repository.Add(user);
		
		return Result<User>.Success(user);
	}
}

Result<T>

Description: Represents the result of an operation that can either succeed with a value or fail with an error. Implements the Result pattern for functional error handling without exceptions.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Usage:

The Result<T> class eliminates the need for try-catch blocks and makes error handling explicit through the type system. It's particularly useful for domain logic and business operations.

Properties:

  • IsSuccess (bool) - Whether the operation succeeded
  • IsFailure (bool) - Whether the operation failed
  • Value (T) - The success value (throws if accessed on failure)
  • Error (Error) - The error (throws if accessed on success)

Static Methods:

  • Success(T value) - Creates a successful result
  • Failure(Error error) - Creates a failed result
  • Failure(string code, string message, string? details) - Creates a failed result with error details

Functional Methods:

  • Map<TNew>(Func<T, TNew> mapper) - Transforms the value if successful
  • Bind<TNew>(Func<T, Result<TNew>> binder) - Chains operations that return results
  • Tap(Action<T> action) - Executes a side effect without changing the result
  • Match<TResult>(Func<T, TResult> onSuccess, Func<Error, TResult> onFailure) - Pattern matches on success/failure

Example:

using CodeMatrix.AspNetCore.Utilities.Models;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
	private readonly IUserService _userService;
	
	public UsersController(IUserService userService)
	{
		_userService = userService;
	}
	
	[HttpGet("{id}")]
	public IActionResult GetUser(int id)
	{
		var result = _userService.GetUser(id);
		
		// Pattern matching approach
		return result.Match<IActionResult>(
			onSuccess: user => Ok(user),
			onFailure: error => error.Code switch
			{
				"Error.NotFound" => NotFound(error.Message),
				"Error.Unauthorized" => Unauthorized(error.Message),
				_ => BadRequest(error.Message)
			});
	}
	
	[HttpPost]
	public IActionResult CreateUser(CreateUserRequest request)
	{
		// Chaining operations with Bind
		var result = ValidateRequest(request)
			.Bind(validRequest => _userService.CreateUser(validRequest))
			.Tap(user => _logger.LogInformation($"User created: {user.Id}"))
			.Map(user => new UserDto { Id = user.Id, Email = user.Email });
		
		if (result.IsFailure)
			return BadRequest(result.Error.Message);
		
		return CreatedAtAction(nameof(GetUser), new { id = result.Value.Id }, result.Value);
	}
	
	private Result<CreateUserRequest> ValidateRequest(CreateUserRequest request)
	{
		if (string.IsNullOrEmpty(request.Email))
			return Error.Validation("Email is required");
		
		if (!IsValidEmail(request.Email))
			return Error.Validation("Invalid email format");
		
		return request;
	}
}

Non-Generic Result:

For operations that don't return a value:

public Result DeleteUser(int id)
{
	var user = _repository.FindById(id);
	
	if (user == null)
		return Result.Failure(Error.NotFound("User not found"));
	
	_repository.Delete(user);
	return Result.Success();
}

PagedResult<T>

Description: Represents a paginated result set with comprehensive metadata about pagination state.

Namespace: CodeMatrix.AspNetCore.Utilities.Models

Usage:

The PagedResult<T> class provides a standardized way to return paginated data from APIs with all necessary pagination information.

Properties:

  • Items (IEnumerable<T>) - The items in the current page
  • Page (int) - Current page number (1-based)
  • PageSize (int) - Number of items per page
  • TotalCount (int) - Total number of items across all pages
  • TotalPages (int) - Total number of pages
  • HasPreviousPage (bool) - Whether there's a previous page
  • HasNextPage (bool) - Whether there's a next page
  • IsFirstPage (bool) - Whether this is the first page
  • IsLastPage (bool) - Whether this is the last page
  • ItemCount (int) - Number of items in current page
  • FirstItemIndex (int) - Index of first item (1-based)
  • LastItemIndex (int) - Index of last item (1-based)

Static Methods:

  • Empty(int page, int pageSize) - Creates an empty result
  • Create(IEnumerable<T> source, int page, int pageSize) - Creates from a collection

Example:

using CodeMatrix.AspNetCore.Utilities.Models;
using CodeMatrix.AspNetCore.Utilities.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
	private readonly ApplicationDbContext _context;
	
	public ProductsController(ApplicationDbContext context)
	{
		_context = context;
	}
	
	[HttpGet]
	public async Task<ActionResult<PagedResult<Product>>> GetProducts(
		[FromQuery] int page = 1,
		[FromQuery] int pageSize = 10,
		[FromQuery] string? category = null)
	{
		var query = _context.Products.AsQueryable();
		
		if (!string.IsNullOrEmpty(category))
			query = query.Where(p => p.Category == category);
		
		query = query.OrderBy(p => p.Name);
		
		// Use extension method for automatic pagination
		var result = await query.ToPagedResultAsync(page, pageSize);
		
		return Ok(result);
	}
	
	[HttpGet("manual")]
	public ActionResult<PagedResult<Product>> GetProductsManual()
	{
		var allProducts = GetAllProducts(); // In-memory collection
		
		// Create paged result from collection
		var result = PagedResult<Product>.Create(allProducts, page: 1, pageSize: 20);
		
		return Ok(result);
	}
}

// API Response example:
// {
//   "items": [...],
//   "page": 1,
//   "pageSize": 10,
//   "totalCount": 150,
//   "totalPages": 15,
//   "hasPreviousPage": false,
//   "hasNextPage": true,
//   "isFirstPage": true,
//   "isLastPage": false,
//   "itemCount": 10,
//   "firstItemIndex": 1,
//   "lastItemIndex": 10
// }

DependencyInjection

HttpClientHelperExtensions

Description: Extension methods for registering HttpClientHelper services in the dependency injection container.

Namespace: CodeMatrix.AspNetCore.Utilities.DependencyInjection

Usage:

The HttpClientHelperExtensions class provides methods to register the HttpClientHelper service with the dependency injection container, along with the necessary HttpClient instances.

Key Methods:

  • AddHttpClientHelper(IServiceCollection services) - Registers the HttpClientHelper with default options.
  • AddHttpClientHelper(IServiceCollection services, Action<HttpClientOptions> configureOptions) - Registers the HttpClientHelper with custom options.

Example:

using CodeMatrix.AspNetCore.Utilities.DependencyInjection;

// In Program.cs or Startup.cs
public void ConfigureServices(IServiceCollection services)
{
	// Add HttpClientHelper with default options
	services.AddHttpClientHelper();
	
	// Or with custom configuration
	services.AddHttpClientHelper(options => 
	{
		options.BaseAddress = "https://api.example.com";
		options.Timeout = TimeSpan.FromSeconds(30);
		options.DefaultHeaders.Add("X-API-Key", "your-api-key");
	});
}

JsonOptionsExtensions

Description: Extension methods for configuring JSON options in ASP.NET Core applications.

Namespace: CodeMatrix.AspNetCore.Utilities.DependencyInjection

Usage:

The JsonOptionsExtensions class provides methods to configure JSON serialization options for ASP.NET Core applications.

Key Methods:

  • AddCodeMatrixJsonOptions(IMvcBuilder builder) - Adds CodeMatrix JSON options to the MVC builder.
  • AddCodeMatrixJsonOptions(IMvcCoreBuilder builder) - Adds CodeMatrix JSON options to the MVC Core builder.

Example:

using CodeMatrix.AspNetCore.Utilities.DependencyInjection;

// In Program.cs or Startup.cs
public void ConfigureServices(IServiceCollection services)
{
	services.AddControllers()
		.AddCodeMatrixJsonOptions();
}

HealthCheckExtensions

Description: Extension methods for configuring health checks with support for SQL Server/Dapper databases and HTTP endpoints.

Namespace: CodeMatrix.AspNetCore.Utilities.DependencyInjection

Usage:

The HealthCheckExtensions class provides convenient methods to add health checks for database connectivity, HTTP endpoints, and custom health checks to your application.

Methods:

AddSqlServerHealthCheck

public static IHealthChecksBuilder AddSqlServerHealthCheck(
	this IHealthChecksBuilder builder,
	string connectionString,
	string name = "sqlserver",
	HealthStatus? failureStatus = null,
	IEnumerable<string>? tags = null)

Adds a health check for SQL Server database connectivity.

AddDapperHealthCheck

public static IHealthChecksBuilder AddDapperHealthCheck(
	this IHealthChecksBuilder builder,
	string connectionString,
	string name = "dapper",
	HealthStatus? failureStatus = null,
	IEnumerable<string>? tags = null)

Adds a health check for Dapper-based database operations. Alias for AddSqlServerHealthCheck with appropriate naming.

AddHttpClientHealthCheck

public static IHealthChecksBuilder AddHttpClientHealthCheck(
	this IHealthChecksBuilder builder,
	string url,
	string? name = null,
	HealthStatus? failureStatus = null,
	IEnumerable<string>? tags = null,
	TimeSpan? timeout = null)

Adds a health check for an HTTP endpoint. Checks if the endpoint returns a successful status code.

AddHttpClientHealthChecks

public static IHealthChecksBuilder AddHttpClientHealthChecks(
	this IHealthChecksBuilder builder,
	Dictionary<string, string> endpoints,
	HealthStatus? failureStatus = null,
	IEnumerable<string>? tags = null,
	TimeSpan? timeout = null)

Adds multiple HTTP endpoint health checks in one call.

AddCustomHealthCheck

public static IHealthChecksBuilder AddCustomHealthCheck(
	this IHealthChecksBuilder builder,
	string name,
	Func<Task<HealthCheckResult>> check,
	HealthStatus? failureStatus = null,
	IEnumerable<string>? tags = null)

Adds a custom health check using a delegate function.

Example:

using CodeMatrix.AspNetCore.Utilities.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;

// In Program.cs or Startup.cs
public void ConfigureServices(IServiceCollection services)
{
	services.AddHealthChecks()
		// Database health checks
		.AddSqlServerHealthCheck(
			connectionString: Configuration.GetConnectionString("DefaultConnection"),
			name: "database",
			tags: new[] { "db", "sql" })
		
		// Dapper-specific health check (same as SQL Server, but clearer naming)
		.AddDapperHealthCheck(
			connectionString: Configuration.GetConnectionString("ReportsDB"),
			name: "reports-database",
			tags: new[] { "db", "dapper", "reports" })
		
		// Single HTTP endpoint health check
		.AddHttpClientHealthCheck(
			url: "https://api.external-service.com/health",
			name: "external-api",
			timeout: TimeSpan.FromSeconds(10),
			tags: new[] { "external", "api" })
		
		// Multiple HTTP endpoints
		.AddHttpClientHealthChecks(
			endpoints: new Dictionary<string, string>
			{
				{ "payment-api", "https://payment.service.com/health" },
				{ "notification-api", "https://notification.service.com/health" },
				{ "auth-api", "https://auth.service.com/health" }
			},
			tags: new[] { "external", "dependencies" })
		
		// Custom health check
		.AddCustomHealthCheck(
			name: "cache",
			check: async () =>
			{
				try
				{
					var cache = serviceProvider.GetRequiredService<ICacheService>();
					var testKey = "__health_check__";
					await cache.SetAsync(testKey, "test", TimeSpan.FromSeconds(10));
					var value = await cache.GetAsync<string>(testKey);
					await cache.RemoveAsync(testKey);
					
					return value == "test" 
						? HealthCheckResult.Healthy("Cache is working")
						: HealthCheckResult.Degraded("Cache read/write issue");
				}
				catch (Exception ex)
				{
					return HealthCheckResult.Unhealthy("Cache is down", ex);
				}
			},
			tags: new[] { "cache" });
}

public void Configure(IApplicationBuilder app)
{
	// Basic health check endpoint
	app.UseHealthChecks("/health");
	
	// Detailed health check with JSON response
	app.UseHealthChecks("/health/detailed", new HealthCheckOptions
	{
		ResponseWriter = async (context, report) =>
		{
			context.Response.ContentType = "application/json";
			var response = new
			{
				status = report.Status.ToString(),
				checks = report.Entries.Select(e => new
				{
					name = e.Key,
					status = e.Value.Status.ToString(),
					description = e.Value.Description,
					duration = e.Value.Duration.TotalMilliseconds,
					tags = e.Value.Tags
				}),
				totalDuration = report.TotalDuration.TotalMilliseconds
			};
			await context.Response.WriteAsJsonAsync(response);
		}
	});
	
	// Health check with tag filtering (only check databases)
	app.UseHealthChecks("/health/db", new HealthCheckOptions
	{
		Predicate = check => check.Tags.Contains("db")
	});
}

Health Check Response Examples:

Basic endpoint (/health):

Healthy

Detailed endpoint (/health/detailed):

{
  "status": "Healthy",
  "checks": [
    {
      "name": "database",
      "status": "Healthy",
      "description": "Database connection is healthy.",
      "duration": 45.2,
      "tags": ["db", "sql"]
    },
    {
      "name": "external-api",
      "status": "Healthy",
      "description": "HTTP endpoint https://api.external-service.com/health is healthy. Status: OK",
      "duration": 120.5,
      "tags": ["external", "api"]
    },
    {
      "name": "cache",
      "status": "Healthy",
      "description": "Cache is working",
      "duration": 15.8,
      "tags": ["cache"]
    }
  ],
  "totalDuration": 181.5
}

Integration with Docker/Kubernetes:

# docker-compose.yml
services:
  api:
    image: myapi:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

# Kubernetes deployment.yaml
spec:
  containers:
  - name: api
    image: myapi:latest
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5

Advanced: Custom Health Check Classes

using Microsoft.Extensions.Diagnostics.HealthChecks;
using CodeMatrix.AspNetCore.Utilities.Interfaces;

public class RabbitMQHealthCheck : IHealthCheck
{
	private readonly IRabbitMQConnection _connection;
	
	public RabbitMQHealthCheck(IRabbitMQConnection connection)
	{
		_connection = connection;
	}
	
	public async Task<HealthCheckResult> CheckHealthAsync(
		HealthCheckContext context,
		CancellationToken cancellationToken = default)
	{
		try
		{
			var isConnected = await _connection.IsConnectedAsync(cancellationToken);
			
			if (isConnected)
				return HealthCheckResult.Healthy("RabbitMQ connection is healthy");
			
			return HealthCheckResult.Unhealthy("RabbitMQ is not connected");
		}
		catch (Exception ex)
		{
			return HealthCheckResult.Unhealthy("RabbitMQ health check failed", ex);
		}
	}
}

// Register the custom health check
services.AddHealthChecks()
	.AddCheck<RabbitMQHealthCheck>("rabbitmq", tags: new[] { "messaging" });

Contributing

Contributions are welcome! If you'd like to contribute to this project, please follow these steps:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature-name
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin feature/your-feature-name
  5. Submit a pull request

Please make sure your code follows the existing style and includes appropriate tests and documentation.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Version History

Version 1.0.24

  • New Feature - Database Abstraction Layer:
    • ORM-Agnostic Database Access: Choose between Dapper, Entity Framework Core, or ADO.NET without changing application code
      • Four core abstractions: IDbCommandExecutor, IStoredProcedureExecutor, IAsyncQueryExecutor, IDbConnectionFactory
      • Provider implementations for Dapper, EF Core, and ADO.NET included
      • Conditional compilation based on INCLUDE_DATABASE_FEATURES flag
    • Dependency Injection Extensions: Easy setup with DatabaseExtensions
      • AddDapperDatabase(connectionString) - Register Dapper as the database provider
      • AddEntityFrameworkQueryExecutor() - Register EF Core query executor for pagination
      • AddAdoNetDatabase(connectionString) - Register ADO.NET as the database provider
      • AddDatabaseConnectionFactory(factory) - Register custom connection factory for other databases
    • Provider Implementations:
      • DapperCommandExecutor - Dapper-based implementation using existing Dapper extension methods
      • EFCoreQueryExecutor - Entity Framework Core async query execution wrapper
      • AdoNetCommandExecutor - Raw ADO.NET implementation with full control
      • SqlServerConnectionFactory - SQL Server connection factory with IDbConnection interface
    • Enhanced QueryableExtensions: Pagination now supports custom query executors
      • ToPagedResultAsync(IAsyncQueryExecutor, page, pageSize) - Provider-agnostic pagination
      • Backward-compatible overload for direct EF Core usage
      • Helper methods in DbHelpers for IDataReader mapping
    • Health Check Integration: AddDatabaseHealthCheck() uses IDbConnectionFactory from DI
    • Benefits:
      • 🔄 Switch ORMs without code changes
      • 🧩 Seamless DI integration
      • 🎯 Clean architecture and separation of concerns
      • ✅ Easy unit testing with mocks
      • 📦 No vendor lock-in
  • Version-Specific EF Core Packages:
    • EF Core 8.0.11 for .NET 8.0 targets
    • EF Core 9.0.1 for .NET 9.0 targets
    • Prevents compatibility issues with .NET 10 preview packages
  • Comprehensive Documentation: Added migration guide, testing examples, and provider comparison in README

Version 1.0.23

  • New Features - Modern Development Patterns:
    • Result Pattern: Added Result<T> and Error classes for functional error handling without exceptions
      • Eliminates try-catch chains and makes error handling explicit through the type system
      • Supports functional composition with Map, Bind, Tap, and Match methods
      • Implicit conversions for cleaner syntax
      • Factory methods for common error types (Validation, NotFound, Unauthorized, Conflict, Failure)
    • Guard Clauses: Added Guard helper class with comprehensive argument validation methods
      • Automatic parameter name capture using CallerArgumentExpression
      • Methods: NotNull, NotNullOrEmpty, NotNullOrWhiteSpace, NotDefault, NotNegative, Positive, InRange, Against, Requires
      • Reduces boilerplate validation code by 70-80%
    • Caching Abstractions: Added ICacheService interface and CacheService implementation
      • Unified API for both in-memory and distributed caching (Redis, SQL Server, etc.)
      • Methods: GetOrSetAsync, GetAsync, SetAsync, RemoveAsync, ExistsAsync, RefreshAsync, RemoveByPrefixAsync
      • Easy to mock for unit testing
      • Supports custom expiration times per cache entry
    • Pagination: Added PagedResult<T> model and QueryableExtensions for database pagination
      • Rich pagination metadata (TotalPages, HasNextPage, FirstItemIndex, LastItemIndex, etc.)
      • Extension methods: ToPagedResultAsync, ToPagedResult, Paginate
      • Optimized for Entity Framework Core with async support
      • Standardized API response format for all paginated endpoints
    • Health Checks: Added HealthCheckExtensions for easy health check configuration
      • Methods: AddSqlServerHealthCheck, AddDapperHealthCheck, AddHttpClientHealthCheck, AddHttpClientHealthChecks, AddCustomHealthCheck
      • Built-in health checks for databases and HTTP endpoints
      • Support for custom health check implementations
      • Integration with Docker, Kubernetes, and monitoring tools
  • Comprehensive Documentation: All new features include detailed examples, best practices, and integration patterns
  • Full Test Coverage: Added xUnit + FluentAssertions tests for all new features following existing patterns

Version 1.0.22

  • Major Performance Improvements:
    • DBHelpers: Added ConcurrentDictionary caching for property mappings (100-200x faster for repeated operations)
      • Pre-computed column-to-property mappings eliminate repeated reflection on every row
      • List capacity pre-allocation for ConvertDataTable reduces memory allocations
      • Case-insensitive column name lookups now built-in with StringComparer.OrdinalIgnoreCase
      • Optimized MapToList and MapToItem to use column ordinals instead of name lookups
    • ObjectExtension: Added property info caching for GetPropertiesAsDictionary (10-50x faster)
      • Properties now cached per type using ConcurrentDictionary
      • Dictionary pre-sized with known property count
    • DateTimeJsonConverter: Added compiled regex caching for flexible fractional seconds parsing (5-10x faster)
      • Pre-compiled static regex patterns with RegexOptions.Compiled
      • Eliminated regex compilation overhead on every parse operation
      • Reduced memory allocations during date parsing
    • Thread Safety: All caching implementations use ConcurrentDictionary for thread-safe concurrent access

Version 1.0.21

  • Renamed Core package ID from CodeMatrix.AspNetCore.Utilities.Core to CodeMatrix.AspNetCore.Utilities

Version 1.0.20

  • Refactored HTTP client to use HttpResult<T> model for structured responses including status code information

Version 1.0.19

  • Renamed InvokePostByteAsync to InvokePostBytesAsync for naming consistency

Version 1.0.18

  • Added flexible HTTP GET/POST methods to HttpClientHelper

Version 1.0.17

  • Added DateOnlyJsonConverter with comprehensive tests

Version 1.0.16

  • Updated dependencies
  • Improved test cancellation handling

Version 1.0.15

  • Improved extension method robustness (StreamExtension, StringExtension, ModelStateExtension)
  • Fixed DateTimeJsonConverter to handle flexible fractional seconds formats
  • Enhanced null handling across various extension methods
  • Updated ModelStateValidationFilter and ModelStateValidationAsyncFilter to return standardized ApiResult responses
  • Fixed memory management in StreamExtension.ConvertToBase64() method

Version 1.0.14

  • Added support for multiple result sets in DapperHelper
  • Enhanced documentation with comprehensive examples
  • Additional features and bug fixes

Version 1.0.13

  • Performance improvements
  • Extended helper methods

Version 1.0.12

  • Bug fixes and stability improvements
  • Enhanced JsonConverter implementations

Version 1.0.11

  • Added additional extension methods
  • Improved error handling

Version 1.0.10

  • Improved DependencyInjection support
  • Enhanced HttpClientHelper capabilities

Version 1.0.9

  • Added more helper methods
  • Bug fixes for serialization

Version 1.0.8

  • Extended model validation filters
  • Added support for more data types

Version 1.0.7

  • Enhanced Dapper integration
  • Additional utility functions

Version 1.0.6

  • Bug fixes and performance improvements
  • Enhanced XML handling

Version 1.0.5

  • Added additional extension methods
  • Improved data conversion utilities

Version 1.0.4

  • Added features and bug fixes
  • Extended converter functionality

Version 1.0.3

  • Improved error handling
  • Added new extension methods

Version 1.0.2

  • Enhanced JSON serialization options
  • Bug fixes for DateTime converters

Version 1.0.1

  • Added Dapper integration improvements
  • Fixed issues with HTTP client helper

Version 1.0.0

  • Initial release
  • Basic functionality for extensions and helpers
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.23 113 7/24/2026
1.0.22 115 6/22/2026
1.0.21 105 5/4/2026
1.0.20 104 4/28/2026
1.0.19 115 4/7/2026
1.0.18 103 4/7/2026
1.0.17 104 4/7/2026
1.0.16 109 4/7/2026
1.0.15 116 3/18/2026
1.0.14 206 10/22/2025
1.0.13 202 10/22/2025