Wird geladen...

FluentValidation in C# verwenden

Lernen Sie FluentValidation in C#, um saubere und wartbare Validierungsregeln zu erstellen.

FluentValidation ist eine beliebte Bibliothek in C#, die es Ihnen ermöglicht, Validierungsregeln mit einer fluenten API zu definieren. Sie ermöglicht die Erstellung wiederverwendbarer und testbarer Regeln, die unabhängig von den Modellklassen sind. Dieser Artikel bietet eine praktische Anleitung – von der Installation bis zu fortgeschrittenen Funktionen, einschließlich der Integration in ASP.NET Core und Unit-Tests.


Installation

Für das Basispaket und die optionale ASP.NET Core-Integration:


dotnet add package FluentValidation
dotnet add package FluentValidation.AspNetCore   # Für Webprojekte

Für Desktop- (WPF/WinForms) oder Serviceprojekte genügt FluentValidation allein.


Grundlegende Verwendung: Ein einfacher Validator

Eine Validator-Klasse erbt von AbstractValidator<T>, und die Regeln werden mit RuleFor definiert.


using FluentValidation;

public class UserDto
{
    public string? Name { get; set; }
    public string? Email { get; set; }
    public int Age { get; set; }
}

public class UserDtoValidator : AbstractValidator<UserDto>
{
    public UserDtoValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage("Name darf nicht leer sein")
            .Length(2, 50).WithMessage("Name muss zwischen 2 und 50 Zeichen lang sein");

        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("E-Mail ist erforderlich")
            .EmailAddress().WithMessage("Bitte eine gültige E-Mail-Adresse eingeben");

        RuleFor(x => x.Age)
            .InclusiveBetween(18, 120).WithMessage("Das Alter muss zwischen 18 und 120 liegen");
    }
}

Validierung aufrufen: var result = new UserDtoValidator().Validate(model); Fehler können über result.Errors gelesen werden.


Nachrichten, Schweregrad und Kaskadenmodus


RuleFor(x => x.Name)
    .Cascade(CascadeMode.Stop)
    .NotEmpty().WithMessage("Name ist erforderlich").WithSeverity(Severity.Error)
    .MinimumLength(2).WithMessage("Name muss mindestens 2 Zeichen haben");

Bedingte Regeln: When / Unless

Wird verwendet, um eine Regel nur unter bestimmten Bedingungen auszuführen.


RuleFor(x => x.Email)
  .NotEmpty().EmailAddress();

RuleFor(x => x.Age)
  .GreaterThan(0);

RuleFor(x => x)
  .Must(x => x.Email!.EndsWith("@company.com"))
  .When(x => x.Age >= 18)
  .WithMessage("Benutzer ab 18 Jahren müssen eine Firmen-E-Mail verwenden");

Verbundene Objekte und Sammlungen

Verwenden Sie SetValidator für verschachtelte Modelle und RuleForEach für Sammlungselemente.


public class AddressDto { public string? City { get; set; } public string? Zip { get; set; } }

public class AddressValidator : AbstractValidator<AddressDto>
{
    public AddressValidator()
    {
        RuleFor(x => x.City).NotEmpty();
        RuleFor(x => x.Zip).NotEmpty().Length(5, 10);
    }
}

public class CustomerDto
{
    public string? Name { get; set; }
    public AddressDto? Address { get; set; }
    public List<string> Phones { get; set; } = new();
}

public class CustomerValidator : AbstractValidator<CustomerDto>
{
    public CustomerValidator()
    {
        RuleFor(x => x.Name).NotEmpty();

        RuleFor(x => x.Address)
            .NotNull().WithMessage("Adresse ist erforderlich")
            .SetValidator(new AddressValidator());

        RuleForEach(x => x.Phones)
            .NotEmpty().Matches(@"^\+?\d{7,15}$")
            .WithMessage("Ungültige Telefonnummer");
    }
}

Benutzerdefinierte und Asynchrone Regeln

Verwenden Sie Must/MustAsync für komplexe oder serviceabhängige Validierungen.


RuleFor(x => x.Email)
  .MustAsync(async (email, ct) =>
  {
      // Beispiel: Eindeutigkeitsprüfung (Pseudo-Repository)
      bool exists = await _userRepository.ExistsByEmailAsync(email, ct);
      return !exists;
  })
  .WithMessage("E-Mail ist bereits registriert");

Szenariobasierte Validierung mit RuleSet

Regeln können für verschiedene Szenarien gruppiert werden (z. B. Create, Update).


public class ProductDto { public string? Name { get; set; } public decimal Price { get; set; } }

public class ProductValidator : AbstractValidator<ProductDto>
{
    public ProductValidator()
    {
        RuleSet("Create", () =>
        {
            RuleFor(x => x.Name).NotEmpty();
            RuleFor(x => x.Price).GreaterThan(0);
        });

        RuleSet("Update", () =>
        {
            RuleFor(x => x.Price).GreaterThanOrEqualTo(0);
        });
    }
}

// Verwendung:
// validator.Validate(model, options: o => o.IncludeRuleSets("Create"));

Anpassen und Lokalisieren von Fehlermeldungen


RuleFor(x => x.Email)
  .WithName("E-Mail")
  .NotEmpty().WithMessage("{PropertyName} darf nicht leer sein")
  .EmailAddress().WithMessage("{PropertyName} ist ungültig");

ASP.NET Core Integration

Registrieren Sie Services, um die automatische Modellvalidierung und die Integration mit ModelState zu aktivieren.


// Program.cs (Minimal API / ASP.NET Core 7+)
using FluentValidation;
using FluentValidation.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddFluentValidationAutoValidation();  // Funktioniert automatisch mit MVC-Model-Binding
builder.Services.AddValidatorsFromAssemblyContaining<UserDtoValidator>(); // Durchsucht alle Validatoren

var app = builder.Build();
app.MapControllers();
app.Run();

Im Controller ist kein manueller Aufruf erforderlich; nach dem Model-Binding wird FluentValidation automatisch ausgeführt und schreibt die Fehler in ModelState.


Erweiterte Themen: Abhängigkeiten, Injektion und Wiederverwendbare Regeln


public class NameRules : AbstractValidator<UserDto>
{
    public NameRules()
        => RuleFor(x => x.Name).NotEmpty().MaximumLength(50);
}

public class UserDtoValidator2 : AbstractValidator<UserDto>
{
    public UserDtoValidator2()
    {
        Include(new NameRules());
        RuleFor(x => x.Email).EmailAddress();
    }
}

Testbarkeit: FluentValidation.TestHelper

Verwenden Sie Hilfserweiterungen, um Validatoren schnell zu testen.


dotnet add package FluentValidation.TestHelper

using FluentValidation.TestHelper;
using Xunit;

public class UserDtoValidatorTests
{
    [Fact]
    public void Name_Darf_Nicht_Leer_Sein()
    {
        var v = new UserDtoValidator();
        var result = v.TestValidate(new UserDto { Name = "" });
        result.ShouldHaveValidationErrorFor(x => x.Name);
    }

    [Fact]
    public void Email_Sollte_Gueltig_Sein()
    {
        var v = new UserDtoValidator();
        var result = v.TestValidate(new UserDto { Email = "x" });
        result.ShouldHaveValidationErrorFor(x => x.Email);
    }
}

Praxisbeispiel: Registrierungsformular + Adresse & Telefonnummern

Das folgende Beispiel zeigt Benutzerregistrierung, geschachtelte Adressvalidierung und die Validierung von Telefonlisten.


public class RegisterRequest
{
    public string? FullName { get; set; }
    public string? Email { get; set; }
    public AddressDto? Address { get; set; }
    public List<string> Phones { get; set; } = new();
}

public class RegisterValidator : AbstractValidator<RegisterRequest>
{
    public RegisterValidator(IEmailBlacklistService blacklist)
    {
        RuleFor(x => x.FullName)
            .NotEmpty().WithMessage("Vor- und Nachname ist erforderlich")
            .MaximumLength(80);

        RuleFor(x => x.Email)
            .NotEmpty().EmailAddress()
            .MustAsync(async (mail, ct) => !await blacklist.IsBlacklistedAsync(mail, ct))
            .WithMessage("E-Mail wird nicht akzeptiert");

        RuleFor(x => x.Address)
            .NotNull().SetValidator(new AddressValidator());

        RuleForEach(x => x.Phones)
            .NotEmpty()
            .Matches(@"^\+?\d{7,15}$").WithMessage("Ungültiges Telefonnummernformat");

        // Bedingung: Wenn Land 'TR', muss die Postleitzahl 5-stellig sein
        When(x => x.Address?.Country == "TR", () =>
        {
            RuleFor(x => x.Address!.Zip)
                .Matches(@"^\d{5}$").WithMessage("Für TR muss die Postleitzahl 5-stellig sein");
        });
    }
}

Leistung und Beste Praktiken


TL;DR

  • FluentValidation trennt Regeln vom Modell und bietet eine lesbare, testbare Validierung.
  • RuleFor, RuleForEach und SetValidator sind die Grundbausteine.
  • Verwenden Sie When/Unless für bedingte Regeln; RuleSet für szenariobasierte Validierungen.
  • Must/MustAsync ist ideal für benutzerdefinierte oder externe Dienstprüfungen.
  • Aktivieren Sie in ASP.NET Core die automatische Integration mit AddFluentValidationAutoValidation().
  • Schützen Sie Ihre Validatoren mit Unit-Tests unter Verwendung von TestHelper.

Ähnliche Artikel