1 Preface
In programs, scenarios where data verification is required often exist, and data verification is necessary. The front-end data verification is mainly to reduce server request pressure and improve user experience; the back-end data verification is mainly to ensure the correctness of the data and the robustness of the system.
The data verification scheme described in this article is based on the officialModel validation, which is also the way that the author only learned during the recent interview process [I have previously confused: Model validation and Data annotation methods for EF model configuration].
Note: There are some differences in model verification between MVC and API. This article mainly describes model verification under the API.
1.1 Scenarios for data verification
The more traditional verification methods are as follows:
public string TraditionValidation(TestModel model) { if (()) { return "The name cannot be empty!"; } if ( > 10) { return "The length of the name cannot exceed 10!"; } return "Verification passed!"; }
In the function, each attribute of the model is verified separately.
Although functions can be reused with the model, they are indeed not elegant enough.
The official provides a model validation method, and the following will propose corresponding solutions based on this method.
1.2 The context of this article
Let’s briefly introduce the use of model validation, and then propose two custom solutions.
Finally, I will roughly interpret the source code related to AspNetCore.
2 Model verification
2.1 Introduction
The official method of model validation is to add validation attributes to model attributes, configure verification rules and corresponding error messages (ErrorMessage). When the verification fails, an error message that the verification fails will be returned.
Among them, in addition to the built-in verification features, users can also customize verification features (this article will not be expanded). For details, please check the section on Custom Features.
In MVC, you need to call it through the following code (in action):
if (!) { return View(movie); }
In the API, as long as the controller has[ApiController] Feature: If the model verification fails, the HTTP400 corresponding containing the error message will be automatically returned. For details, see Automatic HTTP 400 response.
2.2 Basic use
(1) Custom model
In the following code,[Required]
Indicates that this property is required,ErrorMessage = ""
The verification information returned for the verification feature fails.
public class TestModel { [Required(ErrorMessage = "The name cannot be empty!")] [StringLength(10, ErrorMessage = "The length of the name cannot exceed 10 characters!")] public string? Name { get; set; } [Phone(ErrorMessage = "The cell phone format is wrong!")] public string? Phone { get; set; } }
(2) Controller code
There is on the controller[ApiController]
Features can trigger:
[ApiController] [Route("[controller]/[action]")] public class TestController : ControllerBase { [HttpPost] public TestModel ModelValidation(TestModel model) { return model; } }
(3) Test
Enter illegal data, in the format as follows:
{ "name": "string string", "email": "111" }
The output information is as follows:
{
"type": "/html/rfc7231#section-6.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"traceId": "00-4d4df1b3618a97a6c50d5fe45884543d-81ac2a79523fd282-00",
"errors": {
"Name": [
"The length of the name cannot exceed 10 characters!"
],
"Email": [
"Emailbox format error!"
]
}
}
2.3 Built-in features
Some of the official built-in features such as:
[ValidateNever]: Indicates that the attributes or parameters should be excluded from verification.
[CreditCard]: Verify that the attribute has a credit card format.
[Compare]: Verify that the two attributes in the model match.
[EmailAddress]: Verify that the attribute has an email format.
[Phone]: Verify that the attribute has a phone number format.
[Range]: Verify that the attribute value is within the specified range.
[RegularExpression]: Verify that the attribute value matches the specified regular expression.
[Required]: Verify that the field is not null.
[StringLength]: Verify that the string attribute value does not exceed the specified length limit.
[URL]: Verify that the attribute has URL format.
[Remote]: Verify input on the client by calling the operation method on the server.
Can be found in the namespaceA complete list of validation properties.
3 Custom data verification
3.1 Introduction
Since the format returned by the official model verification is different from the format actually required by our program, this part mainly replaces the return format of the model verification, and the ability to use is actually the model verification.
3.2 Preparation
Prepare a unified return format:
public class ApiResult { public int Code { get; set; } public string? Msg { get; set; } public object? Data { get; set; } }
When data verification fails:
Code is 400, indicating that there is a problem with the requested data.
Msg defaults to: data verification fails! For front-end prompts.
Data is an error message detail, used for front-end prompts.
like:
{ "code": 400, "msg": "Data verification failed!", "data": [ "The length of the name cannot exceed 10 characters!", "Emailbox format is wrong!" ] }
3.3 Plan 1: Replace the factory
replaceApiBehaviorOptions
Defined by default inInvalidModelStateResponseFactory
, in:
<ApiBehaviorOptions>(options => { = actionContext => { //Get the model field that failed to verify var errors = .Where(s => != null && == ) .SelectMany(s => !.()) .Select(e => ) .ToList(); // Unified return format var result = new ApiResult() { Code = StatusCodes.Status400BadRequest, Msg = "Data verification failed!", Data = errors }; return new BadRequestObjectResult(result); }; });
3.4 Solution 2: Custom filters
(1) Custom filter
public class DataValidationFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // If the result has been set by other filters, skip verification if ( != null) return; // If the verification is passed, skip the subsequent action if () return; // Get the failed verification information list var errors = .Where(s => != null && == ) .SelectMany(s => !.()) .Select(e => ) .ToArray(); // Unified return format var result = new ApiResult() { Code = StatusCodes.Status400BadRequest, Msg = "Data verification failed!", Data = errors }; // Set the result = new BadRequestObjectResult(result); } public void OnActionExecuted(ActionExecutedContext context) { } }
(2) Disable the default filter
In:
<ApiBehaviorOptions>(options => { // Disable the default model verification filter = true; });
(3) Enable custom filter
In:
<MvcOptions>(options => { // Add a custom model verification filter globally <DataValidationFilter>(); });
3.5 Test
Enter illegal data, in the format as follows:
{ "name": "string string", "email": "111" }
The output information is as follows:
{
"code": 400,
"msg": "Data verification failed!",
"data": [
"The length of the name cannot exceed 10 characters!",
"Emailbox format is wrong!"
]
}
3.6 Summary
Both solutions are actually similar (actually based on filter Filter), and can be selected according to personal needs.
Among them, the filter implemented by AspNetCore isModelStateInvalidFilter
, its Order = -2000, can arrange the filter order in the program according to the actual situation of the program.
4 Source code interpretation
4.1 Basic introduction
The AspNetCore model validates the source code related to this area, mainly by registering a default factory.InvalidModelStateResponseFactory
(Depend onApiBehaviorOptionsSetup
rightApiBehaviorOptions
Configure it, it is actually aFunc
), and use a filter (forModelStateInvalidFilter
,Depend onModelStateInvalidFilterFactory
Generate), to control model verification and return results (return aBadRequestObjectResult
orObjectResult
)。
Among them, the most important one isApiBehaviorOptions
ofSuppressModelStateInvalidFilter
andInvalidModelStateResponseFactory
property. The former controls whether the default filter is enabled, and the latter generates the results of model verification.
4.2 MvcServiceCollectionExtensions
The statements for registering the controller in the newly created WebAPI template are as follows:
();
The call is in the source codeThe method is extracted as follows:
// public static IMvcBuilder AddControllers(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } var builder = AddControllersCore(services); return new MvcBuilder(, ); }
Another method will be calledAddControllersCore
:
// private static IMvcCoreBuilder AddControllersCore(IServiceCollection services) { // This method excludes all of the view-related services by default. var builder = services .AddMvcCore() .AddApiExplorer() .AddAuthorization() .AddCors() .AddDataAnnotations() .AddFormatterMappings(); if () { ( <IActionDescriptorChangeProvider, HotReloadService>()); } return builder; }
Among them, the relevant ones areAddMvcCore()
:
// public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException(nameof(services)); } var environment = GetServiceFromCollection<IWebHostEnvironment>(services); var partManager = GetApplicationPartManager(services, environment); (partManager); ConfigureDefaultFeatureProviders(partManager); ConfigureDefaultServices(services); AddMvcCoreServices(services); var builder = new MvcCoreBuilder(services, partManager); return builder; }
inAddMvcCoreServices(services)
The method will execute the following method. Since this method is too long, a sentence of code related to model verification will be extracted here:
// internal static void AddMvcCoreServices(IServiceCollection services) { ( <IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>()); }
Mainly configure the defaultApiBehaviorOptions
。
4.3 ApiBehaviorOptionsSetup
The main code is as follows:
internal class ApiBehaviorOptionsSetup : IConfigureOptions<ApiBehaviorOptions> { private ProblemDetailsFactory? _problemDetailsFactory; public void Configure(ApiBehaviorOptions options) { = context => { _problemDetailsFactory ??= <ProblemDetailsFactory>(); return ProblemDetailsInvalidModelStateResponse(_problemDetailsFactory, context); }; ConfigureClientErrorMapping(options); } }
For attributesInvalidModelStateResponseFactory
Configure a default factory, which will do these actions when executing:
GetProblemDetailsFactory
(Singleton) service instance, callProblemDetailsInvalidModelStateResponse
Get oneIActionResult
As a response result.
ProblemDetailsInvalidModelStateResponse
The method is as follows:
// internal static IActionResult ProblemDetailsInvalidModelStateResponse(ProblemDetailsFactory problemDetailsFactory, ActionContext context) { var problemDetails = (, ); ObjectResult result; if ( == 400) { // For compatibility with , continue producing BadRequestObjectResult instances if the status code is 400. result = new BadRequestObjectResult(problemDetails); } else { result = new ObjectResult(problemDetails) { StatusCode = , }; } ("application/problem+json"); ("application/problem+xml"); return result; }
The method will eventually return aBadRequestObjectResult
orObjectResult
。
4.4 ModelStateInvalidFilter
The above introduction is finishedInvalidModelStateResponseFactory
Registration, so when will this factory be called?
The main code of the default filter for model verification is as follows:
public class ModelStateInvalidFilter : IActionFilter, IOrderedFilter { internal const int FilterOrder = -2000; private readonly ApiBehaviorOptions _apiBehaviorOptions; private readonly ILogger _logger; public int Order => FilterOrder; public void OnActionExecuting(ActionExecutingContext context) { if ( == null && !) { _logger.ModelStateInvalidFilterExecuting(); = _apiBehaviorOptions.InvalidModelStateResponseFactory(context); } } }
You can see thatOnActionExecuting
In, when no other filters set the result ( == null
), and the model verification fails (!
) will be calledInvalidModelStateResponseFactory
Factory verification, get the return result.
The main source code for model verification is as mentioned above.
4.5 Other supplements
(1) The execution order of the filter
The default filter's Order is -2000, and its triggering time is generally earlier (model verification should also be as early as possible).
Execution order of filter pipelines: The smaller the Order value, the more you execute the Executing method first, and then the Executed method (i.e., first in and then out).
(2) Creation and registration of default filters
I didn't look at this part carefully, the routine is probably like this: through the filter provider (DefaultFilterProvider
), obtain implementationIFilterFactory
Instance of the interface, callCreateInstance
Method generates filters and adds filters to filter containers (IFilterContainer
)。
The default filtering factory class for model verification is:ModelStateInvalidFilterFactory
5 sample codes
The complete code for this example can be obtained from here:
Gitee:/lisheng741/testnetcore/tree/master/Filter/DataAnnotationTest
Github:/lisheng741/testnetcore/tree/master/Filter/DataAnnotationTest
Reference source
AspNetCore source code
AspNetCore WebApi: Data Verification
Core official documentation >> Advanced >> Model verification
This is the end of this article about Core 6.0 data verification based on model verification. For more related Core model verification content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!