7 天玩转 ASP.NET MVC — 第 3 天
目錄
- 第 1 天
- 第 2 天
- 第 3 天
- 第 4 天
- 第 5 天
- 第 6 天
- 第 7 天
0. 前言
我們假定你在開始學習時已經(jīng)閱讀了前兩天的學習內(nèi)容。在第 2 天我們完成了關于顯示 Employees 列表的項目。
在第三天,我們將會通過介紹數(shù)據(jù)訪問層和數(shù)據(jù)入口將它升級到一個新的層次。
1. 數(shù)據(jù)訪問層
在真實場景的項目中,如果沒有 Database,那么這個項目是未完成的。在我們的項目中,我們還沒有談到數(shù)據(jù)庫。第三天的首個 Lab 將會學習數(shù)據(jù)庫和數(shù)據(jù)庫層。
這里我們將使用 SQL Server 和 Entity Framework 來創(chuàng)建 Database 和 Database 訪問層。
簡單來說,什么是 Entity Framework?
這是一個 ORM 工具。ORM 代表的是 Object Relational Mapping。即:對象關系映射。
在 RDBMS 領域中,我們所談論的 Tables 表格和 Columns 列的這些方面,在 .NET 領域(面向對象領域)中被稱為 Classes 類,對象和屬性。
當我們思考任何有關數(shù)據(jù)驅動應用的方式時,都可以得出以下兩種方式:
書寫代碼來和數(shù)據(jù)庫打交道(被稱為數(shù)據(jù)訪問層和數(shù)據(jù)庫邏輯)
書寫代碼來將數(shù)據(jù)庫數(shù)據(jù)映射到面向對象中,反之亦然。
ORM 是一個工具,可以自動做如上兩件事。Entity Framework 是微軟的 ORM 工具。
什么是 Code First 方法?
在 Entity Framework 中,我們可以使用如下三種的任意方法:
Database First 方法。創(chuàng)建一個有表,列和關系的數(shù)據(jù)庫。Entity Framework 將會生成對應的 Model 類(業(yè)務實體)和數(shù)據(jù)訪問層代碼。
Model First 方法。在這個方法中,Model 類和它們之間的聯(lián)系將會被 Model 設計者在 Visual Studio 中被手動定義。然后 Entity Framework 會自動創(chuàng)建數(shù)據(jù)訪問層和擁有表、列以及關系的數(shù)據(jù)庫。
Code First 方法。在這個方法中,手動創(chuàng)建 POCO 類。這些類中的關系將會被代碼所定義。當應用第一次執(zhí)行時,Entity Framework 將會自動在數(shù)據(jù)庫服務器上創(chuàng)建數(shù)據(jù)訪問層和擁有表、列以及關系的數(shù)據(jù)庫。
什么是 POCO 類?
POCO 代表的是「Plain Old CLR Objects」。POCO 類代表的是我們所創(chuàng)建的簡單 .NET 類。在我們之前的例子中, Employee 類是一個簡單的 POCO。
2. Lab 8 — 向項目中添加數(shù)據(jù)訪問層
第一步:創(chuàng)建數(shù)據(jù)庫
連接 SQL Server 然后創(chuàng)建一個新的數(shù)據(jù)庫,命名為「SalesERPDB」。
第二步:創(chuàng)建 ConnectionString
打開 Web.config 文件,然后在 Configuration 區(qū)域內(nèi)添加如下片段:
<connectionStrings> <add connectionString="Data Source=(local);Initial Catalog=SalesERPDB;Integrated Security=True"name="SalesERPDAL" providerName="System.Data.SqlClient"/> </connectionStrings>第三步:添加 Entity Framework 引用
右擊項目-> 管理 Nuget 包。搜索 Entity Framework,然后點擊安裝。
第四步:創(chuàng)建數(shù)據(jù)訪問層
在根目錄下創(chuàng)建一個新文件夾,命名為「DataAccessLayer」,然后在里面創(chuàng)建一個新的類,命名為「SalesERPDAL」。
在類頂部寫引用聲明如下
using System.Data.Entity;-
繼承 DbContext 的類「SalesERPDAL」
public class SalesERPDAL: DbContext
{
}
第五步:為 Employee 類創(chuàng)建主鍵
打開 Employee 類并在類頂部聲明如下:
using System.ComponentModel.DataAnnotations;在 Employee 類中添加 EmployeeId 屬性,然后將其標注為 Key 屬性。
public class Employee {[Key]public int EmployeeId { get; set; }public string FirstName { get; set; }public string LastName { get; set; }public int Salary { get; set; } }第六步:定義映射
在「SalesERPDAL」類中添加如下聲明語句:
using WebApplication1.Models;在 SalesERPDAL 類中重寫 OnModelCreating 方法。
protected override void OnModelCreating(DbModelBuilder modelBuilder) {modelBuilder.Entity<employee>().ToTable("TblEmployee");base.OnModelCreating(modelBuilder); }注意:上述代碼中的片段「TblEmployee」代表的是表名。在運行時講自動被創(chuàng)建。
第七步:在數(shù)據(jù)庫中創(chuàng)建 Employees 屬性
在「SalesERPDAL」類中創(chuàng)建一個新屬性,命名為 Employee,如下所示:
public DbSet<employee> Employees{get;set;}DbSet 將會展示所有可以在數(shù)據(jù)庫中查詢到的 Employees。
第八步:改變業(yè)務層代碼,從數(shù)據(jù)庫中讀取數(shù)據(jù)
打開 EmployeeBusinessLayer 類,在頂部加上聲明如下:
using WebApplication1.DataAccessLayer;現(xiàn)在改變 GetEmployees 方法如下:
public List<employee> GetEmployees() {SalesERPDAL salesDal = new SalesERPDAL();return salesDal.Employees.ToList(); }第九步:執(zhí)行并測試
按下 F5,并執(zhí)行應用。
現(xiàn)在的數(shù)據(jù)庫中,我們沒有任何的 Employees,所以我們看見的是一個空白的 Grid。
查看數(shù)據(jù)庫,現(xiàn)在我們可以在 TblEmployee 表中看到所有的列。
第十步:插入測試數(shù)據(jù)
向 TblEmployee 表中插入一些測試數(shù)據(jù)。
第十一步:執(zhí)行并測試應用
按下 F5 并再次運行應用。
Lab 8 的 Q&A
什么是 DbSet?
DbSet 簡單地表示了可以從數(shù)據(jù)庫中查詢到的實體集合。當我們再次寫一個 Linq 查詢時,DbSet 對象會對查詢進行內(nèi)存轉換,然后觸發(fā)數(shù)據(jù)庫。
在我們的例子中,「Employee」是一個 DbSet,它承載了所有可以從數(shù)據(jù)庫中查詢到的 Employee 實體對象。每一次我們嘗試訪問「Employees」時,它都將從“TblEmployee”表中獲取記錄,然后將其轉換為「Employees」對象并返回集合。
數(shù)據(jù)庫連接串和數(shù)據(jù)訪問層是如何連接的?
Mapping 通過名稱來實現(xiàn)。在我們的例子中,ConnectionString 名稱和數(shù)據(jù)訪問層類的名稱是一樣的,即「SalesERPDAL」,因此它們是自動映射的。
我們可以更改 ConnectionString 的名稱嗎?
答案是肯定的。在這個例子中,我們需要在數(shù)據(jù)訪問層類中定義一個構造函數(shù)如下:
public SalesERPDAL():base("NewName") { }3. 組織所有
我們需要做幾個改變,使得所有是有組織和有意義的。
第一步:重命名
「TestController」換名為 「EmployeeController」。
GetView 行為方法改為 Index。
Test 文件夾(Views 文件夾下) 改為 Employee
「MyView」視圖改為「Index」。
第二步:從 EmployeeListViewModel 中刪除 UserName 屬性
第三步:從視圖中刪除 UserName
打開 View/Employee.Index.cshtml 視圖,然后從中刪除 UserName。
簡單來說,就是刪除如下代碼:
Hello @Model.UserName <hr />第四步:在 EmployeeController 中更改 Index 行為方法
更改 EmployeeController 中的 Index 行為方法如下:
public ActionResult Index() {………………employeeListViewModel.Employees = empViewModels;//employeeListViewModel.UserName = "Admin";-->Remove this line -->Change1return View("Index", employeeListViewModel);//-->Change View Name -->Change 2 }現(xiàn)在執(zhí)行的 URL 將會為:「…/Employee/Index」。
4. Lab 9 — 創(chuàng)建 Data Entry Screen
第一步:創(chuàng)建 Action 方法
在 EmployeeController 中創(chuàng)建一個 Action 方法,命名為「AddNew」,如下:
public ActionResult AddNew() {return View("CreateEmployee"); }第二步:創(chuàng)建 View
在文件夾 View/Employee 下創(chuàng)建一個 View,命名為「CreateEmployee」。代碼如下:
@{Layout = null; } <!DOCTYPE html> <html><head><meta name="viewport" content="width=device-width" /><title>CreateEmployee</title></head><body><div><form action="/Employee/SaveEmployee" method="post">First Name: <input type="text" id="TxtFName" name="FirstName" value="" /><br />Last Name: <input type="text" id="TxtLName" name="LastName" value="" /><br />Salary: <input type="text" id="TxtSalary" name="Salary" value="" /><br /><input type="submit" name="BtnSave" value="Save Employee" /><input type="button" name="BtnReset" value="Reset" /></form></div></body> </html>第三步:在 Index 視圖中創(chuàng)建一個鏈接
打開 Index.cshtml,然后增加一個超鏈接指向 AddNew 行為的URL。
<ahref="/Employee/AddNew">Add New</a>第四步:執(zhí)行并測試應用
按下 F5 并執(zhí)行應用。
Lab 9 的 Q&A
Form 標簽的目的是什么?
在第一天的系列學習中,我們已經(jīng)明白了「Web 世界不會遵循事件驅動編程模型。它遵循的是請求響應模型。終端用戶發(fā)出請求,然后服務器給出響應。」Form 標簽是 HTML 中做出響應的其中一種方式。只要標簽里的提交按鈕被點擊,一個請求就將發(fā)送給動作屬性中指定的 URL 中。
Form 標簽中的方法屬性是什么?
它決定了請求的類型。請求也許是如下的其中一種:get、post、put 或者是 delete。
Get:當我們想獲取什么數(shù)據(jù)時
Post:當我們想創(chuàng)建什么數(shù)據(jù)時
Put:當我們想更新什么數(shù)據(jù)時
Delete:當我們想刪除什么數(shù)據(jù)時
運用 Form 標簽和通過瀏覽器地址欄或者超鏈接來發(fā)出請求,有何區(qū)別?
當我們使用 Form 標簽來發(fā)送請求時,所有輸入控件中的值都會伴隨著請求一起被處理。
Checkbox、Radio 按鈕和 Dropdowns 控件中的值也會被發(fā)送嗎?
答案是肯定的。所有輸入控件(輸入類型為 Text,Radio,Checkbox)以及 Dropdowns(表示的是被選中的元素)都會被發(fā)送。
輸入的值如何發(fā)送給服務器?
當請求的類型是 Get、Put 或者 Delete 時,輸入的值會以查詢字符串參數(shù)的方式發(fā)送。
當請求的類型是 Post 時,輸入的值會以 Post 數(shù)據(jù)發(fā)送。
輸入控件中的 Name 屬性的目的是什么?
就像我們之前所談論的,當按鈕被點擊時,輸入控件中的值將會隨著請求一起被發(fā)送。這會使得服務器在這個時刻接收到多于一個的值。為了在發(fā)送值的時候,單獨區(qū)別每一個值,就會為它們附加上一個 Key,這個 Key 就是「Name」屬性。
Name 和 Id 屬性的目的是否是相同的?
答案是否定的。就像剛才的問題所說,當請求發(fā)生時,「Name」屬性被 HTML 所使用,而「Id」屬性被開發(fā)者所使用,為一些 JavaScript 實現(xiàn)一些動態(tài)功能。
「input type = submit」 和 「input type = button」有什么區(qū)別?
當我向服務器發(fā)送請求時,Submit 按鈕會被特殊用到。而一個簡單的按鈕是用來處理一些客戶端的行為的。簡單的按鈕不會自己做一些事情。
5. Lab 10 — 在服務器/Controller 獲取 Posted 數(shù)據(jù)
第一步:創(chuàng)建 SaveEmployee 行為方法
在 Employee 控制器中創(chuàng)建一個行為方法,命名為 SaveEmployee,代碼如下:
public string SaveEmployee(Employee e) {return e.FirstName + "|"+ e.LastName+"|"+e.Salary; }第二步:執(zhí)行并測試
按下 F5 并執(zhí)行應用。
Lab 10 的 Q&A
在 Action 方法里,Textbox 的值是如何更新 Employee 對象的?
在 ASP.NET MVC 中,存有一個概念,叫做 Model Binder。
無論何時一個包含參數(shù)的請求向 Action 方法發(fā)送時,Model Binder 都會自動執(zhí)行。
Model Binder 將會遍歷方法的所有原始參數(shù),然后將它們與發(fā)送過來的數(shù)據(jù)的參數(shù)的名稱相對比。(發(fā)送過來的數(shù)據(jù)意味著要么是 Posted 數(shù)據(jù),或者是查詢字符串)。當匹配成功時,會依照發(fā)送過來的數(shù)據(jù)分配給參數(shù)。
當 Model Binder 遍歷完每一個類參數(shù)中的每一個屬性后,然后和發(fā)送過來的數(shù)據(jù)做對比。當匹配成功后,依照發(fā)送過來的數(shù)據(jù)分配給參數(shù)。
當兩個參數(shù)是特指的,將會發(fā)生什么?例如第一個是「Employee」,第二個是「FirstName」?
FirstName 將會在初始的變量 FirstName 中更新,也會在 e.FirstName 屬性中更新。
ModelBinder 可以和組合關系一起運用嗎?
答案是肯定的。但是在這個情形下控件的名稱應該被給出。例如:
Customer 和 Address 類的代碼如下:
public class Customer {public string FName{get;set;}public Address address{get;set;} } public class Address {public string CityName{get;set;}public string StateName{get;set;} }在這種情形下,HTML 如下:
... ... ... <input type="text" name="FName"> <input type="text" name="address.CityName"> <input type="text" name="address.StateName"> ... ... ...6. Lab 11 — 重置和取消按鈕
第一步:開始重置和取消按鈕
增加一個重置和取消按鈕,代碼如下:
... ... ... <input type="submit" name="BtnSubmit” value="Save Employee" /><input type="button" name="BtnReset" value="Reset" onclick="ResetForm();" /><input type="submit" name="BtnSubmit" value="Cancel" />注意:保存和取消按鈕都有相同的「Name」屬性,即「BtnSubmit」。
第二步:定義 ResetForm 方法
在 HTML 頂部區(qū)域增加一個 Script 標簽,用于創(chuàng)建一個 JavaScript 方法,命名為 ResetForm。代碼如下:
<script>function ResetForm() {document.getElementById('TxtFName').value = "";document.getElementById('TxtLName').value = "";document.getElementById('TxtSalary').value = "";} </script>第三步:在 EmployeeController 的 SaveEmployee 行為方法中實現(xiàn)取消點擊事件。
改變 SaveEmployee 行為方法如下:
public ActionResult SaveEmployee(Employee e, string BtnSubmit) {switch (BtnSubmit){case "Save Employee":return Content(e.FirstName + "|" + e.LastName + "|" + e.Salary);case "Cancel":return RedirectToAction("Index");}return new EmptyResult(); }第四步:執(zhí)行應用
按下 F5 并執(zhí)行應用。通過點擊“Add New”鏈接導航到 AddNew 屏幕。
第五步:測試重置功能
第六步:測試 Save 和 Cancel 功能
Lab 11 的 Q&A
為什么保存和取消按鈕的名稱是一樣的?
我們知道,一旦提交按鈕被點擊,一個請求就會被發(fā)送到服務器端。并且伴隨著請求,所有輸入控件的值也一起被發(fā)送。
Submit 按鈕也是一個輸入按鈕。因為按鈕的值也會被發(fā)送。
當保存按鈕被點擊時,保存按鈕的值,即「Save Employee」將會隨著請求一起被發(fā)送。當取消按鈕被點擊時,取消按鈕的值,即「Cancel」將會隨著請求一起被發(fā)送。
在 Action 方法中。Model Binder 將會處理這些工作。它將會依照輸入的值(伴隨著請求)更新參數(shù)的值。
實現(xiàn)多個提交按鈕的方式是什么?
這里有多個方式。我介紹其中三種。
- 隱藏 Form 元素
第一步:在視圖中創(chuàng)建一個隱藏 Form 元素
<form action="/Employee/CancelSave" id="CancelForm" method="get" style="display:none"> </form>第二步:改變 Submit 按鈕為一個常規(guī)按鈕,并且通過 JavaScript 將上面的 Form 發(fā)送
<input type="button" name="BtnSubmit" value="Cancel" onclick="document.getElementById('CancelForm').submit()" />-
運用 JavaScript 動態(tài)地改變動作 URL
... ... <input type="submit" name="BtnSubmit" value="Save Employee" οnclick="document.getElementById('EmployeeForm').action = '/Employee/SaveEmployee'" /> ... <input type="submit" name="BtnSubmit" value="Cancel" οnclick="document.getElementById('EmployeeForm').action = '/Employee/CancelSave'" /> Ajax
不再運用 Submit 按鈕,取而代之的是簡單的輸入按鈕,然后運用 JQuery 或者其它庫來實現(xiàn)純 Ajxa 請求。
為什么我們在實現(xiàn)重置功能時,沒使用「input type = reset」?
「input type = reset」控件不會清除值,它只是將控件的值改為默認的值。例如:
<input type="text" name="FName" value="Sukesh">在這個例子中,控件的默認值是「Sukesh」。
如果我們運用「input type = reset」來實現(xiàn)重置功能,那么每一次點擊重置按鈕,默認的值「Sukesh」將會被設置到 Textbox 中。
當名稱沒有與類中的屬性名稱匹配時,會怎樣?
這是一個在面試中經(jīng)常被問到的常規(guī)問題。
例如我們有一段 HTML 代碼如下:
First Name: <input type="text" id="TxtFName" name="FName" value="" /><br /> Last Name: <input type="text" id="TxtLName" name="LName" value="" /><br /> Salary: <input type="text" id="TxtSalary" name="Salary" value="" /><br />現(xiàn)在我們的 Model 類包含的屬性名稱有 FirstName,LastName 和 Salary。因此默認的 Model Binder 將不會在這里處理。
在這種情形下,我們有三種解決方案:
-
在 Action 方法內(nèi)部,運用 Request.Form 語法來檢索發(fā)送過來的值,然后手動構造 Model 對象如下:
public ActionResult SaveEmployee()
{
Employee e = new Employee();
e.FirstName = Request.Form["FName"];
e.LastName = Request.Form["LName"];
e.Salary = int.Parse(Request.Form["Salary"])
...
...
} -
運用參數(shù)名稱,然后手動創(chuàng)建 Model 對象如下:
public ActionResult SaveEmployee(string FName, string LName, int Salary)
{
Employee e = new Employee();
e.FirstName = FName;
e.LastName = LName;
e.Salary = Salary;
...
...
} 創(chuàng)建自定義的 Model Binder 來替換默認的 Model Binder。
第一步:創(chuàng)建自定義的 Model Binder
public class MyEmployeeModelBinder : DefaultModelBinder {protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType){Employee e = new Employee();e.FirstName = controllerContext.RequestContext.HttpContext.Request.Form["FName"];e.LastName = controllerContext.RequestContext.HttpContext.Request.Form["LName"];e.Salary = int.Parse(controllerContext.RequestContext.HttpContext.Request.Form["Salary"]);return e;} }第二步:用這個新的 Model Binder 來替換默認的 Model Binder
public ActionResult SaveEmployee([ModelBinder(typeof(MyEmployeeModelBinder))]Employee e, string BtnSubmit) {...... }RedirectToFunction 函數(shù)是做什么的?
它用來產(chǎn)生 RedirectToRouteResult,就像 ViewResult 和 ContentResult一樣(在第一天學習中探討)。RedirectToRouteResult 是 ActionResult 的子類,它代表的是間接的響應。當瀏覽器接到 RedirectToRouteResult,它就會產(chǎn)生新的請求到一個新的行為方法。
注:這里瀏覽器對新的請求是有責任的,因此 URL 將會更新到一個新的 URL。
什么是 EmptyResult?
它是 ActionResult 的其中一個子類。當瀏覽器接到的響應是 EmptyResult 時,它將會簡單地呈現(xiàn)一個空白屏幕。它簡單地代表「No Result」。
在我們的例子中,這種情形不會發(fā)生。只要確保所有的代碼路徑返回的值。
注:當 Action 方法返回的值是空的,結果等同于 EmptyResult。
7. Lab 12 — 在數(shù)據(jù)庫中保存記錄并更新 Grid
第一步:在 EmployeeBusinessLayer 中創(chuàng)建 SaveEmployee
public Employee SaveEmployee(Employee e) {SalesERPDAL salesDal = new SalesERPDAL();salesDal.Employees.Add(e);salesDal.SaveChanges();return e; }第二步:改變 SaveEmployee 行為方法
在 EmployeeController 中,改變 SaveEmployee 行為方法,代碼如下:
public ActionResult SaveEmployee(Employee e, string BtnSubmit) {switch (BtnSubmit){case "Save Employee":EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();empBal.SaveEmployee(e);return RedirectToAction("Index");case "Cancel":return RedirectToAction("Index");}return new EmptyResult(); }第三步:執(zhí)行并測試
按下 F5 并執(zhí)行應用。導航到 Data 入口屏幕并輸入一些合法的值。
8. Lab 13 — 增加服務器端認證
在 Lab 10 中,我們已經(jīng)了解了 Model Binder 的基本功能。現(xiàn)在我們來更多地了解下。
Model Binder 通過發(fā)送過來的數(shù)據(jù)來更新 Employee 對象。
但是這個不是 Model Binder 的唯一功能。Model Binder 還更新 ModelState。
它有一個屬性,稱為 IsValid,這個決定了 Model(即 Employee 對象)是否更新成功了。如果服務器端的認證失敗了,Model 將不會更新。
它承載了認證錯誤。例如:ModelState["FirstName"].Errors 包含了與 First Name 相關的錯誤。
它承載了發(fā)送過來的數(shù)據(jù)值。(Posted 數(shù)據(jù)或者是查詢字符串數(shù)據(jù))
在 ASP.NET MVC 中,我們運用 DataAnnotations 來實現(xiàn)服務器端的認證。
在了解 Data Annotation 之前,我們先了解一些 Model Binder。
ModelBinder 如何處理初始數(shù)據(jù)類型?
當 Action 方法包含初始類型參數(shù)時,Model Binder 將會把參數(shù)的名稱與發(fā)送過來的數(shù)據(jù)進行對比。(發(fā)送過來的數(shù)據(jù)是 Posted 數(shù)據(jù)或者是查詢字符串)
當匹配成功時,將會依照發(fā)送過來的數(shù)據(jù)分配給參數(shù)。
當匹配失敗時,參數(shù)將會被分配給默認值。(對于整型的默認值是0,對于字符串的默認值是 null)
當數(shù)據(jù)類型不匹配的異常被拋出時,這種情況下不會進行分配操作。
Model Binder 如何處理類?
當參數(shù)是一個類參數(shù),Model Binder 將會遍歷所有類的所有屬性,并且將每一個屬性名稱與發(fā)送過來的數(shù)據(jù)做對比。
- 當匹配成功時,如果發(fā)送過來的數(shù)據(jù)是空的。
Null 值將會被分配給屬性。如果不能分配,默認的值將會被設置,并且 ModelState.IsValid 將會被設置為 false。
當 Null 值可以分配給屬性時,這將會被視作不合法的值,因為屬性附上了認證,因此 ModelState.IsValid 將會被設置為 false。
- 當匹配成功時,發(fā)送過來的數(shù)據(jù)不為空。
當數(shù)據(jù)類型不匹配時,將不會分配值。或者服務器端的認證失敗,將分配 Null 值。此時 ModelState.IsValid 將會被設置為 false。如果不能分配 Null 值,默認的值將會被設置。
- 當匹配不成功時,參數(shù)將會被分配為默認值。(對于整型的默認值是0,對于字符串的默認值是 null)。在這種情形下,ModelState.IsValid 將不會受到影響。
現(xiàn)在讓我們了解一下如何向項目中增加認證功能。
第一步:運用 DataAnnotations 裝飾屬性。
在 Model 文件夾下打開 Employee 類,運用 DataAnnotation 來裝飾 FirstName 和 LastName,代碼如下:
public class Employee { ... ...[Required(ErrorMessage="Enter First Name")]public string FirstName { get; set; }[StringLength(5,ErrorMessage="Last Name length should not be greater than 5")]public string LastName { get; set; } ... ... }第二步:改變 SaveEmployee 行為方法。
打開 EmployeeController,改變 SaveEmloyee 行為方法如下:
public ActionResult SaveEmployee(Employee e, string BtnSubmit) {switch (BtnSubmit){case "Save Employee":if (ModelState.IsValid){EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();empBal.SaveEmployee(e);return RedirectToAction("Index");}else{return View("CreateEmployee ");}case "Cancel":return RedirectToAction("Index");}return new EmptyResult(); }注:正如你所看見的,當 SaveEmployee 按鈕點擊后, ModelState.IsValid 失敗,ViewResult 指向「CreateEmloyee」視圖。
第三步:在視圖中呈現(xiàn)錯誤
改變「Views/Index/CreateEmployee.cshtml」中的代碼如下。
這次我們將會運用「Table」標簽來格式化一下 UI。
<table><tr><td>First Name:</td><td><input type="text" id="TxtFName" name="FirstName" value="" /></td></tr><tr><td colspan="2" align="right">@Html.ValidationMessage("FirstName")</td></tr><tr><td>Last Name:</td><td><input type="text" id="TxtLName" name="LastName" value="" /></td></tr><tr><td colspan="2" align="right">@Html.ValidationMessage("LastName")</td></tr><tr><td>Salary:</td><td><input type="text" id="TxtSalary" name="Salary" value="" /></td></tr><tr><td colspan="2" align="right">@Html.ValidationMessage("Salary")</td></tr><tr><td colspan="2"><input type="submit" name="BtnSubmit" value="Save Employee" /><input type="submit" name="BtnSubmit" value="Cancel" /><input type="button" name="BtnReset" value="Reset" onclick="ResetForm();" /></td></tr> </table>第四步:執(zhí)行并測試
按下 F5 并執(zhí)行應用。導航到「Employee/AddNew」行為方法,并測試應用。
- Test 1
- Test 2
注:你也許會遇到如下錯誤。
「The model backing the 'SalesERPDAL' context has changed since the database was created. Consider using Code First Migrations to update the database.」
為了解決這個錯誤,僅僅在 Global.asax 文件中的 Application_Start 中添加如下聲明:
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<SalesERPDAL>());Database 類在命名空間 System.Data.Entity 中。
Lab 13 的 Q&A
@Html.ValidationMessage 是做什么事情的?
@ 意味著是 Razor 代碼。
Html 是 視圖中的 HtmlHelper 類的實例。
ValidationMessage 是 HtmlHelper 類的方法,用于呈現(xiàn)錯誤信息。
ValidationMessage 函數(shù)如何工作的?
ValidationMessage 是一個函數(shù)。它在運行時執(zhí)行。就像我們之前所探討的,ModelBinder 更新 ModelState。ValidationMessage 根據(jù) Key 值來從 ModelState 中獲取錯誤信息并呈現(xiàn)。
例如:ValidationMessage("FirstName") 呈現(xiàn)有關 First Name 的錯誤信息。
我們還有其它類似于 Required 和 StringLength 的屬性嗎?
答案是肯定的。如下所示:
DataType。確保數(shù)據(jù)是指定的類型,例如郵箱、信用卡號、URL 等。
EnumDataTypeAttribute。確保在 Enumeration 中存在該值。
Range Attribute。確保值在一個指定的區(qū)域內(nèi)。
Regular。認證值是否是指定的表達式。
Required。確保值是存在的。
StringLength。認證字符串中的最大和最小字符長度。
Salary 是如何認證的?
我們并沒有向 Salary 屬性添加 Data Annotation,但是它依然得到了認證。原因是這樣的,在更新模型的時候,Model Binder 依然考慮到了屬性的數(shù)據(jù)類型。
在 Test 1 中,我們保持 Salary 為一個空字符串。在這種情形下,因為我們有 Model Binder,ModelState.IsValid 將會為失敗的并且 ModelState 將會承載與 Salary 相關的錯誤認證信息,這些信息將會通過 Html.ValidationMessage("Salary") 被顯示在 View 中。
在 Test 2 中,Salary 數(shù)據(jù)類型匹配失敗,因此認證也是失敗的。
這意味著,默認情況下,整型屬性將會被強制?
答案是肯定的。不僅僅是整型,所有的值類型都會被強制,因為它們不能為 Null 值。
如果我們想有一個非 Required 整型域該如何?
把它設置為 Nullable?
public int? Salary{get;set;}如何特定為 Salary 改變認證信息?
默認情況下,認證是支持 Salary的(因為它是整數(shù)類型),不會允許我們改變認證信息。我們可以通過 Regular 表達式、Range 或者是 Custom Validator來同樣達到這個目的。
為什么當認證失敗時,值會被清空?
因為這是一個新的請求。數(shù)據(jù)入口視圖將會在開始被呈現(xiàn),并且在呈現(xiàn)之后和發(fā)展視圖是一樣的,但是和請求視圖是不一樣的。我們將會在第四天的學習中來學習如何保持值不變。
我們可以明確地讓 Model Binder 來執(zhí)行嗎?
答案是肯定的。只需要簡單地從 Action 方法中移走參數(shù)。默認情況下,它將會停止運行中的默認 Model Binder。
在這種情形下,我們可以運用 UpdateModel 函數(shù)如下:
Employee e = new Employee(); UpdateModel<employee>(e);注:UpdateModel 不會處理原始數(shù)據(jù)類型。
UpdateModel 方法 和 TryUpdateModel 方法有什么區(qū)別?
TryUpdateModel 和 UpdateModel 是一樣的,除了一個附加的優(yōu)勢。
當 Model 由于任意原因適配失敗時,UpdateModel 將會拋出異常。這種情況下,ModelState.IsValid 函數(shù)將不會有任何左右。
TryUpdateModel 是將 Employee 對象和函數(shù)參數(shù)保持精確地一致。如果更新失敗了,ModelState.IsValid 將會為 False。
客戶端的認證是如何的?
這個可以被手動完成,或者我們可以運用 HTML Helper 類。
我們將會在第四天的學習中探討這兩種手動的客戶端認證方式,以及運用 HTML Helper 來自動客戶端認證。
我們可以為一個屬性附加上多個 DataAnnotation 嗎?
答案是肯定的。在這種情形下,多個認證都會被觸發(fā)。
9. 自定義服務器端認證
第一步:創(chuàng)建自定義認證
創(chuàng)建一個新的類,叫做 FirstNameValidation。
public class FirstNameValidation:ValidationAttribute {protected override ValidationResult IsValid(object value, ValidationContext validationContext){if (value == null) // Checking for Empty Value{return new ValidationResult("Please Provide First Name");}else{if (value.ToString().Contains("@")){return new ValidationResult("First Name should contain @");}}return ValidationResult.Success;} }第二步:向 First Name 中附加認證
打開 Employee 類,然后將 FirstName 屬性的默認的「Required」屬性移走,附加上 FirstNameValidation。
[FirstNameValidation] public string FirstName { get; set; }第三步:執(zhí)行并測試
按下 F5,導航到「Employee/AddNew」動作。
- Test 1
- Test 2
10. 總結
這里我們完成了第三天的學習。在第四天學習中,我們將會提升項目到一個新的版本。第四天的學習事項如下:
實現(xiàn)客戶端的認證。
理解 HTML Helper。
實現(xiàn) Authentication。
部分視圖增加 Footers。
原文地址:Learn MVC Project in 7 days
本文由OneAPM工程師編譯 ,想技術文章,請訪問OneAPM官方技術博客。
總結
以上是生活随笔為你收集整理的7 天玩转 ASP.NET MVC — 第 3 天的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: HDOJ 题目分类
- 下一篇: JavaScript模块化-requir