In this post I describe a handy validation attribute used during ASP.NET Core model binding for verifying that a GUID doesn't have the default value of Guid.Empty
. I always find myself re-creating it in projects, so now I can just copy it from here!
Why do you need a not-empty validation attribute?
You might be wondering why a "not-empty" attribute is necessary. Isn't that the same thing as the Required
attribute? Let's say you have an action method on an API controller that updates the name of a user. For example:
[ApiController]
public class UserController: Controller
{
[HttpPut("/user/name")]
public IActionResult UpdateName(UpdateNameModel model)
{
if(!ModelState.IsValid)
{
return BadReques(ModelState);
}
_userService.UpdateName(model);
return Ok();
}
}
This method binds the body of the request to an UpdateNameModel
object which contains the Id
of the user to update, and the new name for the user:
public class UpdateNameModel
{
public Guid Id { get; set; }
[Required(AllowEmptyStrings = false)]
[StringLength(100)]
public string Name { get; set; }
}
As you would expect, The string
Name
is marked as required using the [Required]
attribute, and has a validation attribute for the string length. Now, we know the Id
property is also required, but how to achieve that with ValidationAttribute
s? The obvious answer would be to add the [Required]
attribute, but unfortunately that won't work.
The [Required]
attribute checks that the decorated property isn't null. But Guid
is a struct that will never be null, much like int
, DateTime
, and other struct values. That means that Id
will always have a value, whether the request body includes a value for it or not. If an Id
isn't provided, it will have the default
value, which for Guid
is a value with all zeros (also exposed as Guid.Empty
):
00000000-0000-0000-0000-000000000000
Because of this, adding the [Required]
attribute to Id
achieves nothing; the attribute will always say the Id
is valid. Instead, you have a few options:
- Use
BindRequired
to ensure a value forId
was provided in the body of the request. Filip has a great description of that approach here. - Make the property nullable. If you use a
Guid?
instead of aGuid
then the value can be null, so adding theRequired
attribute confirms it's not null. This works, but feels clumsy to me. - Manually check the
Guid
is not empty after running other validation, or by usingIValidatableObject
. This seems messy and overkill for such a simple validation requirement. - Create a custom attribute.
The goal of this post is to be able to apply a validation attribute to the Id
property of our model to validate it has a not-empty/not-default value. e.g.
public class UpdateNameModel
{
[NotEmpty]
public Guid Id { get; set; }
public string Name { get; set; }
}
The NotEmptyAttribute
for Guid
s
The attribute we need has the following characteristics:
- For
Guid
properties: Returns valid for any value exceptGuid.Empty
- For
Guid?
properties: Returns valid for any value exceptGuid.Empty
. Note that it should return valid fornull
values - if the value should not benull
or empty, use two attributes:[Required, NotEmpty]
- For all other types: All values should be valid, including
null
, as this attribute doesn't make sense for non-Guid
properties. The behaviour here is essentially undefined, so you could have the attribute always return invalid if you prefer.
The following attribute satisfies all those properties:
[AttributeUsage(
AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter,
AllowMultiple = false)]
public class NotEmptyAttribute : ValidationAttribute
{
public const string DefaultErrorMessage = "The {0} field must not be empty";
public NotEmptyAttribute() : base(DefaultErrorMessage) { }
public override bool IsValid(object value)
{
//NotEmpty doesn't necessarily mean required
if (value is null)
{
return true;
}
switch (value)
{
case Guid guid:
return guid != Guid.Empty;
default:
return true;
}
}
}
You can add this attribute to the previously described UpdateNameModel
model to guard against empty values in Id
. Note that this guards both against the case where the request body contained no value for Id
, and also the case where an explicit empty (i.e. all zeros) Guid
was provided.
public class UpdateNameModel
{
[NotEmpty]
public Guid Id { get; set; }
[Required(AllowEmptyStrings = false)]
[StringLength(100)]
public string Name { get; set; }
}
The NotDefaultAttribute
for structs in general
The [NotEmpty]
attribute is one I find I need commonly as ID values are often provided as Guid
s, and Guid.Empty
is rarely used. However it's possible that you may want to use a similar approach for other structs. For example, you might want a DateTime
to not have the default value DateTime.MinValue
.
One option is to create different attributes for each struct you care about, for example a NotMinValueAttribute
for the case of DateTime
. Alternatively, we could create a more general attribute that can be applied to any struct.
Instead of comparing to a specific value (Guid.Empty
or DateTime.MinValue
), in the general case we compare a value to the default
for that data type. For consistency with other validation attributes, we won't apply the validation to null
values - null
values will always be valid unless you've also applied the Required
attribute.
Note: I haven't extensively tested this attribute, it was just something I thought about when writing up the
NotEmpty
attribute! It assumes that it's possible to create an instance of the default value, so non-public types or types containing generic-parameters will cause runtime exceptions. It's probably unlikely you're using those types in your public models, but it's something to keep in mind.
public class NotDefaultAttribute : ValidationAttribute
{
public const string DefaultErrorMessage = "The {0} field must not have the default value";
public NotDefaultAttribute() : base(DefaultErrorMessage) { }
public override bool IsValid(object value)
{
//NotDefault doesn't necessarily mean required
if (value is null)
{
return true;
}
var type = value.GetType();
if (type.IsValueType)
{
var defaultValue = Activator.CreateInstance(type);
return !value.Equals(defaultValue);
}
// non-null ref type
return true;
}
}
The overall structure is very similar to the NotEmpty
attribute, as you might expect. We start by checking for null
, and returning true if that's the case.
To find the default value, we need to get the runtime Type
of the provided value
using GetType()
. If we have a value-type (like Guid
or DateTime
) then we can use Activator.CreateInstance()
to create the default value of the type. We can then compare the provided value
to the defaultValue
and return false
if they match. If the type is not a value type (therefore it's a reference type), we already know the type doesn't have the default
value (null
), so we return true
.
One interesting point is that due to the various boxing of values used in this method, you must use the Equals()
method, not the ==
and !=
operators when comparing a value to its default. You can see the result of not doing this in the following test.
public class NotDefaultTests
{
[Fact]
public void WhenEmpty_NotValid()
{
var validator = new NotDefaultAttribute();
var isValid = validator.IsValid(Guid.Empty);
Assert.False(isValid); // Fails if you use != instead of !value.Equals(defaultValue)
}
}
It's also worth pointing out that if you're using FluentValidation, the built-in NotEmpty()
validator already handles checking for default (and null
) values.
Summary
In this post I described a [NotEmpty]
validation attribute that can be used to detect when a Guid
property on a model is bound to the default value Guid.Empty
. This can happen either because the value isn't model bound at all, or it's explicitly model bound to the default value. I also showed a more general version of the attribute that validates a struct doesn't have its default value.