SoFunction
Updated on 2025-04-08

Core 6.0 Data verification function based on model verification

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

replaceApiBehaviorOptionsDefined 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 onApiBehaviorOptionsSetuprightApiBehaviorOptionsConfigure it, it is actually aFunc), and use a filter (forModelStateInvalidFilter,Depend onModelStateInvalidFilterFactoryGenerate), to control model verification and return results (return aBadRequestObjectResultorObjectResult)。

Among them, the most important one isApiBehaviorOptionsofSuppressModelStateInvalidFilterandInvalidModelStateResponseFactoryproperty. 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 attributesInvalidModelStateResponseFactoryConfigure a default factory, which will do these actions when executing:

GetProblemDetailsFactory(Singleton) service instance, callProblemDetailsInvalidModelStateResponseGet oneIActionResultAs a response result.

ProblemDetailsInvalidModelStateResponseThe 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 aBadRequestObjectResultorObjectResult

4.4 ModelStateInvalidFilter

The above introduction is finishedInvalidModelStateResponseFactoryRegistration, 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 thatOnActionExecutingIn, when no other filters set the result ( == null), and the model verification fails (!) will be calledInvalidModelStateResponseFactoryFactory 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 implementationIFilterFactoryInstance of the interface, callCreateInstanceMethod 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!