Building on the infrastructure defined last time, let’s look at how we can avoid the use of magic strings when working with metadata.

When earlier discussing how to model metadata, I used Property and ErrorType as examples of common metadata - and posted this example that demonstrates an overhead of boilerplate.

private ValidationResult ValidateFullName()
    => Validation.ErrorWhen(
        string.IsNullOrEmpty(FullName),
        "Mandatory property 'FullName' not supplied.")
        .WithProperty(nameof(FullName))
        .WithErrorType("Mandatory");

Instead, using the techniques developed last time we can restate this more simply:

private ValidationResult ValidateFullName()
    => Validation.ErrorWhen(
        string.IsNullOrEmpty(FullName),
        "Mandatory property 'FullName' not supplied.",
        _fullName,
        _mandatory);

To be fair, doing this requires a couple of private members defined elsewhere - but as they’re reusable across multiple validation methods and instances, the tradeoff is pretty good:

private static readonly ValidationMetadata _fullName
    = new ValidationMetadata("Property", nameof(FullName));

private static readonly ValidationMetadata _mandatory
    = new ValidationMetadata("ErrorType", "Mandatory");
  • By making these declarations static, we avoid increasing the memory footprint of each instance and we make it easy to reuse them across the entire lifetime of the application.
  • By making them private class members, instead of local variables, we gain the ability to reuse them across different validation rules.
  • By making ValidationMetadata an immutable type, we make it safe to reuse these wherever desired (if it was mutable, we’d want to create a fresh copy for each use, just to be safe).

But can we do better? These declarations still have the very magic strings we said we would like to avoid.

Yes we can - by creating a helper class that wraps each magic string with well named methods.

public static class MetadataFactory
{

We define the string identifier we’ll use for storing the name of the property being validated:

    private const string PropertyMetadata = "Property";

Note that this is a private string - we don’t need to, or want to, expose it outside this helper class.

To create our property metadata, we create a simple a factory method, with an informative declarative name, to wrap the use of our private magic string.

    public static ValidationMetadata PropertyName(string propertyName)
        => new ValidationMetadata(PropertyMetadata, propertyName);

To extract the value out again, an extension method that uses the same magic string to pull values out from a ValidationResultWithMetadata in a safe way:

    public static string PropertyName(
        this ValidationResultWithMetadata validation)
        => validation.Metadata.TryGetValue(PropertyMetadata, out var v)
           && v is string name
           ? name
           : string.Empty;

It’s worth pointing out that this is close to my upper limit for using expression bodied syntax; if it was any longer, I think the more traditional block syntax would be clearer.

Lastly, a predicate to make it easy to check whether a specific property name is present.

    public static bool HasPropertyName(
        this ValidationResultWithMetadata validation,
        string propertyName)
        => string.Equals( 
            PropertyName(validation),
            propertyName,
            StringComparison.OrdinalIgnoreCase);
}

Assuming a similar support for error types, we can now redeclare our original static members using those factory methods:

using static MetadataFactory;

private static readonly ValidationMetadata _fullName
    = PropertyName(nameof(FullName));

private static readonly ValidationMetadata _mandatory
    = ErrorType("Mandatory");

Our consumers, the developers using the validation library no longer need to deal with the magic strings at all. Of course they still exist, but they’ve been hidden away where they belong, an implementation detail of the MetadataFactory helper class.

Prior post in this series:
Capturing Validation Metadata

Comments

blog comments powered by Disqus