Fluent Validation in Blazor

Michael F. | Softwareentwickler
04/2025

Fluent Validation in Blazor

Eine relativ einfache Validierung von Eingabemasken kann bereits mit den Bordmitteln von .NET durch das Hinzufügen von ValidationAttribute-Dekorationen implementiert werden. Schnell stößt man jedoch an Grenzen, wodurch aufwändige Anpassungen notwendig werden, etwa die Implementierung eigener ValidationAttribute. Ein einfaches Beispiel hierfür wäre die Validierung zweier Datumsfelder, bei der der Wert im zweiten Feld stets größer als der im ersten sein muss. Solch eine Regel ließe sich noch relativ unkompliziert umsetzen.

Doch wie gestaltet sich die Validierung, wenn plötzlich fünf oder mehr Felder mit wechselnden Abhängigkeiten und Regeln überprüft werden müssen? Für solche Szenarien gibt es einige NuGet-Pakete, die diese Anforderungen bereits effizient lösen können. Vorausgesetzt, diese Pakete sind sowohl client- als auch serverseitig in Blazor anwendbar. Im Folgenden werden zwei dieser Pakete näher betrachtet und ihre Vor- und Nachteile gegenübergestellt. Ziel ist es, eine Entscheidungshilfe bereitzustellen, welches Paket sich für spezifische Problemstellungen am besten eignet.

Definition der Validierungsregeln

Es empfiehlt sich, die Validierungsregeln pro Datenklasse in einer separaten Klasse zu definieren, um die Struktur zu optimieren. Ein Beispiel für die Validierung eines Zahlenfelds RangeStart in Abhängigkeit von drei weiteren Feldern (Fixed, NumberLength und RangeEnd) wird nachfolgend erläutert. Das Feld NumberLength bestimmt dabei dynamisch den Zahlenbereich, in dem sich RangeStart bewegen darf. Beispielsweise gilt bei NumberLength = 4 ein Bereich von 1000 bis 9999. Zusätzlich muss RangeStart kleiner als RangeEnd sein. Diese Regeln gelten jedoch nur, wenn Fixed nicht gesetzt ist. Die Konfiguration ist dabei unkompliziert.

public class DemoDTOValidator : AbstractValidator<DemoDTO>
{
    public DemoDTOValidator()
    {
        ...
        RuleFor(dto => dto.RangeStart)
            .NotEmpty()
            .When(dto => !dto.Fixed, applyConditionTo: ApplyConditionTo.AllValidators)
            .WithMessage("Zahlenbereich (von) muss angegeben werden")

            .Must((dto, rangeStart) => IntWithVariableLength(rangeStart, dto.NumberLength))
            .When(dto => !dto.Fixed, applyConditionTo: ApplyConditionTo.AllValidators)
            .WithMessage(dto => $"Die eingegebene Zahl muss eine Länge von {dto.NumberLength} haben")

            .LessThan(dto => dto.RangeEnd)
            .When(dto => !dto.Fixed, applyConditionTo: ApplyConditionTo.AllValidators)
            .WithMessage("Zahlenbereich (von) muss kleiner sein als 'bis'");
        ...
    }

    private bool IntWithVariableLength(int? value, int? length)
    {
        return value.HasValue && length.HasValue && value.Value.ToString().Length == length.Value;
    }
}

Der Validator selbst kann eine beliebige Methode sein (hier: IntWithVariableLength). Diese Methode ist lediglich eine von vielen Regeln, die im Konstruktor der Validator-Klasse definiert werden können. Entsprechend müssen oder können auch Regeln für RangeEnd definiert werden.

Attribut-Validierung

Validation-Attribute müssen nicht zwingend in der Datenklasse definiert sein, können jedoch ergänzend zur FluentValidation genutzt werden, beispielsweise für grundlegende Überprüfungen wie Required oder Range.

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

    [Required(ErrorMessage = "Bitte geben Sie einen Wert ein")]
    [Range(4, 8, ErrorMessage = "{0} muss einen Wert zwischen {1} und {2} haben")]
    [Display(Name = "Zahlenlänge")]
    public int? NumberLength { get; set; }
    ...
    public bool Fixed { get; set; }
    public string? FixedValue { get; set; }
    public int? RangeStart { get; set; }
    public int? RangeEnd { get; set; }
}

Blazorise.FluentValidation

Versionen und Lizenz

  • Verwendete NuGet-Paket-Version: 1.6
  • FluentValidation-Version: 11.9.1
  • Lizenz: Non-Commercial License (im kommerziellen Umfeld kostenpflichtig).
    Link zur Lizenz

Program.cs

In der Program.cs muss beim App-Start zunächst Blazorise registriert und konfiguriert werden. Die Option Immediate ermöglicht eine Validierung bei jeder Tastatureingabe, anstatt erst beim Verlassen des Eingabefelds. Darüber hinaus muss die FluentValidation dem Blazorise-Service hinzugefügt werden.

public class Program
{
    public static void Main(string[] args)
    {
        ...
        builder.Services
            .AddBlazorise(options => options.Immediate = true;)
            .AddBlazoriseFluentValidation();
        ...
        builder.Services
            .AddValidatorsFromAssemblyContaining<DemoDTOValidator>();
        ...
        var host = builder.Build();
        ...
        host.Run();
    }
}

Zusätzlich ist es notwendig, die Validierungsregeln, beispielsweise über ein Assembly, hinzuzufügen.

Angaben in einer Razor-Page

Die Definition der Razor-Page erfolgt wie folgt:

<EditForm EditContext="EditContext">
    <DataAnnotationsValidator />

    <Validations @ref="@fluentValidations" Mode="ValidationMode.Auto" 
                 EditContext="EditContext" HandlerType="typeof(FluentValidationHandler)">

        <Validation>
            <Field>
                <Label>Zahlenlänge (DataAnnotation)</Label>
                <NumericEdit Placeholder="Von" TValue="int?" @bind-Value="Item.NumberLength">
                    <Feedback>
                        <ValidationError />
                    </Feedback>
                </NumericEdit>
            </Field>
        </Validation>

        <Validation>
            <Field>
                <Label>Zahlenbereich</Label>
                <NumericEdit Placeholder="Von" TValue="int?" @bind-Value="Item.RangeStart">
                    <Feedback>
                        <ValidationError />
                    </Feedback>
                </NumericEdit>
            </Field>
        </Validation>

        <Validation>
            <Field>
                <NumericEdit" Placeholder="Bis" TValue="int?" @bind-Value="Item.RangeEnd>
                    <Feedback>
                        <ValidationError />
                    </Feedback>
                </NumericEdit>
            </Field>
        </Validation>
    </Validations>

    <Button Color="Color.Primary" Clicked="@OnSave" Disabled="HasErrors">Save</Button>
</EditForm>

Code-Behind:

public partial class BlazoriseFluentValidation : ComponentBase
{
    public EditContext EditContext { get; set; }
    public DemoDTO Item { get; set; }
    public Validations fluentValidations;

    bool HasErrors { get; set; } = true;

    protected override async Task OnParametersSetAsync()
    {
        Item ??= new() { NumberLength = 5 };
        EditContext = new EditContext(Item);
        EditContext.OnFieldChanged += EditContext_OnFieldChanged;
    }

    private void EditContext_OnFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        bool isValid1 = EditContext.Validate();                 // erfordert <DataAnnotationsValidator />
        bool isValid = fluentValidations.ValidateAll().Result;  // erfordert <Validations @ref="@fluentValidations"
        HasErrors = !(isValid && isValid1);
    }

    protected override async Task OnInitializedAsync()
    {
        await base.OnInitializedAsync();
    }

Damit funktioniert die Validierung. Einziger Nachteil: Die Lizenz des Pakets ist nicht für kommerzielle Nutzung geeignet.

Blazored.FluentValidation

Versionen und Lizenz

  • Verwendete NuGet-Paket-Version: 2.2.0 für .NET 8
  • Lizenz: MIT License (kostenfrei).
    Link zur Lizenz

Dieses Paket basiert ebenfalls auf FluentValidation (Version 11.9.1) und erlaubt dadurch eine einheitliche Definition der Validierungsregeln.

Program.cs

In der Program.cs ist keine weitere Konfiguration notwendig.

Razor-Page

In der Razor-Page befinden sich mehrere Eingabefelder. Für die Validierung relevant sind jedoch nur drei Felder: NumberLength, RangeStart und RangeEnd. Diese Felder sind in der Validierung voneinander abhängig.

Hier das Resultat einer entsprechenden Validierung:

Die Definition der Razor-Page enthält eine EditForm mit den drei Controls, zugehörigen Labels und einer ValidationMessage. Neben dem bekannten DataAnnotationsValidator wird der FluentValidationValidator integriert.

<EditForm EditContext="EditContext">
    <DataAnnotationsValidator />
    <FluentValidationValidator />

    <div class="field">
        <label for="@Item.NumberLength">Zahlenlänge (DataAnnotation)</label>
        <InputNumber class="d-block" @bind-Value="Item.NumberLength" />
        <ValidationMessage TValue="int?" For="() => Item.NumberLength"></ValidationMessage>
    </div>

    <div class="field">
        <label for="@Item.RangeStart">Zahlenbereich</label>
        <InputNumber class="d-block" @bind-Value="Item.RangeStart" placeholder="Von" />
        <ValidationMessage For="() => Item.RangeStart"></ValidationMessage>
    </div>

    <div>
        <InputNumber class="d-block" @bind-Value="Item.RangeEnd" placeholder="Bis" />
        <ValidationMessage TValue="int?" For="() => Item.RangeEnd"></ValidationMessage>
    </div>
    
    <button type="button" class="btn btn-primary" onclick="@OnSave" disabled="@HasErrors">Save</button>

</EditForm>

Im Code-Behind wird der EditContext um einen EventHandler erweitert, der beim Verlassen eines Feldes den gesamten Kontext validiert.

public partial class BlazoredFluentValidation
{
    public EditContext? EditContext { get; set; }
    public DemoDTO Item { get; set; }

    protected override async Task OnInitializedAsync()
    {
        Item ??= new() { NumberLength = 5 };
        EditContext = new EditContext(Item);
        EditContext.OnFieldChanged += EditContext_OnFieldChanged;

        await base.OnInitializedAsync();
    }

    protected override async Task OnParametersSetAsync()
    {
        await base.OnParametersSetAsync();
    }

    private void EditContext_OnFieldChanged(object? sender, FieldChangedEventArgs e)
    {
        bool isValid1 = EditContext.Validate();    // erfordert <DataAnnotationsValidator />
        HasErrors = !(isValid1);
    }

    protected void OnSave()
    {
    }

    public void Dispose()
    {
        if (EditContext is not null)
        {
            EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
        }
    }
}

Damit ist die Einrichtung abgeschlossen. Der Aufwand für dieses Paket ist deutlich geringer als für Blazorise.FluentValidation.

No items found.
Michael F. | Softwareentwickler
Zurück zur Übersicht

Gemeinsam Großes schaffen

Wir freuen uns auf ein kostenloses Erstgespräch mit Ihnen!
Unser Geschäftsführer Tibor Csizmadia und unser Kundenbetreuer Jens Walter stehen Ihnen persönlich zur Verfügung. Profitieren Sie von unserer langjährigen Erfahrung und erhalten Sie eine kompetente Erstberatung in einem unverbindlichen Austausch.
Foto von Tibor

Tibor Csizmadia

Geschäftsführer
Foto von Jens

Jens Walter

Projektmanager
Devware GmbH verpflichtet sich, Ihre Privatsphäre zu schützen. Wir benötigen Ihre Kontaktinformationen, um Sie bezüglich unserer Produkte und Dienstleistungen zu kontaktieren. Mit Klick auf Absenden geben Sie sich damit einverstanden. Weitere Informationen finden Sie unter Datenschutz.
Vielen Dank für Ihre Nachricht!

Wir haben Ihre Anfrage erhalten und melden uns in Kürze bei Ihnen.

Falls Sie in der Zwischenzeit Fragen haben, können Sie uns jederzeit unter Kontaktanfrage@devware.de erreichen.

Wir freuen uns auf die Zusammenarbeit!
Oops! Something went wrong while submitting the form.
KontaktImpressumDatenschutz