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
<PackageReference Include="TAF.Infra.QueryHook" Version="2.1.0" />
<PackageVersion Include="TAF.Infra.QueryHook" Version="2.1.0" />
<PackageReference Include="TAF.Infra.QueryHook" />
paket add TAF.Infra.QueryHook --version 2.1.0
#r "nuget: TAF.Infra.QueryHook, 2.1.0"
#:package TAF.Infra.QueryHook@2.1.0
#addin nuget:?package=TAF.Infra.QueryHook&version=2.1.0
#tool nuget:?package=TAF.Infra.QueryHook&version=2.1.0
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
IQueryHookcontract 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
EntityNameandFieldNamewith 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 implementationsHookContext: Rich context object containing operation detailsOperationType: Enum for database operations (Select, Insert, Update, Delete)EntityName: Type-safe entity name value object with SQL injection protectionFieldName: 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 | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- TAF.Infra.Contract (>= 1.10.5)
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.
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