七天學會ASP.NET MVC (一)——深入理解ASP.NET MVC
七天學會ASP.NET MVC (二)——ASP.NET MVC 數據傳遞
七天學會ASP.NET MVC (三)——ASP.Net MVC 數據處理
數據訪問層
實體框架(EF)簡述
什麼是代碼優先的方法?
實驗8——在項目中添加數據訪問層
關於實驗8
實驗9——創建數據輸入屏幕
實驗10——獲取服務端或控制器端傳遞的數據。
實驗11——重置及取消按鈕
實驗12——保存數據。庫記錄並更新表格
實驗13——添加服務器端驗證
實驗14——自定義服務器端驗證
結論
在實際開發中,如果一個項目不包含任何數據庫,那麼這個項目是不完整的,我們在一二節實例中未涉及數據庫,在本節開始,實驗8中講解一個關於數據庫和數據庫層的實例。
本節將使用SQL Server和EF(Entity Framework)創建相關的數據庫及數據庫訪問層。
EF是一種ORM工具,ORM表示對象關聯映射。
在RDMS中,對象稱為表格和列對象,而在.net中(面向對象)稱為類,對象以及屬性。
任何數據驅動的應用實現的方式有兩種:
1. 通過代碼與數據庫關聯(稱為數據訪問層或數據邏輯層)
2. 通過編寫代碼將數據庫數據映射到面向對象數據,或反向操作。
ORM是一種能夠自動完成這兩種方式的工具。EF是微軟的ORM工具。
EF提供了三種方式來實現項目:
l 數據庫優先方法——創建數據庫,包含表,列以及表之間的關系等,EF會根據數據庫生成相應的Model類(業務實體)及數據訪問層代碼。
l 模型優先方法——模型優先指模型類及模型之間的關系是由Model設計人員在VS中手動生成和設計的,EF將模型生成數據訪問層和數據庫。
l 代碼優先方法——代碼優先指手動創建POCO類。這些類之間的關系使用代碼定義。當應用程序首次執行時,EF將在數據庫服務器中自動生成數據訪問層以及相應的數據庫。
POCO即Plain Old CLR對象,POCO類就是已經創建的簡單.Net類。在上兩節的實例中,Employee類就是一個簡單的POCO類。
1. 創建數據庫
連接SQL SERVER ,創建數據庫 “SalesERPDB”。
1: <connectionStrings>
2: <add connectionString="Data Source=(local);Initial Catalog=SalesERPDB;Integrated Security=True"
3: name="SalesERPDAL"
4: providerName="System.Data.SqlClient"/>
5: </connectionStrings>
3. 添加EF引用
右擊項目->管理Nuget 包。選擇Entity Framework 並點擊安裝。
1: public class SalesERPDAL: DbContext
2: {
3: }
5. 創建Employee類的主鍵
打開Employee類,輸入using語句
1: using System.ComponentModel.DataAnnotations;
添加Employee的屬性,並使用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; } }
6. 定義映射關系
在SalesERPDAL類文件輸入using語句。
1: using WebApplication1.Models;
在 SalesERPDAL 類中重寫 OnModelCreating方法,代碼如下:
1: protected override void OnModelCreating(DbModelBuilder modelBuilder)
2: {
3: modelBuilder.Entity<employee>().ToTable("TblEmployee");
4: base.OnModelCreating(modelBuilder);
5: }
6: </employee>
注意:上述代碼中提到“TblEmployee”是表名稱,是運行時自動生成的。
7. 在數據庫中添加新屬性Employee
在 SalesERPDAL 類中添加新屬性 Employee。
1: public DbSet<employee> Employees{get;set;}
2: </employee>
DbSet表示數據庫中能夠被查詢的所有Employee
8. 改變業務層代碼,並從數據庫中獲取數據
打開 EmployeeBusinessLayer 類,輸入Using 語句。
1: using WebApplication1.DataAccessLayer;
修改GetEmployees 方法:
1: public List<employee> GetEmployees()
2: {
3: SalesERPDAL salesDal = new SalesERPDAL();
4: return salesDal.Employees.ToList();
5: }
6: </employee>
9. 運行並測試
右擊,查看並沒有任何Employee的表格,查看數據庫文件,我們會看到 TblEmployee 表
10. 插入測試數據
在TblEmployee 中插入一些測試數據
11. 運行程序
什麼是數據集?
DbSet數據集是數據庫方面的概念 ,指數據庫中可以查詢的實體的集合。當執行Linq 查詢時,Dbset對象能夠將查詢內部轉換,並觸發數據庫。
在本實例中,數據集是Employees,是所有Employee的實體的集合。當每次需要訪問Employees時,會獲取“TblEmployee”的所有記錄,並轉換為Employee對象,返回Employee對象集。
如何連接數據訪問層和數據庫?
數據訪問層和數據庫之間的映射通過名稱實現的,在實驗8中,ConnectionString(連接字符串)的名稱和數據訪問層的類名稱是相同的,都是SalesERPDAL,因此會自動實現映射。
連接字符串的名稱可以改變嗎?
可以改變,在實驗8中,在數據訪問層中定義了構造函數,如下:
1: public SalesERPDAL():base("NewName")
2: {
3: }
實驗8已經完成,相信大家已經了解基本的原理和操作,接下來我們將做一些改變,使得每件事情都變得有組織有意義:
1. 重命名
2. 刪除EmployeeListViewModel 的 UserName 屬性
3. 刪除View中的 UserName
打開 Views/Employee.Index.cshtml View ,刪除 UserName ,即刪除以下代碼:
1: Hello @Model.UserName
2: <hr />
4. 修改 EmployeeController 中的 Index方法
根據以下代碼修改Index 方法,執行時URL會變成 “…./Employee/Index”:
public ActionResult Index() { …… …… …… employeeListViewModel.Employees = empViewModels; //employeeListViewModel.UserName = "Admin";-->Remove this line -->Change1 return View("Index", employeeListViewModel);//-->Change View Name -->Change 2 }
1. 新建 action 方法
在 EmployeeController 中新建 “AddNew”action 方法:
1: public ActionResult AddNew()
2: {
3: return View("CreateEmployee");
4: }
2. 創建 View
在View/Employee目錄下 新建 View 命名為:CreateEmployee。
1: @{
2: Layout = null;
3: }
4: <!DOCTYPE html>
5: <html>
6: <head>
7: <meta name="viewport" content="width=device-width" />
8: <title>CreateEmployee</title>
9: </head>
10: <body>
11: <div>
12: <form action="/Employee/SaveEmployee" method="post">
13: First Name: <input type="text" id="TxtFName" name="FirstName" value="" /><br />
14: Last Name: <input type="text" id="TxtLName" name="LastName" value="" /><br />
15: Salary: <input type="text" id="TxtSalary" name="Salary" value="" /><br />
16: <input type="submit" name="BtnSave" value="Save Employee" />
17: <input type="button" name="BtnReset" value="Reset" />
18: </form>
19: </div>
20: </body>
21: </html>
3. 創建Index View的鏈接
打開 Index.cshtml 文件,添加指向 AddNew action方法的鏈接
1: <ahref="/Employee/AddNew">Add New</a>
4. 運行
使用Form 標簽的作用是什麼?
在系列文章第一講中,我們已經知道,Web編程模式不是事件驅動的編程模式,是請求響應模式。最終用戶會產生發送請求。Form標簽是HTML中產生請求的一種方式,Form標簽內部的提交按鈕只要一被點擊,請求會被發送到相關的action 屬性。
Form標簽中方法屬性是什麼?
方法屬性決定了請求類型。有四種請求類型:get,post,put以及delete.
使用Form 標簽來生成請求,與通過浏覽器地址欄或超鏈接來生成請求,有什麼區別?
使用Form標簽生成請求時,所有有關輸入的控件值會隨著請求一起發送。
輸入的值是怎樣發送到服務器端的?
當請求類型是Get,Put或Delete時,值會通過查詢語句發送,當請求是Post類型,值會通過Post數據傳送。
使用輸入控件名的作用是什麼?
所有輸入控件的值將隨著請求一起發送。同一時間可能會接收到多個值,為了區分發送到所有值為每個值附加一個Key,這個Key在這裡就是名稱屬性。
名稱和 Id的作用是否相同?
不相同,名稱屬性是HTML內部使用的,當請求被發送時,然而 ID屬性是在JavaScript中開發人員為了實現一些動態功能而調用的。
“input type=submit” 和 “input type=button”的區別是什麼?
提交按鈕在給服務器發送請求而專門使用的,而簡單的按鈕是執行一些自定義的客戶端行為而使用的。按鈕不會自己做任何事情。
1. 創建 SaveEmployee action 方法
在 Employee控制器中創建 名為 ”SaveEmployee“ action 方法:
1: public string SaveEmployee(Employee e)
2: {
3: return e.FirstName + "|"+ e.LastName+"|"+e.Salary;
4: }
2. 運行
action 方法內部的Textbox 值是如何更新 Employee 對象的?
在 Asp.Net MVC中有個 Model Binder的概念:
如果兩個參數是相關聯的會發生什麼狀況,如參數”Employee e“和 “string FirstName”?
FirstName會被元 First Name變量和 e.FirstName 屬性更新。
Model Binder是組合的關系嗎?
是,在實驗 9 中都是根據控件名稱執行的。
例如:
Customer 類和 Address 類:
1: public class Customer
2: {
3: public string FName{get;set;}
4: public Address address{get;set;}
5: }
6: public class Address
7: {
8: public string CityName{get;set;}
9: public string StateName{get;set;}
10: }
Html 代碼
1: ...
2: ...
3: ...
4: <input type="text" name="FName">
5: <input type="text" name="address.CityName">
6: <input type="text" name="address.StateName">
7: ...
8: ...
9: ...
1. 添加重置和取消按鈕
1: ...
2:
3: ...
4:
5: ...
6:
7: <input type="submit" name="BtnSubmit” value="Save Employee" />
8:
9: <input type="button" name="BtnReset" value="Reset" onclick="ResetForm();" />
10:
11: <input type="submit" name="BtnSubmit" value="Cancel" />
2. 定義 ResetForm 函數
在Html的頭部分添加腳本標簽,並編寫JavaScript 函數 命名為”ResetForm“如下:
1: <script>
2: function ResetForm() {
3: document.getElementById('TxtFName').value = "";
4: document.getElementById('TxtLName').value = "";
5: document.getElementById('TxtSalary').value = "";
6: }
7: </script>
3. 在 EmplyeeController 的 SaveEmployee 方法中實現取消按鈕的點擊功能
修改SaveEmployee 方法:
1: public ActionResult SaveEmployee(Employee e, string BtnSubmit)
2: {
3: switch (BtnSubmit)
4: {
5: case "Save Employee":
6: return Content(e.FirstName + "|" + e.LastName + "|" + e.Salary);
7: case "Cancel":
8: return RedirectToAction("Index");
9: }
10: return new EmptyResult();
11: }
4. 運行
5. 測試重置功能
6. 測試保存和取消功能
在實驗11中為什麼將保存和取消按鈕設置為同名?
在日常使用中,點擊提交按鈕之後,請求會被發送到服務器端,所有輸入控件的值都將被發送。提交按鈕也是輸入按鈕的一種。因此提交按鈕的值也會被發送。
當保存按鈕被點擊時,保存按鈕的值也會隨著請求被發送到服務器端,當點擊取消按鈕時,取消按鈕的值”取消“會隨著請求發送。
在Action 方法中,Model Binder 將維護這些工作。會根據接收到的值更新參數值。
實現多重提交按鈕有沒有其他可用的方法?
事實上,有很多可實現的方法。以下會介紹三種方法。
1. 隱藏 Form 元素
1: <form action="/Employee/CancelSave" id="CancelForm" method="get" >
2:
3: </form>
1: <input type="button" name="BtnSubmit" value="Cancel" onclick="document.getElementById('CancelForm').submit()" />2. 使用JavaScript 動態的修改URL
1: <form action="" method="post" id="EmployeeForm" >
2: ...
3: ...
4: <input type="submit" name="BtnSubmit" value="Save Employee" onclick="document.getElementById('EmployeeForm').action = '/Employee/SaveEmployee'" />
5: ...
6: <input type="submit" name="BtnSubmit" value="Cancel" onclick="document.getElementById('EmployeeForm').action = '/Employee/CancelSave'" />
7: </form>
3. Ajax
使用常規輸入按鈕來代替提交按鈕,並且點擊時使用jQuery或任何其他庫來產生純Ajax請求。
為什麼在實現重置功能時,不使用 input type=reset ?
因為輸入類型type=reset 不是清晰的值,僅設置了控件的默認值。如:
1: <input type="text" name="FName" value="Sukesh">
在該實例中控件值為:Sukesh,如果使用type=reset來實現重置功能,當重置按鈕被點擊時,textbox的值會被設置為”Sukesh“。
如果控件名稱與類屬性名稱不匹配會發生什麼情況?
首先來看一段HTML代碼:
1: First Name: <input type="text" id="TxtFName" name="FName" value="" /><br />
2: Last Name: <input type="text" id="TxtLName" name="LName" value="" /><br />
3: Salary: <input type="text" id="TxtSalary" name="Salary" value="" /><br />
Model 類包含屬性名稱如FirstName, LastName 和 Salary。由於默認的Model Binder在該片段內不會發生作用。
我們會給出三種解決方案
1: public ActionResult SaveEmployee()
2: {
3: Employee e = new Employee();
4: e.FirstName = Request.Form["FName"];
5: e.LastName = Request.Form["LName"];
6: e.Salary = int.Parse(Request.Form["Salary"])
7: ...
8: ...
9: }
1: public ActionResult SaveEmployee(string FName, string LName, int Salary)
2: {
3: Employee e = new Employee();
4: e.FirstName = FName;
5: e.LastName = LName;
6: e.Salary = Salary;
7: ...
8: ...
9: }
1. 創建自定義Model Binder
1: public class MyEmployeeModelBinder : DefaultModelBinder
2: {
3: protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
4: {
5: Employee e = new Employee();
6: e.FirstName = controllerContext.RequestContext.HttpContext.Request.Form["FName"];
7: e.LastName = controllerContext.RequestContext.HttpContext.Request.Form["LName"];
8: e.Salary = int.Parse(controllerContext.RequestContext.HttpContext.Request.Form["Salary"]);
9: return e;
10: }
11: }
2. 替換默認的 Model Binder
1: public ActionResult SaveEmployee([ModelBinder(typeof(MyEmployeeModelBinder))]Employee e, string BtnSubmit)
2: {
3: ......
RedirectToAction 函數的功能?
RedirectToAction 生成 RedirectToRouteResult 如ViewResult 和 ContentResult,RedirectToRouteResult是 ActionResult的孩子節點,表示間接響應,當浏覽器接收到RedirectToRouteResult,新Action 方法產生新的請求。
EmptyResult是什麼?
是ActionResult的一個孩子節點,當浏覽器接收到 EmptyResult,作為響應,它會顯示空白屏幕,表示無結果。在本實驗中不會發生EmptyResult。
1. 在EmployeeBusinessLayer 中創建 SaveEmployee,如下:
1: public Employee SaveEmployee(Employee e)
2: {
3: SalesERPDAL salesDal = new SalesERPDAL();
4: salesDal.Employees.Add(e);
5: salesDal.SaveChanges();
6: return e;
7: }
2. 修改 SaveEmployee 的Action 方法
在 EmployeeController 中修改 SaveEmployee ation方法 代碼如下:
1: public ActionResult SaveEmployee(Employee e, string BtnSubmit)
2: {
3: switch (BtnSubmit)
4: {
5: case "Save Employee":
6: EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
7: empBal.SaveEmployee(e);
8: return RedirectToAction("Index");
9: case "Cancel":
10: return RedirectToAction("Index");
11: }
12: return new EmptyResult();
13: }
3.運行
在實驗10中已經了解了Model Binder的基本功能,再來了解一下:
ModelState保存驗證錯誤的詳情。
如:ModelState[“FirstName”],表示將包含所有與First Name相關的錯誤。
保存接收的值(Post 數據或查詢字符串的值)
在Asp.net MVC,將使用 DataAnnotations來執行服務器端的驗證。
在我們了解Data Annotation之前先來了解一些Model Binder知識:
當Action方法包含元類型參數,Model Binder會與參數名稱對比。
當參數為類,Model Binder將通過檢索類所有的屬性,將接收的數據與類屬性名稱比較。
當匹配成功時:
如果接收的值是空,則會將空值分配給屬性,如果無法執行空值分配,會設置缺省值,ModelState.IsValid將設置為fasle。
如果空值分配成功,會考慮值是否合法,ModelState.IsValid將設置為fasle。
如果匹配不成功,參數會被設置為缺省值。在本實驗中ModelState.IsValid不會受影響。
1. 使用 DataAnnotations 裝飾屬性
1: public class Employee
2: {
3: ...
4: ...
5: [Required(ErrorMessage="Enter First Name")]
6: public string FirstName { get; set; }
7:
8: [StringLength(5,ErrorMessage="Last Name length should not be greater than 5")]
9: public string LastName { get; set; }
10: ...
11: ...
12: }
2. 修改 SaveEmployee 方法
1: public ActionResult SaveEmployee(Employee e, string BtnSubmit)
2: {
3: switch (BtnSubmit)
4: {
5: case "Save Employee":
6: if (ModelState.IsValid)
7: {
8: EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
9: empBal.SaveEmployee(e);
10: return RedirectToAction("Index");
11: }
12: else
13: {
14: return View("CreateEmployee ");
15: }
16: case "Cancel":
17: return RedirectToAction("Index");
18: }
19: return new EmptyResult();
20: }
3. 在View中顯示錯誤,修改 Views/Index/CreateEmployee.cshtml 代碼,使用Table標簽如下:
1: <table>
2: <tr>
3: <td>
4: First Name:
5: </td>
6: <td>
7: <input type="text" id="TxtFName" name="FirstName" value="" />
8: </td>
9: </tr>
10: <tr>
11: <td colspan="2" align="right">
12: @Html.ValidationMessage("FirstName")
13: </td>
14: </tr>
15: <tr>
16: <td>
17: Last Name:
18: </td>
19: <td>
20: <input type="text" id="TxtLName" name="LastName" value="" />
21: </td>
22: </tr>
23: <tr>
24: <td colspan="2" align="right">
25: @Html.ValidationMessage("LastName")
26: </td>
27: </tr>
28:
29: <tr>
30: <td>
31: Salary:
32: </td>
33: <td>
34: <input type="text" id="TxtSalary" name="Salary" value="" />
35: </td>
36: </tr>
37: <tr>
38: <td colspan="2" align="right">
39: @Html.ValidationMessage("Salary")
40: </td>
41: </tr>
42:
43: <tr>
44: <td colspan="2">
45: <input type="submit" name="BtnSubmit" value="Save Employee" />
46: <input type="submit" name="BtnSubmit" value="Cancel" />
47: <input type="button" name="BtnReset" value="Reset" onclick="ResetForm();" />
48: </td>
49: </tr>
50: </table>
4. 運行測試
導航到“Employee/AddNew” 方法,執行測試。
測試1。
測試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後添加以下語句
1: Database.SetInitializer(new DropCreateDatabaseIfModelChanges<SalesERPDAL>());
@Html.ValidationMessage是什麼意思?
ValidationMessage 函數是如何工作的?
ValidationMessage 是運行時執行的函數。如之前討論的,ModelBinder更新ModelState。ValidationMessage根據關鍵字顯示ModelState表示的錯誤信息。
如果我們需要非空需求的整數域,該怎麼做?
1: public int? Salary{get;set;}
UpdateModel 和 TryUpdateModel 方法之間的區別是什麼?
TryUpdateModel 與UpdateModel 幾乎是相同的,有點略微差別。
如果Model調整失敗,UpdateModel會拋出異常。就不會使用UpdateModel的 ModelState.IsValid屬性。
TryUpdateModel是將函數參數與Employee對象保持相同,如果更新失敗,ModelState.IsValid會設置為False值。
客戶端驗證是什麼?
客戶端驗證是手動執行的,除非使用HTML 幫助類。我們將在下一節介紹HTML 幫助類。
1. 創建自定義驗證
新建類,並命名為FirstNameValidation,代碼如下:
1: public class FirstNameValidation:ValidationAttribute
2: {
3: protected override ValidationResult IsValid(object value, ValidationContext validationContext)
4: {
5: if (value == null) // Checking for Empty Value
6: {
7: return new ValidationResult("Please Provide First Name");
8: }
9: else
10: {
11: if (value.ToString().Contains("@"))
12: {
13: return new ValidationResult("First Name should contain @");
14: }
15: }
16: return ValidationResult.Success;
17: }
18: }
2. 附加到First Name
打開Employee類,刪除FirstName的默認的Required屬性,添加FirstNameValidation,代碼如下:
1: [FirstNameValidation]
2: public string FirstName { get; set; }
3. 運行
導航到Employee/AddNew
測試1:
測試2:
本節主要講解了數據訪問層相關的知識,如數據驗證,數據更新,數據處理,form表單的使用等。在下一章,我們主要講述以下內容: