Extending Guard Clauses

February 03, 2022#Software Development
Article
Author image.

Eric Fleming, Software Architect

In a previous article we covered Getting Started with Guard Clauses. We introduced what guard clauses were, when they should be used as well as the Ardalis.GuardClauses NuGet package which provides great functionality out of the box.

Another key piece of the NuGet package is the ability to extend it and create your own, custom guard clauses.

Let’s build on the example from the previous article. As a reminder, we had the following requirements:

  • All invitees must have an email address to send an invitation to
  • Other information such as first name / last name is optional

This was ultimately solved by adding a guard clause to ensure the Email was never NullOrEmpty.

public class Invitee
{
    public string Email { get; private set; }
    public string? FirstName { get; private set; }
    public string? LastName { get; private set; }

    public Invitee(string email, string? firstName, string? lastName)
    {
        Email = Guard.Against.NullOrEmpty(email, nameof(Email));
        FirstName = firstName;
        LastName = lastName;
    }
}

Now we need to add the following business requirement:

  • An email must end with .com to be considered a valid email

Out of the box, there is no guard clause which does that specific check but luckily we can extend the Ardalis.GuardClauses package and create our own!

Let’s create an InvalidEmailGuard which will prevent against the Email being NullOrEmpty as well as prevent the Email from not ending in .com.

Note: Email validation is difficult to do with any kind of pattern matching. The best approach to confirm an email is to send an email and verify it that way. The example below is strictly a demonstration.

namespace Ardalis.GuardClauses
{
    public static class InvalidEmailGuard
    {
        public static string InvalidEmail(this IGuardClause guardClause, string email, string parameterName)
        {
            Guard.Against.NullOrEmpty(email, nameof(email));

            var emailSuffix = ".com";

            if (!email.EndsWith(emailSuffix))
            {
                throw new ArgumentException($"Invalid Email - must end in {emailSuffix}", parameterName);
            }

            return email;
        }
    }
}

A couple of items to note about our custom guard clause above:

  • The original check to ensure the Email is not NullOrEmpty is still used
  • Notice the namespace is namespace Ardalis.GuardClauses. This will ensure that your code picks up your extensions no matter where they are in your codebase
  • If the email does not end in .com, an ArgumentException will be thrown. You could also create a custom exception, for example InvalidEmailException and throw that instead.

Back in our Invitee class, we can now update our code to use our custom guard clause:

public class Invitee
{
    public string Email { get; private set; }
    public string? FirstName { get; private set; }
    public string? LastName { get; private set; }

    public Invitee(string email, string? firstName, string? lastName)
    {
        Email = Guard.Against.InvalidEmail(email, nameof(Email));
        FirstName = firstName;
        LastName = lastName;
    }
}

Now our custom InvalidEmail guard clause will prevent the system from creating an Invitee with an email that is NullOrEmpty and doesn’t end in .com.

Another great thing about custom guard clauses is that they are typically very small, and therefore very easy to unit test. Here are a few I wrote for the InvalidEmail guard clause.

using Ardalis.GuardClauses;
using System;
using Xunit;

namespace UnitTests.GuardClauses
{
    public class InvalidEmailGuard
    {
        [Fact]
        public void ThrowsArgumentNullExceptionIfNullOrEmptyEmail()
        {
            string? email = null;
            Assert.Throws<ArgumentNullException>(() => Guard.Against.InvalidEmail(email, nameof(email)));
        }

        [Fact]
        public void ThrowsArgumentExceptionIfEmailDoesNotEndInCorrectSuffix()
        {
            var email = "eric@fleming.invalidsuffix";
            Assert.Throws<ArgumentException>(() => Guard.Against.InvalidEmail(email, nameof(email)));
        }

        [Fact]
        public void ReturnsEmailAddressGivenValidEmail()
        {
            var validEmailAddress = "eric@fleming.com";
            var result = Guard.Against.InvalidEmail(validEmailAddress, nameof(validEmailAddress));

            Assert.Equal(validEmailAddress, result);
        }
    }
}

Copyright © 2024 NimblePros - All Rights Reserved