Created
August 29, 2023 04:51
-
-
Save ejsmith/25bf3a9979432e63b4b7d437b03405c3 to your computer and use it in GitHub Desktop.
Auto validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO.Pipelines; | |
using System.Security.Claims; | |
using Microsoft.AspNetCore.Mvc; | |
using Microsoft.AspNetCore.Mvc.Controllers; | |
using Microsoft.AspNetCore.Mvc.Filters; | |
using Microsoft.AspNetCore.Mvc.Infrastructure; | |
using Microsoft.Extensions.Logging.Abstractions; | |
using MiniValidation; | |
using WebApplication6.Validation; | |
namespace WebApplication6.Validation | |
{ | |
public class AutoValidationActionFilter : IAsyncActionFilter | |
{ | |
private readonly IServiceProvider serviceProvider; | |
public AutoValidationActionFilter(IServiceProvider serviceProvider) | |
{ | |
this.serviceProvider = serviceProvider; | |
} | |
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) | |
{ | |
if (context.Controller is ControllerBase controllerBase && context.ActionDescriptor is ControllerActionDescriptor actionDescriptor) | |
{ | |
var parametersToValidate = actionDescriptor.MethodInfo.GetParameters() | |
.Where(p => p.GetCustomAttributes(typeof(ValidateAttribute), true) != null | |
&& ShouldValidate(p.ParameterType, serviceProvider as IServiceProviderIsService)).ToArray(); | |
foreach (var parameter in parametersToValidate) | |
{ | |
if (context.ActionArguments.TryGetValue(parameter.Name, out var subject) && subject != null) | |
{ | |
var (isValid, errors) = await MiniValidator.TryValidateAsync(subject, serviceProvider, recurse: true); | |
if (!isValid) | |
{ | |
foreach (var error in errors) | |
{ | |
foreach (var errorMessage in error.Value) | |
{ | |
context.ModelState.AddModelError(error.Key, errorMessage); | |
} | |
} | |
} | |
} | |
} | |
if (!context.ModelState.IsValid) | |
{ | |
var validationProblem = controllerBase.ProblemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState); | |
context.Result = new BadRequestObjectResult(validationProblem); | |
return; | |
} | |
} | |
await next(); | |
} | |
private static bool ShouldValidate(Type type, IServiceProviderIsService? isService = null) => | |
!IsNonValidatedType(type, isService) | |
&& MiniValidator.RequiresValidation(type); | |
private static bool IsNonValidatedType(Type type, IServiceProviderIsService? isService) => | |
typeof(HttpContext) == type | |
|| typeof(HttpRequest) == type | |
|| typeof(HttpResponse) == type | |
|| typeof(ClaimsPrincipal) == type | |
|| typeof(CancellationToken) == type | |
|| typeof(IFormFileCollection) == type | |
|| typeof(IFormFile) == type | |
|| typeof(Stream) == type | |
|| typeof(PipeReader) == type | |
|| isService?.IsService(type) == true; | |
} | |
} | |
public static class ValidationExtensions | |
{ | |
public static IServiceCollection AddAutoValidation(this IServiceCollection serviceCollection) | |
{ | |
// Create a default instance of the `ModelStateInvalidFilter` to access the non static property `Order` in a static context. | |
var modelStateInvalidFilter = new ModelStateInvalidFilter(new ApiBehaviorOptions { InvalidModelStateResponseFactory = context => new OkResult() }, NullLogger.Instance); | |
// Make sure we insert the `FluentValidationAutoValidationActionFilter` before the built-in `ModelStateInvalidFilter` to prevent it short-circuiting the request. | |
serviceCollection.Configure<MvcOptions>(options => options.Filters.Add<AutoValidationActionFilter>(modelStateInvalidFilter.Order - 1)); | |
return serviceCollection; | |
} | |
} | |
[AttributeUsage(AttributeTargets.Parameter)] | |
public class ValidateAttribute : Attribute | |
{ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment