Von MVC zu Blazor – Data Annotations für die Validierung

Wichtigkeit der Validierung

Aus meiner Sicht gibt es grundsätzliche Kriterien, die während einer Evaluierung möglicher Technologien und Frameworks für eine Webanwendung beachtet werden müssen. Ein Kriterium ist die Validierung. Es gibt viele technische Wege um den Endbenutzer durch ein Formular zu führen und Hilfestellung zu bieten. Bei fehlender bzw. unvollständiger Validierung kann es zu Missverständnissen, Problemen mit der Datenqualität oder zu Sicherheitsrisiken führen. Aus diesem Grund bewährt es sich auf vorhandene Validierungskonzepte und -techniken aufzubauen. ASP.NET Core MVC und Blazor bieten in Form der Data Annotations eine Möglichkeit, die Validierung einfach aufzubauen. 

Validieren mit Data Annotations

.NET bietet eine Vielzahl von vordefinierten Data Annotations. Eine kleine Auswahl: 

Annotation Beschreibung Beispiel 
Required Überprüft, ob Wert nicht NULL ist. [Required] public string Vorname { get; set } 
StringLength Überprüft, ob der Wert die gewünschte Länge einhaltet. [StringLength(5, MinimumLength = 1)] public string Vorname { get; set; } 
RegularExpression Überprüft, ob der Wert den Regex erfüllt. [RegularExpression(@”^[a-zA-Z”-‘\s]{1,40}$”)] public string Vorname { get; set; } 
Range Überprüft, ob der Wert sich im gewünschten Bereich befindet. [Range(0, 100)] public int? Prozent { get; set; } 
CreditCard Überprüft, ob der Wert eine valide Kreditkartennummer ist. [CreditCard] public string Kreditkarte { get; set; } 
EmailAddress Überprüft, ob der Wert eine valide Email-Adresse ist. [EmailAddress] public string Email { get; set; 

Eine solche Validierung kann mit weiteren Anwendungsfällen ergänzt werden. Dafür braucht es eine neue Klasse, die von ValidationAttribute erbt. Folgend ein Beispiel, indem eine Guid vorhanden sein muss (NULL oder 00000000-0000-0000-0000-000000000000 nicht erlaubt): 

[AttributeUsage(AttributeTargets.Property)] 
public class GuidRequiredAttribute : ValidationAttribute 
{ 
    protected override ValidationResult IsValid(object value, ValidationContext validationContext) 
    { 
        var guidValue = value as Guid?; 
        if (guidValue != null && guidValue != Guid.Empty) 
        { 
            return ValidationResult.Success; 
        } 

        return new ValidationResult(ErrorMessage, new List<string> { validationContext.MemberName }); 
    } 
} 

Verwendung in MVC und Blazor

Validieren auf einem Form aktivieren

MVC

Um im MVC die Validierung aktivieren zu können, wird nach einem POST die gleiche View mit dem bestehenden Model zurückgegeben: 

[HttpPost] 
public IActionResult Index(AdressViewModel model) 
{ 
     if (ModelState.IsValid) 
     { 
          // Valid    
     } 
     return View(model); 
} 

Blazor

Im Blazor braucht es das EditForm und den DataAnnotationsValidator für die Beachtung der Validierung: 

<EditForm Model="Model" OnValidSubmit="@Sumbit"> 
    <DataAnnotationsValidator /> 

    … 

    <input type="submit" value="Absenden" /> 
</EditForm> 

Validierungs-Messages pro Feld anzeigen

MVC

Für die Anzeige der Validierung-Messages pro Feld kann ValidationMessageFor verwendet werden: 

@Html.ValidationMessageFor(Model => Model.Vorname) 

Blazor

Auch im Blazor gibt es dafür einen einfachen Weg:

<ValidationMessage For="@(() => Model.Vorname)" /> 

Validation-Summary

MVC

Um das Summary im MVC anzeigen zu können, braucht es im MVC die Methode ValidationSummary:

@Html.ValidationSummary() 

Blazor

Das Gleiche kann bei Blazor mit dem Komponenten ValidationSummary erreicht werden:

<ValidationSummary /> 

Einsatz von IValidationObject

Wird auf dem zu validierenden Model das IValidationObject implementiert, können dort weitere individuelle Validierungen ergänzt werden: 

public class AdressModel : IValidatableObject 
{ 
    ... 

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) 
    { 
        if (Bis.HasValue && Bis < Von) 
            yield return new ValidationResult( 
                "Das Bis Datum ist kleiner als das Von-Datum. Bitte korrigieren", 
                new List<string> {nameof(Bis)}); 
    } 
} 

Sowohl für MVC als auch für Blazor ist dieser Ansatz anwendbar. Ein wichtiger Hinweis: Die Validate-Methode greift erst, wenn alle Data Annotations des Models gültig sind. 

Remote-Attribute

MVC

Mithilfe dem Remote-Attribute können datenabhängige Validierungen durchgeführt werden. Dafür wird eine Methode im Controller (als Beispiel «UserController») aufbereitet: 

public IActionResult CheckAccountName(string account) 
{ 
    bool isOk = account.ToLower().Trim() != "renegade"; 
    return Json(isOk); 
}  

Sobald jetzt folgende Annotation gesetzt wird, führt es zur Validierung des Benutzernamens basierend auf den existierenden Benutzer. 

[Remote("CheckAccountName", "Home", ErrorMessage = "Der Name kann nicht verwendet werden")]  

Blazor

Eine äquivalente Lösung gibt es für Blazor nicht. Es gibt aber eine ähnliche Möglichkeit. Microsoft verwendet dafür den Begriff «Business logic validation». Mit dieser Option kann ein CustomValidator im EditForm angebunden werden: 

<EditForm Model = "@model" OnValidSubmit = "@HandleValidSubmit" > 
       <DataAnnotationsValidator /> 

       <CustomValidator @ref="customValidator" /> 

Sobald jetzt die grundsätzliche Validierung erfolgreich war, kann diese beim Submit ergänzt werden: 

@code 
{ 
    private CustomValidator customValidator; 
    public AdressViewModel Model { get; set; } = new AdressViewModel(); 

    void Sumbit() 
    { 
        customValidator.ClearErrors(); 
        var errors = new Dictionary<string, List<string>>(); 

        if (Model?.Account?.ToLower().Trim() == "renegade") 
        { 
            errors.Add(nameof(Model.Account), 
                new List<string>() { "Der Name kann nicht verwendet werden" }); 
        } 

        if (errors.Count() > 0) 
        { 
            customValidator.DisplayErrors(errors); 
        } 
        else 
        { 
            // Process the form 
        } 
    }  
} 

Um die CustomValidator-Klasse einsetzten zu können, braucht es im Blazor-Projekt folgendes Objekt (siehe Microsoft-Dokumentation):  

public class CustomValidator : ComponentBase 
{ 
    private ValidationMessageStore messageStore; 

    [CascadingParameter] 
    private EditContext CurrentEditContext { get; set; } 

    protected override void OnInitialized() 
    { 
        if (CurrentEditContext == null) 
        { 
            throw new InvalidOperationException( 
                $"{nameof(CustomValidator)} requires a cascading " + 
                $"parameter of type {nameof(EditContext)}. " + 
                $"For example, you can use {nameof(CustomValidator)} " + 
                $"inside an {nameof(EditForm)}."); 
        } 

        messageStore = new ValidationMessageStore(CurrentEditContext); 

        CurrentEditContext.OnValidationRequested += (s, e) => 
            messageStore.Clear(); 
        CurrentEditContext.OnFieldChanged += (s, e) => 
            messageStore.Clear(e.FieldIdentifier); 
    } 

    public void DisplayErrors(Dictionary<string, List<string>> errors) 
    { 
        foreach (var err in errors) 
        { 
            messageStore.Add(CurrentEditContext.Field(err.Key), err.Value); 
        } 

        CurrentEditContext.NotifyValidationStateChanged(); 
    } 

    public void ClearErrors() 
    { 
        messageStore.Clear(); 
        CurrentEditContext.NotifyValidationStateChanged(); 
    } 
} 

Hast du dich bereits mit den Gemeinsamkeiten und Unterschieden der Validierung mit MVC und Blazor auseinander gesetzt? Dein Feedback ist willkommen!