I'm developing an ASP.NET WebApi2 (.NET 4.7.2) with Owin and Autofac as IOC container. The Dtos shall get validated with FluentValidation before reaching the controller. Here is my project configuration:
public class Startup
{
public void Configuration(IAppBuilder app)
{
...
var config = new HttpConfiguration();
config.Filters.Add(new ValidateDtoStateFilter());
...
FluentValidationModelValidatorProvider.Configure(config);
...
}
}
Validate dto state filter:
public class ValidateDtoStateFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (!actionContext.ModelState.IsValid)
{
throw new ValidationException(actionContext.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key.Split('.')[1].FromPascalToCamelCase(), er.ErrorMessage))));
}
}
}
Autofac builder configuration:
builder.RegisterAssemblyTypes(typeof(MyDtoValidator).Assembly)
.Where(t => t.Name.EndsWith("Validator"))
.AsImplementedInterfaces()
.InstancePerLifetimeScope();
builder.RegisterType<FluentValidationModelValidatorProvider>().As<ModelValidatorProvider>();
builder.RegisterType<AutofacValidatorFactory>().As<IValidatorFactory>().SingleInstance();
Autofac validator factory:
public class AutofacValidatorFactory : ValidatorFactoryBase
{
private readonly IComponentContext _context;
public AutofacValidatorFactory(IComponentContext context)
{
_context = context;
}
public override IValidator CreateInstance(Type validatorType)
{
object instance;
return _context.TryResolve(validatorType, out instance) ? instance as IValidator : null;
}
}
It works fine with POST or PUT endpoints where the dto comes [FromBody]
in the requets payload. It does not work with [FromUri]
dtos, e.g. in a GET request. The validator will be created by Autofac, but in OnActionExecuting
of ValidateDtoStateFilter
actionContext.ModelState
is always true, no matter if the supplied data is valid or not.
How can I achieve that [FromUri]
dtos get validated by the FluentValidation middleware as well?
The issue does not occur any more without my having changed anything.
I could not reproduce this behaviour. Tested with Autofac 6.0.0
and FluentValidation 8.6.1
. However, both [FromUri]
and [FromBody]
dtos set actionContext.ModelState.IsValid
to true when the model is null (either no query string parameters passed or request body is empty). In this case no errors are generated so the validation passes.
This is actually by design. The purpose of FluentValidation is to validate properties on objects, which by definition requires a non-null instance in order to work. https://github.com/FluentValidation/FluentValidation/issues/486
I can think of two ways to work around this limitation.
[HttpGet]
public IHttpActionResult Get([FromUri] MyDto dto)
{
if (dto == null) return BadRequest();
...
}
ValidateDtoStateFilter
class to manually trigger validation of null dtos that have validator defined:public class ValidateDtoStateFilter : ActionFilterAttribute
{
private readonly IDependencyResolver _resolver;
public ValidateDtoStateFilter(IDependencyResolver resolver)
{
_resolver = resolver;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
var nullDtosWithValidatorDefined = actionContext.ActionArguments
.Where(argument => argument.Value == null)
.Select(argument => new
{
argument.Key,
// you may need to account for p.IsOptional and p.DefaultValue here
Type = parameters.FirstOrDefault(p => p.ParameterName == argument.Key)?.ParameterType
})
.Where(argument => argument.Type != null)
.Select(argument => new {
argument.Key,
Validator = (IValidator)_resolver.GetService(typeof(IValidator<>).MakeGenericType(argument.Type))
})
.Where(argument => argument.Validator != null)
.Select(argument => argument.Key);
foreach (var dto in nullDtosWithValidatorDefined)
{
actionContext.ModelState.AddModelError(dto, $"'{dto}' must not be null.");
}
if (!actionContext.ModelState.IsValid)
{
var errors = actionContext
.ModelState
.SelectMany(ms => ms.Value.Errors
.Select(er => new ValidationFailure(ms.Key, er.ErrorMessage))
);
throw new ValidationException(errors);
}
}
}
// in the Startup.cs file the ValidateDtoStateFilter must be added after the DependencyResolver is set to Autofac:
config.DependencyResolver = new AutofacWebApiDependencyResolver(ConfigureAutofac());
config.Filters.Add(new ValidateDtoStateFilter(config.DependencyResolver));
This may not answer your specific problem though, it would be easier if you shared a link to your project or a snippet of your Startup.cs
file, as it could be something with your api configuration.