Introduction
Learn how to control formatting records by overriding ToString method along with a virtual method PrintMembers. By controlling formatting, a developer can format for outputting to a frontend, along with hiding one or more properties or redacting data.
The record used where based on a condition will hide BirthDate, mask SSN, and fully redact UserName and Password. It will also present PhoneNumbers as a comma-delimited list.
public partial record Person(
string FirstName,
string LastName,
DateOnly BirthDate,
string SSN,
string UserName,
string Password,
string[] PhoneNumbers) : ITaxpayer { }
Controlling output
For demonstration, if the project runs from Microsoft Visual Studio, show all property values, and when not executed from Microsoft Visual Studio, redaction is done. When the project is moved to production, either leave the code as is or set RevealSensitiveInformation to false. A json is not used as a user could change the setting. An alternative is to use an environment variable.
Demonstration data
public class MockedData
{
public static List<Person> SamplePeople() =>
[
new("Jane", "Smith", new DateOnly(1990, 5, 22),
"987-65-4321","jsmith","pass1", []),
new("Karen", "Payne", new DateOnly(1985, 1, 15),
"123-45-6789", "kpayne", "pass2",["555-1234", "555-5678","154-3557"]),
new("Michael", "Johnson", new DateOnly(1978, 3, 10),
"987-65-4321", "mjohnson", "pass3",["555-1111", "555-2222"]),
new("Emily", "Davis", new DateOnly(1995, 7, 30),
"456-78-9012", "edavis", "pass4",["555-3333"]),
new("David", "Wilson", new DateOnly(1982, 11, 5),
"321-54-9876", "dwilson", "pass5",["555-4444", "555-5555"]),
];
}
Code to override the default for the above record
The code is placed in a partial class which is optional.
public partial record Person
{
public override string ToString()
{
var builder = new StringBuilder();
var person = this;
if (!Appsettings.Instance.RevealSensitiveInformation)
{
person = person with
{
BirthDate = default,
UserName = "redacted",
Password = "redacted"
};
}
person.PrintMembers(builder);
return builder.ToString();
}
protected virtual bool PrintMembers(StringBuilder builder)
{
if (Appsettings.Instance.RevealSensitiveInformation)
{
builder.Append($"{FirstName,-10}{LastName,-10}{SSN,-13}{BirthDate,-14:MM/dd/yyyy}{UserName,-10}{Password,-12}");
}
else
{
builder.Append($"{FirstName,-10}{LastName,-10}{SSN.MaskSsn(),-13}{BirthDate,-14:MM/dd/yyyy}{UserName,-10}{Password,-12}");
}
if (!(PhoneNumbers?.Length > 0))
{
builder.Append("None");
return true;
}
builder.Append(string.Join(", ", PhoneNumbers));
builder.Append("");
return true;
}
}
ToString override
- Set Person to the current instance.
- If reaction, create and new instance of Person redacting BirthDate, UserName and Password using nondestructive mutation.
- Pass the StringBuilder to PrintMembers which redacts depending on the value of Appsettings.Instance.RevealSensitiveInformation.
For masking SSN, a language extension MaskSsn is utilized.
Usage
internal partial class Program
{
static void Main(string[] args)
{
var people = MockedData.SamplePeople();
AnsiConsole.MarkupLine("[cyan]First Last SSN Birthdate " +
"UserName Password Phone numbers[/]");
foreach (var person in people)
{
AnsiConsole.MarkupLine(person.Colorize());
}
SpectreConsoleHelpers.ExitPrompt();
}
}
Colorize is a language extension method to use Spectre.Console colors for output to the console.
Overriding ToString only
In some cases, using PrintMembers is not necessary, as in the following case, for the conditional output of the Line2 property.
public record Address(string Line1, string? Line2, string City, string Country, string PostCode)
{
public override string ToString() =>
$"{Line1}, {(Line2 is not null ? Line2 + ", " : string.Empty)}{City}, {Country}, {PostCode}";
}
private static void DisplayAddressExample()
{
var addresses = new List<Address>
{
new("123 Main St", null, "Portland", "USA", "97201"),
new("456 Maple Ave", "Apt 4B", "Eugene", "USA", "97401"),
new("789 Oak Dr", "", "Salem", "USA", "97301")
};
foreach (var address in addresses)
{
Console.WriteLine(address);
}
}
Summary
With the information provided, a developer can format and redact data for record types.
Nice posting! Looking forward to talking to you.