七天学会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”。
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”:
?
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表单的使用等。在下一章,我们主要讲述以下内容:
七天学会ASP.NET MVC (三)——ASP.Net MVC 数据处理
原文:http://powertoolsteam.iteye.com/blog/2223020