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
<PackageReference Include="RepletoryLib.Data.Interceptors" Version="1.0.0" />
<PackageVersion Include="RepletoryLib.Data.Interceptors" Version="1.0.0" />
<PackageReference Include="RepletoryLib.Data.Interceptors" />
paket add RepletoryLib.Data.Interceptors --version 1.0.0
#r "nuget: RepletoryLib.Data.Interceptors, 1.0.0"
#:package RepletoryLib.Data.Interceptors@1.0.0
#addin nuget:?package=RepletoryLib.Data.Interceptors&version=1.0.0
#tool nuget:?package=RepletoryLib.Data.Interceptors&version=1.0.0
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.
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, andTenantIdonBaseEntityinstances - 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/UpdatedByfromICurrentUserService - 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:
- Validation --
[ValidateEmail],[ValidateSaPhone],[ValidateSaIdNumber],[ValidateRange],[ValidateRequired]etc. Throws if validation fails. - Normalization --
[Trim],[NormalizeName],[NormalizeEmail],[NormalizePhone],[Uppercase],[Lowercase],[RemoveWhitespace],[TruncateAt] - Conversion --
[MoneyInCents],[StoreAsUtc],[EnumToString],[JsonSerialize],[CsvSerialize],[StoreDateOnly],[StoreAsUnixTimestamp] - Encryption/Hashing --
[AesEncrypt],[RsaEncrypt],[HashStore],[HmacSign],[Base64Encode],[Compress]
What Happens During Read
When entities are materialized from the database:
- Decryption --
[AesEncrypt],[RsaEncrypt],[Base64Encode],[Compress]reverse their transformations - Conversion --
[MoneyInCents]converts cents back to decimal,[EnumToString]parses back to enum - Masking --
[MaskOnRead]masks sensitive values,[Redact]returns"[REDACTED]" - 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 | Versions 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. |
-
net10.0
- BCrypt.Net-Next (>= 4.0.3)
- Microsoft.EntityFrameworkCore (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- RepletoryLib.Common (>= 1.0.0)
- RepletoryLib.Security.Encryption (>= 1.0.0)
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 | 72 | 3/2/2026 |