Skip to content

Commit cac3935

Browse files
committed
Add validation filters with FluentValidation support.
1 parent 018c1fd commit cac3935

File tree

3 files changed

+95
-0
lines changed

3 files changed

+95
-0
lines changed

src/Initium/ApiController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ namespace Initium;
88
[Route("api/[controller]")]
99
[TypeFilter(typeof(ApiExceptionFilter))]
1010
[TypeFilter(typeof(ApiResponseFilter))]
11+
[TypeFilter(typeof(ImplicitValidationFilter))]
1112
public class ApiController : BaseController;
1213

1314
public class ApiController<TService>(TService service) : ApiController where TService : class
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using FluentValidation;
3+
using Initium.Response;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc.Filters;
6+
7+
namespace Initium.Attributes;
8+
9+
/// <summary>
10+
/// An attribute that performs model validation using a specified FluentValidation validator.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Method)]
13+
[SuppressMessage("ReSharper", "UnusedType.Global")]
14+
public class ValidateModelAttribute(Type validatorType) : Attribute, IActionFilter
15+
{
16+
/// <summary>
17+
/// Called before the action method executes. Validates the model using the specified validator.
18+
/// </summary>
19+
/// <param name="context">The context for the action execution.</param>
20+
public void OnActionExecuting(ActionExecutingContext context)
21+
{
22+
var argument = context.ActionArguments.Values.FirstOrDefault();
23+
if (argument == null) return;
24+
25+
if (Activator.CreateInstance(validatorType) is not IValidator validator) return;
26+
27+
var validationContext = new ValidationContext<object>(argument);
28+
var validationResult = validator.Validate(validationContext);
29+
30+
if (validationResult.IsValid) return;
31+
32+
context.Result = ApiResponseBuilder
33+
.CreateFromContext(context.HttpContext)
34+
.WithMessage("One or more validation errors occurred.")
35+
.WithStatusCode(StatusCodes.Status400BadRequest)
36+
.WithErrors(validationResult.Errors)
37+
.BuildAsJsonResult();
38+
}
39+
40+
/// <summary>
41+
/// Called after the action method executes. No operation is performed in this implementation.
42+
/// </summary>
43+
/// <param name="context">The context for the executed action.</param>
44+
public void OnActionExecuted(ActionExecutedContext context) { }
45+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using FluentValidation;
2+
using Initium.Infrastructure;
3+
using Initium.Response;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.AspNetCore.Mvc.Filters;
6+
7+
namespace Initium.Filters;
8+
9+
/// <summary>
10+
/// A filter that performs implicit validation on actions using requests derived from <see cref="BaseRequestWithValidator{T}"/>.
11+
/// </summary>
12+
internal class ImplicitValidationFilter : IActionFilter
13+
{
14+
/// <summary>
15+
/// Called before the action executes, to validate the action arguments.
16+
/// </summary>
17+
/// <param name="context">The context for the action execution.</param>
18+
public void OnActionExecuting(ActionExecutingContext context)
19+
{
20+
var argument = context.ActionArguments.Values.FirstOrDefault();
21+
if (argument == null) return;
22+
23+
var argumentType = argument.GetType();
24+
var baseType = argumentType.BaseType;
25+
26+
if (baseType is not { IsGenericType: true } || baseType.GetGenericTypeDefinition() != typeof(BaseRequestWithValidator<>)) return;
27+
28+
var validatorType = baseType.GetGenericArguments().FirstOrDefault();
29+
if (validatorType == null || Activator.CreateInstance(validatorType) is not IValidator validator) return;
30+
31+
var validationContext = new ValidationContext<object>(argument);
32+
var validationResult = validator.Validate(validationContext);
33+
34+
if (validationResult.IsValid) return;
35+
36+
context.Result = ApiResponseBuilder
37+
.CreateFromContext(context.HttpContext)
38+
.WithMessage("One or more validation errors occurred.")
39+
.WithStatusCode(StatusCodes.Status400BadRequest)
40+
.WithErrors(validationResult.Errors)
41+
.BuildAsJsonResult();
42+
}
43+
44+
/// <summary>
45+
/// Called after the action executes. No operation is performed in this implementation.
46+
/// </summary>
47+
/// <param name="context">The context for the executed action.</param>
48+
public void OnActionExecuted(ActionExecutedContext context) { }
49+
}

0 commit comments

Comments
 (0)