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