RepletoryLib.Data.Interceptors 1.0.0

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

RepletoryLib.Data.Interceptors

Attribute-driven EF Core interceptors for encryption, masking, validation, normalization, and data conversion.

Part of the RepletoryLib ecosystem -- standalone, reusable .NET 10 libraries with zero business logic.

NuGet .NET 10 License: MIT


Overview

RepletoryLib.Data.Interceptors provides a powerful attribute-driven system for applying cross-cutting concerns directly on EF Core entity properties. Instead of scattering encryption, validation, and normalization logic across services, you declare intent with attributes and the interceptors handle the rest during SaveChanges and materialization.

Three interceptors work together:

  • AttributeInterceptor -- Processes all property-level attributes (validation, normalization, encryption, conversion, masking)
  • AuditInterceptor -- Stamps CreatedAt, UpdatedAt, CreatedBy, UpdatedBy, and TenantId on BaseEntity instances
  • SoftDeleteInterceptor -- Converts hard-delete operations into soft-deletes

Key Features

  • 30+ attributes covering encryption, hashing, validation, normalization, masking, and data conversion
  • Phase-based processing -- Validation runs before normalization, which runs before encryption
  • Save and read phases -- Encryption on save, decryption on read; masking on read only
  • South African validators -- ID number (Luhn), phone number, and phone format normalization
  • Audit stamping -- Automatic CreatedAt/UpdatedAt/CreatedBy/UpdatedBy from ICurrentUserService
  • PII tracking -- Mark fields as personally identifiable with optional auto-encryption

Installation

dotnet add package RepletoryLib.Data.Interceptors

Or add to your .csproj:

<PackageReference Include="RepletoryLib.Data.Interceptors" Version="1.0.0" />

Note: RepletoryLib packages are published to a local BaGet feed. See the main repository README for feed configuration.

Dependencies

Package Type
RepletoryLib.Common RepletoryLib
RepletoryLib.Security.Encryption RepletoryLib
Microsoft.EntityFrameworkCore NuGet (10.0.0)
BCrypt.Net-Next NuGet (4.0.3)

Quick Start

1. Register interceptors

using RepletoryLib.Data.Interceptors;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRepletoryInterceptors(builder.Configuration);

2. Configure your DbContext to use interceptors

public class AppDbContext : DbContext
{
    private readonly IEnumerable<IInterceptor> _interceptors;

    public AppDbContext(DbContextOptions<AppDbContext> options, IEnumerable<IInterceptor> interceptors)
        : base(options)
    {
        _interceptors = interceptors;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.AddInterceptors(_interceptors);
    }
}

3. Decorate your entities

using RepletoryLib.Common.Entities;
using RepletoryLib.Data.Interceptors.Attributes;

public class Member : BaseEntity
{
    [NormalizeName]
    [Trim]
    public string FullName { get; set; } = string.Empty;

    [NormalizeEmail]
    [ValidateEmail]
    [AesEncrypt]
    public string Email { get; set; } = string.Empty;

    [NormalizePhone(PhoneFormat.E164)]
    [ValidateSaPhone]
    [MaskOnRead(MaskType.Phone)]
    public string PhoneNumber { get; set; } = string.Empty;

    [ValidateSaIdNumber]
    [AesEncrypt]
    [PiiField]
    public string IdNumber { get; set; } = string.Empty;
}

Configuration

InterceptorOptions

Property Type Default Description
AesKey string "" AES-256 key (32 characters)
AesIv string "" AES initialization vector (16 characters)
RsaPublicKey string "" PEM-encoded RSA public key
RsaPrivateKey string "" PEM-encoded RSA private key
HmacSecret string "" Secret key for HMAC-SHA256
DefaultTimezone string "UTC" Default IANA timezone for display
AutoEncryptPii bool false Automatically encrypt fields marked [PiiField]
EnableAuditStamping bool true Enable automatic audit field stamping
EnableSoftDelete bool true Convert hard-deletes to soft-deletes
EnableAttributeInterception bool true Enable all attribute-based interception

Section name: "Interceptors"

{
  "Interceptors": {
    "AesKey": "your-32-character-aes-256-key!!",
    "AesIv": "your-16-char-iv!",
    "EnableAuditStamping": true,
    "EnableSoftDelete": true,
    "AutoEncryptPii": false,
    "DefaultTimezone": "Africa/Johannesburg"
  }
}

Usage Examples

Complete Entity with All Attribute Types

using RepletoryLib.Common.Entities;
using RepletoryLib.Data.Interceptors.Attributes;

public class Customer : BaseEntity
{
    // Normalization
    [NormalizeName]
    [Trim]
    public string FullName { get; set; } = string.Empty;

    // Validation + Normalization + Encryption
    [NormalizeEmail]
    [ValidateEmail]
    [AesEncrypt]
    [PiiField]
    public string Email { get; set; } = string.Empty;

    // Phone formatting + Validation + Masking
    [NormalizePhone(PhoneFormat.E164)]
    [ValidateSaPhone]
    [MaskOnRead(MaskType.Phone)]
    public string PhoneNumber { get; set; } = string.Empty;

    // SA ID validation + Encryption + PII
    [ValidateSaIdNumber]
    [AesEncrypt]
    [PiiField]
    public string IdNumber { get; set; } = string.Empty;

    // Money stored as cents (decimal 150.50 -> long 15050)
    [MoneyInCents]
    [ValidateRange(0, 10_000_000)]
    public decimal MonthlyIncome { get; set; }

    // Complex type serialized as JSON
    [JsonSerialize]
    public List<string> Tags { get; set; } = new();

    // DateTime handling
    [StoreAsUtc]
    [DisplayInTimezone("Africa/Johannesburg")]
    public DateTime DateOfBirth { get; set; }

    // Password hashing (one-way)
    [HashStore(HashAlgorithmType.BCrypt)]
    public string Pin { get; set; } = string.Empty;

    // Enum stored as string
    [EnumToString]
    public CustomerStatus Status { get; set; }
}

What Happens During Save

When SaveChangesAsync is called, the AttributeInterceptor processes properties in phases:

  1. Validation -- [ValidateEmail], [ValidateSaPhone], [ValidateSaIdNumber], [ValidateRange], [ValidateRequired] etc. Throws if validation fails.
  2. Normalization -- [Trim], [NormalizeName], [NormalizeEmail], [NormalizePhone], [Uppercase], [Lowercase], [RemoveWhitespace], [TruncateAt]
  3. Conversion -- [MoneyInCents], [StoreAsUtc], [EnumToString], [JsonSerialize], [CsvSerialize], [StoreDateOnly], [StoreAsUnixTimestamp]
  4. Encryption/Hashing -- [AesEncrypt], [RsaEncrypt], [HashStore], [HmacSign], [Base64Encode], [Compress]

What Happens During Read

When entities are materialized from the database:

  1. Decryption -- [AesEncrypt], [RsaEncrypt], [Base64Encode], [Compress] reverse their transformations
  2. Conversion -- [MoneyInCents] converts cents back to decimal, [EnumToString] parses back to enum
  3. Masking -- [MaskOnRead] masks sensitive values, [Redact] returns "[REDACTED]"
  4. Display -- [DisplayInTimezone] converts UTC to the specified timezone

Audit Stamping

The AuditInterceptor automatically sets audit fields on BaseEntity instances:

// On Add:
entity.CreatedAt = DateTime.UtcNow;
entity.CreatedBy = currentUser.UserId;
entity.TenantId = currentUser.TenantId;

// On Update:
entity.UpdatedAt = DateTime.UtcNow;
entity.UpdatedBy = currentUser.UserId;

Soft-Delete Interception

The SoftDeleteInterceptor converts hard-deletes to soft-deletes:

// When you call:
context.Customers.Remove(customer);
await context.SaveChangesAsync();

// The interceptor changes it to:
customer.IsDeleted = true;
customer.DeletedAt = DateTime.UtcNow;
customer.DeletedBy = currentUser.UserId;
// Entity is NOT removed from the database

Attribute Reference

Validation Attributes (Save Phase)

Attribute Description
[ValidateRequired] Non-null, non-empty, non-whitespace
[ValidateEmail] Valid email format
[ValidateMinLength(n)] Minimum string length
[ValidateMaxLength(n)] Maximum string length
[ValidateRange(min, max)] Numeric value within range
[ValidateRegex(pattern)] Matches regular expression
[ValidateUrl] Valid absolute HTTP/HTTPS URL
[ValidateSaIdNumber] 13-digit SA ID with Luhn checksum
[ValidateSaPhone] SA phone in E.164, international, or local format

Normalization Attributes (Save Phase)

Attribute Description
[Trim] Remove leading/trailing whitespace
[NormalizeName] Title case with whitespace normalization
[NormalizeEmail] Lowercase and trim
[NormalizePhone(format)] Convert to E164, LocalSA, or Display format
[Uppercase] Convert to uppercase
[Lowercase] Convert to lowercase
[RemoveWhitespace] Strip all whitespace characters
[TruncateAt(length)] Truncate to maximum length

Conversion Attributes (Save/Read)

Attribute Save Read Description
[MoneyInCents] decimal → long (x100) long → decimal (/100) Store monetary values as cents
[StoreAsUtc] Convert to UTC -- Ensure UTC storage
[StoreDateOnly] Strip time component -- Store date without time
[StoreAsUnixTimestamp] DateTime → long (ms) long → DateTime Unix timestamp storage
[EnumToString] Enum → string name string → Enum Store enums as strings
[JsonSerialize] Object → JSON string JSON → Object Serialize complex types
[CsvSerialize] Collection → CSV CSV → Collection Serialize as CSV
[DisplayInTimezone(tz)] -- UTC → timezone Display in IANA timezone

Encryption & Encoding Attributes (Save/Read)

Attribute Save Read Description
[AesEncrypt] Encrypt (AES-256-CBC) Decrypt Symmetric encryption
[RsaEncrypt] Encrypt (RSA-OAEP) Decrypt Asymmetric encryption
[HashStore] Hash (BCrypt/SHA) -- One-way hash (irreversible)
[HmacSign] HMAC-SHA256 sign -- Message authentication
[Base64Encode] Encode Decode Base64 encoding
[Compress] GZip + Base64 Decompress Compression

Privacy Attributes

Attribute Phase Description
[PiiField] Metadata Marks property as PII (auto-encrypt if AutoEncryptPii = true)
[MaskOnRead(type)] Read Masks value: Email, Phone, IdNumber, CreditCard, Custom
[Redact] Read Always returns "[REDACTED]"

Integration with Other RepletoryLib Packages

Package Relationship
RepletoryLib.Common BaseEntity for audit/soft-delete fields, ICurrentUserService for stamping
RepletoryLib.Security.Encryption Powers [AesEncrypt], [RsaEncrypt], [HashStore] attributes
RepletoryLib.Data.EntityFramework Shares DbContext; interceptors process entities during SaveChanges
RepletoryLib.Utilities.Validation Complements attribute validation with FluentValidation

Testing

Interceptors are EF Core SaveChangesInterceptor instances. For unit testing, test the entity logic separately or use an in-memory database:

[Fact]
public void NormalizeName_attribute_applies_title_case()
{
    // Test the normalization logic directly
    var member = new Member { FullName = "  john  DOE  " };

    // After interceptor processing:
    // FullName should become "John Doe"
}

[Fact]
public void ValidateEmail_rejects_invalid_format()
{
    var member = new Member { Email = "not-an-email" };

    // SaveChangesAsync should throw due to [ValidateEmail]
}

Troubleshooting

Issue Solution
Attributes not processing Ensure interceptors are registered with AddRepletoryInterceptors and added to DbContext via AddInterceptors
Validation errors on save Check the exception message -- it details which attribute and property failed
Decrypted values are garbled Verify AesKey and AesIv match between environments
Audit fields not stamped Ensure ICurrentUserService is registered and EnableAuditStamping is true
Hard-delete bypassing soft-delete Ensure EnableSoftDelete is true in InterceptorOptions

License

This project is licensed under the MIT License.

Copyright (c) 2024-2026 Repletory.


For complete documentation, infrastructure setup, and configuration reference, see the RepletoryLib main repository.

Product Compatible and additional computed target framework versions.
.NET 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.0 73 3/2/2026