您好,登錄后才能下訂單哦!
本文小編為大家詳細(xì)介紹“ASP.NET Core 6.0基于模型驗(yàn)證的數(shù)據(jù)驗(yàn)證功能怎么實(shí)現(xiàn)”,內(nèi)容詳細(xì),步驟清晰,細(xì)節(jié)處理妥當(dāng),希望這篇“ASP.NET Core 6.0基于模型驗(yàn)證的數(shù)據(jù)驗(yàn)證功能怎么實(shí)現(xiàn)”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學(xué)習(xí)新知識吧。
在程序中,需要進(jìn)行數(shù)據(jù)驗(yàn)證的場景經(jīng)常存在,且數(shù)據(jù)驗(yàn)證是有必要的。前端進(jìn)行數(shù)據(jù)驗(yàn)證,主要是為了減少服務(wù)器請求壓力,和提高用戶體驗(yàn);后端進(jìn)行數(shù)據(jù)驗(yàn)證,主要是為了保證數(shù)據(jù)的正確性,保證系統(tǒng)的健壯性。
本文描述的數(shù)據(jù)驗(yàn)證方案,是基于官方的模型驗(yàn)證(Model validation),也是筆者近期面試過程中才得知的方式【之前個人混淆了:模型驗(yàn)證(Model validation)和 EF 模型配置的數(shù)據(jù)注釋(Data annotation)方式】。
注:MVC 和 API 的模型驗(yàn)證有些許差異,本文主要描述的是 API 下的模型驗(yàn)證。
比較傳統(tǒng)的驗(yàn)證方式如下:
public string TraditionValidation(TestModel model) { if (string.IsNullOrEmpty(model.Name)) { return "名字不能為空!"; } if (model.Name.Length > 10) { return "名字長度不能超過10!"; } return "驗(yàn)證通過!"; }
在函數(shù)中,對模型的各個屬性分別做驗(yàn)證。
雖然函數(shù)能與模型配合重復(fù)使用,但是確實(shí)不夠優(yōu)雅。
官方提供了模型驗(yàn)證(Model validation)的方式,下面將會基于這種方式,提出相應(yīng)的解決方案。
先大概介紹一下模型驗(yàn)證(Model validation)的使用,隨后提出兩種自定義方案。
最后會大概解讀一下 AspNetCore 這一塊相關(guān)的源碼。
官方提供的模型驗(yàn)證(Model validation)的方式,是通過在模型屬性上添加驗(yàn)證特性(Validation attributes),配置驗(yàn)證規(guī)則以及相應(yīng)的錯誤信息(ErrorMessage)。當(dāng)驗(yàn)證不通過時,將會返回驗(yàn)證不通過的錯誤信息。
其中,除了內(nèi)置的驗(yàn)證特性,用戶也可以自定義驗(yàn)證特性(本文不展開),具體請自行查看自定義特性一節(jié)。
在 MVC 中,需要通過如下代碼來調(diào)用(在 action 中):
if (!ModelState.IsValid) { return View(movie); }
在 API 中,只要控制器擁有 [ApiController] 特性,如果模型驗(yàn)證不通過,將自動返回包含錯誤信息的 HTTP400 相應(yīng),詳細(xì)請參閱自動 HTTP 400 響應(yīng)。
如下代碼中,[Required]
表示該屬性為必須,ErrorMessage = ""
為該驗(yàn)證特性驗(yàn)證不通過時,返回的驗(yàn)證信息。
public class TestModel { [Required(ErrorMessage = "名字不能為空!")] [StringLength(10, ErrorMessage = "名字長度不能超過10個字符!")] public string? Name { get; set; } [Phone(ErrorMessage = "手機(jī)格式錯誤!")] public string? Phone { get; set; } }
控制器上有 [ApiController]
特性即可觸發(fā):
[ApiController] [Route("[controller]/[action]")] public class TestController : ControllerBase { [HttpPost] public TestModel ModelValidation(TestModel model) { return model; } }
輸入不合法的數(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": [
"郵箱格式錯誤!"
]
}
}
官方列出的一些內(nèi)置特性如:
[ValidateNever]:指示屬性或參數(shù)應(yīng)從驗(yàn)證中排除。
[CreditCard]:驗(yàn)證屬性是否具有信用卡格式。
[Compare]:驗(yàn)證模型中的兩個屬性是否匹配。
[EmailAddress]:驗(yàn)證屬性是否具有電子郵件格式。
[Phone]:驗(yàn)證屬性是否具有電話號碼格式。
[Range]:驗(yàn)證屬性值是否在指定的范圍內(nèi)。
[RegularExpression]:驗(yàn)證屬性值是否與指定的正則表達(dá)式匹配。
[Required]:驗(yàn)證字段是否不為 null。
[StringLength]:驗(yàn)證字符串屬性值是否不超過指定長度限制。
[URL]:驗(yàn)證屬性是否具有 URL 格式。
[Remote]:通過在服務(wù)器上調(diào)用操作方法來驗(yàn)證客戶端上的輸入。
可以在命名空間中找到 System.ComponentModel.DataAnnotations 驗(yàn)證屬性的完整列表。
由于官方模型驗(yàn)證返回的格式與我們程序?qū)嶋H需要的格式有差異,所以這一部分主要是替換模型驗(yàn)證的返回格式,使用的實(shí)際上還是模型驗(yàn)證的能力。
準(zhǔn)備一個統(tǒng)一返回格式:
public class ApiResult { public int Code { get; set; } public string? Msg { get; set; } public object? Data { get; set; } }
當(dāng)數(shù)據(jù)驗(yàn)證不通過時:
Code 為 400,表示請求數(shù)據(jù)存在問題。
Msg 默認(rèn)為:數(shù)據(jù)驗(yàn)證不通過!用于前端提示。
Data 為錯誤信息明細(xì),用于前端提示。
如:
{ "code": 400, "msg": "數(shù)據(jù)驗(yàn)證不通過!", "data": [ "名字長度不能超過10個字符!", "郵箱格式錯誤!" ] }
替換 ApiBehaviorOptions
中默認(rèn)定義的 InvalidModelStateResponseFactory
,在 Program.cs 中:
builder.Services.Configure<ApiBehaviorOptions>(options => { options.InvalidModelStateResponseFactory = actionContext => { //獲取驗(yàn)證失敗的模型字段 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ù)驗(yàn)證不通過!", Data = errors }; return new BadRequestObjectResult(result); }; });
public class DataValidationFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // 如果其他過濾器已經(jīng)設(shè)置了結(jié)果,則跳過驗(yàn)證 if (context.Result != null) return; // 如果驗(yàn)證通過,跳過后面的動作 if (context.ModelState.IsValid) return; // 獲取失敗的驗(yàn)證信息列表 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ù)驗(yàn)證不通過!", Data = errors }; // 設(shè)置結(jié)果 context.Result = new BadRequestObjectResult(result); } public void OnActionExecuted(ActionExecutedContext context) { } }
在 Program.cs 中:
builder.Services.Configure<ApiBehaviorOptions>(options => { // 禁用默認(rèn)模型驗(yàn)證過濾器 options.SuppressModelStateInvalidFilter = true; });
在 Program.cs 中:
builder.Services.Configure<MvcOptions>(options => { // 全局添加自定義模型驗(yàn)證過濾器 options.Filters.Add<DataValidationFilter>(); });
輸入不合法的數(shù)據(jù),格式如下:
{ "name": "string string", "email": "111" }
輸出信息如下:
{
"code": 400,
"msg": "數(shù)據(jù)驗(yàn)證不通過!",
"data": [
"名字長度不能超過10個字符!",
"郵箱格式錯誤!"
]
}
兩種方案實(shí)際上都是差不多的(實(shí)際上都是基于過濾器 Filter 的),可以根據(jù)個人需要選擇。
其中 AspNetCore 默認(rèn)實(shí)現(xiàn)的過濾器為 ModelStateInvalidFilter
,其 Order = -2000,可以根據(jù)程序?qū)嶋H情況,對程序內(nèi)的過濾器順序進(jìn)行編排。
AspNetCore 模型驗(yàn)證這一塊相關(guān)的源碼,主要是通過注冊一個默認(rèn)工廠 InvalidModelStateResponseFactory
(由 ApiBehaviorOptionsSetup
對 ApiBehaviorOptions
進(jìn)行配置,實(shí)際上是一個 Func
),以及使用一個過濾器(為 ModelStateInvalidFilter
,由 ModelStateInvalidFilterFactory
生成),來控制模型驗(yàn)證以及返回結(jié)果(返回一個 BadRequestObjectResult
或 ObjectResult
)。
其中,最主要的是 ApiBehaviorOptions
的 SuppressModelStateInvalidFilter
和 InvalidModelStateResponseFactory
屬性。這兩個屬性,前者控制默認(rèn)過濾器是否啟用,后者生成模型驗(yàn)證的結(jié)果。
新建的 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í)行如下方法,由于這個方法太長,這里將與模型驗(yàn)證相關(guān)的一句代碼摘出來:
// MvcServiceCollectionExtensions.cs internal static void AddMvcCoreServices(IServiceCollection services) { services.TryAddEnumerable( ServiceDescriptor.Transient<IConfigureOptions<ApiBehaviorOptions>, ApiBehaviorOptionsSetup>()); }
主要是配置默認(rèn)的 ApiBehaviorOptions
。
主要代碼如下:
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
配置一個默認(rèn)工廠,這個工廠在執(zhí)行時,會做這些動作:
獲取 ProblemDetailsFactory
(Singleton)服務(wù)實(shí)例,調(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; }
該方法最終會返回一個 BadRequestObjectResult
或 ObjectResult
。
上面介紹完了 InvalidModelStateResponseFactory
的注冊,那么是何時調(diào)用這個工廠呢?
模型驗(yàn)證默認(rèn)的過濾器主要代碼如下:
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
中,當(dāng)沒有其他過濾器設(shè)置結(jié)果(context.Result == null
),且模型驗(yàn)證不通過(!context.ModelState.IsValid
)時,會調(diào)用 InvalidModelStateResponseFactory
工廠的驗(yàn)證,獲取返回結(jié)果。
模型驗(yàn)證最主要的源碼就如上所述。
(1)過濾器的執(zhí)行順序
默認(rèn)過濾器的 Order 為 -2000,其觸發(fā)時機(jī)一般是較早的(模型驗(yàn)證也是要盡可能早)。
過濾器管道的執(zhí)行順序:Order 值越小,越先執(zhí)行 Executing 方法,越后執(zhí)行 Executed 方法(即先進(jìn)后出)。
(2)默認(rèn)過濾器的創(chuàng)建和注冊
這一部分個人沒有細(xì)看,套路大概是這樣的:通過過濾器提供者(DefaultFilterProvider
),獲取實(shí)現(xiàn) IFilterFactory
接口的實(shí)例,調(diào)用 CreateInstance
方法生成過濾器,并將過濾器添加到過濾器容器中(IFilterContainer
)。
其中模型驗(yàn)證的默認(rèn)過濾的工廠類為:ModelStateInvalidFilterFactory
讀到這里,這篇“ASP.NET Core 6.0基于模型驗(yàn)證的數(shù)據(jù)驗(yàn)證功能怎么實(shí)現(xiàn)”文章已經(jīng)介紹完畢,想要掌握這篇文章的知識點(diǎn)還需要大家自己動手實(shí)踐使用過才能領(lǐng)會,如果想了解更多相關(guān)內(nèi)容的文章,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。