七天学会ASP.NET MVC (一)——深入理解ASP.NET MVC
七天学会ASP.NET MVC (二)——ASP.NET MVC 数据传递
七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理
七天学会ASP.NET MVC (四)——用户授权认证问题
七天学会ASP.NET MVC (五)——Layout页面使用和用户角色管理
数据访问层
实体框架(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”。
2. 创建连接字符串(ConnectionString)
打开Web.Config 文件,在< Configuration >标签内添加以下代码:
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 并点击安装。
4. 创建数据访问层
1: public class SalesERPDAL: DbContext
2: {
3: }
5. 创建Employee类的主键
打开Employee类,输入using语句
1: using System.ComponentModel.DataAnnotations;
添加Employee的属性,并使用Key 关键字标识主键。
1
2
3
4
5
6
7
8
|
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" style="display:none">
2:
3: </form>
1: <input type="button" name="BtnSubmit" value="Cancel" onclick="document.getElementById('CancelForm').submit()" />
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表单的使用等。在下一章,我们主要讲述以下内容: