欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

ASP.NET?Core?6.0?基于模型驗證的數(shù)據(jù)驗證功能

 更新時間:2022年07月25日 09:56:09   作者:蘆薈柚子茶  
這篇文章主要介紹了ASP.NET?Core?6.0?基于模型驗證的數(shù)據(jù)驗證,本文描述的數(shù)據(jù)驗證方案,是基于官方的模型驗證(Model validation),需要的朋友可以參考下

1 前言

在程序中,需要進行數(shù)據(jù)驗證的場景經(jīng)常存在,且數(shù)據(jù)驗證是有必要的。前端進行數(shù)據(jù)驗證,主要是為了減少服務(wù)器請求壓力,和提高用戶體驗;后端進行數(shù)據(jù)驗證,主要是為了保證數(shù)據(jù)的正確性,保證系統(tǒng)的健壯性。

本文描述的數(shù)據(jù)驗證方案,是基于官方的模型驗證(Model validation),也是筆者近期面試過程中才得知的方式【之前個人混淆了:模型驗證(Model validation)和 EF 模型配置的數(shù)據(jù)注釋(Data annotation)方式】。

注:MVC 和 API 的模型驗證有些許差異,本文主要描述的是 API 下的模型驗證。

1.1 數(shù)據(jù)驗證的場景

比較傳統(tǒng)的驗證方式如下:

public string TraditionValidation(TestModel model)
{
    if (string.IsNullOrEmpty(model.Name))
    {
        return "名字不能為空!";
    }
    if (model.Name.Length > 10)
    {
        return "名字長度不能超過10!";
    }

    return "驗證通過!";
}

在函數(shù)中,對模型的各個屬性分別做驗證。

雖然函數(shù)能與模型配合重復(fù)使用,但是確實不夠優(yōu)雅。

官方提供了模型驗證(Model validation)的方式,下面將會基于這種方式,提出相應(yīng)的解決方案。

1.2 本文的脈絡(luò)

先大概介紹一下模型驗證(Model validation)的使用,隨后提出兩種自定義方案。

最后會大概解讀一下 AspNetCore 這一塊相關(guān)的源碼。

2 模型驗證

2.1 介紹

官方提供的模型驗證(Model validation)的方式,是通過在模型屬性上添加驗證特性(Validation attributes),配置驗證規(guī)則以及相應(yīng)的錯誤信息(ErrorMessage)。當驗證不通過時,將會返回驗證不通過的錯誤信息。

其中,除了內(nèi)置的驗證特性,用戶也可以自定義驗證特性(本文不展開),具體請自行查看自定義特性一節(jié)。

在 MVC 中,需要通過如下代碼來調(diào)用(在 action 中):

if (!ModelState.IsValid)
{
    return View(movie);
}

在 API 中,只要控制器擁有 [ApiController] 特性,如果模型驗證不通過,將自動返回包含錯誤信息的 HTTP400 相應(yīng),詳細請參閱自動 HTTP 400 響應(yīng)。

2.2 基本使用

(1)自定義模型

如下代碼中,[Required] 表示該屬性為必須,ErrorMessage = "" 為該驗證特性驗證不通過時,返回的驗證信息。

public class TestModel
{
    [Required(ErrorMessage = "名字不能為空!")]
    [StringLength(10, ErrorMessage = "名字長度不能超過10個字符!")]
    public string? Name { get; set; }

    [Phone(ErrorMessage = "手機格式錯誤!")]
    public string? Phone { get; set; }
}

(2)控制器代碼

控制器上有 [ApiController] 特性即可觸發(fā):

[ApiController]
[Route("[controller]/[action]")]
public class TestController : ControllerBase
{
    [HttpPost]
    public TestModel ModelValidation(TestModel model)
    {
        return model;
    }
}

(3)測試

輸入不合法的數(shù)據(jù),格式如下:

{
  "name": "string string",
  "email": "111"
}

輸出信息如下:

{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "traceId": "00-4d4df1b3618a97a6c50d5fe45884543d-81ac2a79523fd282-00",
  "errors": {
    "Name": [
      "名字長度不能超過10個字符!"
    ],
    "Email": [
      "郵箱格式錯誤!"
    ]
  }
}

2.3 內(nèi)置特性

官方列出的一些內(nèi)置特性如:

[ValidateNever]:指示屬性或參數(shù)應(yīng)從驗證中排除。

[CreditCard]:驗證屬性是否具有信用卡格式。

[Compare]:驗證模型中的兩個屬性是否匹配。

[EmailAddress]:驗證屬性是否具有電子郵件格式。

[Phone]:驗證屬性是否具有電話號碼格式。

[Range]:驗證屬性值是否在指定的范圍內(nèi)。

[RegularExpression]:驗證屬性值是否與指定的正則表達式匹配。

[Required]:驗證字段是否不為 null。

[StringLength]:驗證字符串屬性值是否不超過指定長度限制。

[URL]:驗證屬性是否具有 URL 格式。

[Remote]:通過在服務(wù)器上調(diào)用操作方法來驗證客戶端上的輸入。

可以在命名空間中找到 System.ComponentModel.DataAnnotations 驗證屬性的完整列表。

3 自定義數(shù)據(jù)驗證

3.1 介紹

由于官方模型驗證返回的格式與我們程序?qū)嶋H需要的格式有差異,所以這一部分主要是替換模型驗證的返回格式,使用的實際上還是模型驗證的能力。

3.2 前置準備

準備一個統(tǒng)一返回格式:

public class ApiResult
{
    public int Code { get; set; }
    public string? Msg { get; set; }
    public object? Data { get; set; }
}

當數(shù)據(jù)驗證不通過時:

Code 為 400,表示請求數(shù)據(jù)存在問題。

Msg 默認為:數(shù)據(jù)驗證不通過!用于前端提示。

Data 為錯誤信息明細,用于前端提示。

如:

{
  "code": 400,
  "msg": "數(shù)據(jù)驗證不通過!",
  "data": [
    "名字長度不能超過10個字符!",
    "郵箱格式錯誤!"
  ]
}

3.3 方案1:替換工廠

替換 ApiBehaviorOptions 中默認定義的 InvalidModelStateResponseFactory,在 Program.cs 中:

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = actionContext =>
    {
        //獲取驗證失敗的模型字段 
        var errors = actionContext.ModelState
            .Where(s => s.Value != null && s.Value.ValidationState == ModelValidationState.Invalid)
            .SelectMany(s => s.Value!.Errors.ToList())
            .Select(e => e.ErrorMessage)
            .ToList();

        // 統(tǒng)一返回格式
        var result = new ApiResult()
        {
            Code = StatusCodes.Status400BadRequest,
            Msg = "數(shù)據(jù)驗證不通過!",
            Data = errors
        };

        return new BadRequestObjectResult(result);
    };
});

3.4 方案2:自定義過濾器

(1)自定義過濾器

public class DataValidationFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // 如果其他過濾器已經(jīng)設(shè)置了結(jié)果,則跳過驗證
        if (context.Result != null) return;

        // 如果驗證通過,跳過后面的動作
        if (context.ModelState.IsValid) return;

        // 獲取失敗的驗證信息列表
        var errors = context.ModelState
            .Where(s => s.Value != null && s.Value.ValidationState == ModelValidationState.Invalid)
            .SelectMany(s => s.Value!.Errors.ToList())
            .Select(e => e.ErrorMessage)
            .ToArray();

        // 統(tǒng)一返回格式
        var result = new ApiResult()
        {
            Code = StatusCodes.Status400BadRequest,
            Msg = "數(shù)據(jù)驗證不通過!",
            Data = errors
        };

        // 設(shè)置結(jié)果
        context.Result = new BadRequestObjectResult(result);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
    }
}

(2)禁用默認過濾器

在 Program.cs 中:

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
    // 禁用默認模型驗證過濾器
    options.SuppressModelStateInvalidFilter = true;
});

(3)啟用自定義過濾器

在 Program.cs 中:

builder.Services.Configure<MvcOptions>(options =>
{
    // 全局添加自定義模型驗證過濾器
    options.Filters.Add<DataValidationFilter>();
});

3.5 測試

輸入不合法的數(shù)據(jù),格式如下:

{
  "name": "string string",
  "email": "111"
}

輸出信息如下:

{
  "code": 400,
  "msg": "數(shù)據(jù)驗證不通過!",
  "data": [
    "名字長度不能超過10個字符!",
    "郵箱格式錯誤!"
  ]
}

3.6 總結(jié)

兩種方案實際上都是差不多的(實際上都是基于過濾器 Filter 的),可以根據(jù)個人需要選擇。

其中 AspNetCore 默認實現(xiàn)的過濾器為 ModelStateInvalidFilter ,其 Order = -2000,可以根據(jù)程序?qū)嶋H情況,對程序內(nèi)的過濾器順序進行編排。

4 源碼解讀

4.1 基本介紹

AspNetCore 模型驗證這一塊相關(guān)的源碼,主要是通過注冊一個默認工廠 InvalidModelStateResponseFactory(由 ApiBehaviorOptionsSetupApiBehaviorOptions 進行配置,實際上是一個 Func),以及使用一個過濾器(為 ModelStateInvalidFilter,由 ModelStateInvalidFilterFactory 生成),來控制模型驗證以及返回結(jié)果(返回一個 BadRequestObjectResultObjectResult)。

其中,最主要的是 ApiBehaviorOptionsSuppressModelStateInvalidFilterInvalidModelStateResponseFactory 屬性。這兩個屬性,前者控制默認過濾器是否啟用,后者生成模型驗證的結(jié)果。

4.2 MvcServiceCollectionExtensions

新建的 WebAPI 模板的 Program.cs 中注冊控制器的語句如下:

builder.Services.AddControllers();

調(diào)用的是源碼中 MvcServiceCollectionExtensions.cs 的方法,摘出來如下:

// MvcServiceCollectionExtensions.cs 
public static IMvcBuilder AddControllers(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }

    var builder = AddControllersCore(services);
    return new MvcBuilder(builder.Services, builder.PartManager);
}

會調(diào)用另一個方法 AddControllersCore

// MvcServiceCollectionExtensions.cs 
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 (MetadataUpdater.IsSupported)
    {
        services.TryAddEnumerable(
            ServiceDescriptor.Singleton<IActionDescriptorChangeProvider, HotReloadService>());
    }

    return builder;
}

其中相關(guān)的是 AddMvcCore()

// MvcServiceCollectionExtensions.cs 
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);
    services.TryAddSingleton(partManager);

    ConfigureDefaultFeatureProviders(partManager);
    ConfigureDefaultServices(services);
    AddMvcCoreServices(services);

    var builder = new MvcCoreBuilder(services, partManager);

    return builder;
}

其中 AddMvcCoreServices(services) 方法會執(zhí)行如下方法,由于這個方法太長,這里將與模型驗證相關(guān)的一句代碼摘出來:

// MvcServiceCollectionExtensions.cs 
internal static void AddMvcCoreServices(IServiceCollection services)
{
    services.TryAddEnumerable(
    	ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>());
}

主要是配置默認的 ApiBehaviorOptions。

4.3 ApiBehaviorOptionsSetup

主要代碼如下:

internal class ApiBehaviorOptionsSetup : IConfigureOptions<ApiBehaviorOptions>
{
    private ProblemDetailsFactory? _problemDetailsFactory;

    public void Configure(ApiBehaviorOptions options)
    {
        options.InvalidModelStateResponseFactory = context =>
        {
            _problemDetailsFactory ??= context.HttpContext.RequestServices.GetRequiredService<ProblemDetailsFactory>();
            return ProblemDetailsInvalidModelStateResponse(_problemDetailsFactory, context);
        };

        ConfigureClientErrorMapping(options);
    }
}

為屬性 InvalidModelStateResponseFactory 配置一個默認工廠,這個工廠在執(zhí)行時,會做這些動作:

獲取 ProblemDetailsFactory (Singleton)服務(wù)實例,調(diào)用 ProblemDetailsInvalidModelStateResponse 獲取一個 IActionResult 作為響應(yīng)結(jié)果。

ProblemDetailsInvalidModelStateResponse 方法如下:

// ApiBehaviorOptionsSetup.cs 
internal static IActionResult ProblemDetailsInvalidModelStateResponse(ProblemDetailsFactory problemDetailsFactory, ActionContext context)
{
    var problemDetails = problemDetailsFactory.CreateValidationProblemDetails(context.HttpContext, context.ModelState);
    ObjectResult result;
    if (problemDetails.Status == 400)
    {
        // For compatibility with 2.x, continue producing BadRequestObjectResult instances if the status code is 400.
        result = new BadRequestObjectResult(problemDetails);
    }
    else
    {
        result = new ObjectResult(problemDetails)
        {
            StatusCode = problemDetails.Status,
        };
    }
    result.ContentTypes.Add("application/problem+json");
    result.ContentTypes.Add("application/problem+xml");

    return result;
}

該方法最終會返回一個 BadRequestObjectResultObjectResult。

4.4 ModelStateInvalidFilter

上面介紹完了 InvalidModelStateResponseFactory 的注冊,那么是何時調(diào)用這個工廠呢?

模型驗證默認的過濾器主要代碼如下:

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 (context.Result == null && !context.ModelState.IsValid)
        {
            _logger.ModelStateInvalidFilterExecuting();
            context.Result = _apiBehaviorOptions.InvalidModelStateResponseFactory(context);
        }
    }
}

可以看到,在 OnActionExecuting 中,當沒有其他過濾器設(shè)置結(jié)果(context.Result == null),且模型驗證不通過(!context.ModelState.IsValid)時,會調(diào)用 InvalidModelStateResponseFactory 工廠的驗證,獲取返回結(jié)果。

模型驗證最主要的源碼就如上所述。

4.5 其他補充

(1)過濾器的執(zhí)行順序

默認過濾器的 Order 為 -2000,其觸發(fā)時機一般是較早的(模型驗證也是要盡可能早)。

過濾器管道的執(zhí)行順序:Order 值越小,越先執(zhí)行 Executing 方法,越后執(zhí)行 Executed 方法(即先進后出)。

(2)默認過濾器的創(chuàng)建和注冊

這一部分個人沒有細看,套路大概是這樣的:通過過濾器提供者(DefaultFilterProvider),獲取實現(xiàn) IFilterFactory 接口的實例,調(diào)用 CreateInstance 方法生成過濾器,并將過濾器添加到過濾器容器中(IFilterContainer)。

其中模型驗證的默認過濾的工廠類為:ModelStateInvalidFilterFactory

5 示例代碼

本文示例的完整代碼,可以從這里獲取:

Gitee:https://gitee.com/lisheng741/testnetcore/tree/master/Filter/DataAnnotationTest

Github:https://github.com/lisheng741/testnetcore/tree/master/Filter/DataAnnotationTest

參考來源

AspNetCore源碼

手把手教你AspNetCore WebApi:數(shù)據(jù)驗證

ASP.NET Core 官方文檔>>高級>>模型驗證

到此這篇關(guān)于ASP.NET Core 6.0 基于模型驗證的數(shù)據(jù)驗證的文章就介紹到這了,更多相關(guān)ASP.NET Core模型驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論