![]() |
VOOZH | about |
dotnet add package TAF.Infra.QueryHook --version 2.3.1
NuGet\Install-Package TAF.Infra.QueryHook -Version 2.3.1
<PackageReference Include="TAF.Infra.QueryHook" Version="2.3.1" />
<PackageVersion Include="TAF.Infra.QueryHook" Version="2.3.1" />Directory.Packages.props
<PackageReference Include="TAF.Infra.QueryHook" />Project file
paket add TAF.Infra.QueryHook --version 2.3.1
#r "nuget: TAF.Infra.QueryHook, 2.3.1"
#:package TAF.Infra.QueryHook@2.3.1
#addin nuget:?package=TAF.Infra.QueryHook&version=2.3.1Install as a Cake Addin
#tool nuget:?package=TAF.Infra.QueryHook&version=2.3.1Install as a Cake Tool
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.
IQueryHook contract for flexible hook implementationsEntityName and FieldName with validationdotnet add package TAF.Infra.QueryHook
The library provides configurable hook methods that can be used independently based on your configuration:
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) │
└──────────────┘ └───────────────┘
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 objectusing 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}";
}
}
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");
}
}
}
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";
}
}
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;
}
}
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 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 |
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; }
}
public enum OperationType
{
Select = 1,
Insert = 2,
Update = 3,
Delete = 4
}
// 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);
}
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"); ❌
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
}
}
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);
}
}
Contributions are welcome! Please feel free to submit a Pull Request.
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 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
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. |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.3.1 | 182 | 5/20/2026 |
| 2.3.0 | 495 | 4/1/2026 |
| 2.2.3 | 170 | 3/13/2026 |
| 2.1.0 | 219 | 1/31/2026 |
| 2.0.4 | 1,787 | 12/30/2025 |
| 2.0.3 | 154 | 12/30/2025 |
| 2.0.2 | 612 | 11/11/2025 |
| 2.0.1 | 665 | 10/16/2025 |
| 2.0.0 | 208 | 10/16/2025 |
| 1.3.0 | 279 | 10/14/2025 |
| 1.2.0 | 266 | 10/8/2025 |
| 1.1.0 | 209 | 10/8/2025 |
| 1.0.1 | 220 | 9/24/2025 |
| 1.0.0 | 229 | 9/24/2025 |
v2.3.1: Added CorrelationId to HookContext
- Added CorrelationId (Guid) property to HookContext
- Propagated from BusContext at all RecordRepository instantiation points
- Enables hooks and custom scripts to trace execution back to the originating HTTP request
- No breaking changes - additive only
v2.2.1: Added BulkRecords property to HookContext
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