Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes
{
[AttributeUsage(AttributeTargets.Parameter)]
public class AutoValidateSpecificAttribute : Attribute
{
public string[] RuleSets { get; }

public AutoValidateSpecificAttribute(params string[] ruleSets)
{
RuleSets = ruleSets;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using FluentValidation;
using FluentValidation.Internal;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
Expand Down Expand Up @@ -69,7 +70,10 @@ public async Task OnActionExecutionAsync(ActionExecutingContext actionExecutingC
var validatorInterceptor = validator as IValidatorInterceptor;
var globalValidationInterceptor = serviceProvider.GetService<IGlobalValidationInterceptor>();

IValidationContext validationContext = new ValidationContext<object>(subject);
var autoValidateSpecificAttribute = parameterInfo?.GetCustomAttribute<AutoValidateSpecificAttribute>();

IValidationContext validationContext = ValidationContext<object>
.CreateWithOptions(subject, str => DefineValidationStrategy(str, autoValidateSpecificAttribute?.RuleSets));

if (validatorInterceptor != null)
{
Expand Down Expand Up @@ -155,5 +159,17 @@ private void HandleUnvalidatedEntries(ActionExecutingContext context)
}
}
}

private void DefineValidationStrategy(ValidationStrategy<object> validationStrategy, string[]? ruleSets)
{
if (ruleSets != null && ruleSets.Length > 0)
{
validationStrategy = validationStrategy.IncludeRuleSets(ruleSets);
return;
}

validationStrategy = validationStrategy
.IncludeRulesNotInRuleSet();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,10 @@ public static bool HasCustomAttribute<TAttribute>(this ParameterInfo parameterIn
{
return parameterInfo.CustomAttributes.Any(attribute => attribute.AttributeType == typeof(TAttribute));
}

public static TAttribute? GetCustomAttribute<TAttribute>(this ParameterInfo parameterInfo) where TAttribute : Attribute
{
return parameterInfo.GetCustomAttributes(true).FirstOrDefault(attribute => attribute is TAttribute) as TAttribute;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Options;
using NSubstitute;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Attributes;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Configuration;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Filters;
using SharpGrip.FluentValidation.AutoValidation.Mvc.Interceptors;
Expand Down Expand Up @@ -176,6 +177,89 @@ public async Task OnActionExecutionAsync_WithInstanceTypeDifferentThanParameterT
Assert.Contains(validationFailuresValues[0].First(), badRequestObjectResultValidationProblemDetails.Errors[nameof(CreatePersonRequest.Name)][0]);
}

[Fact]
public async Task TestOnActionExecutionAsync_WithSpecificRuleSets_UseSpecificRuleSets()
{
var actionArguments = new Dictionary<string, object?>
{
{
nameof(TestRuleSetModel), new TestRuleSetModel
{
Parameter1 = "Value 1",
Parameter2 = "Value 2",
Parameter3 = "Value 3",
Parameter4 = null,
}
},
};
var controllerActionDescriptor = new ControllerActionDescriptor
{
Parameters = new List<ParameterDescriptor>
{
new ControllerParameterDescriptor()
{
Name = nameof(TestRuleSetModel),
ParameterType = typeof(TestRuleSetModel),
BindingInfo = new BindingInfo {BindingSource = BindingSource.Body},
ParameterInfo = typeof(TestRuleSetController)
.GetMethod(nameof(TestRuleSetController.TestAction))
.GetParameters()
.First(x => x.ParameterType == typeof(TestRuleSetModel))
}
},
};
var validationFailures = new Dictionary<string, string[]>
{
[nameof(TestRuleSetModel.Parameter1)] = [$"'{nameof(TestRuleSetModel.Parameter1)}' must be 5 characters in length. You entered 7 characters."],
[nameof(TestRuleSetModel.Parameter2)] = [$"'{nameof(TestRuleSetModel.Parameter2)}' must be 5 characters in length. You entered 7 characters."],
[nameof(TestRuleSetModel.Parameter3)] = [$"'{nameof(TestRuleSetModel.Parameter3)}' must be 5 characters in length. You entered 7 characters."]
};

var validationProblemDetails = new ValidationProblemDetails(validationFailures);
var modelStateDictionary = new ModelStateDictionary();

var serviceProvider = Substitute.For<IServiceProvider>();
var problemDetailsFactory = Substitute.For<ProblemDetailsFactory>();
var fluentValidationAutoValidationResultFactory = Substitute.For<IFluentValidationAutoValidationResultFactory>();
var autoValidationMvcConfiguration = Substitute.For<IOptions<AutoValidationMvcConfiguration>>();
var httpContext = Substitute.For<HttpContext>();
var controller = Substitute.For<TestController>();
var actionContext = Substitute.For<ActionContext>(httpContext, Substitute.For<RouteData>(), controllerActionDescriptor, modelStateDictionary);
var actionExecutingContext = Substitute.For<ActionExecutingContext>(actionContext, new List<IFilterMetadata>(), actionArguments, new object());
var actionExecutedContext = Substitute.For<ActionExecutedContext>(actionContext, new List<IFilterMetadata>(), new object());

serviceProvider.GetService(typeof(IValidator<>).MakeGenericType(typeof(TestRuleSetModel))).Returns(new TestRuleSetModelValidator());
serviceProvider.GetService(typeof(IGlobalValidationInterceptor)).Returns(new GlobalValidationInterceptor());
serviceProvider.GetService(typeof(ProblemDetailsFactory)).Returns(problemDetailsFactory);

problemDetailsFactory.CreateValidationProblemDetails(httpContext, modelStateDictionary).Returns(validationProblemDetails);
fluentValidationAutoValidationResultFactory.CreateActionResult(actionExecutingContext, validationProblemDetails).Returns(new BadRequestObjectResult(validationProblemDetails));
httpContext.RequestServices.Returns(serviceProvider);
actionExecutingContext.Controller.Returns(controller);
actionExecutingContext.ActionDescriptor = controllerActionDescriptor;
actionExecutingContext.ActionArguments.Returns(actionArguments);
autoValidationMvcConfiguration.Value.Returns(new AutoValidationMvcConfiguration());

var actionFilter = new FluentValidationAutoValidationActionFilter(fluentValidationAutoValidationResultFactory, autoValidationMvcConfiguration);

await actionFilter.OnActionExecutionAsync(actionExecutingContext, () => Task.FromResult(actionExecutedContext));

var modelStateDictionaryValues = modelStateDictionary.Values.ToList();
var validationFailuresValues = validationFailures.Values.ToList();
var badRequestObjectResult = (BadRequestObjectResult)actionExecutingContext.Result!;
var badRequestObjectResultValidationProblemDetails = (ValidationProblemDetails)badRequestObjectResult.Value!;

Assert.Equal(validationFailuresValues.Count, modelStateDictionaryValues.Count);

Assert.Contains(validationFailuresValues[0].First(), modelStateDictionaryValues[0].Errors.Select(error => error.ErrorMessage));
Assert.Contains(validationFailuresValues[1].First(), modelStateDictionaryValues[1].Errors.Select(error => error.ErrorMessage));
Assert.Contains(validationFailuresValues[2].First(), modelStateDictionaryValues[2].Errors.Select(error => error.ErrorMessage));

Assert.Contains(validationFailuresValues[0].First(), badRequestObjectResultValidationProblemDetails.Errors[nameof(TestRuleSetModel.Parameter1)][0]);
Assert.Contains(validationFailuresValues[1].First(), badRequestObjectResultValidationProblemDetails.Errors[nameof(TestRuleSetModel.Parameter2)][0]);
Assert.Contains(validationFailuresValues[2].First(), badRequestObjectResultValidationProblemDetails.Errors[nameof(TestRuleSetModel.Parameter3)][0]);
}

public class AnimalsController : ControllerBase
{
}
Expand Down Expand Up @@ -249,4 +333,53 @@ private class GlobalValidationInterceptor : IGlobalValidationInterceptor
return null;
}
}

private class TestRuleSetController : ControllerBase
{
public IActionResult TestAction([AutoValidateSpecific("testRuleSet")] TestRuleSetModel model)
{
return Ok();
}
}

private class TestRuleSetModel
{
public string? Parameter1 { get; set; }
public string? Parameter2 { get; set; }
public string? Parameter3 { get; set; }
public string? Parameter4 { get; set; }
}

private class TestRuleSetModelValidator : AbstractValidator<TestRuleSetModel>, IValidatorInterceptor
{
public TestRuleSetModelValidator()
{
RuleFor(x => x.Parameter1).Empty();
RuleFor(x => x.Parameter2).Empty();
RuleFor(x => x.Parameter3).Empty();
RuleFor(x => x.Parameter4).Empty();

RuleSet("testRuleSet", () =>
{
RuleFor(x => x.Parameter1)
.Length(5);

RuleFor(x => x.Parameter2)
.Length(5);

RuleFor(x => x.Parameter3)
.Length(5);
});
}

public IValidationContext? BeforeValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext)
{
return null;
}

public ValidationResult? AfterValidation(ActionExecutingContext actionExecutingContext, IValidationContext validationContext)
{
return null;
}
}
}