Die Validierung eingehender Daten ist eine Standardanforderung bei der Implementierung deiner Web-API. Denn bevor deine Datenverarbeitung stattfinden kann, musst du die Integrität der eingehenden Daten sicherstellen. ASP.NET bietet dir die Möglichkeit dieser Anforderung leicht nachzukommen, denn du kannst benutzereigene Validatoren komfortabel integrieren.
Vorteile
Folgende Vorteile bieten sich dir:
- Du erstellst modulare Validatoren nach dem Single-Responsibility-Prinzip.
- Deine Validatoren lassen sich über Attribute auf Datenmodell-Ebene integrieren.
- Dein Business-Code legt den Fokus auf Funktionalität und ist frei von Validierung.
- Die Unit-Tests deiner Validatoren werden stark vereinfacht.
Einstieg
Du implementierst im Folgenden einen Validator für ein konkretes Anwendungsbeispiel. Dein Validator soll sicherstellen, dass eine Zeichenkette lediglich aus Groß- und Kleinbuchstaben besteht. Der fertige Validator wird dann zur Überprüfung eines Nutzernamens verwendet. Der Nutzername wird über einen HTTP Request an eine Web-API übermittelt.
Schritt eins – Klasse erzeugen
Erstelle zu Beginn eine neue Klasse inkl. zugehöriger Datei und benenne beide dann nach folgendem Muster {UseCase}Attribute. Der Name soll für dieses Beispiel LettersOnlyAttribute
lauten.
// File: LettersOnlyAttribute.cs namespace Validation.Attributes; public class LettersOnlyAttribute { }
Schritt zwei – Referenzen hinzufügen
Füge dann die folgenden Referenzen zu Beginn der Klassendatei ein.
using System; using System.Globalization; using System.ComponentModel.DataAnnotations;
Schritt drei – Klasse ableiten und dekorieren
Leite anschließend von der Basisklasse ValidationAttribute
ab und setze den sealed
Modifizierer, um weitere Ableitungen zu verhindern. Dekoriere deine Klasse dann mit dem AttributeUsage
Attribut, um dessen Verwendbarkeit zu definieren. Du kannst die folgenden Einstellungen wählen.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] public sealed class LettersOnlyAttribute : ValidationAttribute { }
Die Wahl der AttributeTargets
definiert die Anwendbarkeit des Validators auf die gewünschten Elemente in deinem Code. Durch AllowMultiple
definierst du, ob mehr als eine Instanz des Validators auf jeweils ein Code Element angewendet werden kann. Hier ist der Standardwert false
.
Schritt vier – Validierung durchführen
Überschreibe nun die IsValid
Methode der Basisklasse ValidationAttribute
. Zunächst wandelst du auf den Zieldatentypen um und implementierst dann deine Validierungslogik.
public override bool IsValid(object? value) { if (value is null) return false; var text = (string)value; return IsLettersOnly(text); }
Der Rückgabewert der Validierungslogik muss dabei vom Datentyp bool
sein. Hier kannst du z.B. die Klasse Regex
zur Überprüfung regulärer Ausdrücke verwenden.
// using System.Text.RegularExpressions; private static bool IsLettersOnly(string text) { var regex = new Regex("^[a-zA-Z]*$"); return regex.IsMatch(text); }
Schritt fünf – Fehlermeldung formatieren (optional)
Optional überschreibst du noch die FormatErrorMessage
Methode. So kannst du die Fehlermeldung ein wenig sprechender machen. Die Fehlermeldung wird automatisch erzeugt, wenn die Validierung nicht bestanden wird.
public override string FormatErrorMessage(string name) { return string.Format(CultureInfo.CurrentCulture, $"The property, field or parameter '{name}' is invalid, " + "because only letters are allowed."); }
Unit-Test
Für den Unit-Test reicht es jetzt aus, die IsValid
Methode der Klasse LettersOnlyAttribute
zu testen.
using FluentAssertions; using Validation.Attributes; using Xunit; namespace Validation.Test.Attributes; public class LettersOnlyAttributeTest { private readonly LettersOnlyAttribute _uut; public LettersOnlyAttributeTest() { _uut = new LettersOnlyAttribute(); } [Theory] [InlineData("ValidInput", true)] [InlineData("Invalid_Input", false)] public void Test_IsValid(string input, bool result) { var actual = _uut.IsValid(input); actual.Should().Be(result); } }
Anwendungsbeispiel
Im folgenden Anwendungsbeispiel nutzt du das erstellte Validation Attribute, um den Datentransfer in deine Web-API abzusichern. Dein Validator soll hier den Namen eines Nutzers validieren. Der Nutzername darf sich also nur aus Groß- und Kleinbuchstaben zusammensetzen.
Variante A – im Datenmodell (Property)
Du platzierst den Validator direkt im Datenmodell User
des Nutzers.
public class User { [LettersOnly] public string Name { get; set; } = ""; public int Age { get; set; } = 0; }
Die Daten werden über den Controller TestController
in die Web-API gereicht und dort verarbeitet.
namespace WebApi.Controllers; [ApiController] public class TestController : ControllerBase { public TestController() { } [HttpPost("api/test-user")] public IActionResult TestUser([FromBody] User user) { /* Place your business code here. */ return Ok(user); } }
Die Methode TestUser
entspricht einem HTTP POST Request mit Datentransfer über den Request Body.
curl -X POST "https://localhost:5001/api/test-user" \ -H "accept: */*" \ -H "Content-Type: application/json" \ -d "{\"name\":\"ArthurDent\",\"age\":42}"
Variante B – an der Controller-Methode (Parameter)
Du hast ebenfalls die Möglichkeit den Validator direkt auf einem Parameter deiner Controller-Methode zu integrieren.
[HttpPost("api/test-letters-only")] public IActionResult TestLettersOnly([FromQuery] [LettersOnly] string text) { /* Place your business code here. */ return Ok(text); }
Die Methode TestLettersOnly
entspricht einem HTTP POST Request mit Datentransfer über den Request URL.
curl -X POST "https://localhost:5001/api/test-letters-only?text=ArthurDent" \ -H "accept: */*" \ -d ""
Integrationstest
Starte die Web-API Anwendung und führe die gezeigten HTTP Requests über deine Console aus. Wenn der Validator deine Dateneingabe akzeptiert, dann wird dein Business-Code ausgeführt. Im gezeigten Beispiel schickt die Web-API die Eingabe im HTTP Response Body einfach wieder zurück.
// Status code 200 (Ok) - Response body: { "name": "ArthurDent", "age": 42 }
Sollte der Validator deine Dateneingabe allerdings ablehnen, weil du gegen das Prüfkriterium aus der Methode IsLettersOnly
verstößt, dann wird der Controller deinen HTTP Request ablehnen. Der Controller wird dann mit dem Status Code 400 (Bad Request) antworten und folgenden Response Body zurückgeben. Dein Business-Code wird nicht ausgeführt.
// Status code 400 (Bad Request) - Response body: { "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "00-48f9cc895b0ce04e89a4e48cf1046e5e-f81c15186260824d-00", "errors": { "Name": [ "The property, field or parameter 'Name' is invalid, because only letters are allowed. The value of 'Name' is 'ArthurDent_42', but must match regex pattern '^[a-zA-Z]*$'." ] } }
Fazit
Das besprochene Beispiel zeigt dir hoffentlich, wie du eigene Validation Attributes unter ASP.NET implementierst und in deine Web-API integrierst. Wenn du in Zukunft Validation Attributes verwenden möchtest, dann berücksichtige sie schon frühzeitig bei der Planung deiner Web-API.
Den Code zum Blog findest du auch auf GitHub.
Für einen schnellen Einstieg findest du im GitHub Repository eine Web-API (inkl. Swagger) startklar für deine eigenen Tests. Außerdem kannst du dir die Implementierung eines weiteren Validators OfLegalAgeAttribute
anschauen, der das Alter des Nutzers Age
auf Volljährigkeit prüft.
Happy Coding!