PiiMasking.Core
0.0.1
dotnet add package PiiMasking.Core --version 0.0.1
NuGet\Install-Package PiiMasking.Core -Version 0.0.1
<PackageReference Include="PiiMasking.Core" Version="0.0.1" />
<PackageVersion Include="PiiMasking.Core" Version="0.0.1" />
<PackageReference Include="PiiMasking.Core" />
paket add PiiMasking.Core --version 0.0.1
#r "nuget: PiiMasking.Core, 0.0.1"
#:package PiiMasking.Core@0.0.1
#addin nuget:?package=PiiMasking.Core&version=0.0.1
#tool nuget:?package=PiiMasking.Core&version=0.0.1
PiiMasking
PiiMasking is a .NET library for masking personally identifiable information in JSON output. It plugs into System.Text.Json, supports Microsoft.Extensions.Options, and optionally integrates with ASP.NET Core MVC so you can annotate selected string properties and mask them consistently during serialization.
The library is designed for outbound data shaping. Your in-memory models stay unchanged, and incoming JSON is still deserialized as plain text.
Why use it
- Apply masking declaratively with
[PiiMasking] - Keep masking behavior configuration-driven
- Support ASP.NET Core MVC without per-model converter wiring
- Extend the pipeline with contributors, named strategies, or a custom transform
- Preserve normal deserialization behavior for incoming payloads
Packages
| Package | Purpose |
|---|---|
PiiMasking.Core |
Core library: settings, built-in masking rules, [PiiMasking], JSON modifier/converter, and DI registration |
PiiMasking.AspNetCore |
ASP.NET Core MVC integration via AddPiiMaskingMvcJson() |
Supported target frameworks:
net8.0net10.0
Installation
Install the core package:
dotnet add package PiiMasking.Core
If you want ASP.NET Core MVC integration, add the companion package as well:
dotnet add package PiiMasking.AspNetCore
Quick start
ASP.NET Core
Register the services:
builder.Services.AddPiiMasking(builder.Configuration);
builder.Services.AddPiiMaskingMvcJson();
Configure the library:
{
"PiiMasking": {
"Enabled": true,
"MaskSuffix": "****",
"LiteralWordMaskSeparators": [" on behalf of "]
}
}
Annotate the properties you want masked:
public sealed class UserDto
{
[PiiMasking(MaskEachWord = true)]
public string? DisplayName { get; set; }
[PiiMasking(AsEmail = true)]
public string? Email { get; set; }
}
With masking enabled, values such as these:
DisplayName = "Abe David"
Email = "samson@contoso.com"
serialize to JSON like this:
{
"displayName": "Ab**** Da****",
"email": "Sa****@contoso.com"
}
How it works
- Add
[PiiMasking]to astringproperty. - Configure
PiiMaskingSettingsthroughIConfiguration. - When
PiiMasking:Enabledistrue, outbound JSON is masked during serialization. - Deserialization still reads incoming JSON as plain text.
By default, [PiiMasking] applies segment-style masking. You can opt into other behaviors such as email masking, per-word masking, or named strategies.
Configuration
| Setting | Description | Default |
|---|---|---|
Enabled |
Turns masking on or off | true when omitted |
MaskSuffix |
Suffix appended to masked values | **** |
LiteralWordMaskSeparators |
Separators used by literal-aware masking | Empty |
Built-in masking modes
| Attribute usage | Example input | Example output |
|---|---|---|
[PiiMasking] |
samson |
Sa**** |
[PiiMasking(AsEmail = true)] |
samson.user@mail.example.com |
Sa****@mail.example.com |
[PiiMasking(MaskEachWord = true)] |
Abe David Smith |
Ab**** Da**** Sm**** |
[PiiMasking(MaskEachWordRespectingLiterals = true)] |
John Doe on behalf of Jane Smith |
Jo**** Do**** on behalf of Ja**** Sm**** |
Notes:
- Email masking only masks the local part before
@. - Literal-aware masking uses
PiiMasking:LiteralWordMaskSeparatorsand preserves the matched literal text from the source. - If a value already includes the configured mask suffix, the built-in logic avoids double-masking it.
LeaveRemainderUnmaskedAfterLiteralscan be combined withMaskEachWordRespectingLiteralswhen you want text after the last literal separator to remain unchanged.
Extensibility
IPiiMaskingPropertyContributor
Use contributors when masking needs to depend on application-specific rules, such as a particular property name or DTO shape.
The default transform evaluates registered contributors in registration order. The first non-null result wins. If every contributor returns null, the built-in rules run.
builder.Services.AddSingleton<IPiiMaskingPropertyContributor, DisplayNamePlainIdContributor>();
public sealed class DisplayNamePlainIdContributor : IPiiMaskingPropertyContributor
{
public string? TryMask(PropertyInfo property, string value, PiiMaskingAttribute marker, PiiMaskingSettings settings)
{
if (property.Name != nameof(UserDto.DisplayName))
{
return null;
}
return MyMasking.MaskDisplayNameWithPlainUserId(value, settings.MaskSuffix);
}
}
IPiiMaskingExecutionStrategy
Use a named strategy when you want to select a masking rule declaratively through the attribute instead of encoding everything in booleans.
Execution order in the default pipeline is:
IPiiMaskingPropertyContributorIPiiMaskingExecutionStrategyselected byMode- Built-in
[PiiMasking]rules
If Mode is set and no registered strategy has a matching Name, serialization fails fast with an InvalidOperationException.
public sealed class RedactPhoneStrategy : IPiiMaskingExecutionStrategy
{
public const string Name = "Phone";
string IPiiMaskingExecutionStrategy.Name => Name;
public string? Mask(string value, PiiMaskingAttribute marker, PiiMaskingSettings settings)
{
return "****";
}
}
Register and use it like this:
builder.Services.AddPiiMasking(builder.Configuration);
builder.Services.AddSingleton<IPiiMaskingExecutionStrategy, RedactPhoneStrategy>();
builder.Services.AddPiiMaskingMvcJson();
[PiiMasking(Mode = RedactPhoneStrategy.Name)]
public string? Mobile { get; set; }
IPiiMaskedPropertyStringTransform
AddPiiMasking registers IPiiMaskedPropertyStringTransform and uses PiiMaskingPropertyStringTransform by default. If you have a custom serializer or converter pipeline, call Transform(property, value) so your output stays aligned with [PiiMasking].
If you want to replace the default behavior, register your own IPiiMaskedPropertyStringTransform before calling AddPiiMasking.
PiiMaskingTextFormatter
PiiMaskingTextFormatter.Apply(...) is a lightweight helper for applying the built-in rules directly. It does not resolve contributors or named execution strategies.
Manual JsonSerializerOptions
For non-MVC scenarios, register the JSON modifier directly.
Preferred overload when using DI:
var strategies = serviceProvider.GetServices<IPiiMaskingExecutionStrategy>().ToList();
var transform = serviceProvider.GetRequiredService<IPiiMaskedPropertyStringTransform>();
options.AddPiiMaskingJsonModifier(piiMaskingSettingsMonitor, transform, strategies);
There is also a built-in-only overload:
options.AddPiiMaskingJsonModifier(piiMaskingSettingsMonitor);
Use the overload that accepts IPiiMaskedPropertyStringTransform when you want contributors to participate.
Development
Run the test suite:
dotnet test
Create NuGet packages locally:
dotnet pack -c Release -o ./artifacts
CI and publishing
The .github/workflows/dotnet.yml workflow:
- Restores, builds, tests, and packs on pushes and pull requests
- Builds and packs on manual runs
- Publishes packages to NuGet on release publication
To publish from CI:
- Add a
NUGET_API_KEYrepository secret with permission to pushPiiMasking.CoreandPiiMasking.AspNetCore. - Publish a GitHub release with the target version tag, such as
1.0.1orv1.0.1. - Confirm the published packages on nuget.org.
The release workflow maps the release tag to Version and PackageVersion, stripping a leading v when present.
License
Licensed under the MIT License.
| 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 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
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
-
net8.0
- Microsoft.Extensions.Configuration.Abstractions (>= 8.0.0)
- Microsoft.Extensions.DependencyInjection (>= 8.0.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 8.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on PiiMasking.Core:
| Package | Downloads |
|---|---|
|
PiiMasking.AspNetCore
ASP.NET Core MVC JSON integration for PiiMasking. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.0.1 | 35 | 4/9/2026 |