using System.Collections.Generic;
[AttributeUsage(AttributeTargets.Property)]
public abstract class DataMaskAttribute : Attribute
public string VaultIdentifier { get; private set; }
public DataMaskAttribute(string vaultIdentifier)
VaultIdentifier = vaultIdentifier ?? throw new ArgumentNullException($"Must pass {nameof(vaultIdentifier)} to {nameof(DataMaskAttribute)}");
public interface IStringMask
public string Mask(string src);
public abstract class StringMaskBase : DataMaskAttribute, IStringMask
protected StringMaskBase(string vaultIdentifier) : base(vaultIdentifier)
public abstract string Mask(string src);
public class StaticStringMask : StringMaskBase
public static readonly string DefaultValue = "<redacted>";
private readonly string _value;
public StaticStringMask(string vaultIdentifier) : base(vaultIdentifier)
public StaticStringMask(string vaultIdentifier, string value) : base(vaultIdentifier)
_value = value ?? DefaultValue;
public override string Mask(string src)
[StaticStringMask("ssn")]
public string SSN { get; set; }
[StaticStringMask("name", "***")]
public string Name { get; set; }
public static void Main()
var props = p.GetType().GetProperties();
foreach (PropertyInfo propInfo in props)
dynamic masker = propInfo.GetCustomAttribute<DataMaskAttribute>();
dynamic unmaskedValue = propInfo.GetValue(p);
dynamic maskedValue = masker.Mask(unmaskedValue);
Console.WriteLine($"mask class: {masker.GetType()}");
Console.WriteLine($"unmasked value: {unmaskedValue}");
Console.WriteLine($"masked value: {maskedValue}");