TAF.Infra.QueryHook 2.1.0

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

TAF.Infra.QueryHook

A common interface library for QueryHook functionality that provides a standardized contract for performing configured actions. Enables consistent implementation of query hook patterns across different services and applications with configurable action execution.

🚀 Features

  • Configurable Hook Interface: Common IQueryHook contract for flexible hook implementations
  • Three Configurable Hook Methods:
    • ValidateAsync: Validate configurations, results, or business rules
    • PreExecuteAsync: Execute custom operations before main actions (e.g., before user creation)
    • PostExecuteAsync: Execute custom operations after main actions (e.g., send notifications after user creation)
  • Independent Hook Execution: Each method can be configured and used independently based on requirements
  • Type-Safe Value Objects: Strongly-typed EntityName and FieldName with validation
  • Multi-Tenant Support: Built-in context for tenant, user, app, and environment isolation
  • SQL Injection Protection: Type-safe entity and field names with validation
  • Operation Type Support: Handle Select, Insert, Update, and Delete operations
  • Extensible Context: Rich context object with fields, errors, logs, and results
  • Clean Architecture: Domain-driven design with value objects and enums

📦 Installation

dotnet add package TAF.Infra.QueryHook

🗃️ Architecture Overview

The library provides configurable hook methods that can be used independently based on your configuration:

Hook Configuration Examples

Configuration 1: Pre + Post Hook
┌──────────────┐    ┌─────────────┐    ┌───────────────┐
│ User Action  │ -> │ PreExecute  │ -> │ PostExecute   │
│              │    │ (Custom Op) │    │ (Send Email)  │
└──────────────┘    └─────────────┘    └───────────────┘

Configuration 2: Validation Only
┌──────────────┐    ┌─────────────┐
│ User Action  │ -> │ Validate    │
│              │    │ (Check Rules)│
└──────────────┘    └─────────────┘

Configuration 3: Post Hook Only
┌──────────────┐    ┌───────────────┐
│ User Action  │ -> │ PostExecute   │
│              │    │ (Audit Log)   │
└──────────────┘    └───────────────┘

Core Components

  • IQueryHook: Main interface for configurable hook implementations
  • HookContext: Rich context object containing operation details
  • OperationType: Enum for database operations (Select, Insert, Update, Delete)
  • EntityName: Type-safe entity name value object with SQL injection protection
  • FieldName: Type-safe field name value object

🚀 Quick Start

1. Implement Hook for Pre-Execute Custom Operations

using TAF.Infra.QueryHook.Interfaces;
using TAF.Infra.QueryHook.Models;
using TAF.Infra.QueryHook.Enums;

public class UserCreationHook : IQueryHook
{
    public async Task ValidateAsync(HookContext context)
    {
        // Not used in this configuration - can be empty or throw NotImplementedException
    }

    public async Task PreExecuteAsync(HookContext context)
    {
        // Custom operation before user is added
        if (context.Operation == OperationType.Insert && context.EntityName == "Users")
        {
            // Example: Generate unique user code
            var userCode = await GenerateUniqueUserCode();
            context.Fields["UserCode"] = userCode;

            // Example: Set default values
            context.Fields["Status"] = "Pending";
            context.Fields["CreatedDate"] = DateTime.UtcNow;

            context.Logs.Add($"Pre-execute: Generated user code {userCode}");
        }
    }

    public async Task PostExecuteAsync(HookContext context)
    {
        // Not used in this configuration - can be empty or throw NotImplementedException
    }

    private async Task<string> GenerateUniqueUserCode()
    {
        // Your custom logic to generate unique code
        return $"USER_{DateTime.UtcNow:yyyyMMddHHmmss}";
    }
}

2. Implement Hook for Post-Execute Notifications

public class UserNotificationHook : IQueryHook
{
    private readonly IEmailService _emailService;
    private readonly IAuditService _auditService;

    public UserNotificationHook(IEmailService emailService, IAuditService auditService)
    {
        _emailService = emailService;
        _auditService = auditService;
    }

    public async Task ValidateAsync(HookContext context)
    {
        // Not used in this configuration
    }

    public async Task PreExecuteAsync(HookContext context)
    {
        // Not used in this configuration
    }

    public async Task PostExecuteAsync(HookContext context)
    {
        // Send email notification after user is added
        if (context.Operation == OperationType.Insert && context.EntityName == "Users")
        {
            var email = context.Fields["Email"]?.ToString();
            var name = context.Fields["Name"]?.ToString();

            if (!string.IsNullOrEmpty(email))
            {
                await _emailService.SendWelcomeEmailAsync(email, name);
                context.Logs.Add($"Welcome email sent to {email}");
            }

            // Log audit trail
            await _auditService.LogUserCreation(context.UserId, context.TenantId);
            context.Logs.Add("User creation logged for audit");
        }

        // Send notification for user deletion
        if (context.Operation == OperationType.Delete && context.EntityName == "Users")
        {
            await _emailService.SendAccountClosureNotificationAsync(context);
            context.Logs.Add("Account closure notification sent");
        }
    }
}

3. Implement Hook for Validation

public class UserValidationHook : IQueryHook
{
    public async Task ValidateAsync(HookContext context)
    {
        // Validate configuration requirements
        if (context.Operation == OperationType.Insert && context.EntityName == "Users")
        {
            // Validate email format
            if (context.Fields.TryGetValue("Email", out var emailObj))
            {
                var email = emailObj?.ToString();
                if (string.IsNullOrEmpty(email) || !IsValidEmail(email))
                {
                    context.Errors.Add("Valid email address is required");
                }
            }

            // Validate business rules
            if (context.Fields.TryGetValue("Age", out var ageObj) &&
                int.TryParse(ageObj?.ToString(), out var age))
            {
                if (age < 18)
                {
                    context.Errors.Add("User must be at least 18 years old");
                }
            }

            // Validate configuration-specific requirements
            var requiredRole = GetConfiguredRequiredRole(context.TenantId);
            if (!context.Fields.ContainsKey("Role") ||
                context.Fields["Role"]?.ToString() != requiredRole)
            {
                context.Errors.Add($"User must have role: {requiredRole}");
            }
        }
    }

    public async Task PreExecuteAsync(HookContext context)
    {
        // Not used in this configuration
    }

    public async Task PostExecuteAsync(HookContext context)
    {
        // Not used in this configuration
    }

    private bool IsValidEmail(string email)
    {
        // Email validation logic
        return email.Contains("@");
    }

    private string GetConfiguredRequiredRole(Guid tenantId)
    {
        // Get required role from configuration
        return "StandardUser";
    }
}

4. Using Hooks Based on Configuration

using TAF.Infra.QueryHook.Models;
using TAF.Infra.QueryHook.Enums;
using TAF.Infra.QueryHook.ValueObjects;

public class UserService
{
    private readonly List<IQueryHook> _configuredHooks;

    public UserService(IConfiguration config, IServiceProvider serviceProvider)
    {
        _configuredHooks = LoadConfiguredHooks(config, serviceProvider);
    }

    public async Task CreateUserAsync(CreateUserRequest request)
    {
        var context = new HookContext
        {
            Operation = OperationType.Insert,
            EntityName = new EntityName("Users"),
            UserId = GetCurrentUserId(),
            TenantId = GetCurrentTenantId(),
            AppId = GetCurrentAppId(),
            EnvironmentId = GetCurrentEnvironmentId(),
            Fields = new Dictionary<FieldName, object>
            {
                { new FieldName("Name"), request.Name },
                { new FieldName("Email"), request.Email },
                { new FieldName("Age"), request.Age }
            }
        };

        // Execute configured hooks
        foreach (var hook in _configuredHooks)
        {
            // Only execute if configured for this hook type
            if (IsValidationHookConfigured(hook))
            {
                await hook.ValidateAsync(context);
            }

            if (IsPreExecuteHookConfigured(hook))
            {
                await hook.PreExecuteAsync(context);
            }
        }

        // Check for validation errors
        if (context.Errors.Any())
        {
            throw new ValidationException(string.Join(", ", context.Errors));
        }

        // Execute actual database operation
        var result = await _userRepository.CreateAsync(context.Fields);
        context.Result = result;

        // Execute post-execute hooks if configured
        foreach (var hook in _configuredHooks)
        {
            if (IsPostExecuteHookConfigured(hook))
            {
                await hook.PostExecuteAsync(context);
            }
        }
    }

    private List<IQueryHook> LoadConfiguredHooks(IConfiguration config, IServiceProvider serviceProvider)
    {
        var hooks = new List<IQueryHook>();

        // Load hooks based on configuration
        if (config.GetValue<bool>("Hooks:UserCreation:Enabled"))
            hooks.Add(serviceProvider.GetService<UserCreationHook>());

        if (config.GetValue<bool>("Hooks:UserNotification:Enabled"))
            hooks.Add(serviceProvider.GetService<UserNotificationHook>());

        if (config.GetValue<bool>("Hooks:UserValidation:Enabled"))
            hooks.Add(serviceProvider.GetService<UserValidationHook>());

        return hooks;
    }
}

📚 API Reference

Core Interface

public interface IQueryHook
{
    /// <summary>
    /// Validates configurations, results, or business rules.
    /// Use when you need to validate something before or during operations.
    /// Errors added to context.Errors will indicate validation failures.
    /// </summary>
    Task ValidateAsync(HookContext context);

    /// <summary>
    /// Executes custom operations before the main action.
    /// Use when you need to perform operations before main execution
    /// (e.g., before user creation, set default values, generate codes).
    /// Can modify fields and context.
    /// </summary>
    Task PreExecuteAsync(HookContext context);

    /// <summary>
    /// Executes custom operations after the main action.
    /// Use when you need to perform operations after main execution
    /// (e.g., after user creation, send notifications, log audit trail).
    /// Receives the operation result for further processing.
    /// </summary>
    Task PostExecuteAsync(HookContext context);
}

Hook Usage Patterns

Hook Method Common Use Cases Configuration Example
ValidateAsync • Business rule validation<br>• Configuration validation<br>• Result validation<br>• Permission checks Email format, age limits, required fields
PreExecuteAsync • Set default values<br>• Generate codes/IDs<br>• Data transformation<br>• Pre-processing User code generation, status setting, date stamps
PostExecuteAsync • Send notifications<br>• Audit logging<br>• Cache updates<br>• External API calls Welcome emails, audit trails, cache invalidation

Context Object

public sealed class HookContext
{
    public required OperationType Operation { get; init; }
    public required EntityName EntityName { get; init; }
    public required Guid UserId { get; init; }
    public required Guid TenantId { get; init; }
    public required Guid AppId { get; init; }
    public required Guid EnvironmentId { get; init; }
    public required Dictionary<FieldName, object> Fields { get; init; }
    public string? WhereClause { get; init; }
    public List<string> Errors { get; init; } = [];
    public List<string> Logs { get; init; } = [];
    public object? Result { get; init; }
}

Operation Types

public enum OperationType
{
    Select = 1,
    Insert = 2,
    Update = 3,
    Delete = 4
}

Value Objects

// Strongly-typed entity name with SQL validation
public readonly record struct EntityName
{
    public EntityName(string value);
    public string Value { get; }
    public static bool TryCreate(string? value, out EntityName entityName);
    public static implicit operator string(EntityName entityName);
    public static implicit operator EntityName(string value);
}

// Strongly-typed field name
public readonly record struct FieldName
{
    public FieldName(string value);
    public string Value { get; }
    public static bool TryCreate(string? value, out FieldName fieldName);
    public static implicit operator string(FieldName fieldName);
    public static implicit operator FieldName(string value);
}

🛡️ Security Features

SQL Injection Protection

The EntityName value object provides built-in SQL injection protection:

// Safe - valid SQL identifier
var validEntity = new EntityName("Users"); ✅

// Throws exception - prevents SQL injection
var invalidEntity = new EntityName("Users; DROP TABLE Users--"); ❌
var invalidEntity2 = new EntityName("Users' OR '1'='1"); ❌

Validation Rules

  • Entity Names: Must start with letter/underscore, contain only letters/digits/underscores
  • Field Names: Must not be null/empty, maximum 128 characters
  • Context Validation: All required context properties must be provided

🔧 Advanced Configuration Patterns

Conditional Hook Execution

public async Task PreExecuteAsync(HookContext context)
{
    // Only execute for specific entities
    if (context.EntityName == "Users")
    {
        // Custom user pre-processing
    }
    else if (context.EntityName == "Orders")
    {
        // Custom order pre-processing
    }
}

Multi-Purpose Hooks

public class ComprehensiveUserHook : IQueryHook
{
    public async Task ValidateAsync(HookContext context)
    {
        // Used when validation is configured
        await ValidateBusinessRules(context);
    }

    public async Task PreExecuteAsync(HookContext context)
    {
        // Used when pre-execute is configured
        await SetDefaultValues(context);
    }

    public async Task PostExecuteAsync(HookContext context)
    {
        // Used when post-execute is configured
        await SendNotifications(context);
    }
}

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

This project is licensed under the MIT License.


Built with ❤️ for configurable and flexible hook systems

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 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on TAF.Infra.QueryHook:

Package Downloads
TAF.MetaData.SDK

Professional HTTP-based SDK for TAF Metadata Service. v10.0.0: Clean release - no caching dependencies. Features Clean Architecture, SOLID principles, explicit BusContext parameter support, comprehensive error handling, screen management, batch relations support, and GUID-based lookups.

TAF.CodeExecutor.SDK

SDK for consuming TAF.CodeExecutor microservice. Supports QueryHook execution (single and batch) and WorkflowAction execution.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.0 193 1/31/2026
2.0.4 605 12/30/2025
2.0.3 127 12/30/2025
2.0.2 530 11/11/2025
2.0.1 613 10/16/2025
2.0.0 183 10/16/2025
1.3.0 257 10/14/2025
1.2.0 247 10/8/2025
1.1.0 190 10/8/2025
1.0.1 200 9/24/2025
1.0.0 204 9/24/2025

v2.1.0: Added Metadata dictionary to HookContext
- Added Dictionary<string, object?> Metadata property to HookContext
- Enables passing additional data between hook stages (e.g. system-managed fields)
- No breaking changes - additive only

v2.0.3: Updated TAF.Infra.Contract dependency
- Upgraded TAF.Infra.Contract from 1.3.0 to 1.10.5
- No breaking changes

v2.0.2: Added BulkInsert operation type
- Added BulkInsert = 5 to OperationType enum
- Enables bulk insert operation tracking and hook execution
- No breaking changes - additive only

v2.0.1: Maintenance release

v2.0.0: Separated Hook Interfaces for Selective Execution
- BREAKING: Removed IQueryHook parent interface
- Added 3 separate interfaces: IValidationHook, IPreExecutionHook, IPostExecutionHook
- Added ExecutionType enum (1=Validate, 2=PreExecute, 3=PostExecute)
- Enables selective hook method execution for performance optimization
- Migration: Replace IQueryHook with specific interface(s) needed