{"id":22065583,"url":"https://github.com/karenpayneoregon/fluent-validation-tips","last_synced_at":"2026-01-11T01:47:05.446Z","repository":{"id":110840589,"uuid":"586095334","full_name":"karenpayneoregon/fluent-validation-tips","owner":"karenpayneoregon","description":"FluentValidation tips","archived":false,"fork":false,"pushed_at":"2024-04-12T17:49:06.000Z","size":2283,"stargazers_count":2,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-04-13T20:02:30.777Z","etag":null,"topics":["csharp-core","csharp-library","fluentvalidation","netcore7"],"latest_commit_sha":null,"homepage":"https://dev.to/karenpayneoregon/fluentvalidation-tips-c-3olf","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karenpayneoregon.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-01-06T23:40:50.000Z","updated_at":"2024-04-15T01:44:38.144Z","dependencies_parsed_at":"2024-01-03T15:42:51.736Z","dependency_job_id":"5d198817-9b4f-4147-b3b5-a7fa4dfbb05a","html_url":"https://github.com/karenpayneoregon/fluent-validation-tips","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Ffluent-validation-tips","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Ffluent-validation-tips/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Ffluent-validation-tips/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Ffluent-validation-tips/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karenpayneoregon","download_url":"https://codeload.github.com/karenpayneoregon/fluent-validation-tips/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246662089,"owners_count":20813688,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp-core","csharp-library","fluentvalidation","netcore7"],"created_at":"2024-11-30T19:20:07.243Z","updated_at":"2026-01-11T01:47:05.403Z","avatar_url":"https://github.com/karenpayneoregon.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# FluentValidation tips\n\n[FluentValidation](https://docs.fluentvalidation.net/en/latest/) is a popular .NET library for building strongly-typed validation rules. You can use this library to replace [Data Annotations](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-7.0) in your web application or applications that do not natively support Data Annotations. It also provides an easy way to create validation rules for the properties in your models/classes, taking out the complexity of validation.\n\nThe provided documentation allows a developer get up to speed quickly although it's easy to miss some of the nuances to stream line validation code.\n\nLet's look at setting validation for the following class.\n\n```csharp\npublic class Person\n{\n    public string UserName { get; set; }\n    public string EmailAddress { get; set; }\n    public string Password { get; set; }\n    public string PasswordConfirmation { get; set; }\n}\n```\n\nBy the docs the following is all that is needed to setup rules for each property.\n\n```csharp\npublic class PersonValidator : AbstractValidator\u003cPerson\u003e\n{\n    public PersonValidator()\n    {\n        RuleFor(person =\u003e person.UserName)\n            .NotEmpty()\n            .MinimumLength(3);\n\n        RuleFor(person =\u003e person.EmailAddress).EmailAddress();\n        RuleFor(person =\u003e person.Password.Length)\n            .GreaterThan(7);\n\n        RuleFor(person =\u003e person.Password)\n            .Equal(p =\u003e p.PasswordConfirmation);\n\n    }\n}\n```\n\nTo use the `PersonValidator`, create an instance of the `Person` class with data that validates correctly.\n\n```csharp\nPerson person = new()\n{\n    UserName = \"billyBob\",\n    Password = \"my@Password\",\n    EmailAddress = \"billyBob@gmailcom\",\n    PasswordConfirmation = \"my@Password1\"\n};\n```\n\nCreate an instance of `PersonValidator` followed by calling the `Validate` method passing in the person.\n\n```csharp\nPersonValidator validator = new();\nValidationResult result = validator.Validate(person);\n```\n\nNext\n\n```csharp\nif (result.IsValid)\n{\n    // all properties have valid data\n}\nelse\n{\n    // one or more properties failed to validate, \n    // iterate through result.Errors to create a message to the user\n}\n```\n\n# Predicate Validator\n\n[Predicate Validator](https://docs.fluentvalidation.net/en/latest/custom-validators.html?highlight=IRuleBuilder#predicate-validator) allows a developer to write custom validators. The following sets up a rule for a string property named PhoneNumber to be `xxx-xxxx`\n\n\n```csharp\npublic static class Extensions\n{\n    public static IRuleBuilderOptions\u003cT, string\u003e MatchPhoneNumber\u003cT\u003e(this IRuleBuilder\u003cT, string\u003e rule)\n        =\u003e rule.Matches(@\"^(1-)?\\d{3}-\\d{4}$\").WithMessage(\"Invalid phone number\");\n}\n```\n\nThe validator\n\n```csharp\npublic class PhoneNumberValidator : AbstractValidator\u003cPerson\u003e\n{\n    public PhoneNumberValidator()\n    {\n        RuleFor(person =\u003e person.PhoneNumber)\n            .MatchPhoneNumber();\n    }\n}\n```\n\nThen in this case include the validator in the PersonValidator.\n\n```csharp\npublic class PersonValidator : AbstractValidator\u003cPerson\u003e\n{\n    public PersonValidator()\n    {\n\n        Include(new UserNameValidator());\n        Include(new EmailAddressValidator());\n        Include(new PasswordValidator());\n        Include(new PhoneNumberValidator());\n\n    }\n}\n```\n\nRun a test.\n\n```csharp\n[TestMethod]\n[TestTraits(Trait.Validation)]\npublic void InvalidPhoneNumberTest()\n{\n    // arrange\n    var person = EmployeeInstance;\n\n    person.PhoneNumber = \"11-999\";\n\n    PersonValidator validator = new();\n\n    // act\n    ValidationResult result = validator.Validate(person);\n\n    // assert\n    Assert.IsTrue(result.HasErrorMessage(\"Invalid phone number\"));\n\n}\n```\n\n# EF Core example\n\nIn this example the goal is to validate that in a Category table, prior to saving to the database ensure the category name is unique.\n\n\n```csharp\npublic partial class Categories\n{\n    public int CategoryId { get; set; }\n    public string CategoryName { get; set; }\n}\n```\n\nThe following method will be used in the `CategoryValidator` below.\n\n```csharp\npublic static class ValidationHelpers\n{\n    /// \u003csummary\u003e\n    /// Validate we are not going to add a duplicate category name\n    /// \u003c/summary\u003e\n    /// \u003cparam name=\"category\"\u003e\u003c/param\u003e\n    /// \u003cparam name=\"name\"\u003e\u003c/param\u003e\n    /// \u003creturns\u003e\u003c/returns\u003e\n    public static bool UniqueName(Categories category, string name)\n    {\n        Context context = new Context();\n        var categoryItem = context.Categories.AsEnumerable()\n            .SingleOrDefault(cat =\u003e\n                string.Equals(cat.CategoryName.ToLower(), name.ToLower(), StringComparison.OrdinalIgnoreCase));\n\n\n\n        if (categoryItem == null)\n        {\n            return true;\n        }\n\n        return categoryItem.CategoryId == category.CategoryId;\n    }\n}\n```\n\nThe validator\n\n```csharp\npublic class CategoryValidator : AbstractValidator\u003cCategories\u003e\n{\n    public CategoryValidator()\n    {\n        RuleFor(category =\u003e category.CategoryName)\n            .Must((cat, x) =\u003e \n                ValidationHelpers.UniqueName(cat, cat.CategoryName));\n    }\n}\n```\n\nUnit test\n\n- First test checks to see if there is a category name in the table for Produce which there is.\n- Second test checks to see if there is a category name in the table Coffee where there is not.\n\n```csharp\n[TestClass]\npublic partial class NorthWindTest : TestBase\n{\n\n    [TestMethod]\n    [TestTraits(Trait.Validation)]\n    public void CategoryNameExistsTest()\n    {\n        Categories category = new() { CategoryName = \"Produce\" };\n\n        CategoryValidator validator = new();\n        ValidationResult result = validator.Validate(category);\n\n        Assert.IsFalse(result.IsValid);\n    }\n\n    [TestMethod]\n    [TestTraits(Trait.Validation)]\n    public void CategoryNameDoesNExistsTest()\n    {\n        Categories category = new() { CategoryName = \"Coffee\" };\n\n        CategoryValidator validator = new();\n        ValidationResult result = validator.Validate(category);\n\n        Assert.IsTrue(result.IsValid);\n    }\n}\n```\n\n\n\n# ASP.NET \n\n- See the FluentValidation [documentation](https://docs.fluentvalidation.net/en/latest/aspnet.html) which describe several approaches to implement with dependency injection.\n- Here is an [excellent example](https://baskarmib.netlify.app/content/posts/2022/12/22/implementing-validations-in-net-core-api/) for implementing in ASP.NET Core\n\n\n# Tips\n\nAbove validation rules are setup in one class\n\n```csharp\npublic class PersonValidator : AbstractValidator\u003cPerson\u003e\n{\n    public PersonValidator()\n    {\n        RuleFor(person =\u003e person.UserName)\n            .NotEmpty()\n            .MinimumLength(3);\n\n        RuleFor(person =\u003e person.EmailAddress).EmailAddress();\n        RuleFor(person =\u003e person.Password.Length)\n            .GreaterThan(7);\n\n        RuleFor(person =\u003e person.Password)\n            .Equal(p =\u003e p.PasswordConfirmation);\n\n    }\n}\n```\n\nUsing [Include](https://docs.fluentvalidation.net/en/latest/including-rules.html) method. Simple example, create an Employee class which inherits Person class where the Employee class has the same properties as the Person class along with a property Manager.\n\n```csharp\npublic class Employee : Person\n{\n    public string Manager { get; set; }\n}\n```\n\nThe Include method allows showing rules e.g.\n\nRule for `UserName` which can be used for Person and Employee\n\n```csharp\npublic class UserNameValidator : AbstractValidator\u003cPerson\u003e\n{\n    public UserNameValidator()\n    {\n        RuleFor(person =\u003e person.UserName)\n            .NotEmpty()\n            .MinimumLength(3);\n    }\n}\n```\n\nCreate a validator class for EmailAddress which has a builtin Email Address validator but as per the documention only checks to see if the email address has a `@` symbol. So a tip in a tip, slip in data annotaions attribute [EmailAddressAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.emailaddressattribute?view=net-7.0). \n\n\u003e **Note**\n\u003e There is not one way to validate an email address that can satisfy all developers so decide what works best for you and implement in the class below.\n\n\n```csharp\npublic class EmailAddressValidator : AbstractValidator\u003cPerson\u003e\n{\n    public EmailAddressValidator()\n    {\n        RuleFor(person =\u003e person.EmailAddress)\n            .Must((person, b) =\u003e new EmailAddressAttribute().IsValid(person.EmailAddress));\n    }\n}\n```\n\nPassword confirmation\n\n```csharp\npublic class PasswordValidator : AbstractValidator\u003cPerson\u003e\n{\n\n    public PasswordValidator()\n    {\n\n        RuleFor(person =\u003e person.Password.Length)\n            .GreaterThan(7);\n\n        RuleFor(person =\u003e person.Password)\n            .Equal(p =\u003e p.PasswordConfirmation)\n            .WithState(x =\u003e StatusCodes.PasswordsMisMatch);\n\n    }\n}\n```\n\n**Setup a Person validator using Include**\n\n```csharp\npublic class PersonValidator : AbstractValidator\u003cPerson\u003e\n{\n    public PersonValidator()\n    {\n\n        Include(new UserNameValidator());\n        Include(new EmailAddressValidator());\n        Include(new PasswordValidator());\n    }\n}\n```\n\n**Setup a Employee validator using Include**\n\nFirst step is to create a validator class to validate there is a manager for an employee.\n\n\u003e **Note**\n\u003e Manager names are hard code, consider obtaining manager names from a cached data source.\n\n```csharp\npublic class ManagerValidator : AbstractValidator\u003cEmployee\u003e\n{\n    public ManagerValidator()\n    {\n        \n        List\u003cstring\u003e managers = new List\u003cstring\u003e() {\"Jim Adams\", \"Mary Jones\"};\n\n        RuleFor(emp =\u003e emp.Manager)\n            .Must((employee, name) =\u003e managers.Contains(employee.Manager))\n            .WithMessage(\"Invalid manager name\");\n\n    }\n}\n```\n\nNext create the Employee validator\n\n```csharp\npublic class EmployeeValidator : AbstractValidator\u003cEmployee\u003e\n{\n    public EmployeeValidator()\n    {\n        Include(new UserNameValidator());\n        Include(new PasswordValidator());\n        Include(new EmailAddressValidator());\n        Include(new ManagerValidator());\n        RuleFor(person =\u003e person.Manager)\n            .NotEmpty();\n    }\n}\n```\n\n\n\n\n# Related\n\n[Validating application data with Fluent Validation](https://github.com/karenpayneoregon/teaching-simple-validation) provides similar code samples along with using a `json` file to store rule data.\n\n```json\n{\n  \"FirstNameSettings\": {\n    \"MinimumLength\": 3,\n    \"MaximumLength\": 10,\n    \"WithName\": \"First name\"\n  },\n  \"LastNameSettings\": {\n    \"MinimumLength\": 5,\n    \"MaximumLength\": 30,\n    \"WithName\": \"Last name\"\n  }\n}\n```\n\nAnd PreValidation\n\n```csharp\nprotected override bool PreValidate(ValidationContext\u003cCustomer\u003e context, ValidationResult result)\n{\n    if (context.InstanceToValidate is null)\n    {\n        result.Errors.Add(new ValidationFailure(\"\", $\"Dude, must have a none null instance of {nameof(Customer)}\"));\n        return false;\n    }\n\n    return true;\n}\n```\n\n# See also\n\nAnother [repository](https://github.com/karenpayneoregon/ssn-validator) to check out if there is a need to validate SSN.\n\n# Requires\n\n- Microsoft Visual Studio 2022 17.4.x or higher\n\n# Summary\n\nUsing [FluentValidation](https://docs.fluentvalidation.net/en/latest/) is one way to perform validation, may or may not be right for every developer, some may want to use [Data Annotations](https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-7.0) or a third party library like [Postsharp](https://www.postsharp.net/).\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenpayneoregon%2Ffluent-validation-tips","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarenpayneoregon%2Ffluent-validation-tips","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenpayneoregon%2Ffluent-validation-tips/lists"}