Modellvalidierung in ASP.NET Core MVC und Razor Pages

In diesem Artikel wird erläutert, wie Benutzereingaben in einer ASP.NET Core MVC- oder Razor Pages-App validiert werden.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).

Modellstatus

Der Modellstatus stellt Fehler dar, die aus zwei Subsystemen kommen: Modellbindung und Modellvalidierung. Fehler, die von der Modellbindung herrühren, sind im allgemeinen Datenkonvertierungsfehler. Beispielsweise wird ein "x" in ein Integerfeld eingegeben. Die Modellvalidierung erfolgt nach der Modellbindung und meldet Fehler, bei denen die Daten nicht den Geschäftsregeln entsprechen. Beispielsweise wird eine 0 in einem Feld eingegeben, das eine Bewertung zwischen 1 und 5 erwartet.

Sowohl Modellbindung als auch Modellvalidierung finden noch vor der Ausführung einer Controlleraktion oder einer Razor Pages-Handlermethode statt. Bei Web-Apps liegen die Überprüfung von ModelState.IsValid und entsprechende Maßnahmen im Verantwortungsbereich der App. Web-Apps zeigen die Seite normalerweise mit einer Fehlermeldung erneut an. Im folgenden Razor Pages-Beispiel wird das veranschaulicht:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Für ASP.NET Core MVC mit Controllern und Ansichten zeigt das folgende Beispiel, wie Sie ModelState.IsValid in einer Controlleraktion überprüfen können:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Web-API-Controller müssen ModelState.IsValid nicht überprüfen, wenn sie das [ApiController]-Attribut enthalten. In diesem Fall wird eine automatische HTTP 400-Antwort zurückgegeben, die Fehlerdetails beinhaltet, wenn der Modellstatus ungültig ist. Weitere Informationen finden Sie unter Automatische HTTP 400-Antworten.

Validierung erneut ausführen

Die Validierung erfolgt automatisch, aber ggf. möchten Sie sie manuell wiederholen. Eine Szenario wäre, dass Sie einen Wert für eine Eigenschaft berechnen und möchten, dass die Validierung erneut ausgeführt wird, nachdem der berechnete Wert für die Eigenschaft festgelegt wurde. Um die Validierung erneut ausführen zu können, rufen Sie ModelStateDictionary.ClearValidationState auf, um die für das jeweilige Modell spezifische Validierung zu löschen, und rufen Sie dann TryValidateModel auf:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Validierungsattribute

Mit Validierungsattributen können Sie Validierungsregeln für Modelleigenschaften angeben. Im folgenden Beispiel aus der Beispiel-App sehen Sie eine Modellklasse, die mit Validierungsattributen versehen wurde. Das [ClassicMovie]-Attribut ist ein benutzerdefiniertes Validierungsattribut. Die anderen sind integriert. Nicht gezeigt wird hier das Attribut [ClassicMovieWithClientValidator], das eine Alternative darstellt, ein benutzerdefiniertes Attribut zu implementieren.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Integrierte Attribute

Im Folgenden sind einige der integrierten Validierungsattribute aufgeführt:

  • [ValidateNever]: Gibt an, dass eine Eigenschaft oder ein Parameter von der Validierung ausgeschlossen werden soll
  • [CreditCard]: Überprüft, ob die Eigenschaft ein Kreditkartenformat aufweist. Es sind zusätzliche jQuery-Validierungsmethoden erforderlich.
  • [Compare]: Überprüft, ob zwei Eigenschaften in einem Modell miteinander übereinstimmen
  • [EmailAddress]: Überprüft, ob die Eigenschaft ein E-Mail-Format aufweist
  • [Phone]: Überprüft, ob die Eigenschaft ein Telefonnummernformat aufweist
  • [Range]: Überprüft, ob der Eigenschaftenwert im vorgegebenen Bereich liegt
  • [RegularExpression]: Überprüft, ob der Eigenschaftswert mit dem angegebenen regulären Ausdruck übereinstimmt.
  • [Required]: Überprüft, ob das Feld nicht NULL ist. Weitere Informationen zum Verhalten dieses Attributs finden Sie unter [Required]-Attribut.
  • [StringLength]: Überprüft, ob ein Zeichenfolgeneigenschaftswert kürzer ist als die angegebene Längenbeschränkung.
  • [Url]: Überprüft, ob die Eigenschaft ein URL-Format aufweist.
  • [Remote]: Überprüft die Eingabe auf dem Client, indem eine Aktionsmethode auf dem Server abgerufen wird. Weitere Informationen zum Verhalten dieses Attributs finden Sie unter [Remote]-Attribut.

Im System.ComponentModel.DataAnnotations-Namespace finden Sie eine vollständige Liste der Validierungsattribute.

Fehlermeldungen

Mit Validierungsattributen können Sie die Fehlermeldung angeben, die im Fall einer ungültigen Eingabe angezeigt werden soll. Beispiel:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

Intern rufen die Attribute die Methode String.Format mit einem Platzhalter für den Feldnamen und manchmal zusätzliche Platzhalter auf. Beispiel:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Bei Anwendung auf eine Name-Eigenschaft wäre die Fehlermeldung, die vom vorangehenden Code erstellt wurde, die Folgende: „Name length must be between 6 and 8“ (Die Länge des Namens muss zwischen 6 und 8 Zeichen betragen).

Wenn Sie herausfinden möchten, welche Parameter an die Methode String.Format für die Fehlermeldung eines bestimmten Attributs übergeben werden, sehen Sie sich den DataAnnotations-Quellcode an.

Verwenden von JSON-Eigenschaftsnamen in Validierungsfehlern

Wenn ein Validierungsfehler auftritt, erzeugt die Modellvalidierung standardmäßig ein ModelStateDictionary mit dem Eigenschaftsnamen als Fehlerschlüssel. Einige Apps, z. B. Single-Page-Apps, profitieren von der Verwendung von JSON-Eigenschaftsnamen für Validierungsfehler, die von Web-APIs generiert werden. Der folgende Code konfiguriert die Validierung für die Verwendung von SystemTextJsonValidationMetadataProvider für JSON-Eigenschaftsnamen:

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Der folgende Code konfiguriert die Validierung für die Verwendung von NewtonsoftJsonValidationMetadataProvider für JSON-Eigenschaftsnamen beim Verwenden von Json.NET:

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Ein Beispiel für die Richtlinie zur Verwendung der Camel-Case-Schreibweise finden Sie unter Program.cs auf GitHub.

Non-Nullable-Verweistypen und das [Required]-Attribut

Das Validierungssystem behandelt Parameter oder Eigenschaften, die keine NULL-Werte zulassen, als ob diese ein [Required(AllowEmptyStrings = true)]-Attribut hätten. Durch das Aktivieren von Nullable-Kontexten beginnt MVC implizit mit der Validierung von Non-Nullable-Eigenschaften oder -Parametern, als ob sie ein [Required(AllowEmptyStrings = true)]-Attribut aufweisen würden. Betrachten Sie folgenden Code:

public class Person
{
    public string Name { get; set; }
}

Wenn die App mit <Nullable>enable</Nullable> erstellt wurde, führt ein fehlender Wert für Name in einem JSON- oder Formularbeitrag zu einem Validierungsfehler. Dies kann widersprüchlich erscheinen, da das Attribut „[Required(AllowEmptyStrings = true)]“ impliziert ist, aber dieses Verhalten wird erwartete, da leere Zeichenfolgen standardmäßig in NULL konvertiert werden. Verwenden Sie einen Nullable-Verweistyp, damit NULL oder fehlende Werte für die Name-Eigenschaft angegeben werden können:

public class Person
{
    public string? Name { get; set; }
}

Dieses Verhalten kann deaktiviert werden, indem SuppressImplicitRequiredAttributeForNonNullableReferenceTypes in Program.cs konfiguriert wird:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

[Required]-Validierung auf dem Server

Auf dem Server wird ein erforderlicher Wert als fehlend betrachtet, wenn für eine Eigenschaft NULL festgelegt wurde. Ein Feld, das keine NULL-Werte zulässt, ist immer gültig, und die Fehlermeldung des [Required]-Attributs wird nie angezeigt.

Die Modellbindung für eine Eigenschaft, die keine NULL-Werte zulässt, schlägt jedoch möglicherweise fehl, und führt zu einer Fehlermeldung wie The value '' is invalid (Der Wert „“ ist ungültig). Wenn Sie eine benutzerdefinierte Fehlermeldung für die serverseitige Validierung von Typen, die nicht NULL zulassen, angeben möchten, gibt es die folgenden Optionen:

  • Lassen Sie für das Feld NULL-Werte zu (z. B. decimal? statt decimal). Nullable<T>-Werttypen werden wie gewöhnliche Nullable-Typen behandelt.

  • Geben Sie die Standardfehlermeldung an, die von der Modellbindung verwendet werden soll, wie es im folgenden Beispiel gezeigt wird:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    Weitere Informationen zu Fehlern bei der Modellbindung, für die Sie Standardmeldungen festlegen können, finden Sie unter DefaultModelBindingMessageProvider.

[Required]-Validierung auf dem Client

Typen und Zeichenfolgen, für die keine NULL-Werte festgelegt werden können, werden auf dem Client anders behandelt wie auf dem Server. Auf dem Client:

  • Ein Wert gilt nur als vorhanden, wenn eine Eingabe für ihn erfolgt ist. Deshalb werden bei der clientseitigen Validierung Typen, für die keine NULL-Werte festgelegt werden können, gleich behandelt wie Nullable-Typen.
  • Leerzeichen in einem Zeichenfolgenfeld werden von der jQuery Validation-Required-Methode als gültige Eingabe betrachtet. Die serverseitige Validierung betrachtet ein erforderliches Zeichenfolgenfeld als ungültig, wenn nur Leerzeichen eingegeben werden.

Wie bereits gesagt wurde, werden Typen, die keine NULL-Werte zulassen, so behandelt, als hätten sie ein [Required(AllowEmptyStrings = true)]-Attribut. Das heißt, die clientseitige Validierung erfolgt, auch wenn Sie das [Required(AllowEmptyStrings = true)]-Attribut nicht anwenden. Wenn Sie das Attribut jedoch nicht anwenden, erhalten Sie eine Standardfehlermeldung. Wenn Sie eine benutzerdefinierte Fehlermeldung angeben möchten, verwenden Sie das Attribut.

[Remote]-Attribut

Das [Remote]-Attribut implementiert die clientseitige Validierung, für die eine Methode auf dem Server aufgerufen werden muss, um zu bestimmen, ob die Feldeingabe gültig ist. So muss die App z. B. überprüfen, ob eine Benutzername bereits verwendet wird.

So wird die Remotevalidierung implementiert:

  1. Erstellen Sie eine Aktionsmethode für JavaScript, die aufgerufen werden soll. Die jQuery-Validierungsmethode remote erwartet eine JSON-Antwort:

    • true bedeutet, die Eingabedaten sind gültig.
    • false, undefined oder null bedeutet, dass die Eingabe ungültig ist. Verwenden Sie die Standardfehlermeldung.
    • Jede andere Zeichenfolge bedeutet, dass die Eingabe ungültig ist. Verwenden Sie die Zeichenfolge als benutzerdefinierte Fehlermeldung.

    Hier finden Sie ein Beispiel einer Aktionsmethode, die eine benutzerdefinierte Fehlermeldung zurückgibt:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Versehen Sie in der Modellklasse die Eigenschaft mit einem [Remote]-Attribut, das auf die Validierungsaktionsmethode zeigt, wie es im folgenden Beispiel gezeigt wird:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

Die serverseitige Validierung muss auch für Clients implementiert werden, für die JavaScript deaktiviert ist.

Zusätzliche Felder

Die AdditionalFields-Eigenschaft des [Remote]-Attributs ermöglicht Ihnen die Validierung von Kombinationen von Feldern für Daten auf dem Server. Wenn z. B. das User-Modell die Eigenschaften FirstName und LastName hätte, sollten Sie überprüfen, dass kein bereits vorhandener Benutzer diese Namenskombination verwendet. Das folgende Beispiel zeigt die Verwendung von AdditionalFields:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields könnte explizit auf die Zeichenfolgen „FirstName“ und „LastName“ festgelegt werden, aber die Verwendung des nameof-Operators vereinfacht das spätere Refactoring. Die Aktionsmethode für diese Validierung muss sowohl firstName- als auch lastName-Argumente akzeptieren:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Wenn der Benutzer einen Vor- oder einen Nachnamen eingibt, führt JavaScript einen Remoteaufruf durch, um zu prüfen, ob dieses Paar Namen bereits verwendet wurde.

Wenn Sie mindestens zwei weitere Felder überprüfen möchten, geben Sie sie als eine mit Kommas getrennte Liste an. Wenn Sie z. B. dem Modell eine MiddleName-Eigenschaft hinzufügen möchten, legen Sie das [Remote]-Attribut wie im folgenden Code veranschaulicht fest:

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields muss wie alle anderen Attributargumente ein konstanter Ausdruck sein. Aus diesem Grund verwenden Sie keine interpolierte Zeichenfolge oder rufen Join auf, um AdditionalFields zu initialisieren.

Alternativen zu integrierten Attributen

Wenn Sie eine Validierung benötigen, die nicht von integrierten Attributen bereitgestellt wird, haben Sie die folgenden Möglichkeiten:

Benutzerdefinierte Attribute

Für Szenarios, die die integrierten Validierungsattribute nicht verarbeiten können, können Sie benutzerdefinierte Validierungsattribute erstellen. Erstellen Sie eine Klasse, die von ValidationAttribute erbt, und überschreiben Sie die IsValid-Methode.

Die IsValid-Methode akzeptiert ein Objekt namens value. Dies ist die Eingabe, die überprüft werden soll. Ein Überladen akzeptiert auch ein ValidationContext-Objekt, das zusätzliche Informationen bereitstellt, z. B. die Modellinstanz, die von der Modellbindung erstellt wurde.

Im folgenden Beispiel wird überprüft, ob das Veröffentlichungsdatum eines Films aus dem Genre Classic (Klassiker) zeitlich nicht hinter einer angegebenen Jahreszahl liegt. Das [ClassicMovie]-Attribut:

  • wird nur auf dem Server ausgeführt.
  • Für Filmklassiker wird das Veröffentlichungsdatum überprüft:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

Die obenstehende movie-Variable stellt ein Movie-Objekt dar, das die Daten der Formularübermittlung enthält. Schlägt die Validierung fehl, wird die Klasse ValidationResult mit einer Fehlermeldung zurückgegeben.

IValidatableObject

Im vorherigen Beispiel wurde nur mit Movie-Typen gearbeitet. Eine andere Option zur Validierung auf Klassenebene ist die Implementierung von IValidatableObject in der Modellklasse, wie im folgenden Beispiel dargestellt wird:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Benutzerdefinierte Validierung

Der folgende Code zeigt, wie Sie nach der Untersuchung des Modells einen Modellfehler hinzufügen:

if (Contact.Name == Contact.ShortName)
{
    ModelState.AddModelError("Contact.ShortName", 
                             "Short name can't be the same as Name.");
}

Der folgende Code implementiert den Validierungstest in einem Controller:

if (contact.Name == contact.ShortName)
{
    ModelState.AddModelError(nameof(contact.ShortName),
                             "Short name can't be the same as Name.");
}

Mit dem folgenden Code wird überprüft, ob Telefonnummer und E-Mail eindeutig sind:

public async Task<IActionResult> OnPostAsync()
{
    // Attach Validation Error Message to the Model on validation failure.          

    if (Contact.Name == Contact.ShortName)
    {
        ModelState.AddModelError("Contact.ShortName", 
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == Contact.PhoneNumber))
    {
        ModelState.AddModelError("Contact.PhoneNumber",
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == Contact.Email))
    {
        ModelState.AddModelError("Contact.Email", "The Email is already in use.");
    }

    if (!ModelState.IsValid || _context.Contact == null || Contact == null)
    {
        // if model is invalid, return the page with the model state errors.
        return Page();
    }
    _context.Contact.Add(Contact);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Der folgende Code implementiert den Validierungstest in einem Controller:

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Id,Name,ShortName,Email,PhoneNumber")] Contact contact)
{
    // Attach Validation Error Message to the Model on validation failure.
    if (contact.Name == contact.ShortName)
    {
        ModelState.AddModelError(nameof(contact.ShortName),
                                 "Short name can't be the same as Name.");
    }

    if (_context.Contact.Any(i => i.PhoneNumber == contact.PhoneNumber))
    {
        ModelState.AddModelError(nameof(contact.PhoneNumber),
                                  "The Phone number is already in use.");
    }
    if (_context.Contact.Any(i => i.Email == contact.Email))
    {
        ModelState.AddModelError(nameof(contact.Email), "The Email is already in use.");
    }

    if (ModelState.IsValid)
    {
        _context.Add(contact);
        await _context.SaveChangesAsync();
        return RedirectToAction(nameof(Index));
    }
    return View(contact);
}

Die Überprüfung auf eine eindeutige Telefonnummer oder E-Mail erfolgt in der Regel ebenfalls per Remotevalidierung.

ValidationResult

Betrachten wir einmal das folgende benutzerdefinierte ValidateNameAttribute:

public class ValidateNameAttribute : ValidationAttribute
{
    public ValidateNameAttribute()
    {
        const string defaultErrorMessage = "Error with Name";
        ErrorMessage ??= defaultErrorMessage;
    }

    protected override ValidationResult? IsValid(object? value,
                                         ValidationContext validationContext)
    {
        if (value == null || string.IsNullOrWhiteSpace(value.ToString()))
        {
            return new ValidationResult("Name is required.");
        }

        if (value.ToString()!.ToLower().Contains("zz"))
        {

            return new ValidationResult(
                        FormatErrorMessage(validationContext.DisplayName));
        }

        return ValidationResult.Success;
    }
}

Im folgenden Code wird das benutzerdefinierte [ValidateName]-Attribut angewendet:

public class Contact
{
    public Guid Id { get; set; }

    [ValidateName(ErrorMessage = "Name must not contain `zz`")] 
    public string? Name { get; set; }
    public string? Email { get; set; }
    public string? PhoneNumber { get; set; }
}

Wenn das Modell zz enthält, wird ein neues ValidationResult zurückgegeben.

Validierung der Knoten auf oberster Ebene

Knoten der obersten Ebene sind unter anderem:

  • Aktionsparameter
  • Controllereigenschaften
  • Seitenhandlerparameter
  • Seitenmodelleigenschaften

An ein Modell gebundene Knoten auf oberster Ebene und Modelleigenschaften werden überprüft. Im folgenden Beispiel aus der Beispiel-App verwendet die VerifyPhone-Methode die RegularExpressionAttribute-Klasse, um die phone-Aktionsparameter zu überprüfen:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Knoten auf oberster Ebene können BindRequiredAttribute mit Validierungsattributen verwenden. Im folgenden Beispiel aus der Beispiel-App gibt die CheckAge-Methode an, dass der age-Parameter aus der Abfragezeichenfolge gebunden sein muss, wenn das Formular übermittelt wird:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Auf der Seite für die Altersprüfung (CheckAge.cshtml) sind zwei Formulare vorhanden. Das erste Formular übermittelt einen Age-Wert von 99 als Abfragezeichenfolgen-Parameter: https://localhost:5001/Users/CheckAge?Age=99.

Wenn ein ordnungsgemäß formatierter age-Parameter aus der Abfragezeichenfolge übermittelt wird, wird das Formular überprüft.

Das zweite Formular auf der Seite für die Altersprüfung übermittelt den Age-Wert im Anforderungstext, worauf die Validierung fehlschlägt. Die Bindung schlägt fehl, da der age-Parameter aus einer Abfragezeichenfolge stammen muss.

Maximale Fehleranzahl

Die Validierung stoppt, wenn die maximale Anzahl an Fehlern erreicht wird. Diese liegt standardmäßig bei 200. Sie können diese Zahl mit dem folgenden Code in Program.cs konfigurieren:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Maximale Rekursion

ValidationVisitor durchläuft den Objektgraph des Modells, das überprüft wird. Bei Modellen, die umfassend oder unendlich rekursiv sind, führt die Validierung möglicherweise zu einem Stapelüberlauf. MvcOptions.MaxValidationDepth stellt eine Möglichkeit dar, die Validierung frühzeitig zu beenden, wenn die Besucherrekursion eine konfigurierte Tiefe überschreitet. Der Standardwert von MvcOptions.MaxValidationDepth ist „32“.

Automatischer Kurzschluss

Die Validierung wird automatisch kurzgeschlossen (übersprungen), wenn der Modellgraph keine Validierung erfordert. Objekte, deren Validierung durch die Runtime übersprungen wird, beinhalten Primitivsammlungen (z. B. byte[], string[], Dictionary<string, string>) und komplexe Objektgraphen, die keine Validierungssteuerelemente haben.

Clientseitige Validierung

Die Überprüfung auf Clientseite verhindert die Übermittlung solange, bis das Formular gültig ist. Die Schaltfläche „Übermitteln“ führt JavaScript aus, das entweder das Formular übermittelt oder Fehlermeldungen anzeigt.

Die clientseitige Validierung umgeht einen unnötigen Roundtrip zum Server, wenn es in einem Formular Eingabefehler gibt. Die folgenden Skriptverweise in _Layout.cshtml und _ValidationScriptsPartial.cshtml unterstützen die clientseitige Validierung:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

Beim jQuery Unobtrusive Validation-Skript handelt es sich um eine benutzerdefinierte Front-End-Bibliothek von Microsoft, die auf dem beliebten Plug-In jQuery Validation basiert. Ohne jQuery Unobtrusive Validation müssten Sie dieselbe Validierungslogik an zwei unterschiedlichen Stellen codieren: einmal in den Attributen der Validierung auf Serverseite für Modelleigenschaften und einmal in den Skripts auf Clientseite. Stattdessen verwenden die Taghilfsprogramme und die HTML-Hilfsprogramme die Validierungsattribute und Typmetadaten aus den Modelleigenschaften, um HTML 5-Attribute des Typs data- in den Formularelementen zu rendern, die validiert werden müssen. „jQuery Unobtrusive Validation“ analysiert dann die data--Attribute und übergibt die Logik an „jQuery Validation“, wodurch die serverseitige Validierungslogik im Prinzip auf den Client kopiert wird. Sie können Validierungsfehler im Client anzeigen, indem Sie die relevanten Taghilfsprogramme wie folgt verwenden:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Die vorangegangenen Taghilfsprogramme rendern die folgende HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Beachten Sie, dass die data--Attribute in der HTML-Ausgabe mit den Validierungsattributen für die Movie.ReleaseDate-Eigenschaft übereinstimmen. Das data-val-required-Attribut enthält eine Fehlermeldung, die angezeigt wird, wenn der Benutzer das Datumsfeld für die Veröffentlichung nicht ausfüllt. „jQuery Unobtrusive Validation“ übergibt diesen Wert an die jQuery-Validierungsmethode required(), die diese Meldung dann im zugehörigen <span>-Element anzeigt.

Die Datentypvalidierung basiert auf dem .NET-Typ einer Eigenschaft, es sei denn, dieser wird von einem [DataType]-Attribut überschrieben. Browser haben ihre eigenen Standardfehlermeldungen, aber das jQuery Validation Unobtrusive Validation-Paket kann diese Meldungen überschreiben. [DataType]-Attribute und Unterklassen wie [EmailAddress] ermöglichen das Angeben der Fehlermeldung.

Unaufdringliche Validierung

Informationen zur unaufdringlichen Validierung finden Sie in diesem GitHub-Issue.

Hinzufügen der Validierung zu dynamischen Formularen

„jQuery Unobtrusive Validation“ übergibt die Validierungslogik und -parameter an „jQuery Validation“, wenn die Seite das erste Mal geladen wird. Deshalb funktioniert die Validierung nicht automatisch bei dynamisch generierten Formularen. Wenn Sie die Validierung aktivieren möchten, müssen Sie jQuery Unobtrusive Validation auffordern, das dynamische Formular direkt nach dem Erstellen zu analysieren. Beispielweise wird im nachfolgenden Code dargestellt, wie Sie die Validierung auf Clientseite für ein Formular einrichten können, das über AJAX hinzugefügt wurde.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

Die $.validator.unobtrusive.parse()-Methode akzeptiert einen jQuery-Selektor für ihr einziges Argument. Diese Methode fordert „jQuery Unobtrusive Validation“ auf, die data--Attribute von Formularen in diesem Selektor zu analysieren. Die Werte diese Attribute werden dann an das Plug-In „jQuery Validation“ übergeben.

Hinzufügen der Validierung zu dynamischen Steuerelementen

Die $.validator.unobtrusive.parse()-Methode funktioniert für ein gesamtes Formular, nicht für individuelle, dynamisch erstellte Steuerelemente wie <input> und <select/>. Wenn das Formular erneut analysiert werden soll, entfernen Sie die Validierungsdaten, die bei der vorherigen Analyse des Formulars hinzugefügt wurden, wie es im folgenden Beispiel gezeigt wird:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Benutzerdefinierte clientseitige Validierung

Die benutzerdefinierte clientseitige Validierung erfolgt über die Generierung von HTML-Attributen des Typs data-, die mit einem benutzerdefinierten jQuery Validation-Adapter funktionieren. Der folgende Beispieladaptercode wurde für die [ClassicMovie]- und [ClassicMovieWithClientValidator]-Attribute geschrieben, die zuvor in diesem Artikel bereits eingeführt wurden:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Informationen zum Schreiben dieser Adapter finden Sie in der jQuery Validation-Dokumentation.

Die Verwendung eines Adapters für ein bestimmtes Feld wird von data--Attributen ausgelöst, die Folgendes tun:

  • Das Feld wird so gekennzeichnet, dass es validiert wird (data-val="true").
  • Ein Name für die Validierungsregel und ein Text für die Fehlermeldung werden bestimmt (z. B. data-val-rulename="Error message.").
  • Zusätzliche Parameter werden bereitgestellt, die vom Validierungssteuerelement benötigt werden (z. B. data-val-rulename-param1="value").

Im folgenden Beispiel sehen Sie die data--Attribute des ClassicMovie-Attributs der Beispiel-App:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Wie bereits gesagt, verwenden Taghilfsprogramme und HTML-Hilfsprogramme Informationen der Validierungsattribute, um data--Attribute zu rendern. Es gibt zwei Möglichkeiten, Code zu schreiben, der zur Erstellung von benutzerdefinierten HTML-Attributen des Typs data- führt:

  • Erstellen einer Klasse, die von AttributeAdapterBase<TAttribute> ableitet, und einer Klasse, die IValidationAttributeAdapterProvider implementiert, und Registrieren Ihres Attributs und des dazugehörigen Adapters in der Abhängigkeitsinjektion. Diese Methode folgt dem Single-Responsibility-Prinzip, nach dem sich der serverbezogene und der clientbezogene Validierungscode in unterschiedlichen Klassen befinden. Der Adapter bietet außerdem den Vorteil, dass andere Dienste in der Dependency Injection (DI) bei Bedarf zur Verfügung stehen, da der Adapter in der DI registriert ist.
  • Implementieren von IClientModelValidator in Ihrer ValidationAttribute-Klasse. Diese Methode ist geeignet, wenn das Attribut für keinerlei serverseitige Validierung zuständig ist und keine Dienste aus der Abhängigkeitsinjektion benötigt.

AttributeAdapter für die clientseitige Validierung

Diese Methode, bei der data--Attribute in HTML gerendert werden, wird vom ClassicMovie-Attribut in der Beispiel-App verwendet. So fügen Sie die Clientvalidierung mithilfe dieser Methode hinzu:

  1. Erstellen Sie eine Attributadapterklasse für das benutzerdefinierte Validierungsattribut. Leiten Sie Klasse aus AttributeAdapterBase<TAttribute>ab. Erstellen Sie eine AddValidation-Methode, die der gerenderten Ausgabe data--Attribute hinzufügt, wie es im folgenden Beispiel gezeigt wird:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. Erstellen Sie eine Adapteranbieterklasse, die IValidationAttributeAdapterProvider implementiert. Übergeben Sie in der GetAttributeAdapter-Methode dem Konstruktor des Adapters das benutzerdefinierte Attribut, wie es im folgenden Beispiel gezeigt wird:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Registrieren Sie den Adapteranbieter für die Abhängigkeitsinjektion in Program.cs:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator für die clientseitige Validierung

Diese Methode, bei der data--Attribute in HTML gerendert werden, wird vom ClassicMovieWithClientValidator-Attribut in der Beispiel-App verwendet. So fügen Sie die Clientvalidierung mithilfe dieser Methode hinzu:

  • Implementieren Sie im benutzerdefinierten Validierungsattribut die IClientModelValidator-Schnittstelle, und erstellen Sie eine AddValidation-Methode. Fügen Sie in der AddValidation-Methode die data--Attribute für die Validierung hinzu, wie es im folgenden Beispiel gezeigt wird:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Deaktivieren der clientseitigen Validierung

Im folgenden Code wird die Clientvalidierung in Razor Pages deaktiviert:

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Weitere Optionen zum Deaktivieren der clientseitigen Validierung:

  • Kommentieren Sie den Verweis auf _ValidationScriptsPartial in allen .cshtml-Dateien aus.
  • Entfernen Sie den Inhalt der Datei Pages\Shared_ValidationScriptsPartial.cshtml.

Der vorherige Ansatz verhindert nicht die clientseitige Validierung der ASP.NET Core-Klassenbibliothek IdentityRazor. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten.

Problemdetails

Problemdetails sind nicht das einzige Antwortformat zur Beschreibung eines HTTP-API-Fehlers, sie werden jedoch häufig verwendet, um Fehler für HTTP-APIs zu melden.

Der Dienst für Problemdetails implementiert die IProblemDetailsService-Schnittstelle, die das Erstellen von Problemdetails in ASP.NET Core unterstützt. Die Erweiterungsmethode AddProblemDetails in IServiceCollection registriert die Standardimplementierung von IProblemDetailsService.

In ASP.NET Core-Apps generiert die folgende Middleware HTTP-Antworten mit Problemdetails, wenn AddProblemDetails aufgerufen wird. Dies gilt jedoch nicht, wenn der Accept-HTTP-Anforderungsheader keinen der vom registrierten IProblemDetailsWriter unterstützten Inhaltstyp enthält (Standard: application/json):

Zusätzliche Ressourcen

In diesem Artikel wird erläutert, wie Benutzereingaben in einer ASP.NET Core MVC- oder Razor Pages-App validiert werden.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).

Modellstatus

Der Modellstatus stellt Fehler dar, die aus zwei Subsystemen kommen: Modellbindung und Modellvalidierung. Fehler, die von der Modellbindung herrühren, sind im allgemeinen Datenkonvertierungsfehler. Beispielsweise wird ein "x" in ein Integerfeld eingegeben. Die Modellvalidierung erfolgt nach der Modellbindung und meldet Fehler, bei denen die Daten nicht den Geschäftsregeln entsprechen. Beispielsweise wird eine 0 in einem Feld eingegeben, das eine Bewertung zwischen 1 und 5 erwartet.

Sowohl Modellbindung als auch Modellvalidierung finden noch vor der Ausführung einer Controlleraktion oder einer Razor Pages-Handlermethode statt. Bei Web-Apps liegen die Überprüfung von ModelState.IsValid und entsprechende Maßnahmen im Verantwortungsbereich der App. Web-Apps zeigen die Seite normalerweise mit einer Fehlermeldung erneut an. Im folgenden Razor Pages-Beispiel wird das veranschaulicht:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Für ASP.NET Core MVC mit Controllern und Ansichten zeigt das folgende Beispiel, wie Sie ModelState.IsValid in einer Controlleraktion überprüfen können:

public async Task<IActionResult> Create(Movie movie)
{
    if (!ModelState.IsValid)
    {
        return View(movie);
    }

    _context.Movies.Add(movie);
    await _context.SaveChangesAsync();

    return RedirectToAction(nameof(Index));
}

Web-API-Controller müssen ModelState.IsValid nicht überprüfen, wenn sie das [ApiController]-Attribut enthalten. In diesem Fall wird eine automatische HTTP 400-Antwort zurückgegeben, die Fehlerdetails beinhaltet, wenn der Modellstatus ungültig ist. Weitere Informationen finden Sie unter Automatische HTTP 400-Antworten.

Validierung erneut ausführen

Die Validierung erfolgt automatisch, aber ggf. möchten Sie sie manuell wiederholen. Eine Szenario wäre, dass Sie einen Wert für eine Eigenschaft berechnen und möchten, dass die Validierung erneut ausgeführt wird, nachdem der berechnete Wert für die Eigenschaft festgelegt wurde. Um die Validierung erneut ausführen zu können, rufen Sie ModelStateDictionary.ClearValidationState auf, um die für das jeweilige Modell spezifische Validierung zu löschen, und rufen Sie dann TryValidateModel auf:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Validierungsattribute

Mit Validierungsattributen können Sie Validierungsregeln für Modelleigenschaften angeben. Im folgenden Beispiel aus der Beispiel-App sehen Sie eine Modellklasse, die mit Validierungsattributen versehen wurde. Das [ClassicMovie]-Attribut ist ein benutzerdefiniertes Validierungsattribut. Die anderen sind integriert. Nicht gezeigt wird hier das Attribut [ClassicMovieWithClientValidator], das eine Alternative darstellt, ein benutzerdefiniertes Attribut zu implementieren.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Integrierte Attribute

Im Folgenden sind einige der integrierten Validierungsattribute aufgeführt:

  • [ValidateNever]: Gibt an, dass eine Eigenschaft oder ein Parameter von der Validierung ausgeschlossen werden soll
  • [CreditCard]: Überprüft, ob die Eigenschaft ein Kreditkartenformat aufweist. Es sind zusätzliche jQuery-Validierungsmethoden erforderlich.
  • [Compare]: Überprüft, ob zwei Eigenschaften in einem Modell miteinander übereinstimmen
  • [EmailAddress]: Überprüft, ob die Eigenschaft ein E-Mail-Format aufweist
  • [Phone]: Überprüft, ob die Eigenschaft ein Telefonnummernformat aufweist
  • [Range]: Überprüft, ob der Eigenschaftenwert im vorgegebenen Bereich liegt
  • [RegularExpression]: Überprüft, ob der Eigenschaftswert mit dem angegebenen regulären Ausdruck übereinstimmt.
  • [Required]: Überprüft, ob das Feld nicht NULL ist. Weitere Informationen zum Verhalten dieses Attributs finden Sie unter [Required]-Attribut.
  • [StringLength]: Überprüft, ob ein Zeichenfolgeneigenschaftswert kürzer ist als die angegebene Längenbeschränkung.
  • [Url]: Überprüft, ob die Eigenschaft ein URL-Format aufweist.
  • [Remote]: Überprüft die Eingabe auf dem Client, indem eine Aktionsmethode auf dem Server abgerufen wird. Weitere Informationen zum Verhalten dieses Attributs finden Sie unter [Remote]-Attribut.

Im System.ComponentModel.DataAnnotations-Namespace finden Sie eine vollständige Liste der Validierungsattribute.

Fehlermeldungen

Mit Validierungsattributen können Sie die Fehlermeldung angeben, die im Fall einer ungültigen Eingabe angezeigt werden soll. Beispiel:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

Intern rufen die Attribute die Methode String.Format mit einem Platzhalter für den Feldnamen und manchmal zusätzliche Platzhalter auf. Beispiel:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Bei Anwendung auf eine Name-Eigenschaft wäre die Fehlermeldung, die vom vorangehenden Code erstellt wurde, die Folgende: „Name length must be between 6 and 8“ (Die Länge des Namens muss zwischen 6 und 8 Zeichen betragen).

Wenn Sie herausfinden möchten, welche Parameter an die Methode String.Format für die Fehlermeldung eines bestimmten Attributs übergeben werden, sehen Sie sich den DataAnnotations-Quellcode an.

Non-Nullable-Verweistypen und das [Required]-Attribut

Das Validierungssystem behandelt Parameter oder Eigenschaften, die keine NULL-Werte zulassen, als ob diese ein [Required(AllowEmptyStrings = true)]-Attribut hätten. Durch das Aktivieren von Nullable-Kontexten beginnt MVC implizit mit der Validierung von Non-Nullable-Eigenschaften in nicht generischen Typen oder Parametern, als ob sie ein [Required(AllowEmptyStrings = true)]-Attribut aufweisen würden. Betrachten Sie folgenden Code:

public class Person
{
    public string Name { get; set; }
}

Wenn die App mit <Nullable>enable</Nullable> erstellt wurde, führt ein fehlender Wert für Name in einem JSON- oder Formularbeitrag zu einem Validierungsfehler. Verwenden Sie einen Nullable-Verweistyp, damit NULL oder fehlende Werte für die Name-Eigenschaft angegeben werden können:

public class Person
{
    public string? Name { get; set; }
}

Dieses Verhalten kann deaktiviert werden, indem SuppressImplicitRequiredAttributeForNonNullableReferenceTypes in Program.cs konfiguriert wird:

builder.Services.AddControllers(
    options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

Non-Nullable-Eigenschaften in generischen Typen und das [Required]-Attribut

Non-Nullable-Eigenschaften für generische Typen müssen das [Required]-Attribut enthalten, wenn der Typ erforderlich ist. Im folgenden Code ist TestRequired nicht erforderlich:

public class WeatherForecast<T>
{
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

Im folgenden Code ist TestRequired explizit als erforderlich gekennzeichnet:

using System.ComponentModel.DataAnnotations;

public class WeatherForecast<T>
{
    [Required]
    public string TestRequired { get; set; } = null!;
    public T? Inner { get; set; }
}

[Required]-Validierung auf dem Server

Auf dem Server wird ein erforderlicher Wert als fehlend betrachtet, wenn für eine Eigenschaft NULL festgelegt wurde. Ein Feld, das keine NULL-Werte zulässt, ist immer gültig, und die Fehlermeldung des [Required]-Attributs wird nie angezeigt.

Die Modellbindung für eine Eigenschaft, die keine NULL-Werte zulässt, schlägt jedoch möglicherweise fehl, und führt zu einer Fehlermeldung wie The value '' is invalid (Der Wert „“ ist ungültig). Wenn Sie eine benutzerdefinierte Fehlermeldung für die serverseitige Validierung von Typen, die nicht NULL zulassen, angeben möchten, gibt es die folgenden Optionen:

  • Lassen Sie für das Feld NULL-Werte zu (z. B. decimal? statt decimal). Nullable<T>-Werttypen werden wie gewöhnliche Nullable-Typen behandelt.

  • Geben Sie die Standardfehlermeldung an, die von der Modellbindung verwendet werden soll, wie es im folgenden Beispiel gezeigt wird:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

    Weitere Informationen zu Fehlern bei der Modellbindung, für die Sie Standardmeldungen festlegen können, finden Sie unter DefaultModelBindingMessageProvider.

[Required]-Validierung auf dem Client

Typen und Zeichenfolgen, für die keine NULL-Werte festgelegt werden können, werden auf dem Client anders behandelt wie auf dem Server. Auf dem Client:

  • Ein Wert gilt nur als vorhanden, wenn eine Eingabe für ihn erfolgt ist. Deshalb werden bei der clientseitigen Validierung Typen, für die keine NULL-Werte festgelegt werden können, gleich behandelt wie Nullable-Typen.
  • Leerzeichen in einem Zeichenfolgenfeld werden von der jQuery Validation-Required-Methode als gültige Eingabe betrachtet. Die serverseitige Validierung betrachtet ein erforderliches Zeichenfolgenfeld als ungültig, wenn nur Leerzeichen eingegeben werden.

Wie bereits gesagt wurde, werden Typen, die keine NULL-Werte zulassen, so behandelt, als hätten sie ein [Required(AllowEmptyStrings = true)]-Attribut. Das heißt, die clientseitige Validierung erfolgt, auch wenn Sie das [Required(AllowEmptyStrings = true)]-Attribut nicht anwenden. Wenn Sie das Attribut jedoch nicht anwenden, erhalten Sie eine Standardfehlermeldung. Wenn Sie eine benutzerdefinierte Fehlermeldung angeben möchten, verwenden Sie das Attribut.

[Remote]-Attribut

Das [Remote]-Attribut implementiert die clientseitige Validierung, für die eine Methode auf dem Server aufgerufen werden muss, um zu bestimmen, ob die Feldeingabe gültig ist. So muss die App z. B. überprüfen, ob eine Benutzername bereits verwendet wird.

So wird die Remotevalidierung implementiert:

  1. Erstellen Sie eine Aktionsmethode für JavaScript, die aufgerufen werden soll. Die jQuery-Validierungsmethode remote erwartet eine JSON-Antwort:

    • true bedeutet, die Eingabedaten sind gültig.
    • false, undefined oder null bedeutet, dass die Eingabe ungültig ist. Verwenden Sie die Standardfehlermeldung.
    • Jede andere Zeichenfolge bedeutet, dass die Eingabe ungültig ist. Verwenden Sie die Zeichenfolge als benutzerdefinierte Fehlermeldung.

    Hier finden Sie ein Beispiel einer Aktionsmethode, die eine benutzerdefinierte Fehlermeldung zurückgibt:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Versehen Sie in der Modellklasse die Eigenschaft mit einem [Remote]-Attribut, das auf die Validierungsaktionsmethode zeigt, wie es im folgenden Beispiel gezeigt wird:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; } = null!;
    

Zusätzliche Felder

Die AdditionalFields-Eigenschaft des [Remote]-Attributs ermöglicht Ihnen die Validierung von Kombinationen von Feldern für Daten auf dem Server. Wenn z. B. das User-Modell die Eigenschaften FirstName und LastName hätte, sollten Sie überprüfen, dass kein bereits vorhandener Benutzer diese Namenskombination verwendet. Das folgende Beispiel zeigt die Verwendung von AdditionalFields:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; } = null!;

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; } = null!;

AdditionalFields könnte explizit auf die Zeichenfolgen „FirstName“ und „LastName“ festgelegt werden, aber die Verwendung des nameof-Operators vereinfacht das spätere Refactoring. Die Aktionsmethode für diese Validierung muss sowohl firstName- als auch lastName-Argumente akzeptieren:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Wenn der Benutzer einen Vor- oder einen Nachnamen eingibt, führt JavaScript einen Remoteaufruf durch, um zu prüfen, ob dieses Paar Namen bereits verwendet wurde.

Wenn Sie mindestens zwei weitere Felder überprüfen möchten, geben Sie sie als eine mit Kommas getrennte Liste an. Wenn Sie z. B. dem Modell eine MiddleName-Eigenschaft hinzufügen möchten, legen Sie das [Remote]-Attribut wie im folgenden Code veranschaulicht fest:

[Remote(action: "VerifyName", controller: "Users",
    AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields muss wie alle anderen Attributargumente ein konstanter Ausdruck sein. Aus diesem Grund verwenden Sie keine interpolierte Zeichenfolge oder rufen Join auf, um AdditionalFields zu initialisieren.

Alternativen zu integrierten Attributen

Wenn Sie eine Validierung benötigen, die nicht von integrierten Attributen bereitgestellt wird, haben Sie die folgenden Möglichkeiten:

Benutzerdefinierte Attribute

Für Szenarios, die die integrierten Validierungsattribute nicht verarbeiten können, können Sie benutzerdefinierte Validierungsattribute erstellen. Erstellen Sie eine Klasse, die von ValidationAttribute erbt, und überschreiben Sie die IsValid-Methode.

Die IsValid-Methode akzeptiert ein Objekt namens value. Dies ist die Eingabe, die überprüft werden soll. Ein Überladen akzeptiert auch ein ValidationContext-Objekt, das zusätzliche Informationen bereitstellt, z. B. die Modellinstanz, die von der Modellbindung erstellt wurde.

Im folgenden Beispiel wird überprüft, ob das Veröffentlichungsdatum eines Films aus dem Genre Classic (Klassiker) zeitlich nicht hinter einer angegebenen Jahreszahl liegt. Das [ClassicMovie]-Attribut:

  • wird nur auf dem Server ausgeführt.
  • Für Filmklassiker wird das Veröffentlichungsdatum überprüft:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
        => Year = year;

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult? IsValid(
        object? value, ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value!).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

Die obenstehende movie-Variable stellt ein Movie-Objekt dar, das die Daten der Formularübermittlung enthält. Schlägt die Validierung fehl, wird die Klasse ValidationResult mit einer Fehlermeldung zurückgegeben.

IValidatableObject

Im vorherigen Beispiel wurde nur mit Movie-Typen gearbeitet. Eine andere Option zur Validierung auf Klassenebene ist die Implementierung von IValidatableObject in der Modellklasse, wie im folgenden Beispiel dargestellt wird:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; } = null!;

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; } = null!;

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Validierung der Knoten auf oberster Ebene

Knoten der obersten Ebene sind unter anderem:

  • Aktionsparameter
  • Controllereigenschaften
  • Seitenhandlerparameter
  • Seitenmodelleigenschaften

An ein Modell gebundene Knoten auf oberster Ebene und Modelleigenschaften werden überprüft. Im folgenden Beispiel aus der Beispiel-App verwendet die VerifyPhone-Methode die RegularExpressionAttribute-Klasse, um die phone-Aktionsparameter zu überprüfen:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Knoten auf oberster Ebene können BindRequiredAttribute mit Validierungsattributen verwenden. Im folgenden Beispiel aus der Beispiel-App gibt die CheckAge-Methode an, dass der age-Parameter aus der Abfragezeichenfolge gebunden sein muss, wenn das Formular übermittelt wird:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Auf der Seite für die Altersprüfung (CheckAge.cshtml) sind zwei Formulare vorhanden. Das erste Formular übermittelt einen Age-Wert von 99 als Abfragezeichenfolgen-Parameter: https://localhost:5001/Users/CheckAge?Age=99.

Wenn ein ordnungsgemäß formatierter age-Parameter aus der Abfragezeichenfolge übermittelt wird, wird das Formular überprüft.

Das zweite Formular auf der Seite für die Altersprüfung übermittelt den Age-Wert im Anforderungstext, worauf die Validierung fehlschlägt. Die Bindung schlägt fehl, da der age-Parameter aus einer Abfragezeichenfolge stammen muss.

Maximale Fehleranzahl

Die Validierung stoppt, wenn die maximale Anzahl an Fehlern erreicht wird. Diese liegt standardmäßig bei 200. Sie können diese Zahl mit dem folgenden Code in Program.cs konfigurieren:

builder.Services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

builder.Services.AddSingleton
    <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();

Maximale Rekursion

ValidationVisitor durchläuft den Objektgraph des Modells, das überprüft wird. Bei Modellen, die umfassend oder unendlich rekursiv sind, führt die Validierung möglicherweise zu einem Stapelüberlauf. MvcOptions.MaxValidationDepth stellt eine Möglichkeit dar, die Validierung frühzeitig zu beenden, wenn die Besucherrekursion eine konfigurierte Tiefe überschreitet. Der Standardwert von MvcOptions.MaxValidationDepth ist „32“.

Automatischer Kurzschluss

Die Validierung wird automatisch kurzgeschlossen (übersprungen), wenn der Modellgraph keine Validierung erfordert. Objekte, deren Validierung durch die Runtime übersprungen wird, beinhalten Primitivsammlungen (z. B. byte[], string[], Dictionary<string, string>) und komplexe Objektgraphen, die keine Validierungssteuerelemente haben.

Clientseitige Validierung

Die Überprüfung auf Clientseite verhindert die Übermittlung solange, bis das Formular gültig ist. Die Schaltfläche „Übermitteln“ führt JavaScript aus, das entweder das Formular übermittelt oder Fehlermeldungen anzeigt.

Die clientseitige Validierung umgeht einen unnötigen Roundtrip zum Server, wenn es in einem Formular Eingabefehler gibt. Die folgenden Skriptverweise in _Layout.cshtml und _ValidationScriptsPartial.cshtml unterstützen die clientseitige Validierung:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.3/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.12/jquery.validate.unobtrusive.js"></script>

Beim jQuery Unobtrusive Validation-Skript handelt es sich um eine benutzerdefinierte Front-End-Bibliothek von Microsoft, die auf dem beliebten Plug-In jQuery Validation basiert. Ohne jQuery Unobtrusive Validation müssten Sie dieselbe Validierungslogik an zwei unterschiedlichen Stellen codieren: einmal in den Attributen der Validierung auf Serverseite für Modelleigenschaften und einmal in den Skripts auf Clientseite. Stattdessen verwenden die Taghilfsprogramme und die HTML-Hilfsprogramme die Validierungsattribute und Typmetadaten aus den Modelleigenschaften, um HTML 5-Attribute des Typs data- in den Formularelementen zu rendern, die validiert werden müssen. „jQuery Unobtrusive Validation“ analysiert dann die data--Attribute und übergibt die Logik an „jQuery Validation“, wodurch die serverseitige Validierungslogik im Prinzip auf den Client kopiert wird. Sie können Validierungsfehler im Client anzeigen, indem Sie die relevanten Taghilfsprogramme wie folgt verwenden:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Die vorangegangenen Taghilfsprogramme rendern die folgende HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Beachten Sie, dass die data--Attribute in der HTML-Ausgabe mit den Validierungsattributen für die Movie.ReleaseDate-Eigenschaft übereinstimmen. Das data-val-required-Attribut enthält eine Fehlermeldung, die angezeigt wird, wenn der Benutzer das Datumsfeld für die Veröffentlichung nicht ausfüllt. „jQuery Unobtrusive Validation“ übergibt diesen Wert an die jQuery-Validierungsmethode required(), die diese Meldung dann im zugehörigen <span>-Element anzeigt.

Die Datentypvalidierung basiert auf dem .NET-Typ einer Eigenschaft, es sei denn, dieser wird von einem [DataType]-Attribut überschrieben. Browser haben ihre eigenen Standardfehlermeldungen, aber das jQuery Validation Unobtrusive Validation-Paket kann diese Meldungen überschreiben. [DataType]-Attribute und Unterklassen wie [EmailAddress] ermöglichen das Angeben der Fehlermeldung.

Unaufdringliche Validierung

Informationen zur unaufdringlichen Validierung finden Sie in diesem GitHub-Issue.

Hinzufügen der Validierung zu dynamischen Formularen

„jQuery Unobtrusive Validation“ übergibt die Validierungslogik und -parameter an „jQuery Validation“, wenn die Seite das erste Mal geladen wird. Deshalb funktioniert die Validierung nicht automatisch bei dynamisch generierten Formularen. Wenn Sie die Validierung aktivieren möchten, müssen Sie jQuery Unobtrusive Validation auffordern, das dynamische Formular direkt nach dem Erstellen zu analysieren. Beispielweise wird im nachfolgenden Code dargestellt, wie Sie die Validierung auf Clientseite für ein Formular einrichten können, das über AJAX hinzugefügt wurde.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

Die $.validator.unobtrusive.parse()-Methode akzeptiert einen jQuery-Selektor für ihr einziges Argument. Diese Methode fordert „jQuery Unobtrusive Validation“ auf, die data--Attribute von Formularen in diesem Selektor zu analysieren. Die Werte diese Attribute werden dann an das Plug-In „jQuery Validation“ übergeben.

Hinzufügen der Validierung zu dynamischen Steuerelementen

Die $.validator.unobtrusive.parse()-Methode funktioniert für ein gesamtes Formular, nicht für individuelle, dynamisch erstellte Steuerelemente wie <input> und <select/>. Wenn das Formular erneut analysiert werden soll, entfernen Sie die Validierungsdaten, die bei der vorherigen Analyse des Formulars hinzugefügt wurden, wie es im folgenden Beispiel gezeigt wird:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Benutzerdefinierte clientseitige Validierung

Die benutzerdefinierte clientseitige Validierung erfolgt über die Generierung von HTML-Attributen des Typs data-, die mit einem benutzerdefinierten jQuery Validation-Adapter funktionieren. Der folgende Beispieladaptercode wurde für die [ClassicMovie]- und [ClassicMovieWithClientValidator]-Attribute geschrieben, die zuvor in diesem Artikel bereits eingeführt wurden:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Informationen zum Schreiben dieser Adapter finden Sie in der jQuery Validation-Dokumentation.

Die Verwendung eines Adapters für ein bestimmtes Feld wird von data--Attributen ausgelöst, die Folgendes tun:

  • Das Feld wird so gekennzeichnet, dass es validiert wird (data-val="true").
  • Ein Name für die Validierungsregel und ein Text für die Fehlermeldung werden bestimmt (z. B. data-val-rulename="Error message.").
  • Zusätzliche Parameter werden bereitgestellt, die vom Validierungssteuerelement benötigt werden (z. B. data-val-rulename-param1="value").

Im folgenden Beispiel sehen Sie die data--Attribute des ClassicMovie-Attributs der Beispiel-App:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Wie bereits gesagt, verwenden Taghilfsprogramme und HTML-Hilfsprogramme Informationen der Validierungsattribute, um data--Attribute zu rendern. Es gibt zwei Möglichkeiten, Code zu schreiben, der zur Erstellung von benutzerdefinierten HTML-Attributen des Typs data- führt:

  • Erstellen einer Klasse, die von AttributeAdapterBase<TAttribute> ableitet, und einer Klasse, die IValidationAttributeAdapterProvider implementiert, und Registrieren Ihres Attributs und des dazugehörigen Adapters in der Abhängigkeitsinjektion. Diese Methode folgt dem Single-Responsibility-Prinzip, nach dem sich der serverbezogene und der clientbezogene Validierungscode in unterschiedlichen Klassen befinden. Der Adapter bietet außerdem den Vorteil, dass andere Dienste in der Dependency Injection (DI) bei Bedarf zur Verfügung stehen, da der Adapter in der DI registriert ist.
  • Implementieren von IClientModelValidator in Ihrer ValidationAttribute-Klasse. Diese Methode ist geeignet, wenn das Attribut für keinerlei serverseitige Validierung zuständig ist und keine Dienste aus der Abhängigkeitsinjektion benötigt.

AttributeAdapter für die clientseitige Validierung

Diese Methode, bei der data--Attribute in HTML gerendert werden, wird vom ClassicMovie-Attribut in der Beispiel-App verwendet. So fügen Sie die Clientvalidierung mithilfe dieser Methode hinzu:

  1. Erstellen Sie eine Attributadapterklasse für das benutzerdefinierte Validierungsattribut. Leiten Sie Klasse aus AttributeAdapterBase<TAttribute>ab. Erstellen Sie eine AddValidation-Methode, die der gerenderten Ausgabe data--Attribute hinzufügt, wie es im folgenden Beispiel gezeigt wird:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(
            ClassicMovieAttribute attribute, IStringLocalizer? stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext)
            => Attribute.GetErrorMessage();
    }
    
  2. Erstellen Sie eine Adapteranbieterklasse, die IValidationAttributeAdapterProvider implementiert. Übergeben Sie in der GetAttributeAdapter-Methode dem Konstruktor des Adapters das benutzerdefinierte Attribut, wie es im folgenden Beispiel gezeigt wird:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter? GetAttributeAdapter(
            ValidationAttribute attribute, IStringLocalizer? stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Registrieren Sie den Adapteranbieter für die Abhängigkeitsinjektion in Program.cs:

    builder.Services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    builder.Services.AddSingleton
        <IValidationAttributeAdapterProvider, CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator für die clientseitige Validierung

Diese Methode, bei der data--Attribute in HTML gerendert werden, wird vom ClassicMovieWithClientValidator-Attribut in der Beispiel-App verwendet. So fügen Sie die Clientvalidierung mithilfe dieser Methode hinzu:

  • Implementieren Sie im benutzerdefinierten Validierungsattribut die IClientModelValidator-Schnittstelle, und erstellen Sie eine AddValidation-Methode. Fügen Sie in der AddValidation-Methode die data--Attribute für die Validierung hinzu, wie es im folgenden Beispiel gezeigt wird:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
            => Year = year;
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult? IsValid(
            object? value, ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value!).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private static bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Deaktivieren der clientseitigen Validierung

Im folgenden Code wird die Clientvalidierung in Razor Pages deaktiviert:

builder.Services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Weitere Optionen zum Deaktivieren der clientseitigen Validierung:

  • Kommentieren Sie den Verweis auf _ValidationScriptsPartial in allen .cshtml-Dateien aus.
  • Entfernen Sie den Inhalt der Datei Pages\Shared_ValidationScriptsPartial.cshtml.

Der vorherige Ansatz verhindert nicht die clientseitige Validierung der ASP.NET Core-Klassenbibliothek IdentityRazor. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten.

Zusätzliche Ressourcen

In diesem Artikel wird erläutert, wie Benutzereingaben in einer ASP.NET Core MVC- oder Razor Pages-App validiert werden.

Zeigen Sie Beispielcode an, oder laden Sie diesen herunter (Vorgehensweise zum Herunterladen).

Modellstatus

Der Modellstatus stellt Fehler dar, die aus zwei Subsystemen kommen: Modellbindung und Modellvalidierung. Fehler, die von der Modellbindung herrühren, sind im allgemeinen Datenkonvertierungsfehler. Beispielsweise wird ein "x" in ein Integerfeld eingegeben. Die Modellvalidierung erfolgt nach der Modellbindung und meldet Fehler, bei denen die Daten nicht den Geschäftsregeln entsprechen. Beispielsweise wird eine 0 in einem Feld eingegeben, das eine Bewertung zwischen 1 und 5 erwartet.

Sowohl Modellbindung als auch Modellvalidierung finden noch vor der Ausführung einer Controlleraktion oder einer Razor Pages-Handlermethode statt. Bei Web-Apps liegen die Überprüfung von ModelState.IsValid und entsprechende Maßnahmen im Verantwortungsbereich der App. Web-Apps zeigen die Seite normalerweise mit einer Fehlermeldung erneut an:

public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Web-API-Controller müssen ModelState.IsValid nicht überprüfen, wenn sie das [ApiController]-Attribut enthalten. In diesem Fall wird eine automatische HTTP 400-Antwort zurückgegeben, die Fehlerdetails beinhaltet, wenn der Modellstatus ungültig ist. Weitere Informationen finden Sie unter Automatische HTTP 400-Antworten.

Validierung erneut ausführen

Die Validierung erfolgt automatisch, aber ggf. möchten Sie sie manuell wiederholen. Eine Szenario wäre, dass Sie einen Wert für eine Eigenschaft berechnen und möchten, dass die Validierung erneut ausgeführt wird, nachdem der berechnete Wert für die Eigenschaft festgelegt wurde. Um die Validierung erneut ausführen zu können, rufen Sie ModelStateDictionary.ClearValidationState auf, um die für das jeweilige Modell spezifische Validierung zu löschen, und rufen Sie dann TryValidateModel auf:

public async Task<IActionResult> OnPostTryValidateAsync()
{
    var modifiedReleaseDate = DateTime.Now.Date;
    Movie.ReleaseDate = modifiedReleaseDate;

    ModelState.ClearValidationState(nameof(Movie));
    if (!TryValidateModel(Movie, nameof(Movie)))
    {
        return Page();
    }

    _context.Movies.Add(Movie);
    await _context.SaveChangesAsync();

    return RedirectToPage("./Index");
}

Validierungsattribute

Mit Validierungsattributen können Sie Validierungsregeln für Modelleigenschaften angeben. Im folgenden Beispiel aus der Beispiel-App sehen Sie eine Modellklasse, die mit Validierungsattributen versehen wurde. Das [ClassicMovie]-Attribut ist ein benutzerdefiniertes Validierungsattribut. Die anderen sind integriert. Nicht gezeigt wird hier das Attribut [ClassicMovieWithClientValidator], das eine Alternative darstellt, ein benutzerdefiniertes Attribut zu implementieren.

public class Movie
{
    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [ClassicMovie(1960)]
    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }
}

Integrierte Attribute

Im Folgenden sind einige der integrierten Validierungsattribute aufgeführt:

  • [ValidateNever]: Gibt an, dass eine Eigenschaft oder ein Parameter von der Validierung ausgeschlossen werden soll
  • [CreditCard]: Überprüft, ob die Eigenschaft ein Kreditkartenformat aufweist. Es sind zusätzliche jQuery-Validierungsmethoden erforderlich.
  • [Compare]: Überprüft, ob zwei Eigenschaften in einem Modell miteinander übereinstimmen
  • [EmailAddress]: Überprüft, ob die Eigenschaft ein E-Mail-Format aufweist
  • [Phone]: Überprüft, ob die Eigenschaft ein Telefonnummernformat aufweist
  • [Range]: Überprüft, ob der Eigenschaftenwert im vorgegebenen Bereich liegt
  • [RegularExpression]: Überprüft, ob der Eigenschaftswert mit dem angegebenen regulären Ausdruck übereinstimmt.
  • [Required]: Überprüft, ob das Feld nicht NULL ist. Weitere Informationen zum Verhalten dieses Attributs finden Sie unter [Required]-Attribut.
  • [StringLength]: Überprüft, ob ein Zeichenfolgeneigenschaftswert kürzer ist als die angegebene Längenbeschränkung.
  • [Url]: Überprüft, ob die Eigenschaft ein URL-Format aufweist.
  • [Remote]: Überprüft die Eingabe auf dem Client, indem eine Aktionsmethode auf dem Server abgerufen wird. Weitere Informationen zum Verhalten dieses Attributs finden Sie unter [Remote]-Attribut.

Im System.ComponentModel.DataAnnotations-Namespace finden Sie eine vollständige Liste der Validierungsattribute.

Fehlermeldungen

Mit Validierungsattributen können Sie die Fehlermeldung angeben, die im Fall einer ungültigen Eingabe angezeigt werden soll. Beispiel:

[StringLength(8, ErrorMessage = "Name length can't be more than 8.")]

Intern rufen die Attribute die Methode String.Format mit einem Platzhalter für den Feldnamen und manchmal zusätzliche Platzhalter auf. Beispiel:

[StringLength(8, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 6)]

Bei Anwendung auf eine Name-Eigenschaft wäre die Fehlermeldung, die vom vorangehenden Code erstellt wurde, die Folgende: „Name length must be between 6 and 8“ (Die Länge des Namens muss zwischen 6 und 8 Zeichen betragen).

Wenn Sie herausfinden möchten, welche Parameter an die Methode String.Format für die Fehlermeldung eines bestimmten Attributs übergeben werden, sehen Sie sich den DataAnnotations-Quellcode an.

Non-Nullable-Verweistypen und das [Required]-Attribut

Das Validierungssystem behandelt Parameter oder Eigenschaften, die keine NULL-Werte zulassen, als ob diese ein [Required(AllowEmptyStrings = true)]-Attribut hätten. Durch das Aktivieren von Nullable-Kontexten beginnt MVC implizit mit der Validierung von Non-Nullable-Eigenschaften oder -Parametern, als ob sie ein [Required(AllowEmptyStrings = true)]-Attribut aufweisen würden. Betrachten Sie folgenden Code:

public class Person
{
    public string Name { get; set; }
}

Wenn die App mit <Nullable>enable</Nullable> erstellt wurde, führt ein fehlender Wert für Name in einem JSON- oder Formularbeitrag zu einem Validierungsfehler. Verwenden Sie einen Nullable-Verweistyp, damit NULL oder fehlende Werte für die Name-Eigenschaft angegeben werden können:

public class Person
{
    public string? Name { get; set; }
}

Dieses Verhalten kann deaktiviert werden, indem SuppressImplicitRequiredAttributeForNonNullableReferenceTypes in Startup.ConfigureServices konfiguriert wird:

services.AddControllers(options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true);

[Required]-Validierung auf dem Server

Auf dem Server wird ein erforderlicher Wert als fehlend betrachtet, wenn für eine Eigenschaft NULL festgelegt wurde. Ein Feld, das keine NULL-Werte zulässt, ist immer gültig, und die Fehlermeldung des [Required]-Attributs wird nie angezeigt.

Die Modellbindung für eine Eigenschaft, die keine NULL-Werte zulässt, schlägt jedoch möglicherweise fehl, und führt zu einer Fehlermeldung wie The value '' is invalid (Der Wert „“ ist ungültig). Wenn Sie eine benutzerdefinierte Fehlermeldung für die serverseitige Validierung von Typen, die nicht NULL zulassen, angeben möchten, gibt es die folgenden Optionen:

  • Lassen Sie für das Feld NULL-Werte zu (z. B. decimal? statt decimal). Nullable<T>-Werttypen werden wie gewöhnliche Nullable-Typen behandelt.

  • Geben Sie die Standardfehlermeldung an, die von der Modellbindung verwendet werden soll, wie es im folgenden Beispiel gezeigt wird:

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

    Weitere Informationen zu Fehlern bei der Modellbindung, für die Sie Standardmeldungen festlegen können, finden Sie unter DefaultModelBindingMessageProvider.

[Required]-Validierung auf dem Client

Typen und Zeichenfolgen, für die keine NULL-Werte festgelegt werden können, werden auf dem Client anders behandelt wie auf dem Server. Auf dem Client:

  • Ein Wert gilt nur als vorhanden, wenn eine Eingabe für ihn erfolgt ist. Deshalb werden bei der clientseitigen Validierung Typen, für die keine NULL-Werte festgelegt werden können, gleich behandelt wie Nullable-Typen.
  • Leerzeichen in einem Zeichenfolgenfeld werden von der jQuery Validation-Required-Methode als gültige Eingabe betrachtet. Die serverseitige Validierung betrachtet ein erforderliches Zeichenfolgenfeld als ungültig, wenn nur Leerzeichen eingegeben werden.

Wie bereits gesagt wurde, werden Typen, die keine NULL-Werte zulassen, so behandelt, als hätten sie ein [Required(AllowEmptyStrings = true)]-Attribut. Das heißt, die clientseitige Validierung erfolgt, auch wenn Sie das [Required(AllowEmptyStrings = true)]-Attribut nicht anwenden. Wenn Sie das Attribut jedoch nicht anwenden, erhalten Sie eine Standardfehlermeldung. Wenn Sie eine benutzerdefinierte Fehlermeldung angeben möchten, verwenden Sie das Attribut.

[Remote]-Attribut

Das [Remote]-Attribut implementiert die clientseitige Validierung, für die eine Methode auf dem Server aufgerufen werden muss, um zu bestimmen, ob die Feldeingabe gültig ist. So muss die App z. B. überprüfen, ob eine Benutzername bereits verwendet wird.

So wird die Remotevalidierung implementiert:

  1. Erstellen Sie eine Aktionsmethode für JavaScript, die aufgerufen werden soll. Die jQuery-Validierungsmethode remote erwartet eine JSON-Antwort:

    • true bedeutet, die Eingabedaten sind gültig.
    • false, undefined oder null bedeutet, dass die Eingabe ungültig ist. Verwenden Sie die Standardfehlermeldung.
    • Jede andere Zeichenfolge bedeutet, dass die Eingabe ungültig ist. Verwenden Sie die Zeichenfolge als benutzerdefinierte Fehlermeldung.

    Hier finden Sie ein Beispiel einer Aktionsmethode, die eine benutzerdefinierte Fehlermeldung zurückgibt:

    [AcceptVerbs("GET", "POST")]
    public IActionResult VerifyEmail(string email)
    {
        if (!_userService.VerifyEmail(email))
        {
            return Json($"Email {email} is already in use.");
        }
    
        return Json(true);
    }
    
  2. Versehen Sie in der Modellklasse die Eigenschaft mit einem [Remote]-Attribut, das auf die Validierungsaktionsmethode zeigt, wie es im folgenden Beispiel gezeigt wird:

    [Remote(action: "VerifyEmail", controller: "Users")]
    public string Email { get; set; }
    

Zusätzliche Felder

Die AdditionalFields-Eigenschaft des [Remote]-Attributs ermöglicht Ihnen die Validierung von Kombinationen von Feldern für Daten auf dem Server. Wenn z. B. das User-Modell die Eigenschaften FirstName und LastName hätte, sollten Sie überprüfen, dass kein bereits vorhandener Benutzer diese Namenskombination verwendet. Das folgende Beispiel zeigt die Verwendung von AdditionalFields:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(LastName))]
[Display(Name = "First Name")]
public string FirstName { get; set; }

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName))]
[Display(Name = "Last Name")]
public string LastName { get; set; }

AdditionalFields könnte explizit auf die Zeichenfolgen „FirstName“ und „LastName“ festgelegt werden, aber die Verwendung des nameof-Operators vereinfacht das spätere Refactoring. Die Aktionsmethode für diese Validierung muss sowohl firstName- als auch lastName-Argumente akzeptieren:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyName(string firstName, string lastName)
{
    if (!_userService.VerifyName(firstName, lastName))
    {
        return Json($"A user named {firstName} {lastName} already exists.");
    }

    return Json(true);
}

Wenn der Benutzer einen Vor- oder einen Nachnamen eingibt, führt JavaScript einen Remoteaufruf durch, um zu prüfen, ob dieses Paar Namen bereits verwendet wurde.

Wenn Sie mindestens zwei weitere Felder überprüfen möchten, geben Sie sie als eine mit Kommas getrennte Liste an. Wenn Sie z. B. dem Modell eine MiddleName-Eigenschaft hinzufügen möchten, legen Sie das [Remote]-Attribut wie im folgenden Code veranschaulicht fest:

[Remote(action: "VerifyName", controller: "Users", AdditionalFields = nameof(FirstName) + "," + nameof(LastName))]
public string MiddleName { get; set; }

AdditionalFields muss wie alle anderen Attributargumente ein konstanter Ausdruck sein. Aus diesem Grund verwenden Sie keine interpolierte Zeichenfolge oder rufen Join auf, um AdditionalFields zu initialisieren.

Alternativen zu integrierten Attributen

Wenn Sie eine Validierung benötigen, die nicht von integrierten Attributen bereitgestellt wird, haben Sie die folgenden Möglichkeiten:

Benutzerdefinierte Attribute

Für Szenarios, die die integrierten Validierungsattribute nicht verarbeiten können, können Sie benutzerdefinierte Validierungsattribute erstellen. Erstellen Sie eine Klasse, die von ValidationAttribute erbt, und überschreiben Sie die IsValid-Methode.

Die IsValid-Methode akzeptiert ein Objekt namens value. Dies ist die Eingabe, die überprüft werden soll. Ein Überladen akzeptiert auch ein ValidationContext-Objekt, das zusätzliche Informationen bereitstellt, z. B. die Modellinstanz, die von der Modellbindung erstellt wurde.

Im folgenden Beispiel wird überprüft, ob das Veröffentlichungsdatum eines Films aus dem Genre Classic (Klassiker) zeitlich nicht hinter einer angegebenen Jahreszahl liegt. Das [ClassicMovie]-Attribut:

  • wird nur auf dem Server ausgeführt.
  • Für Filmklassiker wird das Veröffentlichungsdatum überprüft:
public class ClassicMovieAttribute : ValidationAttribute
{
    public ClassicMovieAttribute(int year)
    {
        Year = year;
    }

    public int Year { get; }

    public string GetErrorMessage() =>
        $"Classic movies must have a release year no later than {Year}.";

    protected override ValidationResult IsValid(object value,
        ValidationContext validationContext)
    {
        var movie = (Movie)validationContext.ObjectInstance;
        var releaseYear = ((DateTime)value).Year;

        if (movie.Genre == Genre.Classic && releaseYear > Year)
        {
            return new ValidationResult(GetErrorMessage());
        }

        return ValidationResult.Success;
    }
}

Die obenstehende movie-Variable stellt ein Movie-Objekt dar, das die Daten der Formularübermittlung enthält. Schlägt die Validierung fehl, wird die Klasse ValidationResult mit einer Fehlermeldung zurückgegeben.

IValidatableObject

Im vorherigen Beispiel wurde nur mit Movie-Typen gearbeitet. Eine andere Option zur Validierung auf Klassenebene ist die Implementierung von IValidatableObject in der Modellklasse, wie im folgenden Beispiel dargestellt wird:

public class ValidatableMovie : IValidatableObject
{
    private const int _classicYear = 1960;

    public int Id { get; set; }

    [Required]
    [StringLength(100)]
    public string Title { get; set; }

    [DataType(DataType.Date)]
    [Display(Name = "Release Date")]
    public DateTime ReleaseDate { get; set; }

    [Required]
    [StringLength(1000)]
    public string Description { get; set; }

    [Range(0, 999.99)]
    public decimal Price { get; set; }

    public Genre Genre { get; set; }

    public bool Preorder { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Genre == Genre.Classic && ReleaseDate.Year > _classicYear)
        {
            yield return new ValidationResult(
                $"Classic movies must have a release year no later than {_classicYear}.",
                new[] { nameof(ReleaseDate) });
        }
    }
}

Validierung der Knoten auf oberster Ebene

Knoten der obersten Ebene sind unter anderem:

  • Aktionsparameter
  • Controllereigenschaften
  • Seitenhandlerparameter
  • Seitenmodelleigenschaften

An ein Modell gebundene Knoten auf oberster Ebene und Modelleigenschaften werden überprüft. Im folgenden Beispiel aus der Beispiel-App verwendet die VerifyPhone-Methode die RegularExpressionAttribute-Klasse, um die phone-Aktionsparameter zu überprüfen:

[AcceptVerbs("GET", "POST")]
public IActionResult VerifyPhone(
    [RegularExpression(@"^\d{3}-\d{3}-\d{4}$")] string phone)
{
    if (!ModelState.IsValid)
    {
        return Json($"Phone {phone} has an invalid format. Format: ###-###-####");
    }

    return Json(true);
}

Knoten auf oberster Ebene können BindRequiredAttribute mit Validierungsattributen verwenden. Im folgenden Beispiel aus der Beispiel-App gibt die CheckAge-Methode an, dass der age-Parameter aus der Abfragezeichenfolge gebunden sein muss, wenn das Formular übermittelt wird:

[HttpPost]
public IActionResult CheckAge([BindRequired, FromQuery] int age)
{

Auf der Seite für die Altersprüfung (CheckAge.cshtml) sind zwei Formulare vorhanden. Das erste Formular übermittelt einen Age-Wert von 99 als Abfragezeichenfolgen-Parameter: https://localhost:5001/Users/CheckAge?Age=99.

Wenn ein ordnungsgemäß formatierter age-Parameter aus der Abfragezeichenfolge übermittelt wird, wird das Formular überprüft.

Das zweite Formular auf der Seite für die Altersprüfung übermittelt den Age-Wert im Anforderungstext, worauf die Validierung fehlschlägt. Die Bindung schlägt fehl, da der age-Parameter aus einer Abfragezeichenfolge stammen muss.

Maximale Fehleranzahl

Die Validierung stoppt, wenn die maximale Anzahl an Fehlern erreicht wird. Diese liegt standardmäßig bei 200. Sie können diese Zahl mit dem folgenden Code in Startup.ConfigureServices konfigurieren:

services.AddRazorPages()
    .AddMvcOptions(options =>
    {
        options.MaxModelValidationErrors = 50;
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
            _ => "The field is required.");
    });

services.AddSingleton<IValidationAttributeAdapterProvider,
    CustomValidationAttributeAdapterProvider>();

Maximale Rekursion

ValidationVisitor durchläuft den Objektgraph des Modells, das überprüft wird. Bei Modellen, die umfassend oder unendlich rekursiv sind, führt die Validierung möglicherweise zu einem Stapelüberlauf. MvcOptions.MaxValidationDepth stellt eine Möglichkeit dar, die Validierung frühzeitig zu beenden, wenn die Besucherrekursion eine konfigurierte Tiefe überschreitet. Der Standardwert von MvcOptions.MaxValidationDepth ist „32“.

Automatischer Kurzschluss

Die Validierung wird automatisch kurzgeschlossen (übersprungen), wenn der Modellgraph keine Validierung erfordert. Objekte, deren Validierung durch die Runtime übersprungen wird, beinhalten Primitivsammlungen (z. B. byte[], string[], Dictionary<string, string>) und komplexe Objektgraphen, die keine Validierungssteuerelemente haben.

Clientseitige Validierung

Die Überprüfung auf Clientseite verhindert die Übermittlung solange, bis das Formular gültig ist. Die Schaltfläche „Übermitteln“ führt JavaScript aus, das entweder das Formular übermittelt oder Fehlermeldungen anzeigt.

Die clientseitige Validierung umgeht einen unnötigen Roundtrip zum Server, wenn es in einem Formular Eingabefehler gibt. Die folgenden Skriptverweise in _Layout.cshtml und _ValidationScriptsPartial.cshtml unterstützen die clientseitige Validierung:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.19.1/jquery.validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-validation-unobtrusive/3.2.11/jquery.validate.unobtrusive.js"></script>

Beim jQuery Unobtrusive Validation-Skript handelt es sich um eine benutzerdefinierte Front-End-Bibliothek von Microsoft, die auf dem beliebten Plug-In jQuery Validation basiert. Ohne jQuery Unobtrusive Validation müssten Sie dieselbe Validierungslogik an zwei unterschiedlichen Stellen codieren: einmal in den Attributen der Validierung auf Serverseite für Modelleigenschaften und einmal in den Skripts auf Clientseite. Stattdessen verwenden die Taghilfsprogramme und die HTML-Hilfsprogramme die Validierungsattribute und Typmetadaten aus den Modelleigenschaften, um HTML 5-Attribute des Typs data- in den Formularelementen zu rendern, die validiert werden müssen. „jQuery Unobtrusive Validation“ analysiert dann die data--Attribute und übergibt die Logik an „jQuery Validation“, wodurch die serverseitige Validierungslogik im Prinzip auf den Client kopiert wird. Sie können Validierungsfehler im Client anzeigen, indem Sie die relevanten Taghilfsprogramme wie folgt verwenden:

<div class="form-group">
    <label asp-for="Movie.ReleaseDate" class="control-label"></label>
    <input asp-for="Movie.ReleaseDate" class="form-control" />
    <span asp-validation-for="Movie.ReleaseDate" class="text-danger"></span>
</div>

Die vorangegangenen Taghilfsprogramme rendern die folgende HTML:

<div class="form-group">
    <label class="control-label" for="Movie_ReleaseDate">Release Date</label>
    <input class="form-control" type="date" data-val="true"
        data-val-required="The Release Date field is required."
        id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">
    <span class="text-danger field-validation-valid"
        data-valmsg-for="Movie.ReleaseDate" data-valmsg-replace="true"></span>
</div>

Beachten Sie, dass die data--Attribute in der HTML-Ausgabe mit den Validierungsattributen für die Movie.ReleaseDate-Eigenschaft übereinstimmen. Das data-val-required-Attribut enthält eine Fehlermeldung, die angezeigt wird, wenn der Benutzer das Datumsfeld für die Veröffentlichung nicht ausfüllt. „jQuery Unobtrusive Validation“ übergibt diesen Wert an die jQuery-Validierungsmethode required(), die diese Meldung dann im zugehörigen <span>-Element anzeigt.

Die Datentypvalidierung basiert auf dem .NET-Typ einer Eigenschaft, es sei denn, dieser wird von einem [DataType]-Attribut überschrieben. Browser haben ihre eigenen Standardfehlermeldungen, aber das jQuery Validation Unobtrusive Validation-Paket kann diese Meldungen überschreiben. [DataType]-Attribute und Unterklassen wie [EmailAddress] ermöglichen das Angeben der Fehlermeldung.

Unaufdringliche Validierung

Informationen zur unaufdringlichen Validierung finden Sie in diesem GitHub-Issue.

Hinzufügen der Validierung zu dynamischen Formularen

„jQuery Unobtrusive Validation“ übergibt die Validierungslogik und -parameter an „jQuery Validation“, wenn die Seite das erste Mal geladen wird. Deshalb funktioniert die Validierung nicht automatisch bei dynamisch generierten Formularen. Wenn Sie die Validierung aktivieren möchten, müssen Sie jQuery Unobtrusive Validation auffordern, das dynamische Formular direkt nach dem Erstellen zu analysieren. Beispielweise wird im nachfolgenden Code dargestellt, wie Sie die Validierung auf Clientseite für ein Formular einrichten können, das über AJAX hinzugefügt wurde.

$.get({
    url: "https://url/that/returns/a/form",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add form. " + errorThrown);
    },
    success: function(newFormHTML) {
        var container = document.getElementById("form-container");
        container.insertAdjacentHTML("beforeend", newFormHTML);
        var forms = container.getElementsByTagName("form");
        var newForm = forms[forms.length - 1];
        $.validator.unobtrusive.parse(newForm);
    }
})

Die $.validator.unobtrusive.parse()-Methode akzeptiert einen jQuery-Selektor für ihr einziges Argument. Diese Methode fordert „jQuery Unobtrusive Validation“ auf, die data--Attribute von Formularen in diesem Selektor zu analysieren. Die Werte diese Attribute werden dann an das Plug-In „jQuery Validation“ übergeben.

Hinzufügen der Validierung zu dynamischen Steuerelementen

Die $.validator.unobtrusive.parse()-Methode funktioniert für ein gesamtes Formular, nicht für individuelle, dynamisch erstellte Steuerelemente wie <input> und <select/>. Wenn das Formular erneut analysiert werden soll, entfernen Sie die Validierungsdaten, die bei der vorherigen Analyse des Formulars hinzugefügt wurden, wie es im folgenden Beispiel gezeigt wird:

$.get({
    url: "https://url/that/returns/a/control",
    dataType: "html",
    error: function(jqXHR, textStatus, errorThrown) {
        alert(textStatus + ": Couldn't add control. " + errorThrown);
    },
    success: function(newInputHTML) {
        var form = document.getElementById("my-form");
        form.insertAdjacentHTML("beforeend", newInputHTML);
        $(form).removeData("validator")    // Added by jQuery Validation
               .removeData("unobtrusiveValidation");   // Added by jQuery Unobtrusive Validation
        $.validator.unobtrusive.parse(form);
    }
})

Benutzerdefinierte clientseitige Validierung

Die benutzerdefinierte clientseitige Validierung erfolgt über die Generierung von HTML-Attributen des Typs data-, die mit einem benutzerdefinierten jQuery Validation-Adapter funktionieren. Der folgende Beispieladaptercode wurde für die [ClassicMovie]- und [ClassicMovieWithClientValidator]-Attribute geschrieben, die zuvor in diesem Artikel bereits eingeführt wurden:

$.validator.addMethod('classicmovie', function (value, element, params) {
    var genre = $(params[0]).val(), year = params[1], date = new Date(value);

    // The Classic genre has a value of '0'.
    if (genre && genre.length > 0 && genre[0] === '0') {
        // The release date for a Classic is valid if it's no greater than the given year.
        return date.getUTCFullYear() <= year;
    }

    return true;
});

$.validator.unobtrusive.adapters.add('classicmovie', ['year'], function (options) {
    var element = $(options.form).find('select#Movie_Genre')[0];

    options.rules['classicmovie'] = [element, parseInt(options.params['year'])];
    options.messages['classicmovie'] = options.message;
});

Informationen zum Schreiben dieser Adapter finden Sie in der jQuery Validation-Dokumentation.

Die Verwendung eines Adapters für ein bestimmtes Feld wird von data--Attributen ausgelöst, die Folgendes tun:

  • Das Feld wird so gekennzeichnet, dass es validiert wird (data-val="true").
  • Ein Name für die Validierungsregel und ein Text für die Fehlermeldung werden bestimmt (z. B. data-val-rulename="Error message.").
  • Zusätzliche Parameter werden bereitgestellt, die vom Validierungssteuerelement benötigt werden (z. B. data-val-rulename-param1="value").

Im folgenden Beispiel sehen Sie die data--Attribute des ClassicMovie-Attributs der Beispiel-App:

<input class="form-control" type="date"
    data-val="true"
    data-val-classicmovie="Classic movies must have a release year no later than 1960."
    data-val-classicmovie-year="1960"
    data-val-required="The Release Date field is required."
    id="Movie_ReleaseDate" name="Movie.ReleaseDate" value="">

Wie bereits gesagt, verwenden Taghilfsprogramme und HTML-Hilfsprogramme Informationen der Validierungsattribute, um data--Attribute zu rendern. Es gibt zwei Möglichkeiten, Code zu schreiben, der zur Erstellung von benutzerdefinierten HTML-Attributen des Typs data- führt:

  • Erstellen einer Klasse, die von AttributeAdapterBase<TAttribute> ableitet, und einer Klasse, die IValidationAttributeAdapterProvider implementiert, und Registrieren Ihres Attributs und des dazugehörigen Adapters in der Abhängigkeitsinjektion. Diese Methode folgt dem Single-Responsibility-Prinzip, nach dem sich der serverbezogene und der clientbezogene Validierungscode in unterschiedlichen Klassen befinden. Der Adapter bietet außerdem den Vorteil, dass andere Dienste in der Dependency Injection (DI) bei Bedarf zur Verfügung stehen, da der Adapter in der DI registriert ist.
  • Implementieren von IClientModelValidator in Ihrer ValidationAttribute-Klasse. Diese Methode ist geeignet, wenn das Attribut für keinerlei serverseitige Validierung zuständig ist und keine Dienste aus der Abhängigkeitsinjektion benötigt.

AttributeAdapter für die clientseitige Validierung

Diese Methode, bei der data--Attribute in HTML gerendert werden, wird vom ClassicMovie-Attribut in der Beispiel-App verwendet. So fügen Sie die Clientvalidierung mithilfe dieser Methode hinzu:

  1. Erstellen Sie eine Attributadapterklasse für das benutzerdefinierte Validierungsattribut. Leiten Sie Klasse aus AttributeAdapterBase<TAttribute>ab. Erstellen Sie eine AddValidation-Methode, die der gerenderten Ausgabe data--Attribute hinzufügt, wie es im folgenden Beispiel gezeigt wird:

    public class ClassicMovieAttributeAdapter : AttributeAdapterBase<ClassicMovieAttribute>
    {
        public ClassicMovieAttributeAdapter(ClassicMovieAttribute attribute,
            IStringLocalizer stringLocalizer)
            : base(attribute, stringLocalizer)
        {
    
        }
    
        public override void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage(context));
    
            var year = Attribute.Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public override string GetErrorMessage(ModelValidationContextBase validationContext) =>
            Attribute.GetErrorMessage();
    }
    
  2. Erstellen Sie eine Adapteranbieterklasse, die IValidationAttributeAdapterProvider implementiert. Übergeben Sie in der GetAttributeAdapter-Methode dem Konstruktor des Adapters das benutzerdefinierte Attribut, wie es im folgenden Beispiel gezeigt wird:

    public class CustomValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider
    {
        private readonly IValidationAttributeAdapterProvider baseProvider =
            new ValidationAttributeAdapterProvider();
    
        public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute,
            IStringLocalizer stringLocalizer)
        {
            if (attribute is ClassicMovieAttribute classicMovieAttribute)
            {
                return new ClassicMovieAttributeAdapter(classicMovieAttribute, stringLocalizer);
            }
    
            return baseProvider.GetAttributeAdapter(attribute, stringLocalizer);
        }
    }
    
  3. Registrieren Sie den Adapteranbieter für die Abhängigkeitsinjektion in Startup.ConfigureServices:

    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.MaxModelValidationErrors = 50;
            options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
                _ => "The field is required.");
        });
    
    services.AddSingleton<IValidationAttributeAdapterProvider,
        CustomValidationAttributeAdapterProvider>();
    

IClientModelValidator für die clientseitige Validierung

Diese Methode, bei der data--Attribute in HTML gerendert werden, wird vom ClassicMovieWithClientValidator-Attribut in der Beispiel-App verwendet. So fügen Sie die Clientvalidierung mithilfe dieser Methode hinzu:

  • Implementieren Sie im benutzerdefinierten Validierungsattribut die IClientModelValidator-Schnittstelle, und erstellen Sie eine AddValidation-Methode. Fügen Sie in der AddValidation-Methode die data--Attribute für die Validierung hinzu, wie es im folgenden Beispiel gezeigt wird:

    public class ClassicMovieWithClientValidatorAttribute :
        ValidationAttribute, IClientModelValidator
    {
        public ClassicMovieWithClientValidatorAttribute(int year)
        {
            Year = year;
        }
    
        public int Year { get; }
    
        public void AddValidation(ClientModelValidationContext context)
        {
            MergeAttribute(context.Attributes, "data-val", "true");
            MergeAttribute(context.Attributes, "data-val-classicmovie", GetErrorMessage());
    
            var year = Year.ToString(CultureInfo.InvariantCulture);
            MergeAttribute(context.Attributes, "data-val-classicmovie-year", year);
        }
    
        public string GetErrorMessage() =>
            $"Classic movies must have a release year no later than {Year}.";
    
        protected override ValidationResult IsValid(object value,
            ValidationContext validationContext)
        {
            var movie = (Movie)validationContext.ObjectInstance;
            var releaseYear = ((DateTime)value).Year;
    
            if (movie.Genre == Genre.Classic && releaseYear > Year)
            {
                return new ValidationResult(GetErrorMessage());
            }
    
            return ValidationResult.Success;
        }
    
        private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value)
        {
            if (attributes.ContainsKey(key))
            {
                return false;
            }
    
            attributes.Add(key, value);
            return true;
        }
    }
    

Deaktivieren der clientseitigen Validierung

Im folgenden Code wird die Clientvalidierung in Razor Pages deaktiviert:

services.AddRazorPages()
    .AddViewOptions(options =>
    {
        options.HtmlHelperOptions.ClientValidationEnabled = false;
    });

Weitere Optionen zum Deaktivieren der clientseitigen Validierung:

  • Kommentieren Sie den Verweis auf _ValidationScriptsPartial in allen .cshtml-Dateien aus.
  • Entfernen Sie den Inhalt der Datei Pages\Shared_ValidationScriptsPartial.cshtml.

Der vorherige Ansatz verhindert nicht die clientseitige Validierung der ASP.NET Core-Klassenbibliothek IdentityRazor. Weitere Informationen finden Sie unter Gerüst Identity in ASP.NET Core-Projekten.

Zusätzliche Ressourcen