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
, anArgumentException
will be thrown. You could also create a custom exception, for exampleInvalidEmailException
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);
}
}
}