{"id":369790,"date":"2024-05-21T04:11:12","date_gmt":"2024-05-21T04:11:12","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=369790"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-21T04:11:12","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=369790","title":{"rendered":"<span>Validation of custom data with Minimal API filters, sensible and without fuss<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>I believe anyone would like to study new technologies \u201c<em>on the fly<\/em>\u201d. Any extra-efforts which may arise in comprehending some difficult subject may disgust that person and cause him or her to stop doing it. To me, it would seem kind of obvious why this is so.<\/p>\n<p>To be honest, I would like to be considering a bit about features which were presented in\u00a0<em>ASP.NET<\/em>\u00a0starting from the 6th version and the possibilities that appeared after that in part of high-quality validation of custom data based on usage of the\u00a0<em>Minimal API<\/em>\u00a0filters but I would not like getting too deep into that to keep the topic simple. Let\u2019s just focus on integration of the well-known package \u2018<em>FlatValidator\u2019<\/em>\u00a0into a custom application.<\/p>\n<p>A little retrospect into the past. The\u00a0<em>Minimal API<\/em>\u00a0was announced as a flexible software technique designed to serve\u00a0<em>HTTP-routing<\/em>\u00a0in server-side applications. Previously, the architecture model of the controllers was used for development but the implementation of the\u00a0<em>Minimal API<\/em>\u00a0has given some feeling the time of the controllers is inexorably running out.<\/p>\n<p>If you have heard nothing about all this, it does not matter because further discussion will bring some clarification, now I am just marking the boundaries of the issue that is currently being studied.<\/p>\n<p>Endpoint filters are known mainly within the framework of the\u00a0<em>Minimal API<\/em>\u00a0technique, although they are also available for\u00a0<em>MVC<\/em>\u00a0and\u00a0<em>Razor Pages<\/em>. The filters look like a cross between the\u00a0<em>endpoint request handler<\/em>\u00a0itself and\u00a0<em>middleware<\/em>. The term \u2018<em>middleware<\/em>\u2019 means a software module embedded into the\u00a0<em>HTTP-request<\/em>\u00a0processing chain, the modules are executed sequentially, one after the other. Why am I mentioning this? Previously,\u00a0<em>middlewares<\/em>\u00a0were often used for data validation. Now there is a more preferable method like an implementation of the\u00a0<em>\u2018IEndpointFilter\u2019<\/em>\u00a0interface.<\/p>\n<p>The simplest implementation code for\u00a0<em>\u2018IEndpointFilter\u2019<\/em>\u00a0may look like the codes below:<\/p>\n<pre><code class=\"cs\">public class MyFilter: IEndpointFilter  {     public async ValueTask&lt;object?> InvokeAsync(         EndpointFilterInvocationContext context,         EndpointFilterDelegate next)     {               var result = await next(context);         if (result is string s)        {            result = s.Replace(\"vodka\", \"pineapple juice\");        }        return result;     } }<\/code><\/pre>\n<p>Let\u2019s explore what happens here. We execute the\u00a0<em>next(\u2026)<\/em>\u00a0method which will call the endpoint handler and return a response with user data. Next step it replaces the fragment `<em>vodka<\/em>` with the phrase `<em>pineapple juice<\/em>\u2019 in the response. Everything is absolutely simple.<\/p>\n<p>In this example we add our custom processing after executing the\u00a0<em>next(\u2026)\u00a0<\/em>method, such an approach is called post-processing. In case we do some processing before executing the next() method, it will be called pre-processing.<\/p>\n<p>Let\u2019s try to use implemented codes within the <em>Minimal API<\/em> application.<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);   var app = builder.Build();   var group = app     .MapGroup(string.Empty)     .AddEndpointFilter&lt;MyFilter>();  \/\/ &lt;==  group.MapGet(\"\/\", () => \"Hello World\");  group.MapGet(\"\/drinking\", () => \"I like to drink vodka!\");   app.Run();<\/code><\/pre>\n<p>Attempting to call the endpoint \u2018\/drinking\u2019 will lead to returning the phrase `<em>I like to drink pineapple juice!<\/em>\u2019. Such a surprise for one who loves to drink <em>vodka<\/em>.<\/p>\n<p>So, now the filters of the <em>Minimal API<\/em> have been sorted out a bit, it is time to switch to a more real-life example. First of all, let\u2019s install the main\u00a0<em>\u2018<\/em><a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator<\/u><\/em><\/strong><\/a><em>\u2019<\/em>\u00a0package via\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>NuGet<\/u><\/em><\/strong><\/a>, it will help us to validate our data in a professional manner.<\/p>\n<pre><code class=\"powershell\">\u276f dotnet add package FlatValidator<\/code><\/pre>\n<p>The documentation of the package page says the <em>validator<\/em> is provided to be used in two approaches: inline and derived.<\/p>\n<p>Inline approach allows to define the validation rules at the verification point directly. It may be preferable for some cases and may give developers some advantages since they will be able to see the logic \u201cin place\u201d.<\/p>\n<p>On the other hand, if the project has some conceptual requirements regarding the isolation and structure convention of the business logic, it may be easier to use the classic approach with inheritance of the \u2018<em>FlatValidator&lt;TModel><\/em>\u2019 class.<\/p>\n<p>Let me show a slightly modified example from the documentation, the inline approach is used here:<\/p>\n<pre><code class=\"cs\">\/\/ use asynchronous version var result = await FlatValidator.ValidateAsync(model, v => {    \/\/ m == model, IsEmail() is one of funcs for typical data formats    v.ValidIf(m => m.Email.IsEmail(), \"Invalid email\", m => m.Email);     \/\/ involve custom userService for specific logic    v.ErrorIf(async m => await userService.IsUserExistAsync(m.Email),              \"User already registered\", m => m.Email); }); if (!result) \/\/ check the validation result {    return TypedResults.ValidationProblem(result.ToDictionary()); }<\/code><\/pre>\n<p>In fact, the \u2018<em>ValidIf<\/em>\u2019 and \u2018<em>ErrorIf<\/em>\u2019 functions are the validation rule definitions here. The number of such rules are not limited. The \u2018<em>TypedResults.ValidationProblem<\/em>\u2019 is a part of\u00a0<em>.NET 6+<\/em>, it will return error(s) in the\u00a0<em>HTTP-response<\/em>\u00a0in case it occurs.<\/p>\n<p>The application code part in the concept of<em>\u00a0inline-mode<\/em>\u00a0may look like this:<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);  builder.Services.AddProblemDetails();  var app = builder.Build();   \/\/ Endpoint aka https:\/\/localhost:5000\/todos\/ app.MapPost(\"\/todos\", (Todo todo) => {    var result = FlatValidator.Validate(todo, v =>    {        v.ErrorIf(m => m.Title.IsEmpty(), \"Invalid title.\", m => m.Title);    });    if (!result)    {        return TypedResults.ValidationProblem(result.ToDictionary());    }     \/\/ ....    return Results.Ok(); });   app.Run();  \/\/ Model to test validation functionality public record Todo(string Title, bool IsComplete = false);<\/code><\/pre>\n<p>Pay attention, both\u00a0<em>synchronous<\/em>\u00a0and\u00a0<em>asynchronous<\/em>\u00a0versions are presented in the\u00a0<em>FlatValidator<\/em>\u00a0class.<\/p>\n<p>If the\u00a0<em>inline<\/em>\u00a0approach does not seem appropriate enough to you for some reason, you can derive the\u00a0<em>FlatValidator&lt;><\/em>\u00a0and create an independent class.<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);  builder.Services.AddProblemDetails();  var app = builder.Build();   app.MapPost(\"\/todos\", (Todo todo) => {    var result = new TodoValidator().Validate(todo);    if (!result)    {        return TypedResults.ValidationProblem(result.ToDictionary());    }     \/\/ ....    return Results.Ok(); });   app.Run();  \/\/ Model to test validation functionality public record Todo(string Title, bool IsComplete = false);  \/\/ Implement custom validator for the model Todo public class TodoValidator : FlatValidator&lt;Todo> {    public TodoValidator()    {        v.ErrorIf(m => m.Title.IsEmpty(), \"Invalid title.\", m => m.Title);    } }<\/code><\/pre>\n<p>For separated logic such definition manners may look neater. It is clear that the classes \u2018<em>Todo\u2019<\/em>\u00a0and \u2018<em>TodoValidator\u2019\u00a0<\/em>should each be in their own file.<\/p>\n<p>Well, now we have one step left before implementing a real validation filter. First of all, we have to write the logic of the <em>validator<\/em> calling directly in the filter implementation.<\/p>\n<pre><code class=\"cs\">app.MapPost(\"\/todos\", (Todo todo) => {    return Results.Ok();  }).AddEndpointFilter&lt;ValidationFilter&lt;Todo>>();  \/\/ &lt;==   public class ValidationFilter&lt;T>(          IServiceProvider serviceProvider ) : IEndpointFilter {    public async ValueTask&lt;object?> InvokeAsync(        EndpointFilterInvocationContext context,        EndpointFilterDelegate next)    {        var validators = serviceProvider.GetServices&lt;IFlatValidator&lt;T>>();        foreach (var validator in validators)        {            if (context.Arguments.FirstOrDefault(x =>                    x?.GetType() == typeof(T)) is not T model)            {                return TypedResults.Problem(                    detail: \"Invalid parameter.\",                    statusCode: StatusCodes.Status500InternalServerError                );            }             var result = await validator.ValidateAsync(model);            if (!result)            {              return TypedResults.ValidationProblem(result.ToDictionary());            }        }         \/\/ call next filter in the chain        return await next(context);    } }<\/code><\/pre>\n<p>Have you noticed? The body of the endpoint\u00a0<em>.MapPost(\u201c\/todos\u201d)<\/em>\u00a0generally has been got rid of any logic related to validation at all! All logic is in the filter now.<\/p>\n<p>The validation is guaranteed by\u00a0<em>.AddEndpointFilter&lt;ValidationFilter&lt;Todo>><\/em>.<\/p>\n<p>Since our filter is generic, it may be appropriate for any model type, so that it is enough to implement the class of the <em>validator<\/em> and register it in the \u2018<em>IServiceCollection<\/em>\u2019.<\/p>\n<pre><code class=\"cs\">\/\/ register a validator for the Todo model builder.Services.AddScoped&lt;IFlatValidator&lt;Todo>, TodoValidator>();<\/code><\/pre>\n<p>However, there are no problems with this either. If you would like to automate the registration of all your <em>validators<\/em>, just use the small\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator.DependencyInjection\" rel=\"noopener noreferrer nofollow\"><strong><em><u>NuGet<\/u><\/em><\/strong><\/a>\u00a0additional package\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator.DependencyInjection\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator.DependencyInjection<\/u><\/em><\/strong><\/a>.  <\/p>\n<pre><code class=\"powershell\">\u276f dotnet add package FlatValidator.DependencyInjection<\/code><\/pre>\n<p>It helps you to seek and register all <em>validators<\/em> in the application.  <\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);  builder.Services.AddFlatValidatorsFromAssembly(Assembly.GetExecutingAssembly());<\/code><\/pre>\n<p>In general, to get better acquainted with the capabilities of the\u00a0<em>\u2018<\/em><a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator<\/u><\/em><\/strong><\/a><em>\u2019<\/em>\u00a0package, I recommend taking a look at the\u00a0<a href=\"https:\/\/github.com\/belset\/FlatValidator\" rel=\"noopener noreferrer nofollow\"><strong><em><u>project page<\/u><\/em><\/strong><\/a>, which contains documentation and some good examples.<\/p>\n<p>It\u2019s no secret that writing productive code is quite difficult. Often, it is no less difficult to read optimized codes later with understanding because final performance and code clarity are sometimes two completely different things. Nevertheless, the\u00a0<em>\u2018<\/em><a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator<\/u><\/em><\/strong><\/a><em>\u2019<\/em>\u00a0package managed to get fantastic performance with optimizing source codes but at the same time keeping these codes readable.<\/p>\n<p>And finally, a couple of words about performance. A picture below demonstrates the comparative performance and memory usage. The benchmarks themselves may also be found on the project page, may be downloaded and tested.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/916\/618\/9ed\/9166189edab852559f81806b3de6a628.png\" width=\"1274\" height=\"352\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/916\/618\/9ed\/9166189edab852559f81806b3de6a628.png\"\/><\/figure>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/797621\/\"> https:\/\/habr.com\/ru\/articles\/797621\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>I believe anyone would like to study new technologies \u201c<em>on the fly<\/em>\u201d. Any extra-efforts which may arise in comprehending some difficult subject may disgust that person and cause him or her to stop doing it. To me, it would seem kind of obvious why this is so.<\/p>\n<p>To be honest, I would like to be considering a bit about features which were presented in\u00a0<em>ASP.NET<\/em>\u00a0starting from the 6th version and the possibilities that appeared after that in part of high-quality validation of custom data based on usage of the\u00a0<em>Minimal API<\/em>\u00a0filters but I would not like getting too deep into that to keep the topic simple. Let\u2019s just focus on integration of the well-known package \u2018<em>FlatValidator\u2019<\/em>\u00a0into a custom application.<\/p>\n<p>A little retrospect into the past. The\u00a0<em>Minimal API<\/em>\u00a0was announced as a flexible software technique designed to serve\u00a0<em>HTTP-routing<\/em>\u00a0in server-side applications. Previously, the architecture model of the controllers was used for development but the implementation of the\u00a0<em>Minimal API<\/em>\u00a0has given some feeling the time of the controllers is inexorably running out.<\/p>\n<p>If you have heard nothing about all this, it does not matter because further discussion will bring some clarification, now I am just marking the boundaries of the issue that is currently being studied.<\/p>\n<p>Endpoint filters are known mainly within the framework of the\u00a0<em>Minimal API<\/em>\u00a0technique, although they are also available for\u00a0<em>MVC<\/em>\u00a0and\u00a0<em>Razor Pages<\/em>. The filters look like a cross between the\u00a0<em>endpoint request handler<\/em>\u00a0itself and\u00a0<em>middleware<\/em>. The term \u2018<em>middleware<\/em>\u2019 means a software module embedded into the\u00a0<em>HTTP-request<\/em>\u00a0processing chain, the modules are executed sequentially, one after the other. Why am I mentioning this? Previously,\u00a0<em>middlewares<\/em>\u00a0were often used for data validation. Now there is a more preferable method like an implementation of the\u00a0<em>\u2018IEndpointFilter\u2019<\/em>\u00a0interface.<\/p>\n<p>The simplest implementation code for\u00a0<em>\u2018IEndpointFilter\u2019<\/em>\u00a0may look like the codes below:<\/p>\n<pre><code class=\"cs\">public class MyFilter: IEndpointFilter  {     public async ValueTask&lt;object?> InvokeAsync(         EndpointFilterInvocationContext context,         EndpointFilterDelegate next)     {               var result = await next(context);         if (result is string s)        {            result = s.Replace(\"vodka\", \"pineapple juice\");        }        return result;     } }<\/code><\/pre>\n<p>Let\u2019s explore what happens here. We execute the\u00a0<em>next(\u2026)<\/em>\u00a0method which will call the endpoint handler and return a response with user data. Next step it replaces the fragment `<em>vodka<\/em>` with the phrase `<em>pineapple juice<\/em>\u2019 in the response. Everything is absolutely simple.<\/p>\n<p>In this example we add our custom processing after executing the\u00a0<em>next(\u2026)\u00a0<\/em>method, such an approach is called post-processing. In case we do some processing before executing the next() method, it will be called pre-processing.<\/p>\n<p>Let\u2019s try to use implemented codes within the <em>Minimal API<\/em> application.<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);   var app = builder.Build();   var group = app     .MapGroup(string.Empty)     .AddEndpointFilter&lt;MyFilter>();  \/\/ &lt;==  group.MapGet(\"\/\", () => \"Hello World\");  group.MapGet(\"\/drinking\", () => \"I like to drink vodka!\");   app.Run();<\/code><\/pre>\n<p>Attempting to call the endpoint \u2018\/drinking\u2019 will lead to returning the phrase `<em>I like to drink pineapple juice!<\/em>\u2019. Such a surprise for one who loves to drink <em>vodka<\/em>.<\/p>\n<p>So, now the filters of the <em>Minimal API<\/em> have been sorted out a bit, it is time to switch to a more real-life example. First of all, let\u2019s install the main\u00a0<em>\u2018<\/em><a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator<\/u><\/em><\/strong><\/a><em>\u2019<\/em>\u00a0package via\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>NuGet<\/u><\/em><\/strong><\/a>, it will help us to validate our data in a professional manner.<\/p>\n<pre><code class=\"powershell\">\u276f dotnet add package FlatValidator<\/code><\/pre>\n<p>The documentation of the package page says the <em>validator<\/em> is provided to be used in two approaches: inline and derived.<\/p>\n<p>Inline approach allows to define the validation rules at the verification point directly. It may be preferable for some cases and may give developers some advantages since they will be able to see the logic \u201cin place\u201d.<\/p>\n<p>On the other hand, if the project has some conceptual requirements regarding the isolation and structure convention of the business logic, it may be easier to use the classic approach with inheritance of the \u2018<em>FlatValidator&lt;TModel><\/em>\u2019 class.<\/p>\n<p>Let me show a slightly modified example from the documentation, the inline approach is used here:<\/p>\n<pre><code class=\"cs\">\/\/ use asynchronous version var result = await FlatValidator.ValidateAsync(model, v => {    \/\/ m == model, IsEmail() is one of funcs for typical data formats    v.ValidIf(m => m.Email.IsEmail(), \"Invalid email\", m => m.Email);     \/\/ involve custom userService for specific logic    v.ErrorIf(async m => await userService.IsUserExistAsync(m.Email),              \"User already registered\", m => m.Email); }); if (!result) \/\/ check the validation result {    return TypedResults.ValidationProblem(result.ToDictionary()); }<\/code><\/pre>\n<p>In fact, the \u2018<em>ValidIf<\/em>\u2019 and \u2018<em>ErrorIf<\/em>\u2019 functions are the validation rule definitions here. The number of such rules are not limited. The \u2018<em>TypedResults.ValidationProblem<\/em>\u2019 is a part of\u00a0<em>.NET 6+<\/em>, it will return error(s) in the\u00a0<em>HTTP-response<\/em>\u00a0in case it occurs.<\/p>\n<p>The application code part in the concept of<em>\u00a0inline-mode<\/em>\u00a0may look like this:<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);  builder.Services.AddProblemDetails();  var app = builder.Build();   \/\/ Endpoint aka https:\/\/localhost:5000\/todos\/ app.MapPost(\"\/todos\", (Todo todo) => {    var result = FlatValidator.Validate(todo, v =>    {        v.ErrorIf(m => m.Title.IsEmpty(), \"Invalid title.\", m => m.Title);    });    if (!result)    {        return TypedResults.ValidationProblem(result.ToDictionary());    }     \/\/ ....    return Results.Ok(); });   app.Run();  \/\/ Model to test validation functionality public record Todo(string Title, bool IsComplete = false);<\/code><\/pre>\n<p>Pay attention, both\u00a0<em>synchronous<\/em>\u00a0and\u00a0<em>asynchronous<\/em>\u00a0versions are presented in the\u00a0<em>FlatValidator<\/em>\u00a0class.<\/p>\n<p>If the\u00a0<em>inline<\/em>\u00a0approach does not seem appropriate enough to you for some reason, you can derive the\u00a0<em>FlatValidator&lt;><\/em>\u00a0and create an independent class.<\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);  builder.Services.AddProblemDetails();  var app = builder.Build();   app.MapPost(\"\/todos\", (Todo todo) => {    var result = new TodoValidator().Validate(todo);    if (!result)    {        return TypedResults.ValidationProblem(result.ToDictionary());    }     \/\/ ....    return Results.Ok(); });   app.Run();  \/\/ Model to test validation functionality public record Todo(string Title, bool IsComplete = false);  \/\/ Implement custom validator for the model Todo public class TodoValidator : FlatValidator&lt;Todo> {    public TodoValidator()    {        v.ErrorIf(m => m.Title.IsEmpty(), \"Invalid title.\", m => m.Title);    } }<\/code><\/pre>\n<p>For separated logic such definition manners may look neater. It is clear that the classes \u2018<em>Todo\u2019<\/em>\u00a0and \u2018<em>TodoValidator\u2019\u00a0<\/em>should each be in their own file.<\/p>\n<p>Well, now we have one step left before implementing a real validation filter. First of all, we have to write the logic of the <em>validator<\/em> calling directly in the filter implementation.<\/p>\n<pre><code class=\"cs\">app.MapPost(\"\/todos\", (Todo todo) => {    return Results.Ok();  }).AddEndpointFilter&lt;ValidationFilter&lt;Todo>>();  \/\/ &lt;==   public class ValidationFilter&lt;T>(          IServiceProvider serviceProvider ) : IEndpointFilter {    public async ValueTask&lt;object?> InvokeAsync(        EndpointFilterInvocationContext context,        EndpointFilterDelegate next)    {        var validators = serviceProvider.GetServices&lt;IFlatValidator&lt;T>>();        foreach (var validator in validators)        {            if (context.Arguments.FirstOrDefault(x =>                    x?.GetType() == typeof(T)) is not T model)            {                return TypedResults.Problem(                    detail: \"Invalid parameter.\",                    statusCode: StatusCodes.Status500InternalServerError                );            }             var result = await validator.ValidateAsync(model);            if (!result)            {              return TypedResults.ValidationProblem(result.ToDictionary());            }        }         \/\/ call next filter in the chain        return await next(context);    } }<\/code><\/pre>\n<p>Have you noticed? The body of the endpoint\u00a0<em>.MapPost(\u201c\/todos\u201d)<\/em>\u00a0generally has been got rid of any logic related to validation at all! All logic is in the filter now.<\/p>\n<p>The validation is guaranteed by\u00a0<em>.AddEndpointFilter&lt;ValidationFilter&lt;Todo>><\/em>.<\/p>\n<p>Since our filter is generic, it may be appropriate for any model type, so that it is enough to implement the class of the <em>validator<\/em> and register it in the \u2018<em>IServiceCollection<\/em>\u2019.<\/p>\n<pre><code class=\"cs\">\/\/ register a validator for the Todo model builder.Services.AddScoped&lt;IFlatValidator&lt;Todo>, TodoValidator>();<\/code><\/pre>\n<p>However, there are no problems with this either. If you would like to automate the registration of all your <em>validators<\/em>, just use the small\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator.DependencyInjection\" rel=\"noopener noreferrer nofollow\"><strong><em><u>NuGet<\/u><\/em><\/strong><\/a>\u00a0additional package\u00a0<a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator.DependencyInjection\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator.DependencyInjection<\/u><\/em><\/strong><\/a>.  <\/p>\n<pre><code class=\"powershell\">\u276f dotnet add package FlatValidator.DependencyInjection<\/code><\/pre>\n<p>It helps you to seek and register all <em>validators<\/em> in the application.  <\/p>\n<pre><code class=\"cs\">var builder = WebApplication.CreateBuilder(args);  builder.Services.AddFlatValidatorsFromAssembly(Assembly.GetExecutingAssembly());<\/code><\/pre>\n<p>In general, to get better acquainted with the capabilities of the\u00a0<em>\u2018<\/em><a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator<\/u><\/em><\/strong><\/a><em>\u2019<\/em>\u00a0package, I recommend taking a look at the\u00a0<a href=\"https:\/\/github.com\/belset\/FlatValidator\" rel=\"noopener noreferrer nofollow\"><strong><em><u>project page<\/u><\/em><\/strong><\/a>, which contains documentation and some good examples.<\/p>\n<p>It\u2019s no secret that writing productive code is quite difficult. Often, it is no less difficult to read optimized codes later with understanding because final performance and code clarity are sometimes two completely different things. Nevertheless, the\u00a0<em>\u2018<\/em><a href=\"https:\/\/www.nuget.org\/packages\/FlatValidator\/\" rel=\"noopener noreferrer nofollow\"><strong><em><u>FlatValidator<\/u><\/em><\/strong><\/a><em>\u2019<\/em>\u00a0package managed to get fantastic performance with optimizing source codes but at the same time keeping these codes readable.<\/p>\n<p>And finally, a couple of words about performance. A picture below demonstrates the comparative performance and memory usage. The benchmarks themselves may also be found on the project page, may be downloaded and tested.<\/p>\n<figure class=\"full-width\"><\/figure>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/797621\/\"> https:\/\/habr.com\/ru\/articles\/797621\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-369790","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/369790","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=369790"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/369790\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=369790"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=369790"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=369790"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}