Asynchrone Validierung in ASP.NET Core

Asynchrone Validierung in ASP.NET Core

- Matt

Die haeufigste Validierung, die ich brauche und die am schlechtesten unterzubringen war, ist die Eindeutigkeitspruefung: Ist diese E-Mail schon vergeben? Das laesst sich nicht am Wert allein entscheiden, es braucht einen Blick in die Datenbank. Und genau da hoerte die eingebaute Validierung immer auf.

Warum das lange nicht ging

IValidatableObject.Validate ist synchron, und ValidationAttribute.IsValid ebenso. Man kann darin einen Datenbankzugriff synchron erzwingen, aber das blockiert einen Thread und laedt in ASP.NET Core zu Deadlocks ein. Die Empfehlung lautete deshalb jahrelang: nicht in die Validierung packen. Stattdessen erst ModelState.IsValid durchlaufen lassen und danach im Handler selbst pruefen, mit ModelState.AddModelError nachtragen, falls die E-Mail belegt ist.

Das funktioniert, verteilt die Validierungsregeln aber auf zwei Stellen. Ein Teil steckt in Attributen am Modell, der Rest im Handler, und beim Lesen sieht man dem Modell nicht mehr an, dass da noch eine Regel fehlt.

FluentValidation half nur halb

FluentValidation kennt MustAsync und ValidateAsync, kann also asynchron pruefen. Nur greift die automatische Validierung von ASP.NET Core auf den synchronen Pfad zu. Seit FluentValidation 11 fliegt dann eine AsyncValidatorInvokedSynchronouslyException, weil der Validator asynchrone Regeln enthaelt, die Pipeline sie aber synchron aufrufen will. Der Ausweg war, die automatische Validierung fuer diese Validatoren abzuschalten und ValidateAsync von Hand aufzurufen. Wieder ein Sonderweg.

Was .NET 11 aendert

Mit .NET 10 kam Microsoft.Extensions.Validation, eingeschaltet ueber AddValidation auf der IServiceCollection. Ein Quellgenerator laeuft den Objektgraphen der Minimal-API-Parameter ab und erzeugt die Validierungsinformationen zur Uebersetzungszeit.

.NET 11 setzt oben drauf: Die Validierung darf jetzt asynchron sein. Es gibt AsyncValidationAttribute fuer Parameter, Typen und Eigenschaften, und Typen koennen IAsyncValidatableObject implementieren. Der Datenbankzugriff fuer die Eindeutigkeitspruefung landet damit dort, wo die uebrige Validierung schon steht.

Der Haken

Zwei Dinge, die man in der Doku leicht ueberliest. Erstens verlangen sowohl IAsyncValidatableObject als auch AsyncValidationAttribute, dass man beide Pfade implementiert, den synchronen und den asynchronen. Bei Minimal APIs ruft das Framework immer den asynchronen auf, nie den synchronen, und beide sind nie gemeinsam gedacht. Kann der synchrone Pfad die Pruefung nicht leisten, soll er eine InvalidOperationException werfen. Man schreibt also eine Methode, die absichtlich nie durchlaeuft.

Zweitens: Die eingebaute Unterstuetzung gilt fuer Minimal APIs und Blazor. In MVC ist Microsoft.Extensions.Validation standardmaessig nicht aktiv. Wer noch mit Controllern arbeitet, bleibt vorerst beim Nachtragen im Handler.

Fuer neue Minimal-API-Endpunkte nehme ich das trotzdem. Die Regel steht wieder am Modell, und die Deadlock-Gefahr aus den synchronen Datenbankaufrufen ist weg. Wie die Fehler danach beim Aufrufer ankommen, hatte ich in API-Fehlerbehandlung in ASP.NET Core beschrieben.

Quelle: Validation in ASP.NET Core und FluentValidation, Asynchronous Validation.