1.模型繫結
ASP.NET Core MVC 中的模型繫結將資料從HTTP請求對映到操作方法引數。引數既可以是簡單型別,也可以是複雜型別。MVC 通過抽象繫結解決了這個問題。
2.使用模型繫結
當 MVC 收到一個HTTP 請求時,它會將其路由到一個控制器指定的操作方法。它基於路由資料來決定執行哪個操作,然後將值從HTTP請求繫結到操作方法的引數中,例如
http://afei.com/movies/edit/2
movies/edit/2 通過路由模板路由到 Movies 控制器的 Edit 方法,同時接收到一個可選引數 id 。URL 中的字串是不區分大小寫的。
MVC 將嘗試通過名稱將請求資料繫結到操作引數上。 MVC 將使用引數名稱和其公共可設定的屬性名稱查詢每個引數值。上面的例子,唯一的操作引數被命名為 id ,其中 MVC 繫結到路由值中具有相同名稱的值。除了 路由值, MVC 還繫結請求的各個部分的資料,並按照設定的順序這樣做。
模型繫結查詢資料來源的順序列表:
1. Form values : 通過 HTTP POST 請求傳送的表單資料(包括 jQuery POST 請求)。
2. Route values :由 routing 提供的路由資料集。
3. Query string : URL 的查詢字串的一部分。
表單值,路由資料以及查詢字串都是以鍵值對的形式儲存的。
因為模型繫結要找一個名為 id 的鍵,但是表單中沒有,所以接下來在路由資料中找尋。繫結發生時,該值轉換為整數型別的 2 。使用 Edit(string id)的同一請求轉換為字串 “2” .
如果Action 方法的引數是一個類,比如 Movies 型別,儘管這個類包含簡單型別和複雜型別的屬性,MVC 也可以模型繫結。它使用反射和遞迴遍歷複雜型別尋找匹配的屬性。模型繫結尋找 parameter_name.property_name 的模式去繫結值到屬性上。如果沒有從表單中找到匹配的值,則嘗試只通過 property_name 進行繫結。對於集合型別,模型繫結會去匹配 parameter_name[index] 或只是 [index] 。模型繫結對待字典型別也是一樣,前提是Key 是簡單型別。Key 支援匹配HTML 和 Tag Helpers 為相同的模型型別生成的欄位名。當建立或編輯的繫結資料未通過驗證時,回傳值使得使用者輸入的表單欄位仍然保留,方便使用者輸入。
為了發生繫結,類必須具有公共預設的建構函式,要繫結的成員必須是公共可寫的屬性,當繫結發生時,類只會使用公共預設的建構函式,然後設定屬性。
當一個引數被繫結後,模型繫結停止尋找具有該名稱的值,並繼續繫結下一個引數。如果繫結失敗,MVC 也不會丟擲異常,可以通過ModelState.IsValid 屬性來查詢模型狀態錯誤。
在執行繫結時,需要考慮一些特殊的資料型別:
IFormFile , IEnumerable<IFormFile> :作為 HTTP 請求一部分的一個或多個上傳的檔案。
CancelationToken : 用於取消一部控制器中的活動。
這些型別可以繫結到操作引數或類的屬性上。
一旦模型繫結完成,就會進行驗證。預設模型繫結適合絕大多數開發場景,同時也可以自定義內建的行為。
3.通過特性自定義模型繫結行為
MVC 包含集中可以指定與預設繫結源不同行為的特性。比如,可以通過使用 [BindRequired] 或 [BindNever] 特性指定一個屬性是否需要繫結,或者是否該發生。而且可以覆蓋預設資料來源,指定模型繫結器的資料來源。
[BindRequired] :如果不發生繫結,將新增模型狀態錯誤。
[BingNever] : 告訴模型繫結從不繫結到此引數。
[FromHeader] , [FromQuery] , [FromRoute] , [FromForm] : 使用這些來指定應用的確切繫結源。
[FromServices] : 此特性使用依賴注入來繫結服務的引數。
[FromBody] : 使用配置的格式化程式繫結請求主體中的資料,基於請求的內容型別選擇格式化器。
[ModelBinder] : 用於覆蓋預設模型繫結器,繫結源和名稱。
4.從請求主體繫結格式化的資料
HTTP 請求資料支援各種的格式,包括 JSON , XML 以及其他格式。當使用 [FromBody] 特性的時候,表示要從請求主體中繫結引數。MVC 使用一組配置的格式化程式來根據其內容型別處理請求資料。預設情況下,MVC 包含一個 JsonInputFormatter 類來處理JSON資料,當然也可以新增其他格式化程式來處理XML 和其他自定義格式。
每個操作最多可以有一個 [Frombody] 裝飾的引數。ASP.NET Core MVC 執行時將讀取請求流的職責委託給格式化程式。一旦讀取了引數的請求流,通常不可能再次讀取請求流以繫結其他 [FromBody] 引數。
ASP.NET 基於Content-Type 頭和引數的型別來選擇輸入格式化程式。如果想用 XML 或者其他格式,則必須在 Startup.cs 檔案中配置它,但首先使用 NuGet 來獲取 Microsoft.AspNetCore.Mvc.Formatters.Xml 引用。啟動程式碼如下:
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1) .AddXmlSerializerFormatters(); }
示例中,我們新增一個XML 格式化程式作為此MVC應用程式提供的服務。傳遞給 AddMvc 方法的 options引數允許在應用程式啟動時從MVC新增和管理過濾器,格式化程式和其他系統選項,然後應用各種特性到控制器類或方法上實現預期的效果。
5.模型驗證
在應用程式將資料儲存到資料庫之前,應用程式必須驗證資料。對於資料必須檢查其是否存在潛在的安全隱患,驗證型別和大小是否正確並且符合所定製的規則。儘管驗證的實現可能時冗餘且繁瑣的,但是必要的。在 MVC 中,驗證可以發生在客戶端和服務端。
.NET 已經將驗證抽象為驗證特性。這些特性包含驗證程式碼,從而減少必須編碼。
public class Movies { public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } [Required] [ClassicMovie(1996)] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Required] [StringLength(100)] public string Description { get; set; } [Required] [Range(0,999.99)] public decimal Price { get; set; } [Required] public Genre Genre { get; set; } public bool Preorder { get; set; } }
常見內建驗證屬性:
[CreditCard] : 驗證屬性是否為信用卡格式
[Compare] : 驗證模型中的兩個屬性是否匹配
[EmailAddress] : 驗證屬性是否為電子郵件格式
[Phone] : 驗證屬性是否為電話號碼格式
[Range] : 驗證屬性值是否在給定範圍內
[RegularExpression] : 驗證資料是否與指定的正規表示式匹配
[Required] : 必填的屬性
[StringLength] : 驗證字串屬性的最大長度
[Url] : 驗證屬性是否為網址格式
MVC 支援從 ValidationAttribute 派生的任何特性或者更改模型來實現 IAlidatableObject 即建立自定義驗證特性以用於驗證目的。許多內建的驗證特性可以在 ystem.ComponentModel.DataAnnotations 中找到。
有時候我們需要手動需要驗證模型,可以呼叫 TryValidateModel 方法來驗證,如 TryValidateModel (movie)。
模型狀態表示在HTML表單提交值的一系列驗證錯誤。MVC 將持續驗證欄位直到錯誤數達到最大值(預設200)。可以在 ConfigureServices 方法中配置這個最大值:
services.AddMvc(options => options.MaxModelValidationErrors = 500);
模型驗證發生在每個控制器的操作 被呼叫之前,而檢查 ModelState.IsValid 和做出適當的反應時操作方法的職責。許多情況下,適當的反應是返回某種錯誤響應,理想情況下則是詳細介紹模型驗證失敗的原因。
6.自定義驗證
實現自定義驗證的方法很簡單,只需要繼承 ValidationAttribute,並且重寫 IsValid 方法即可。IsValid 方法接受兩個引數,第一個是名為 value 的 object 物件,第二個物件是名為validationContext 的 ValidationContext 物件。value 指的是自定義驗證器驗證的欄位的值。
下面自定義[ClassicMovie] 特性,該特性檢查 Movies 類的 ReleaseDate 年份是否大於1960:
public class ClassicMovieAttribute:ValidationAttribute { private int _year; public ClassicMovieAttribute(int Year) { _year = Year; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { Movies movie = (Movies)validationContext.ObjectInstance; if (movie.ReleaseDate.Year > _year) { return new ValidationResult("釋出年份不能大於"+_year); } return ValidationResult.Success; } }
這個例子只對 Movies 型別有效,因為在 IsValid 方法中硬編碼了 Movies 型別。一個更好的選擇是 IValidationObject。
通過在 IValidatableObject 介面上實現 Validate 方法,可以將相同的程式碼放在模型中。雖然自定義驗證屬性是用於驗證單個屬性,但實現 IValidationObject 可用於實現類級別驗證;
public class Movies: IValidatableObject { private int _classicYear = 1960; public int Id { get; set; } [Required] [StringLength(100)] public string Title { get; set; } [Required] [DataType(DataType.Date)] public DateTime ReleaseDate { get; set; } [Required] [StringLength(100)] public string Description { get; set; } [Required] [Range(0,999.99)] public decimal Price { get; set; } public bool Preorder { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (ReleaseDate.Year > _classicYear) { yield return new ValidationResult("釋出年份不能大於" + _classicYear,new[] { "ReleaseDate" }); } } }
7.客戶端驗證
客戶端驗證帶來了極大便利,它可以節省時間,不必花費一個來回時間等待伺服器的驗證結果。
你必須引用 Javascript 指令碼來進行客戶端驗證;
jquery-x.x.x.min.js jquery.validate.min.js jquery.validate.unobtrusive.min.js
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.3.1.min.js" asp-fallback-src="~/lib/jquery/dist/jquery.min.js" asp-fallback-test="window.jQuery" crossorigin="anonymous" integrity="sha384-tsQFqpEReu7ZLhBV2VZlAu7zcOV+rXbYlF2cqB8txI/8aZajjp4Bqd+V6D5IgvKT"> </script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.17.0/jquery.validate.min.js" asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator" crossorigin="anonymous" integrity="sha384-rZfj/ogBloos6wzLGpPkkOr/gpkBNLZ6b6yLy4o+ok+t/SAKlL5mvXLr0OXNi1Hp"> </script> <script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.9/jquery.validate.unobtrusive.min.js" asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js" asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive" crossorigin="anonymous" integrity="sha384-ifv0TYDWxBHzvAk2Z0n8R434FL1Rlv/Av18DXE43N/1rvHyOG4izKst0f2iSLdds"> </script>
除了模型屬性的型別後設資料外,MVC 還用 Attribute 通過 Javascript 驗證資料並展示所有錯誤資訊。當使用 MVC 去渲染使用 Tag Helper 或者 HTML Helpers 的表單資料時,它將在需要驗證的表單元素中新增HTML 5 data- attributes ,如下面,MVC 對內建驗證 Attribute 和自定義驗證 Attribute 生成 data - 特性。可以通過 Tag Helper 在客戶端顯示驗證錯誤:
<div class="form-group"> <label asp-for="Id" class="control-label"></label> <input asp-for="Id" class="form-control" /> <span asp-validation-for="Id" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ReleaseDate" class="control-label"></label> <input asp-for="ReleaseDate" class="form-control" /> <span asp-validation-for="ReleaseDate" class="text-danger"></span> </div>
上面的 Tag Helper 渲染的 HTML 如下,注意輸出的 HTML 中,data-特性對應 Name 屬性的驗證 Attribute ,data-val-required 特性包含一個用於展示錯誤訊息,如果沒填寫 ReleaseDate 欄位,則錯誤訊息將隨著<span> 一起顯示:
<div class="form-group"> <label class="control-label" for="Id">Id</label> <input name="Id" class="form-control" id="Id" type="number" value="" data-val-required="The Id field is required." data-val="true"> <span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="Id"></span> </div> <div class="form-group"> <label class="control-label" for="ReleaseDate">ReleaseDate</label> <input name="ReleaseDate" class="form-control" id="ReleaseDate" data-val-required="The ReleaseDate field is required." data-val="true"
type="text" value=""> <span class="text-danger field-validation-valid" data-valmsg-replace="true" data-valmsg-for="ReleaseDate"></span> </div>
客戶端驗證防止表單提交直到有效為止。無論提交表單還是顯示錯誤資訊,提交按鈕都會執行 Javascript 程式碼。
MVC 基於 .NET 屬性的資料型別決定了型別特性值。可以使用 [DataType] 特性來覆蓋。基礎的 [DataType] 特性並不是真正的服務端驗證。瀏覽器選擇自己的錯誤資訊,並顯示,但 Jquery Validation Unobtrusive 包可以重寫訊息,並讓他們顯示方式一致。
8.遠端驗證
當需要在客戶端上使用伺服器上的資料進行驗證的時候,遠端驗證是一個很好的功能。比如,應用程式需要驗證一個使用者名稱是否已經被使用,並且需要查詢大量資料才能執行。下載大量資料驗證會佔用大量資源,也可能暴露敏感資訊。另一個辦法是使用回傳請求來驗證。
兩個步驟就可以實現遠端驗證,首先必須使用 [Remote] 特性註釋模型屬性。 [Remote] 特性接受多個過載,可以使用它將客戶端 Javascript 定向到要呼叫的相應程式碼。下面的程式碼時執行 Users 控制器的 VerfyUser 方法:
public class User { [Remote(action: "VerfyUser",controller:"Users")] public string Name { get; set; } }
然後在 VerfyUser 方法中實現方法,它返回一個 JsonResult :
public class UsersController : Controller { public IActionResult VerfyUser(string name) { if (!_usersRepository.VerfyUser(name)) { return Json(data:$"{name} 已存在"); } return Json(data:true); } }
現在,當使用者輸入名字時,檢視中的 JavaScript 會進行遠端呼叫進行驗證。