原文地址
这是我在CodeProject上面看到的一篇文章,这里是我翻译的中文版,如果有任何翻译不恰当之处,还望各位不吝赐教!
下载 Day_3.zip
我们假设你已经阅读了本系列前面两天的文章,在第2天我们在工程中完成了再表格中显示Employee清单的功能。
在第3天我们将会进入到下一阶段,我们将会介绍数据访问层(DAL)和数据入口界面。
在真正的工程中,没有数据库就是不完整的。在我们的工程中,我们还没有讲到数据库层,第3天的第一个实验就全部围绕数据库和数据库层进行。
在这里我们使用SQL Sever 和Entity Framework分别来创建数据库和数据访问层。
它是一个ORM工具。ORM代表Object Relation Mapping,即对象关系映射。
在RDBMS(译者注:Relational Database Management System,关系数据库管理系统)的世界中,我们说的是表、列,然而在.NET的世界中(一个面向对象的世界),我们谈论的是类、对象、属性。
当我们想到任何数据驱动的程序的时候,我们想到以下两个方面:
● 书写与数据库通信的代码(叫做DAL或者数据库逻辑)
● 书写将数据库数据转换成面向对象的数据,反之亦然
ROM工具会帮我们自动完成这两样工作,儿Entity Framework是微软提供的ORM工具。
在Entity Framework 中我们可以使用以下三种模式:
● Database First模式 — 创建包含表、列、关系的数据库,Entity Framework将会生成相对应的Model类(业务实体Business Entities)和数据访问层代码。
● Model First模式 — 在这个模式里面,Model类以及它们之间的关系由Model设计者在Visual Studio中手工定义,Entity Framework将生成数据访问层和相应的数据库以及表、列、关系等等。
● Code First模式 — 在这个模式里面,手工创建POCO类,这些类之间的关系将会由代码定义。当程序第一次执行的时候,Entity Framework 将会生成数据访问层,数据库中的表、列、关系将会自动在数据库服务器中生成。
POCOS是什么意思?
POCO是“Plain Old CLR Objects”,POCO代表我们创建的简单的.NET类。在我们之前的示例中,Employee类就是一个POCO类。
连接到SQL Server,创建数据库,命名为“SalesERPDB”。
打开Web.config文件,在Configuration部分添加如下部分:
<connectionStrings>
<add connectionString="Data Source=(local);Initial Catalog=SalesERPDB;Integrated Security=True" name="SalesERPDAL" providerName="System.Data.SqlClient"/>
</connectionStrings>
右键点击工程->管理Nuget程序包,点击安装。
● 在根目录下创建“Data Access Layer ”文件夹,在其中创建一个“SalesERPDAL”的新类。
● 引用System.Data.Entity
● 为“SalesERPDAL”添加父类DbContext
public class SalesERPDAL: DbContext
{
}
打开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; }
}
在Employee类代码顶部添加如下声明:
using WebApplication1.Models;
在SalesERPDAL类中重载OnModelCreating方法:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<employee>().ToTable("TblEmployee");
base.OnModelCreating(modelBuilder);
}
提示:在上面的代码中,“TblEmployee”代表表名,它在运行时自动被创建。
在“SalesERPDAL”类中定义一个叫做Employee的新属性:
public DbSet<employee> Employees{get;set;}
DbSet代表了能够从数据库获取的所有Employee数据。
打开EmlpoyeeBusinessLayer类,在头部添加引用声明:
using WebApplication1.DataAccessLayer;
现在更改GetEmployee方法,如下:
public List<employee> GetEmployees()
{
SalesERPDAL salesDal = new SalesERPDAL();
return salesDal.Employees.ToList();
}
什么是DbSet?
DbSet代表了能够从数据库获取的所有实体的集合,当我们对DbSet对象书写Linq查询语句的时候,DbSet内部会将其转换为针对数据库的查询语句。
在我们的例子中,“Employees”就是存放从数据库获取的“Employee”实体的DbSet。当我们每次尝试访问“Employees”,它会获取“TblEmployee”表中所有的记录并将其转换为“Employee”对象然后返回给集合。
connection string和DAL是如何连接的?
映射关系是基于名称建立的,在我们的例子里面,ConnectionString的Name和DAL的Name都是“SalesERPDAL”,因此它们被自动映射。
我们可以更改ConnectionString的Name吗?
可以,这样的话我们需要在DAL类中定一个构造函数,如下:
public SalesERPDAL():base("NewName")
{
}
让我们来做一些修改,让一切更加合理和意义清晰。
Step 1-重命名
● “TestController”->”EmployeeController”
● “GetView”Action方法->”Index”
● Test文件夹(Views文件夹下)->Employee
● “MyView”视图->”Index”
Step 2-从EmployeeListViewModel中移除UserName属性
Step 3-在视图中移除UserName
打开Views/Employee.Index.cs.html视图,移除UserName。
简单地说,就是移除以下代码:
Hello @Model.UserName
Step 4-更改EmployeeController的Index方法
按照如下代码更改EmployeeController的Index方法:
public ActionResult Index()
{
....
....
....
employeeListViewModel.Employees = empViewModels;
//employeeListViewModel.UserName = "Admin";-->Remove this line -->Change1
return View("Index", employeeListViewModel);//-->Change View Name -->Change 2
}
现在,运行的URL变成了“../Employee/Index”
创建名为“AddNew”的Action方法,如下:
public ActionResult AddNew()
{
return View("CreateEmployee");
}
在View/Employee文件夹中创建名为”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.cshtml,添加一个指向AddNew方法URL的hyperlink。
<a href="/Employee/AddNew">Add New</a>
按下F5,执行程序。
form标签的用途是什么?
在第1天里,我们理解了“Web的世界里面并不是事件驱动的编程模式,它遵从请求-响应的模式。终端用户发出请求,服务器发出响应。” Form标签是让请求为HTML格式的一种途径,只要在form标签里面的submit按钮被按下,一个请求就会被发送到在action特性中指定的URL。
form标签的里的method特性是什么?
它决定了请求的类型,请求的类型有以下四种:get,post,put,delete。
针对每一种标准,我们应该采取不同的请求方式:
● Get -> 当我们需要获得一些东西的时候
● Post -> 当我们需要创建一些东西的时候
● Put -> 当我们想要更新一些东西的时候
● Delete -> 当我们需要删除一些东西的时候
用form标签发出请求和通过浏览器地址栏发出请求或者hyperlink发出请求有什么不同?
当请求是在form标签的帮助下发出的时候,所有input控件的value都会跟着请求一起发送,可以用来做处理。
CheckBox、Radio Button、DropDown这些控件的value也会一起被发送吗?
是的,所有input控件(input type=text,type=radio,type=checkbox)以及dropdowns(代表了“select”元素)。
values是如何被发送到服务器的?
当请求是Get、Put或者Delete类型的时候,values将会以Query String参数(译者注:URL参数)的形式发送。
当请求是Post类型的时候,values将会以posted数据形式被发送。
input控件的name特性有什么用途?
正如在之前讨论的,当submit按钮被点击的时候,所有input控件的value都会随着请求一起发送。当服务器接收到value的时候,这些值彼此之间应该能够相互区分,因此每个value都有一个标识,这个标识就是“name”特性。
name和id特性用途一样吗?
不,“name”特性将会在HTML内部发出请求的时候使用,而“id”特性将会被开发人员在JavaScript内使用,用以处理一些动态的问题。
“input type=submit”和“input type=button”之间有什么区别?
Submit按钮的点击会向服务器发出请求,然而简单按钮的点击不会触发任何默认的行为,它被用来完成一些客户端的行为。
在Employee Controller内部创建SaveEmployee action方法,如下:
public string SaveEmployee(Employee e)
{
return e.FirstName + "|"+ e.LastName+"|"+e.Salary;
}
按下F5执行并测试程序。
在action方法中,TextBox的值是如何更新到Employee对象中的?
在ASP.NET MVC中,有一个概念叫做Model Binder(模型绑定器)。
● Model Binder会自动执行,无论何时,一旦指向包含参数的action方法的请求被发出。
● Model Binder将会对所有原始参数进行迭代,将参数的name和进入的数据(进入的数据要么是posted data,要么是Query String)的标识一一进行匹配。当匹配成功地时候,对应的数据将会被分配给该参数。
● 然后,Model Binder将会对所有类型参数(class parameter)的属性进行迭代,将属性的name和进入数据的标识进行匹配。当匹配成功地时候,对应的数据将会被分配给该参数。
当有两个参数被指定,一个是“Employee e”另外一个是“string FirstName”的时候,将会发生什么?
原始参数的“string FirstName”和e.FirstName属性值都会被更新。
(译者注:也就是说,进入的数据标识为“FirstName”的时候,两个参数的匹配都会成功。)
Model Binder可以处理组合关系吗?
可以,这样的话,控件的name需要有针对性的指定。
例如:假设我们有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">
...
...
...
对重置按钮的click引入JavaScript函数,为取消事件按钮添加新的submit按钮。代码如下:
<input type="button" name="BtnReset" value="重新填写" onclick="ResetForm();" />
<input type="submit" name="btn_Save" value="取消" />
在createEmployee.cshtml视图的HTML代码的head标签里面添加一段JavaScript代码,如下:
<script> function ResetForm() { document.getElementById('TxtFName').value = ""; document.getElementById('TxtLName').value = ""; document.getElementById('TxtSalary').value = ""; } </script>
修改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();
}
按下F5执行程序,导航到AddNew界面,点击“Add New”链接。
为何保存按钮和取消按钮有一样的name?
我们知道,只要submit按钮被点击,一个请求就会发送到服务器。所有input控件的value都会一起被发送。
Submit按钮也是一个input按钮,因此,submit的值(请求的触发者)也会被发送。
当保存按钮被点击的时候,保存按钮的值“Save Employee”会跟随请求一起发送;当点击取消按钮的时候,取消按钮的值“Cancel”也会跟随请求一起发送。
在Action方法里面,Model Binder会完成剩下的工作。它会根据进入的数据值来更新参数值。
还有其他的实现多个submit按钮的方法吗?
有很多方法,我这里讨论其中的三种。
1.隐藏form元素
Step 1-在视图中创建隐藏的form元素,如下:
<form action="/Employee/CancelSave" id="CancelForm" method="get" style="display:none">
</form>
Step 2-将submit按钮改为普通按钮,然后用JavaScript提交上述的form。
<input type="button" name="BtnSubmit" value="Cancel" onclick="document.getElementById('CancelForm').submit()" />
2.使用JavaScript动态修改action URL
<form action="" method="post" id="EmployeeForm" >
...
...
<input type="submit" name="BtnSubmit" value="Save Employee" onclick="document.getElementById('EmployeeForm').action = '/Employee/SaveEmployee'" />
...
<input type="submit" name="BtnSubmit" value="Cancel" onclick="document.getElementById('EmployeeForm').action = '/Employee/CancelSave'" />
</form>
3.Ajax
我们不采用submit按钮,而是采用普通的按钮,然后用JQuery或者其它JavaScript库包装纯Ajax请求。
我们为什么不采用“input type=reset”的方式来完成重置功能呢?
Input type=reset的控件不能清除各个控件的value,它仅仅将各个value设置为默认值,例如;
<input type="text" name="FName" value="Sukesh">
上面的例子中,默认的value就是“Sukesh”,如果我们采用input type=reset的方法来执行重置功能的话,重置按钮每次被点击的时候,该控件的值都会被设置为“Sukesh”。
当value的name和类的属性name不匹配的时候该怎么办?
这是在面试的时候经常被问到的问题,假设我们有下面这样一段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 />
现在我们的Model类包含FirstName、Lastname和Salary属性。因此默认的Model Binder在这里就失效了。
这种情况下,我们有如下三种解决方案:
● 在action方法内部,用Request.Form语法获取发送过来的数据,然后手工搭建Model object,如下:
public ActionResult SaveEmployee()
{
Employee e = new Employee();
e.FirstName = Request.Form["FName"];
e.LastName = Request.Form["LName"];
e.Salary = int.Parse(Request.Form["Salary"])
...
...
}
● 使用参数name,然后手动创建Model对象如下:
public ActionResult SaveEmployee(string FName, string LName, int Salary)
{
Employee e = new Employee();
e.FirstName = FName;
e.LastName = LName;
e.Salary = Salary;
...
...
}
● 创建定制的Model Binder,然后用其替换默认的Model Binder,如下:
Step 1-创建定制的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;
}
}
Step 2-替换默认的Model Binder
public ActionResult SaveEmployee([ModelBinder(typeof(MyEmployeeModelBinder))]Employee e, string BtnSubmit)
{
......
RedirectToFunction是干啥的?
它生成 RedirectToRouteResult,和ViewResult以及ContentResult一样(在第1天讨论过)。RedirectToRouteResult是ActionResult的一个子类,它代表了重定向响应。当浏览器接收RedirectToRouteResult的时候,它产生新的请求指向新的action方法。
提示:这里浏览器会负责新的请求,因此新的URL会生成。
什么是EmptyResult?
ActionResult的另一个子类,当浏览器接收到EmptyResult响应的时候,浏览器会展示一个空白界面。它简简单单地代表“NoResult”。
在我们的例子里面,不存在这种情况。只需要保证所有的代码路径都有返回值。
提示:当Action 方法返回类型为Void的时候,等价于EmptyResult。
public Employee SaveEmployee(Employee e)
{
SalesERPDAL salesDal = new SalesERPDAL();
salesDal.Employees.Add(e);
salesDal.SaveChanges();
return e;
}
在EmployeeController中修改SaveEmployee action方法,如下:
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();
}
按下F5运行程序,导航到数据录入界面,输入一些数据。
在实验10中,我们看到了Model Binder的基本功能,让我们了解更多一点。
● Model Binder用posted data更新Employee对象
● 但是这不是Model Binder完成的唯一功能。Model Binder也更新ModelState,ModelState是对Model的状态的概述。
●它拥有一个叫做IsValid的属性,该属性决定了Model(也就是Employee)是否被成功更新。当有任何服务器端验证失败的时候,Model都不会更新。
● 它保存验证错误的信息,例如:ModelState[“FirstName”].Errors将包含跟FirstName相关的错误信息。
● 它保存传递进来的数据(Posted data 或者Query String data)
在ASP.NET MVC,我们使用DataAnnotation来完成服务器端验证。
在我们使用DataAnnotion之前,我们先来了解一些关于Model Binder的更多的信息。
当Action方法包含原始类型参数的时候,Model Binder将对参数名与每一个传递进入的数据的标识进行匹配(传递进入的数据是说posted data或者Query string)。
● 当匹配成功的时候,对应的数据将会分配给该参数。
● 当匹配失败的时候,参数将会被分配默认值(对于整型,默认值为0;对于string类型,默认值为null等等)
● 当由于数据类型匹配错误无法满足方法签名的时候,将会抛出异常。
当参数为类类型的时候,Model Binder将会对该类型的所有属性进行迭代,将其属性名和每一个传递进入的数据的标识进行匹配。
● 当匹配成功地时候
● 如果对应的数据为空,那么
● Null值将会被赋给该属性。如果不能赋予Null值,该属性将会被赋予默认值,
ModelState.Isvalid将会被设置为false。
● 如果Null值可以被赋予,但是被该属性的验证规则认为是非法值,那么该属性将会被赋予
Null值,ModelState.IsValid将会被设置为false。
●如果对应的数据不为空,那么
● 当方法签名由于数据类型匹配错误或者服务器端验证失败而不能满足的时候,Null值将会被分配。
而且ModelState.IsValid将会被设置为false。
● 如果Null值不允许,那么该属性将会被赋予默认值。
● 当匹配失败的时候,参数将会被赋予各自的默认值,在这种情况下,ModelState.IsValid将会保持不受影响。
让我们为我们正在进行的工程添加验证特征吧。
Step 1-用DataAnnotation来装饰属性
打开Employee类,为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; }
...
...
}
Step 2-修改SaveEmployee action方法
打开EmployeeController,修改SaveEmployee方法,如下:
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();
}
提示:正如你可以看到,当ModelState.IsValid为false的时候,SaveEmployee的保存按钮点击的响应将会是一个指向“CreateEmployee”的ActionResult。
Step 3-在View中展示错误信息
修改“View/Index/CreateEmployee.cshtml”的HTML代码,如下。
这次我们对我们的UI界面做一下格式化,利用table标签。
<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>
Step 4-执行并测试
按下F5执行程序,导航到“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()中添加如下声明即可。
Database.SetInitializer<SalesERPDAL>(null);
Database的命名空间为System.Data.Entity。
@Html.ValidationMessage 做了什么?
● @ 代表了Razor代码
● Html是HtmlHelper类在View中的一个实例
● ValidationMessage是HtmlHelper类中用来展示错误信息的函数。
ValidationMessage函数如何工作?
ValidationMessage是一个函数,它在运行时执行。正如我们之前讨论的一样,Model Binder更新ModelState,ValidationMessage根据标识展示ModelState中的错误信息。
例如:ValidationMessage(“FirstName”)展示了跟FirstName相关的错误信息。
有其他的类似于Required和StringLength这样的特性吗?
是的,下面列出了一些:
● DataType - 确保数据拥有特定的数据类型,比如Email、creadit等等。
● EnumDataType - 确保值存在于枚举类型中。
● Range - 确保值出于一个特定的范围内。
● RegularExpression - 用特定的正则表达式来对值进行验证。
● Required - 确保值一定被提供。
● StringLength - 验证字符串的字符个数处在maximum和minimum之间。
Salary是如何验证的?
我们没有为Salary属性添加温和DataAnnotation特性,但是它还是得到了验证。原因是Model Binder在更新model的时候也考虑属性的数据类型。
在 测试1 中 - salary为空字符串,在这个情况下,按照我们的Model Binder解释,ModelState.IsValid将为false,ModelState将会保存跟Salary相关的验证错误信息,由于Html.ValidatonMessage(“Salary”)的缘故,这些信息将会在View中展示。
在 测试2 中 - salary数据类型匹配错误,因此验证失败。
这是否意味着,整型属性将会被强制赋予默认值?
是的,不仅如此,所有的值类型都因如此。因为它们不能为null。
如果我们想要一个非必须的整型字段,应该如何?
要让它可为空?
public int? Salary{get;set;}
如何修改Salary的验证信息?
Salary的默认的验证支持不允许我们修改验证信息,我们可以使用我们自己的验证比如正则表达式、range或者自定义Validator来达成同样的目的。
为何在验证失败的时候所有value都被清空了呢?
因为这是一个新的请求。在一开始绘制的DateEntity视图同之后绘制的视图,从开发人员的角度上来说是一样的,但是从请求的角度来说并不一样。在第4天的时候我们将学习如何保持value。
我们可以显式地让Model Binder执行吗?
可以,只需要移除Action方法的参数,这会阻止Model Binder的默认执行。
这样的话,我们可以使用UpdateModel 函数,如下:
Employee e = new Employee(); UpdateModel<employee>(e);
提示:UpdataModel方法不能用原始数据类型作为参数。
UpdateModel和TryUpdateModel之间有什么区别?
TryUpdateModel除了一个优势之外,其余和UpdateModel一样。UpdateModel将会抛出异常,如果Model适配因为任何原因失败,在这种情况下ModelState.IsValid将没有任何用处。
TryUpdateModel在将Employee对象作为函数参数的方面和UpdateModel完全一样,但当更新失败的饿时候,ModelState.IsValid将会为false。
客户端验证该如何?
客户端验证只有在我们使用HTML类的时候,才应该手工完成。
在第4天的时候,我们将讨论自动客户端验证和手动客户端验证,使用HTML helper类。
我们可以为同一个属性添加多个DataAnnotation特性吗?
可以,这样的话,所有验证都会被执行。
创建一个名为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 not contain @");
}
}
return ValidationResult.Success;
}
}
打开Employee类,移除UserName的默认“Required特性,然后关联FirstNameValidation,如下:
[FirstNameValidation]
public string FirstName { get; set; }
按下F5,导航到“Employee/Addnew”action。
测试1
测试2
在这里,我们完成了第3天的全部内容。在第4天我们将会进一步深入发展我们的工程,下面列出了第4天的清单:
● 完成客户端验证
● 理解HTML helper
● 完成授权(Authentication)
● 用分部视图(partial view)添加Footers
● 用母板页(master pages)创建同一布局
● 自定义请求过滤