Um eine E-Mail-Adresse in ASP.NET Core zu validieren, verlassen sich die
meisten Anwendungen auf die eingebaute [EmailAddress]-DataAnnotation, lassen
das Model Binding sie ausführen und prüfen ModelState.IsValid. Das ist ein
solider erster Schritt — das Attribut wird mit dem Framework ausgeliefert und
weist Unsinn ohne selbst gebaute Regex ab — aber es beantwortet nur die erste
der drei Fragen, die echte Validierung stellt. Ist die Adresse korrekt geformt?
Kann ihre Domain E-Mails empfangen? Existiert das Postfach tatsächlich? ASP.NET
Core beantwortet die erste Frage von Haus aus, braucht für die zweite ein
bekanntes NuGet-Paket und überlässt die dritte einem Netzwerkproblem, das man
besser abgibt. Dieser Leitfaden baut die Prüfung als drei Schichten auf, die in
die framework-eigene Validierungspipeline eingebunden sind, und zeigt genau, wo
[EmailAddress] aufhört. Er ist das Web-Framework-Gegenstück zum
Leitfaden zur E-Mail-Validierung in C#,
der dieselben Grundbausteine außerhalb der Request-Pipeline behandelt.
Die kurze Antwort#
Verwenden Sie [EmailAddress] (oder FluentValidations .EmailAddress()) für
die Syntax, DnsClient.NET für die MX-Abfrage und eine Verifizierungs-API für die
SMTP-Postfachprüfung — das Günstigste zuerst, mit Kurzschluss, sobald eine
Schicht ein eindeutiges Ergebnis liefert. Die DNS- und die API-Schicht sind
asynchrone I/O, passen also nicht in ein synchrones ValidationAttribute; ihr
idiomatischer Platz ist ein FluentValidation-Validator mit über DI injizierten
Diensten. Öffnen Sie keine rohen SMTP-Sockets aus Ihrer Anwendung heraus, um
Postfächer selbst abzutasten: Der ausgehende Port 25 ist auf den meisten Hosts
blockiert, und die Antwort hängt von der Reputation der sendenden IP und vom
Greylisting ab, das Sie nicht nachbauen wollen. Jede Schicht schließt Adressen
günstiger aus als die vorige; nur die letzte kann eine Adresse einschließen.
Falls die Pipeline neu für Sie ist, erklärt
was E-Mail-Verifizierung ist zuerst die
Begriffe.
Schicht 1: Formatvalidierung mit DataAnnotations#
Die eingebaute Prüfung für Schicht eins ist das [EmailAddress]-Attribut aus
System.ComponentModel.DataAnnotations. Setzen Sie es an Ihrem Request-Modell
neben [Required], und das Model Binding führt es bei jedem Bind aus:
using System.ComponentModel.DataAnnotations;
public class SignupRequest
{
[Required]
[EmailAddress]
public string Email { get; init; } = string.Empty;
}
In einem MVC- oder API-Controller, der mit [ApiController] markiert ist,
schließt ein fehlgeschlagenes Attribut vor dem Ausführen Ihres Action-Bodys
mit einer 400-ValidationProblemDetails-Antwort kurz — Sie müssen
ModelState nie von Hand prüfen:
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();
}
}
Machen Sie sich genau klar, was Ihnen das bringt. EmailAddressAttribute prüft
die Form — tatsächlich verlangt es nur ein einzelnes @ mit etwas auf beiden
Seiten — und nicht mehr. Es löst niemals DNS auf, öffnet nie einen Socket und
hat keine Ahnung, ob die Domain existiert. definitely-fake@gmail.com besteht.
info@company-that-folded.com besteht. typo@gmial.com besteht. Alle drei sind
unzustellbar, und kein Attribut, das nur den String liest, wird Ihnen das je
verraten — aus demselben Grund, aus dem
Regex-basierte E-Mail-Validierung scheitert:
Form und Zustellbarkeit sind unterschiedliche Fragen, die eine eine Tatsache
über den String, die andere eine Tatsache über das Internet.
Warum die nächsten beiden Schichten keine Attribute sein können#
Hier ist der Haken, der den Rest dieses Leitfadens prägt: Die
DataAnnotations-Validierung ist synchron. ValidationAttribute.IsValid gibt ein
ValidationResult zurück, kein Task, es gibt also keinen sauberen Weg, eine
DNS-Abfrage oder einen HTTP-Aufruf darin zu await-en. Mit .Result oder
.GetAwaiter().GetResult() zu blockieren, um einen asynchronen Aufruf in ein
synchrones Attribut zu zwingen, lädt unter Last zu Thread-Pool-Aushungerung und
Deadlocks ein — genau das, was Sie auf einem Anmeldepfad nicht wollen.
Deshalb lautet die idiomatische ASP.NET-Core-Antwort für die asynchronen
Schichten FluentValidation: Seine MustAsync-Regeln sind vollwertige,
Task-zurückgebende Validatoren, und es löst Abhängigkeiten über denselben
DI-Container auf wie der Rest Ihrer Anwendung, sodass ein Validator einen
HttpClient oder einen DNS-Dienst in seinem Konstruktor entgegennehmen kann.
Sie können [EmailAddress] für Schicht eins beibehalten und FluentValidation
für Schicht zwei und drei hinzufügen — oder, wie unten, FluentValidations
eigenes .EmailAddress() auch die Syntax abdecken lassen und alle drei Schichten
an einem Ort halten.
Schicht 2: Kann die Domain E-Mails empfangen?#
Eine Domain ohne Mail-Route kann für niemanden E-Mails annehmen, also eliminiert
eine einzige Abfrage tote Domains, falsch geschriebene Firmennamen und
erfundene TLDs. Der Haken: ASP.NET Core hat keinen eingebauten MX-Resolver.
System.Net.Dns löst A- und AAAA-Einträge auf, aber kein MX, daher ist der
Standardansatz DnsClient.NET — ein weit verbreitetes NuGet-Paket. Kapseln Sie es
hinter einer kleinen Schnittstelle, damit der Validator von einer Abstraktion
abhängt und nicht von der Bibliothek:
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;
}
}
}
Trennen Sie die Domain am letzten @ mit einem Bereichsausdruck ab, damit Sie
eine Adresse, die legal mehr als ein @ enthält, nie falsch parsen. Registrieren
Sie einen einzigen LookupClient als Singleton — er ist thread-sicher und cacht
Antworten, sodass eine Instanz pro Request diesen Cache einfach wegwirft. Manche
Domains nehmen E-Mails über einen A-Eintrag ohne MX an; wenn Sie diesen
Sonderfall berücksichtigen wollen, fallen Sie auf QueryType.A zurück, wenn die
MX-Menge leer ist.
Schicht 3: Existiert das Postfach tatsächlich?#
Die Schichten eins und zwei können eine Adresse nur ausschließen. Eine Domain
kann perfekte MX-Einträge veröffentlichen und trotzdem kein Postfach an der
Adresse haben, die Sie in Händen halten — noreply-9f2x@gmail.com ist gültige
Syntax auf einer aktiven Mail-Route und dennoch ein Postfach, das nie angelegt
wurde. Ein bestimmtes Postfach zu bestätigen bedeutet die
SMTP-Zustellkonversation: sich mit dem Mail-Host verbinden, RCPT TO absetzen,
die Antwort lesen und die Verbindung trennen, bevor irgendetwas gesendet wird.
Wie E-Mail-Verifizierung funktioniert
durchläuft diese vollständige Pipeline, samt Catch-all-Domains.
Im Prinzip können Sie das in C# mit einem TcpClient und rohen SMTP-Befehlen
skripten. In der Praxis sollten Sie es nicht von Ihrem Anwendungsserver aus
ausführen: Die meisten Cloud-Anbieter blockieren den ausgehenden Port 25, die
Antwort hängt von der Reputation der IP ab, von der aus Sie sich verbinden, und
empfangende Server betreiben Greylisting und Rate-Limiting bei unbekannten
Absendern. Dies ist die Schicht, die es sich lohnt an eine dafür gebaute
Infrastruktur zu delegieren.
Der Verify-Endpoint von Qualisend führt die gesamte Pipeline von
reputationsverwalteter Infrastruktur aus und liefert in Echtzeit ein Urteil.
Modellieren Sie ihn als typisierten HttpClient, damit Basis-URL, Timeout und
Schlüssel an einem Ort liegen, und deserialisieren Sie den Antwort-Envelope mit
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);
Die Antwort kommt in einem result-Envelope zurück:
{
"result": {
"status": "deliverable",
"score": 95,
"reason": null,
"sub_flags": { "role": false, "disposable": false, "free": false, "catch_all": false }
}
}
status ist Ihr entscheidendes Urteil — deliverable, risky,
undeliverable oder unknown. Der Helfer oben lässt nur undeliverable hart
scheitern und alles andere durch, aber score, reason und sub_flags (Rolle,
Wegwerf, kostenlos, Catch-all) sind da, damit Sie Ihre eigene Richtlinie
anwenden können — risky zur Prüfung zurückhalten, disposable bei der
Anmeldung markieren. POST https://api.qualisend.com/v1/verify ist eine
Platzhalterform; sehen Sie in der API-Referenz unter /developers
nach dem genauen Endpoint, dem Format der Scoped Keys und jedem Feld. Erfinden
Sie keine Felder aus dem obigen Beispiel — lesen Sie sie aus der Dokumentation.
Die Schichten zusammensetzen, um eine E-Mail-Adresse in ASP.NET Core zu validieren#
Setzen Sie nun alle drei in einem FluentValidation-Validator zusammen. Verketten
Sie die Regeln, das Günstigste zuerst, und setzen Sie CascadeMode.Stop, damit
er bei der ersten fehlschlagenden Schicht abbricht — die asynchronen DNS- und
API-Regeln laufen nur bei Adressen, die die Syntax bereits bestanden haben:
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.");
}
}
Verdrahten Sie die Dienste in Program.cs — den LookupClient als Singleton,
den Verifier als typisierten Client mit dem aus der Konfiguration gelesenen
Schlüssel (eine Umgebungsvariable oder User-Secrets in der Produktion, niemals
hart codiert) und den Validator per Assembly-Scan:
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();
IValidator<SignupRequest> zu injizieren und ValidateAsync im Endpoint
aufzurufen ist das aktuell empfohlene Muster — es führt die asynchronen Regeln
korrekt aus, was die alte automatische MVC-Integration nie tat. Derselbe
Validator lässt sich unverändert in einen MVC-Controller einsetzen; lösen Sie
ihn aus dem Konstruktor auf und rufen Sie ValidateAsync auf, bevor Sie die
Datenbank berühren.
Genau diese Reihenfolge ist der ganze Trick: Die Syntaxregel schließt Müll gratis kurz, die lokale DNS-Prüfung schließt tote Domains für nichts aus, und die API wird nur für Adressen aufgerufen, die beides bestanden haben. Es ist dieselbe Dreischichten-Form wie in den Leitfäden für Node.js und C# — die Schichtung, nicht das Framework, macht die Validierung zuverlässig. Lassen Sie diese schnelle Pipeline bei der Anmeldung synchron laufen und behalten Sie die tiefere, im Batch durchgeführte Verifizierung für das Bereinigen einer bestehenden Liste vor, wo Latenz keine Rolle spielt und Gründlichkeit schon.
Häufig gestellte Fragen#
Reicht das EmailAddress-Attribut aus, um eine E-Mail in ASP.NET Core zu validieren?#
Für die Syntax ja — es ist die richtige Prüfung für Schicht eins und die bessere
Wahl als eine selbst gebaute Regex. Aber EmailAddressAttribute validiert die
Form (im Grunde nur ein einzelnes @ mit Text auf beiden Seiten); es löst
niemals DNS auf und kontaktiert keinen Mailserver. Ein erfolgreiches
ModelState.IsValid bedeutet also "sieht aus wie eine E-Mail", nicht "wird
zugestellt". Kombinieren Sie es mit einer MX-Abfrage und einer
SMTP-Postfachprüfung, bevor Sie der Adresse vertrauen.
Wie führe ich in ASP.NET Core eine asynchrone E-Mail-Validierung durch?#
DataAnnotations-Attribute sind synchron, daher gehört eine DNS-Abfrage oder ein
API-Aufruf nicht hinein — den asynchronen Aufruf zu blockieren birgt die Gefahr
von Deadlocks. Verwenden Sie stattdessen FluentValidation: Schreiben Sie
MustAsync-Regeln, injizieren Sie Ihre DNS- und Verifizierungsdienste in den
Validator und rufen Sie await validator.ValidateAsync(request) aus Ihrem
Endpoint oder Controller auf. Das ist der aktuell empfohlene Ansatz, denn die
alte automatische MVC-Integration hat asynchrone Validatoren nie ausgeführt.
Bietet ASP.NET Core eine eingebaute Möglichkeit, MX-Einträge nachzuschlagen?#
Nein. System.Net.Dns löst A- und AAAA-Host-Einträge auf, unterstützt aber kein
MX, weshalb der Standardansatz das NuGet-Paket DnsClient.NET ist. Fragen Sie
QueryType.MX ab, registrieren Sie einen LookupClient als Singleton und
behandeln Sie eine leere Antwortmenge — oder eine DnsResponseException — als
"keine Mail-Route", anstatt sie eine Exception werfen zu lassen.
Sollte ich E-Mails bei der Anmeldung oder beim Bereinigen einer Liste validieren?#
Beides, in unterschiedlicher Tiefe. Führen Sie Syntax und MX-Abfrage bei der Anmeldung synchron aus — sie sind schnell genug, um den Request zu blockieren und sofortiges Feedback zu geben — und handeln Sie auch dort nach dem unmittelbaren API-Urteil; der Leitfaden zur Serverless-Anmeldung zeigt das Muster von Anfang bis Ende. Behalten Sie die tiefere, im Batch durchgeführte Verifizierung für das Bereinigen einer bestehenden Liste vor, wo Latenz keine Rolle spielt.
Bereit, die SMTP-Schicht hinzuzufügen? Die API-Referenz enthält
die genaue /verify-Form, Scoped Keys und Copy-and-paste-Beispiele, oder werfen
Sie eine einzelne Adresse in den kostenlosen E-Mail-Checker,
um zuzusehen, wie ein von [EmailAddress] gebilligter String als
undeliverable zurückkommt.