0
votes

So Basically i wrote a validator for my class with FluentValidation and also a filter to do the validation task for me in my webAPI project, so far it's OK but assume that my User class has firstname,lastname,email,password properties and i have two routes (one for register and the other one for login) and as you might have noticed required properties are different on these route.

Thus,should I really need to write individual validation for each and every action i have?because this makes a lot of code code duplication and it's hard to change.is there any way to just add required condition based on the request coming with single validation class? Any suggestion???

1

1 Answers

2
votes

A better practice would be to use a factory pattern for your validations and use a an action filter to short circuit bad requests. You could validate any action argument(Headers, Request Bodies, etc..) with something like this.

public class TestValidationAttribute : Attribute, IActionFilter
{
    private string _requestModelName;

    public TestValidationAttribute(string requestModelName)
    {
        _requestModelName = requestModelName;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        // using Microsoft.Extensions.DependencyInjection;
        var services = context.HttpContext.RequestServices;
        var accessor = services.GetService<IHttpContextAccessor>();
        var factory = services.GetService<ITestValidatorFactory>();

        var tokens = accessor.HttpContext.GetRouteData().DataTokens;
        if (!tokens.TryGetValue("RouteName", out var routeNameObj))
        {
            throw new Exception($"Action doesn't have a named route.");
        }
        var routeName = routeNameObj.ToString();

        var validator = factory.Create(routeName);
        if (!context.ActionArguments.TryGetValue(_requestModelName, out var model))
        {
            throw new Exception($"Action doesn't have argument named {_requestModelName}.");
        }

        TestModel test;
        try
        {
            test = (TestModel) model;
        }
        catch (InvalidCastException)
        {
            throw new Exception($"Action argument can't be casted to {nameof(TestModel)}.");
        }

        var validation = validator.Validate(test);
        if (!validation.Successful)
        {
            context.Result = new BadRequestObjectResult(validation.ResponseModel);
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

public class TestController : Controller
{
    [HttpPost]
    [Route("Test/{id}", Name = "TestGet")]
    [TestValidation("model")]
    public IActionResult Test(TestModel model)
    {
        return Ok();
    }
}

public class ValidationResult
{
    public bool Successful { get; }
    public ResponseModel ResponseModel { get; }
}

public class TestModel
{
}

public interface ITestValidator
{
    ValidationResult Validate(TestModel model);
}

public interface ITestValidatorFactory
{
    ITestValidator Create(string routeName);
}