瀏覽代碼

Implemented validation aspect

Lukas Angerer 1 年之前
父節點
當前提交
8bab836af9

+ 2 - 0
WebTemplate/AppServer.cs

@@ -4,6 +4,7 @@ using WebTemplate.ServerAspects.Cors;
 using WebTemplate.ServerAspects.Json;
 using WebTemplate.ServerAspects.Spa;
 using WebTemplate.ServerAspects.Swagger;
+using WebTemplate.ServerAspects.Validation;
 using WebTemplate.Status;
 
 namespace WebTemplate;
@@ -22,6 +23,7 @@ public class AppServer
         new CorsModule(),
         new SwaggerModule(),
         new SpaRoutingModule(),
+        new ValidationModule(),
         new StatusEndpointModule(),
         new ControllersModule(),
     };

+ 4 - 0
WebTemplate/Program.cs

@@ -3,3 +3,7 @@ using WebTemplate;
 
 var server = new AppServer();
 server.Start(args);
+
+// After launching the application, you can access
+// * https://localhost:7169/swagger/
+// * https://localhost:7169/v1/status

+ 39 - 0
WebTemplate/ServerAspects/Validation/ValidationFilter.cs

@@ -0,0 +1,39 @@
+using FluentValidation;
+
+namespace WebTemplate.ServerAspects.Validation;
+
+/// <summary>
+/// Since the FluentValidation.AspNetCore package is no longer supported and the documentation under
+/// https://docs.fluentvalidation.net/en/latest/aspnet.html?highlight=asp.net%20core#using-a-filter suggests using
+/// a custom filter, this is what we are doing.
+///
+/// The code is heavily inspired by a "coding short" video by Shawn Wildermuth but customized a bit.
+/// https://www.youtube.com/watch?v=_S-r6SxLGn4
+/// </summary>
+/// <typeparam name="T">The object type that should be validated.</typeparam>
+public class ValidationFilter<T> : IEndpointFilter
+{
+    public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext ctx, EndpointFilterDelegate next)
+    {
+        var validator = ctx.HttpContext.RequestServices.GetService<IValidator<T>>();
+        if (validator is null)
+        {
+            return Results.Problem($"No validator found for type {typeof(T).Name}");
+        }
+        
+        var entity = ctx.Arguments
+            .OfType<T>()
+            .FirstOrDefault(a => a?.GetType() == typeof(T));
+        if (entity is not null)
+        {
+            var validation = await validator.ValidateAsync(entity);
+            if (validation.IsValid)
+            {
+                return await next(ctx);
+            }
+            return Results.ValidationProblem(validation.ToDictionary());
+        }
+        
+        return Results.Problem("Could not find type to validate");
+    }
+}

+ 24 - 0
WebTemplate/ServerAspects/Validation/ValidationModule.cs

@@ -0,0 +1,24 @@
+using FluentValidation;
+
+namespace WebTemplate.ServerAspects.Validation;
+
+/// <summary>
+/// For minimal APIs, you can just add the <see cref="ValidationFilter{T}"/> to any mapped endpoint by doing something
+/// like
+/// <code>
+///   .AddEndpointFilter&lt;ValidationFilter&lt;MyRequest&gt;&gt;()
+/// </code>
+/// </summary>
+public class ValidationModule : IAppConfigurationModule
+{
+    public void ConfigureServices(IServiceCollection services, IConfigurationRoot config)
+    {
+        // If you add a validator as described in https://github.com/FluentValidation/FluentValidation?tab=readme-ov-file
+        // that validator should automatically be registered with this.
+        services.AddValidatorsFromAssemblyContaining(typeof(ValidationModule));
+    }
+
+    public void ConfigureApplication(WebApplication app)
+    {
+    }
+}

+ 2 - 0
WebTemplate/WebTemplate.csproj

@@ -9,6 +9,8 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <PackageReference Include="FluentValidation" Version="11.9.0" />
+    <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.0" />
     <PackageReference Include="GitInfo" Version="3.3.4">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>