Validating emails is a common task when using C# data annotations to maintain data quality. Here, we explore five distinct methods for implementation, providing working code for each. You will see the pitfalls of common techniques and how Abstract API offers a more dependable alternative.
How to Implement Email Validation in Data Annotations
Here are four ways to add email validation to your data models. Each method uses a different C# feature, from built-in attributes to custom validation logic.
Built-in EmailAddressAttribute
You can annotate a property with the [EmailAddress]
attribute. At runtime, the validation pipeline calls the EmailAddressAttribute.IsValid
method. The behavior of this method depends on the .NET framework version.
In .NET 4.x, it relies on a regular expression inspired by RFC-5322. For .NET 5 and newer versions, the regex was removed, and the check became much looser. It essentially passes any string that contains an "@" symbol. Empty strings are considered invalid unless you also add the [Required]
attribute, as there is no AllowEmpty flag.
public class UserDto
{
[EmailAddress(ErrorMessage = "Bad e-mail")]
public string? Email { get; set; }
}
RegularExpressionAttribute with a Custom Pattern
This method involves the [RegularExpression]
attribute. You attach it to a property and provide a custom pattern that you control. This is a classic way to restore the strictness of pre-.NET 5 frameworks or to enforce specific corporate constraints, such as a "company.com only" rule. The same pattern works on both the server and the client.
private const string EmailPattern =
@"^[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+\.)+[A-Za-z]{2,24}$";
public class Employee
{
[RegularExpression(EmailPattern, ErrorMessage = "Corp e-mail only")]
public string Email { get; set; } = "";
}
Custom ValidationAttribute with System.Net.Mail.MailAddress
You can create a custom validation attribute. This requires you to subclass ValidationAttribute
and delegate the validation logic to new MailAddress(email)
. This constructor tokenizes the email address and normalizes the domain with punycode. This approach catches obvious syntax errors without the need to maintain a complex regular expression. The attribute can be reused across different models.
public sealed class StrictEmailAttribute : ValidationAttribute
{
protected override ValidationResult? IsValid(
object? value, ValidationContext ctx)
{
if (value is not string s || string.IsNullOrWhiteSpace(s))
return ValidationResult.Success;
try
{
var addr = new System.Net.Mail.MailAddress(s);
// optional: reject addresses lacking MX
}
catch
{
return new ValidationResult(ErrorMessage ??
$"{ctx.MemberName} is not a valid e-mail.");
}
return ValidationResult.Success;
}
}
Model-Level Validation with IValidatableObject
This technique requires your DTO to implement the IValidatableObject
interface. You then add your logic inside the Validate(IEnumerable<ValidationResult>)
method. Inside this method, you have access to the full object graph.
This allows for the implementation of complex, cross-field rules. For example, you can check if an email address uses a corporate domain when a IsBusinessContact
flag is true. This approach keeps validation attribute noise off the model properties.
public class Contact : IValidatableObject
{
public string? Email { get; set; }
public bool IsBusinessContact { get; set; }
public IEnumerable<ValidationResult> Validate(
ValidationContext ctx)
{
if (string.IsNullOrWhiteSpace(Email))
yield break;
if (IsBusinessContact &&
!Email.EndsWith("@contoso.com",
StringComparison.OrdinalIgnoreCase))
yield return new(
"Business contacts must use a contoso address",
new[] { nameof(Email) });
// Delegate to central StrictEmailAttribute or a MX checker
}
}
Challenges of Implementing Email Validation in Data Annotations
These traditional methods present several validation hurdles. The reliance on regular expressions and inconsistent framework behavior often leads to unreliable outcomes and potential security vulnerabilities for your application.
- RFC compliance is brutally complex. The EmailAddressAttribute uses a simple regex that rejects valid international emails but accepts some malformed inputs, which creates an unreliable filter.
- A spec-valid email does not guarantee deliverability. Mailbox providers have their own rules that differ from the official RFC standard. Syntax checks in data annotations often fail to predict if an email address actually works.
- The behavior of EmailAddressAttribute changes between .NET versions. Older frameworks use a strict regex, while newer ones use a looser check. Identical code can produce different validation results depending on the deployment environment.
- Complex patterns with RegularExpressionAttribute risk catastrophic backtracking, a ReDoS vulnerability. Teams must choose between a fast but unsafe regex or a slow, overly strict one, a difficult trade-off between performance and accuracy.
Validate Emails with Abstract API
Ensure data integrity in your C# project by implementing robust email validation with Data Annotations.
Get started for free
How Abstract API Handles Email Validation in C# Data Annotations
Abstract API addresses the core weaknesses of traditional methods with a multi-layer remote check that verifies email deliverability in real time.
- The standard EmailAddressAttribute in C# now performs little more than a null or empty test. This change means syntactically incorrect or undeliverable addresses often pass validation.
- Traditional attributes cannot see MX records, check against disposable-domain lists, or interpret SMTP responses. This limitation results in "valid" addresses that bounce or pollute analytics.
- The API inserts a remote check that covers syntax, typos, role accounts, free providers, and disposable domains. It also validates MX records and SMTP responses to return a single deliverability verdict.
- You can wrap the API call in a custom ValidationAttribute. This action preserves the declarative model style and avoids changes to controller code or front-end scripts.
- The service receives independent updates, so you automatically gain new filters and improved heuristics without a need to redeploy your own application.
How to Bring Abstract API to Your Dev Environment
Once you know Abstract’s capabilities, you add its email validation API to your project with ease.
- Sign up at Abstract and get your API key from the dashboard.
- Add the System.Net.Http.Json or Newtonsoft.Json NuGet package to your project.
- Store the API key in your appsettings.json file:
"Abstract": { "ApiKey": "YOUR_KEY" }
.
Next, create a custom validation attribute that calls the API, then annotate your model. This approach keeps your model definitions clean and declarative.
public class AbstractEmailAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
var email = value as string;
if (string.IsNullOrWhiteSpace(email)) return ValidationResult.Success;
var cfg = (IConfiguration)context.GetService(typeof(IConfiguration));
var key = cfg["Abstract:ApiKey"];
var url = $"https://emailvalidation.abstractapi.com/v1/?api_key={key}&email={Uri.EscapeDataString(email)}";
using var client = new HttpClient();
var resp = client.GetFromJsonAsync<AbstractResp>(url).GetAwaiter().GetResult();
return resp.deliverability == "DELIVERABLE"
? ValidationResult.Success
: new ValidationResult($"Undeliverable email ({resp.deliverability})");
}
}
record AbstractResp(string deliverability);
// Annotate your model
public class UserDto
{
[AbstractEmail]
public string Email { get; set; }
}
Sample Email Validation Implementation with Abstract API
The API returns a detailed JSON object. The primary field is deliverability, which offers a clear verdict to accept or reject an email. The quality_score quantifies risk, while autocorrect suggests a fix for typos. Other boolean fields let you enforce custom policies, like the rejection of disposable email addresses. Here is a sample response for a valid email:
{
"email":"johnsmith@gmail.com",
"autocorrect":"",
"deliverability":"DELIVERABLE",
"quality_score":0.9,
"is_valid_format":{"value":true,"text":"TRUE"},
"is_free_email":{"value":true,"text":"TRUE"},
"is_disposable_email":{"value":false,"text":"FALSE"},
"is_role_email":{"value":false,"text":"FALSE"},
"is_catchall_email":{"value":false,"text":"FALSE"},
"is_mx_found":{"value":true,"text":"TRUE"},
"is_smtp_valid":{"value":true,"text":"TRUE"}
}
Final Thoughts
Traditional validation attributes perform superficial checks that fail to catch undeliverable emails. Abstract API replaces these weak tests with a real-time, multi-layer verification to ensure you only accept valid emails from legitimate domains. For reliable validation, consider an account on Abstract API and get your free API key.
Validate Emails with Abstract API
Don't let bad data in. Implement proper email validation in C# using data annotations.
Get started for free