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
- Mit
WithMessage()können benutzerdefinierte Fehlermeldungen festgelegt werden. - Mit
WithSeverity(Severity.Error|Warning|Info)wird die Priorität angegeben. - Mit
Cascade(CascadeMode.Stop)wird die Validierung nach dem ersten Fehler gestoppt.
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
- Verwenden Sie
WithName("Feldname"), um einen benutzerfreundlichen Feldnamen anzuzeigen. - Nutzen Sie Ressourcen-Dateien (.resx), um Mehrsprachigkeit zu ermöglichen.
- Für globale Nachrichten kann der LanguageManager angepasst werden.
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
- Injizieren Sie Services oder Repositories in den Konstruktor des Validators, um sie innerhalb von MustAsync zu verwenden.
- Gemeinsam genutzte Regeln können mit
Includewiederverwendet werden:
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
- Schreiben Sie Validatoren zustandslos; nehmen Sie Abhängigkeiten nur im Konstruktor auf.
- Verringern Sie IO-lastige MustAsync-Aufrufe durch Caching in stark frequentierten Umgebungen.
- Verwenden Sie
RuleSetfür szenariobasierte Validierung bei großen Modellen. - Halten Sie Fehlermeldungen kurz und benutzerfreundlich; passen Sie Feldnamen mit
WithNamean. - Erwägen Sie zusätzlich zur Servervalidierung auch eine Client-seitige Validierung.
TL;DR
- FluentValidation trennt Regeln vom Modell und bietet eine lesbare, testbare Validierung.
RuleFor,RuleForEachundSetValidatorsind 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
Dependency Injection Grundlagen in C#
Lernen Sie die Grundlagen von Dependency Injection in C#, um Abhängigkeiten zu verwalten und lose Kopplung zu erreichen.
Klassen, Objekte, Eigenschaften und Methoden in C#
Erlernen Sie die Grundlagen von Klassen, Objekten, Eigenschaften und Methoden in C# für objektorientierte Programmierung.