In the previous article, I introduced the fundamentals of FluentValidation in .NET Core using a Console Application. In addition, I will introduce some ASP.NET Core-based applications in this article.
Suppose we have a class named Student.
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public List<string> Hobbies { get; set; }
}
Now, we want to create an API to query students' hobbies.
So, we create a QueryStudentHobbiesDto class to define the request parameters.
public class QueryStudentHobbiesDto
{
public int? Id { get; set; }
public string Name { get; set; }
}
And let's create the validator first.
public class QueryStudentHobbiesDtoValidator: AbstractValidator<QueryStudentHobbiesDto>
{
public QueryStudentHobbiesDtoValidator()
{
RuleSet("all", () =>
{
RuleFor(x => x.Id).Must(CheckId).WithMessage("id must greater than 0");
RuleFor(x => x.Name).NotNull().When(x=>!x.Id.HasValue).WithMessage("name could not be null");
});
RuleSet("id", () =>
{
RuleFor(x => x.Id).NotNull().WithMessage("id could not be null")
.GreaterThan(0).WithMessage("id must greater than 0");
});
RuleSet("name", () =>
{
RuleFor(x => x.Name).NotNull().WithMessage("name could not be null");
});
}
private bool CheckId(int? id)
{
return !id.HasValue || id.Value > 0;
}
}
Let's begin with a familiar way.
Manual validation
You can regard this usage as a copy of the Console App sample that I showed you in the last article.
// GET api/values/hobbies1
[HttpGet("hobbies1")]
public ActionResult GetHobbies1([FromQuery]QueryStudentHobbiesDto dto)
{
var validator = new QueryStudentHobbiesDtoValidator();
var results = validator.Validate(dto, ruleSet: "all");
return !results.IsValid
? Ok(new { code = -1, data = new List<string>(), msg = results.Errors.FirstOrDefault().ErrorMessage })
: Ok(new { code = 0, data = new List<string> { "v1", "v2" }, msg = "" });
What we need to do are three steps.
Create a new instance of the validator.
Call the Validate method
Return something based on the result of the Validate method.
After running up this project, we may get the following result.
Most of the time, we create a new instance directly. It is not a very good choice. We may use Dependency Injection in the next section.
Dependency Injection(DI)
There are two ways to use DI here. One is using the IValidator directly, the other one is using a middle layer to handle this, such as the BLL layer.
Use IValidator directly
What we need to do is inject IValidator<QueryStudentHobbiesDto> and call the Validate method to handle.
private readonly IValidator<QueryStudentHobbiesDto> _validator;
public ValuesController(IValidator<QueryStudentHobbiesDto> validator)
{
this._validator = validator;
}
// GET api/values/hobbies5
[HttpGet("hobbies5")]
public ActionResult GetHobbies5([FromQuery]QueryStudentHobbiesDto dto)
{
var res = _validator.Validate(dto, ruleSet: "all");
return !res.IsValid
? Ok(new { code = -1, data = new List<string>(), msg = res.Errors.FirstOrDefault().ErrorMessage })
: Ok(new { code = 0, data = new List<string> { "v1", "v2" }, msg = "" });
}
And don't forget to add the following code in the Startup class.
public void ConfigureServices(IServiceCollection services)
{
//inject validator
services.AddSingleton<IValidator<QueryStudentHobbiesDto>, QueryStudentHobbiesDtoValidator>();
}
When we run it up, we will get the same result in a manual way.
Use a middle layer
We also can create a service class to handle the business logic.
public interface IStudentService
{
(bool flag, string msg) QueryHobbies(QueryStudentHobbiesDto dto);
}
public class StudentService : IStudentService
{
private readonly AbstractValidator<QueryStudentHobbiesDto> _validator;
//private readonly IValidator<QueryStudentHobbiesDto> _validator;
public StudentService(AbstractValidator<QueryStudentHobbiesDto> validator)
//public StudentService(IValidator<QueryStudentHobbiesDto> validator)
{
this._validator = validator;
}
public (bool flag, string msg) QueryHobbies(QueryStudentHobbiesDto dto)
{
var res = _validator.Validate(dto, ruleSet: "all");
if(!res.IsValid)
{
return (false, res.Errors.FirstOrDefault().ErrorMessage);
}
else
{
//query ....
return (true, string.Empty);
}
}
}
Go back to the controller.
private readonly IStudentService _service;
public ValuesController(IStudentService service)
{
this._service = service;
}
// GET api/values/hobbies4
[HttpGet("hobbies4")]
public ActionResult GetHobbies4([FromQuery]QueryStudentHobbiesDto dto)
{
var (flag, msg) = _service.QueryHobbies(dto);
return !flag
? Ok(new { code = -1, data = new List<string>(), msg })
: Ok(new { code = 0, data = new List<string> { "v1", "v2" }, msg = "" });
}
This also has an easy way which is similar to Model Binding.
Validator customization
Using the CustomizeValidatorAttribute to configure how the validator will be run.
// GET api/values/hobbies2
[HttpGet("hobbies2")]
public ActionResult GetHobbies2([FromQuery][CustomizeValidator(RuleSet = "all")]QueryStudentHobbiesDto dto)
{
return Ok(new { code = 0, data = new List<string> { "v1", "v2" }, msg = "" });
}
Let's run it up.
It didn't seem to work!
We should add FluentValidation in the Startup class so that we can enable this feature!
public void ConfigureServices(IServiceCollection services)
{
//others...
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
//when using CustomizeValidator, should add the following code.
.AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<Startup>();
//fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
//fv.ImplicitlyValidateChildProperties = true;
});
}
At this time, when we visit api/values/hobbies2, we can find out that we can not get the query result but the validated message.
However, we don't want to return this message to the users. We should follow the previous sample here!
How can we format the result?
We could use Filter to deal with the validated result here.
public class ValidateFilterAttribute : ResultFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
//model valid not pass
if(!context.ModelState.IsValid)
{
var entry = context.ModelState.Values.FirstOrDefault();
var message = entry.Errors.FirstOrDefault().ErrorMessage;
//modify the result
context.Result = new OkObjectResult(new
{
code = -1,
data = new JObject(),
msg= message,
});
}
}
}
And mark the attribute at the action method.
// GET api/values/hobbies3
[HttpGet("hobbies3")]
[ValidateFilter]
public ActionResult GetHobbies3([FromQuery][CustomizeValidator(RuleSet = "all")]QueryStudentHobbiesDto dto)
{
//isn't valid will not visit the okobjectresult, but visit the filter
return Ok(new { code = 0, data = new List<string> { "v1", "v2" }, msg = "" });
}
And we will get what we want.
This article introduced three ways to use FluentValidation in ASP.NET Core.
I hope this will help you!
European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.