Skip to content
Commencez avec 100 crédits de vérification gratuits
Qualisend
Tous les articles
Ingénierie / 8 mai 2026

Valider une adresse e-mail dans ASP.NET Core

9 minutes read

Qualisend team
Fenêtre de code intitulée Program.cs validant un e-mail dans ASP.NET Core à travers trois couches de filtrage successives — syntaxe, MX, boîte aux lettres — jusqu'à un badge vert « délivrable ».

Pour valider une adresse e-mail dans ASP.NET Core, la plupart des applications s'appuient sur l'annotation de données intégrée [EmailAddress], laissent la liaison de modèle l'exécuter et vérifient ModelState.IsValid. C'est un bon premier réflexe — l'attribut est livré avec le framework et rejette les absurdités sans expression régulière codée à la main — mais il ne répond qu'à la première des trois questions que pose une vraie validation. L'adresse a-t-elle la bonne forme ? Son domaine peut-il recevoir du courrier ? La boîte aux lettres existe-t-elle réellement ? ASP.NET Core répond à la première de série, a besoin d'un paquet NuGet bien connu pour la deuxième, et laisse la troisième à un problème de réseau qu'il vaut mieux déléguer. Ce guide construit le contrôle sous la forme de trois couches câblées dans le propre pipeline de validation du framework et montre exactement où [EmailAddress] s'arrête. C'est le pendant « framework web » du guide de validation d'e-mail en C#, qui couvre les mêmes primitives en dehors du pipeline de requête.

La réponse courte#

Utilisez [EmailAddress] (ou le .EmailAddress() de FluentValidation) pour la syntaxe, DnsClient.NET pour la recherche MX et une API de vérification pour le contrôle SMTP de la boîte aux lettres — du moins cher au plus cher, en court-circuitant dès qu'une couche est décisive. Les couches DNS et API relèvent d'entrées/sorties asynchrones, elles ne conviennent donc pas à un ValidationAttribute synchrone ; leur foyer idiomatique est un validateur FluentValidation avec les services injectés via l'injection de dépendances (DI). N'ouvrez pas vous-même des sockets SMTP bruts depuis votre application pour sonder les boîtes aux lettres : le port sortant 25 est bloqué chez la plupart des hébergeurs, et la réponse dépend de la réputation de l'IP d'envoi et du greylisting que vous ne voulez pas réimplémenter. Chaque couche écarte des adresses à moindre coût que la précédente ; seule la dernière peut valider une adresse positivement. Si le pipeline vous est nouveau, ce qu'est la vérification d'e-mail en présente d'abord les termes.

Couche 1 : validation du format avec DataAnnotations#

Le contrôle de première couche intégré est l'attribut [EmailAddress] de System.ComponentModel.DataAnnotations. Placez-le sur votre modèle de requête à côté de [Required], et la liaison de modèle l'exécute à chaque liaison :

using System.ComponentModel.DataAnnotations;

public class SignupRequest
{
    [Required]
    [EmailAddress]
    public string Email { get; init; } = string.Empty;
}

Dans un contrôleur MVC ou API marqué [ApiController], un attribut en échec court-circuite en une réponse 400 ValidationProblemDetails avant que le corps de votre action ne s'exécute — vous n'avez jamais à inspecter ModelState à la main :

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("signup")]
public class SignupController : ControllerBase
{
    [HttpPost]
    public IActionResult Register(SignupRequest request)
    {
        // With [ApiController], an invalid [EmailAddress] already returned 400.
        // Reaching here means the string is well-formed.
        return Ok();
    }
}

Sachez exactement ce que cela vous apporte. EmailAddressAttribute vérifie la forme — en fait il n'exige qu'un unique @ avec quelque chose de chaque côté — et rien de plus. Il ne résout jamais le DNS, n'ouvre jamais de socket, et ignore totalement si le domaine existe. definitely-fake@gmail.com passe. info@company-that-folded.com passe. typo@gmial.com passe. Les trois sont non délivrables, et aucun attribut qui se contente de lire la chaîne ne vous le dira jamais — la même raison pour laquelle la validation d'e-mail par regex échoue : forme et délivrabilité sont deux questions distinctes, l'une un fait sur la chaîne et l'autre un fait sur l'internet.

Pourquoi les deux couches suivantes ne peuvent pas être des attributs#

Voici le nœud qui façonne le reste de ce guide : la validation DataAnnotations est synchrone. ValidationAttribute.IsValid renvoie un ValidationResult, pas une Task : il n'existe donc aucun moyen propre d'await une requête DNS ou un appel HTTP à l'intérieur. Bloquer sur .Result ou .GetAwaiter().GetResult() pour forcer un appel asynchrone dans un attribut synchrone invite à la famine du pool de threads et aux interblocages sous charge — exactement ce dont vous ne voulez pas sur un parcours d'inscription.

C'est pourquoi la réponse idiomatique d'ASP.NET Core pour les couches asynchrones est FluentValidation : ses règles MustAsync sont des validateurs de première classe renvoyant des Task, et il résout les dépendances via le même conteneur DI que le reste de votre application, si bien qu'un validateur peut recevoir un HttpClient ou un service DNS dans son constructeur. Vous pouvez conserver [EmailAddress] pour la couche un et ajouter FluentValidation pour les couches deux et trois, ou — comme ci-dessous — laisser le propre .EmailAddress() de FluentValidation couvrir aussi la syntaxe et regrouper les trois couches au même endroit.

Couche 2 : le domaine peut-il recevoir du courrier ?#

Un domaine sans route de messagerie ne peut accepter de courrier pour personne : une seule recherche élimine donc les domaines morts, les noms d'entreprise mal orthographiés et les TLD inventés. Le hic : ASP.NET Core n'a pas de résolveur MX intégré. System.Net.Dns résout les enregistrements A et AAAA mais pas le MX, d'où l'approche standard reposant sur DnsClient.NET — un paquet NuGet largement utilisé. Enveloppez-le derrière une petite interface pour que le validateur dépende d'une abstraction, pas de la bibliothèque :

using DnsClient; // dotnet add package DnsClient
using System.Linq;

public interface IMailRouteChecker
{
    Task<bool> HasMailRouteAsync(string email, CancellationToken ct = default);
}

public sealed class DnsMailRouteChecker : IMailRouteChecker
{
    private readonly ILookupClient _dns;

    public DnsMailRouteChecker(ILookupClient dns) => _dns = dns;

    public async Task<bool> HasMailRouteAsync(string email, CancellationToken ct = default)
    {
        var domain = email[(email.LastIndexOf('@') + 1)..];
        try
        {
            var response = await _dns.QueryAsync(domain, QueryType.MX, cancellationToken: ct);
            return response.Answers.MxRecords().Any();
        }
        catch (DnsResponseException)
        {
            // No reachable resolver, SERVFAIL, and similar — treat as "check later".
            return false;
        }
    }
}

Détachez le domaine au dernier @ avec une expression de plage (range) afin de ne jamais mal analyser une adresse qui contient légalement plusieurs @. Enregistrez un unique LookupClient en singleton — il est thread-safe et met les réponses en cache, si bien qu'une instance par requête ne fait que jeter ce cache. Certains domaines acceptent le courrier sur un enregistrement A sans MX ; si vous voulez honorer ce cas limite, repliez-vous sur QueryType.A lorsque l'ensemble MX est vide.

Couche 3 : la boîte aux lettres existe-t-elle réellement ?#

Les couches un et deux ne peuvent qu'écarter une adresse par exclusion. Un domaine peut publier des enregistrements MX parfaits et n'avoir malgré tout aucune boîte aux lettres à l'adresse que vous détenez — noreply-9f2x@gmail.com est une syntaxe valide sur une route de messagerie active, et c'est pourtant une boîte aux lettres qui n'a jamais été créée. Confirmer une boîte aux lettres précise suppose la conversation de livraison SMTP : se connecter à l'hôte de messagerie, émettre RCPT TO, lire la réponse et se déconnecter avant d'envoyer quoi que ce soit. Comment fonctionne la vérification d'e-mail parcourt tout ce pipeline, domaines catch-all compris.

En principe, vous pouvez scripter cela en C# avec un TcpClient et des commandes SMTP brutes. En pratique, vous ne devriez pas l'exécuter depuis votre serveur applicatif : la plupart des fournisseurs cloud bloquent le port sortant 25, la réponse dépend de la réputation de l'IP depuis laquelle vous vous connectez, et les serveurs de réception appliquent greylisting et limitation de débit aux expéditeurs inconnus. C'est la couche qui mérite d'être déléguée à une infrastructure conçue pour cela.

L'endpoint de vérification de Qualisend exécute tout le pipeline depuis une infrastructure à réputation gérée et renvoie un verdict en temps réel. Modélisez-le comme un HttpClient typé pour que l'URL de base, le délai d'expiration et la clé vivent au même endroit, et désérialisez l'enveloppe de réponse avec System.Text.Json :

using System.Net.Http.Json;
using System.Text.Json.Serialization;

public interface IEmailVerifier
{
    Task<bool> IsDeliverableAsync(string email, CancellationToken ct = default);
}

public sealed class QualisendVerifier : IEmailVerifier
{
    private readonly HttpClient _http;

    public QualisendVerifier(HttpClient http) => _http = http;

    public async Task<bool> IsDeliverableAsync(string email, CancellationToken ct = default)
    {
        using var response = await _http.PostAsJsonAsync("verify", new { email }, ct);

        // On our own outage or a rate limit, don't block a real signup.
        if (!response.IsSuccessStatusCode)
            return true;

        var body = await response.Content.ReadFromJsonAsync<VerifyResponse>(ct);
        return body?.Result.Status != "undeliverable";
    }
}

public record VerifyResponse(
    [property: JsonPropertyName("result")] VerifyResult Result);

public record VerifyResult(
    [property: JsonPropertyName("status")] string Status,
    [property: JsonPropertyName("score")] int Score,
    [property: JsonPropertyName("reason")] string? Reason,
    [property: JsonPropertyName("sub_flags")] IReadOnlyDictionary<string, bool> SubFlags);

La réponse revient à l'intérieur d'une enveloppe result :

{
  "result": {
    "status": "deliverable",
    "score": 95,
    "reason": null,
    "sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
  }
}

status est votre verdict principal — deliverable, risky, undeliverable ou unknown. L'aide ci-dessus n'échoue durement que sur undeliverable et laisse passer tout le reste, mais score, reason et sub_flags (role, disposable, free, catch-all) sont là pour que vous puissiez appliquer votre propre politique — mettre risky en attente de revue, signaler disposable à l'inscription. POST https://api.qualisend.com/v1/verify est une forme d'exemple ; consultez la référence de l'API sur /developers pour l'endpoint exact, le format des clés à portée limitée et chaque champ. N'inventez pas de champs à partir de l'exemple ci-dessus — lisez-les dans la documentation.

Assembler les couches pour valider une adresse e-mail dans ASP.NET Core#

Composez maintenant les trois dans un validateur FluentValidation. Enchaînez les règles du moins cher au plus cher et fixez CascadeMode.Stop pour qu'il s'arrête à la première couche en échec — les règles asynchrones DNS et API ne s'exécutent que sur les adresses qui ont déjà passé la syntaxe :

using FluentValidation;

public class SignupRequestValidator : AbstractValidator<SignupRequest>
{
    public SignupRequestValidator(IMailRouteChecker dns, IEmailVerifier verifier)
    {
        RuleFor(x => x.Email)
            .Cascade(CascadeMode.Stop)   // bail at the first failing layer
            .NotEmpty()
            .EmailAddress()              // layer 1: syntax
            .MustAsync((email, ct) => dns.HasMailRouteAsync(email, ct))
                .WithMessage("Enter an email on a domain that can receive mail.")
            .MustAsync((email, ct) => verifier.IsDeliverableAsync(email, ct))
                .WithMessage("We couldn't confirm a mailbox at this address.");
    }
}

Câblez les services dans Program.cs — le LookupClient en singleton, le vérificateur en client typé avec la clé lue depuis la configuration (une variable d'environnement ou user-secrets en production, jamais en dur), et le validateur par balayage d'assembly :

using DnsClient;
using FluentValidation;
using System.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ILookupClient>(new LookupClient());
builder.Services.AddScoped<IMailRouteChecker, DnsMailRouteChecker>();

builder.Services.AddHttpClient<IEmailVerifier, QualisendVerifier>(client =>
{
    client.BaseAddress = new Uri("https://api.qualisend.com/v1/");
    client.Timeout = TimeSpan.FromSeconds(10);
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
        "Bearer", builder.Configuration["Qualisend:ApiKey"]);
});

builder.Services.AddValidatorsFromAssemblyContaining<SignupRequestValidator>();

var app = builder.Build();

app.MapPost("/signup", async (
    SignupRequest request,
    IValidator<SignupRequest> validator,
    CancellationToken ct) =>
{
    var result = await validator.ValidateAsync(request, ct);
    if (!result.IsValid)
        return Results.ValidationProblem(result.ToDictionary());

    // Every layer passed — safe to persist.
    return Results.Ok();
});

app.Run();

Injecter IValidator<SignupRequest> et appeler ValidateAsync dans l'endpoint est le schéma recommandé aujourd'hui — il exécute correctement les règles asynchrones, là où l'ancienne intégration automatique de MVC ne le faisait jamais. Le même validateur s'insère sans changement dans un contrôleur MVC ; résolvez-le depuis le constructeur et appelez ValidateAsync avant de toucher à la base de données.

Cet ordonnancement est toute l'astuce : la règle de syntaxe court-circuite les déchets gratuitement, le contrôle DNS local écarte les domaines morts pour rien, et l'API n'est sollicitée que pour les adresses qui ont franchi les deux. C'est la même structure à trois couches que les guides Node.js et C# — c'est le découpage en couches, pas le framework, qui rend la validation fiable. Exécutez ce pipeline rapide de façon synchrone à l'inscription et réservez une vérification plus poussée et par lots au nettoyage d'une liste existante, où la latence n'a pas d'importance et où la rigueur, si.

Foire aux questions#

L'attribut EmailAddress suffit-il à valider un e-mail dans ASP.NET Core ?#

Pour la syntaxe, oui — c'est le bon contrôle de première couche, et un choix plus fiable qu'une expression régulière codée à la main. Mais EmailAddressAttribute valide la forme (en réalité juste un unique @ avec du texte de chaque côté) ; il ne résout jamais le DNS et ne contacte aucun serveur de messagerie, si bien qu'un ModelState.IsValid réussi signifie « ça ressemble à un e-mail », pas « ça arrivera à destination ». Associez-le à une recherche MX et à une vérification SMTP de la boîte aux lettres avant de faire confiance à l'adresse.

Comment exécuter une validation d'e-mail asynchrone dans ASP.NET Core ?#

Les attributs DataAnnotations sont synchrones : une requête DNS ou un appel d'API n'a donc pas sa place dans l'un d'eux — bloquer sur l'appel asynchrone risque de provoquer des interblocages. Utilisez plutôt FluentValidation : écrivez des règles MustAsync, injectez vos services DNS et de vérification dans le validateur, puis appelez await validator.ValidateAsync(request) depuis votre endpoint ou votre contrôleur. C'est l'approche recommandée aujourd'hui, car l'ancienne intégration automatique de MVC n'a jamais exécuté les validateurs asynchrones.

ASP.NET Core dispose-t-il d'un moyen intégré de rechercher les enregistrements MX ?#

Non. System.Net.Dns résout les enregistrements d'hôte A et AAAA mais ne prend pas en charge le MX, d'où l'approche standard reposant sur le paquet NuGet DnsClient.NET. Interrogez QueryType.MX, enregistrez un unique LookupClient en singleton, et traitez un ensemble de réponses vide — ou une DnsResponseException — comme « pas de route de messagerie » plutôt que de le laisser lever une exception.

Faut-il valider les e-mails à l'inscription ou lors du nettoyage d'une liste ?#

Les deux, à des profondeurs différentes. Exécutez la syntaxe et la recherche MX de façon synchrone à l'inscription — elles sont assez rapides pour bloquer la requête et donner un retour instantané — et agissez aussi sur le verdict immédiat de l'API à cet endroit ; le guide de l'inscription serverless montre le schéma de bout en bout. Réservez une vérification plus poussée et par lots au nettoyage d'une liste existante, où la latence n'a pas d'importance.


Prêt à ajouter la couche SMTP ? La référence de l'API fournit la forme exacte de /verify, les clés à portée limitée et des exemples à copier-coller, ou glissez une seule adresse dans le vérificateur d'e-mail gratuit pour voir une chaîne approuvée par [EmailAddress] revenir undeliverable.

Your reputation, protected.

Clean your first list in minutes. 100 free credits, no card required.

Get started