原文地址
这是我在CodeProject上面看到的一篇文章,这里是我翻译的中文版,如果有任何翻译不恰当之处,还望各位不吝赐教!下载 ViewBag_and_ViewData.zip
下载 Strongly_Typed_View.zip
下载 View_Model.zip
下载 Collections.zip
本文将带领大家在7天内逐步学习MVC5——第2天
我们相信,在此之前你已经顺利完成了第一天的内容。
第一天主要聚焦在以下方面:
● 为何选择MVC?
● ASP.NET WebForms和ASP.NET MVC的对比
● 理解Controllers和Views
注意
如果你没有完成前一天的内容,请务必先完成。我们的目标是在今天结束的时候,用最好的方法和现代方法创建一个小的MVC项目。每一个新的实验中,我们要么在之前实验的基础之上添加新功能,要么让之前的实验更完善。
在实验2中创建的视图非常的静态化,在实际场景中视图一般会展示一些动态数据。在接下来的实验中,我们将会在视图中展示一些动态数据。
视图从Controller中以Model的形式接收数据。
Model
在ASP.NET MVC中model代表业务数据。
ViewData是一种数据字典,包含了再Controller和View之间传递的数据。Controller将会向这个数据字典中添加条目,View将会从中读取。我们来做个示例吧。
在Model文件夹中创建新类,命名为Employee,如下:
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Salary { get; set; }
}
创建在GetView方法中Employee对象,如下:
Employee emp = new Employee();
emp.FirstName = "Sukesh";
emp.LastName="Marla";
emp.Salary = 20000;
确保添加了对Employee命名空间的引用,否则就只能用Employee的权限定名了。
using WebApplication1.Models;
在Employee对象中储存ViewData,如下:
ViewData["Employee"] =emp;
return View("MyView");
打开MyView.cshtml文件。
从ViewData中获取Employee的数据,并展示,如下:
<div>
@{
WebApplication1.Models.Employee emp=(WebApplication1.Models.Employee)
ViewData["Employee"];
}
<b>Employee Details </b><br />
Employee Name : @emp.FirstName@emp.LastName <br />
Employee Salary: @emp.Salary.ToString("C")
</div>
按下F5,测试程序。
在Razor代码中,有花括号(“{“和”}”)与没有花括号有什么区别?
在这个实验中,@emp.FirstName可以用如下代码片替换:
@{ Response.Write(emp.FirstName);
}
没有花括号的@语句,仅仅展示后面的变量或者表达式的值。
为什么需要类型转换?
ViewData内存存放的是object类型,每次往里面添加新值的时候都会对其进行装箱操作,转换成object类型。因此每一次尝试提取ViewData中数据的时候,都需要进行拆箱操作。
@emp.FirstName @emp.LastName的含义是什么?
展示First Name,和Last Name,并且在First Name后面添加一个空格。
我们可以只写一个@关键字来完成同样的工作吗?
可以,这样写的语法应该为@(emp.FirstName+” “emp.LastName)
为什么在Controller中创建的Employee的数据是写死的?
只是为了展示的目的,在实际情况中,我们将会从数据库或者WCF或者Web Service或者其他的地方获取数据。
关于Database Logic/Data Access Layer和Business Layer
● DAL是在ASP.NET MVC中没有提到的一层,它一直存在,但是并没被包括在MVC的定义中。
● BL在先前解释过了,是Model的一部分。
● 完整的MVC结构:
ViewBag只是ViewData的一个语法糖衣而已,ViewBag使用C#4.0的动态特性,将ViewDate变得动态。
ViewBag内部使用ViewData。
继续实验3,将第3步的代码替换成如下:
ViewBag.Employee = emp;
将第4步改为如下代码:
@{
WebApplication1.Models.Employee emp = (WebApplication1.Models.Employee)
ViewBag.Employee;
}
Employee Details
Employee Name: @emp.FirstName @emp.LastName
Employee Salary: @emp.Salary.ToString("C")
按下F5,测试程序:
我们能用ViewData传递数据,然后用ViewBag获取数据吗?
可以,反之亦然。正如我之前所说,ViewBag只是ViewData的语法糖衣而已。
ViewData和ViewBag是在Controller和View之间传递数据的不错的选择,但是在实际的工程中,采用它们之中任何一个都不是好的选择。让我们来讨论关于ViewData和ViewBag的一些劣势吧:
效率问题
ViewData内部的数据为object类型,我们需要在使用之前将其转换为正确的类型,在效率方面带来额外的天花板。
非类型安全,没有编译时报错
如果我们尝试将值转换成错误的类型的时候,或者当获取值的时候使用了错的key,我们会获得运行时错误。作为一个好的编程惯例,错误应该在编译的时候被处理掉。
在发送的数据和接收的数据之间没有合适的连接
作为开发人员,我个人认为这是一个主要问题。
在MVC中,Controller和View彼此之间是松散连接的。Controller对于View中发生了什么完全不清楚,反之亦然。
在Controller中我们可以传递一个或者更多的ViewData/ViewBag值,现在,当开发人员书写一个View的时候,他/她需要记住从Controller传递来的是什么。如果Controller和View的开发人员不一样,这将会变得更加困难。彼此之间毫无知觉,会导致很多运行时问题,也会降低开发效率。
引起ViewData和ViewBag三个问题的原因就是数据类型,ViewData内部数据的类型为object.
如果我们能够确定在Controller和View之间传递的数据类型,这些问题都会得到解决,这也就是强类型的Views出现的原因。
让我们来做个示例吧,这次,我们将View的要求提升到另外一个水准。如果Salary大于15000,将会用黄色背景来展示,反之用绿色背景展示。
在View顶部添加如下代码:
@model WebApplication1.Models.Employee
上面的代码将我们的View变成Employee的强类型视图。
现在在View里,仅仅只需要敲下@Model.,智能感应就会获得Model类(Employee)的属性。
Employee Details
Employee Name : @Model.FirstName @Model.LastName
@if(Model.Salary>15000)
{
<span style="background-color:yellow">
Employee Salary: @Model.Salary.ToString("C")
</span>
}
else
{
<span style="background-color:green">
Employee Salary: @Model.Salary.ToString("C")
</span>
}
改变action方法里面的代码如下:
Employee emp = new Employee();
emp.FirstName = "Sukesh";
emp.LastName="Marla";
emp.Salary = 20000;
return View("MyView",emp);
在视图中,是否每次都需要输入类的全部限定名称?
不,我们可以引入一个using声明。
@using WebApplication1.Models
@model Employee
是否所有视图都必须是强类型的?我们可以使用ViewData或者ViewBag吗?
作为最佳做法,我们最好将所有视图作为强类型视图。
我们可以将视图作为多个model的强类型视图吗?
不可以。在实际工程里面,我们经常在同一个视图中展示多个model的时候卡住,解决这个问题的方法会在下一个试验中讨论。
在实验5中我们违反了MVC原则。根据MVC,V指视图,应该是纯UI,不应该在里面包含任何逻辑。我们做的一下三点,完全违反了MVC的原则:
● 将FirstName和LastName附在一起,并且作为全名展示。——逻辑
● 展示Salary的时候附上了货币符号。——逻辑
● 根据Salary的值,用不同的颜色展示Salary。简单地说就是根据Salary的值,更改了HTML元素的外观。——逻辑
除了这三点之外,还有一个值得讨论的问题。
前面说到,我们在往同一个View中展示多种类型的数据的时候遇到了问题。例如:展示Employee数据的时候同时展示用户名(User’s Name)。
用以下两种方法中的任意一种,都可以达成目的:
1. 在Employee类中添加UserName属性。但是,每次我们想展示view中的新数据的时候,就为Employee类型添加似乎不符合逻辑,这些属性跟Employee也许有关,也许无关。这也同样违反了SOLID的SRP原则(Single Response Principle)。
2.使用ViewBag或者ViewData - 但是我们已经讨论过了这种方法的问题了。
ViewModel是在ASP.NET MVC中没有提到的一层,它在Model层和View层中间,扮演着View数据容器的角色。
ViewModel和Model的区别
Model是特定的业务数据,它根据业务和数据库结构创建;ViewModel是特定的视图数据,它根据视图创建。
它到底如何工作
很简单:
● Controller处理用户交互逻辑,简单地说就是处理用户请求。
● Controller获得一个或多个model数据。
● Controller将会决定该请求的最佳匹配视图。
● Controller将会根据从View请求获得的Model数据实例化ViewModel对象。
● controller将会通过ViewData/ViewBag/Strongly Typed View向视图传递ViewModel数据。
● Controller返回视图。
View和ViewModel如何连接呢?
View将会是ViewModel的一个强类型视图。
Model和ViewModel如何连接呢?
Model和ViewModel彼此之间应该是相互独立的,Controller会根据一个或多个Model数据来实例化ViewModel对象。
让我们做一个小实验来更好地理解它。
在工程中创建名叫ViewModels的新文件夹。
为了达成目的,让我们列出对View的所有要求:
1.First Name 和Last Name应该在展示之前就附加好了。
2.Amount应该和货币符号一起展示。
3.Salary应该用不同的颜色展示(根据salary的值不同,颜色不同)。
4.当前User Name应该一同在View中展示。
在ViewModels文件夹里创建一个新的类,叫做EmployeeViewModel,如下:
public class EmployeeViewModel
{
public string EmployeeName { get; set; }
public string Salary { get; set; }
public string SalaryColor { get; set; }
public string UserName{get;set;}
}
请注意,在View Model类中,FirstName和LastName被单一属性EmployeeName所取代。Salary的数据类型为string类型,两个新的属性分别为SalaryColor和UserName。
在实验5中,我们的View是Employee类型的强类型视图,改成EmployeeViewModel类型的强类型视图。
@using WebApplication1.ViewModels
@model EmployeeViewModel
用如下代码替换View部分的代码:
Hello @Model.UserName
<hr />
<div>
<b>Employee Details</b><br />
Employee Name : @Model.EmployeeName <br />
<span style="background-color:@Model.SalaryColor">
Employee Salary: @Model.Salary
</span>
</div>
在GetView方法中,获得model数据,转换成ViewModel对象,如下:
public ActionResult GetView()
{
Employee emp = new Employee();
emp.FirstName = "Sukesh";
emp.LastName="Marla";
emp.Salary = 20000;
EmployeeViewModel vmEmp = new EmployeeViewModel();
vmEmp.EmployeeName = emp.FirstName + " " + emp.LastName;
vmEmp.Salary = emp.Salary.ToString("C");
if(emp.Salary>15000)
{
vmEmp.SalaryColor="yellow";
}
else
{
vmEmp.SalaryColor = "green";
}
vmEmp.UserName = "Admin"
return View("MyView", vmEmp);
}
按下F5按钮,测试输出。
是否每一个Model都需要一个View Model?
不是,是每一个View都有它对应的ViewModel。
让Model和ViewModel之间存在一些联系是否是一个好的做法?
不,最好的做法就是,Model和ViewModel之间完全独立。
我们是否总是需要创建ViewModel?如果View不包含任何展示的逻辑,而且只需要将Model数据原样展示出来的时候该怎么办?
我们应该总是创建ViewModel,每一个View都应该拥有它自己的ViewModel,即使ViewModel包含的属性和Model一样。
我们来讨论一个场景,View没有包含展示逻辑,View需要将Model数据原样展示。假设这次我们没有创建ViewModel。
那么,问题来了,如果在将来的需求中,我们被要求在UI中展示一些新数据或者我们被要求加入一些展示逻辑,我们也许需要一个全新的UI来达成要求了!
因此,我们从一开始就做好准备,创建ViewModel是一个更好的选择。在这个情况里,刚开始的ViewModel和Model几乎一模一样。
在这个实验中我们将在View中展示Employee的List。
从EmployeeViewModel中移除UserName属性。
public class EmployeeViewModel
{
public string EmployeeName { get; set; }
public string Salary { get; set; }
public string SalaryColor { get; set; }
}
在ViewModel文件夹里面新建类EmployeeListViewModel,如下:
public class EmployeeListViewModel
{
public List<employeeviewmodel> Employees { get; set; }
public string UserName { get; set; }
}
让MyView.cshtml成为EmployeeListViewModel的强类型视图。
@using WebApplication1.ViewModels
@model EmployeeListViewModel
<body>
Hello @Model.UserName
<hr />
<div>
<table>
<tr>
<th>Employee Name</th>
<th>Salary</th>
</tr>
@foreach (EmployeeViewModel item in Model.Employees)
{
<tr>
<td>@item.EmployeeName</td>
<td style="background-color:@item.SalaryColor">@item.Salary</td>
</tr>
}
</table>
</div>
</body>
在这个实验中,我们将会把我们的工程带入到下一个水准层次。我们将会添加业务层,创建EmployeeBusinessLayer 类,类中包含GetEmployees方法。
public class EmployeeBusinessLayer
{
public List<employee> GetEmployees()
{
List<employee> employees = new List<employee>();
Employee emp = new Employee();
emp.FirstName = "johnson";
emp.LastName = " fernandes";
emp.Salary = 14000;
employees.Add(emp);
emp = new Employee();
emp.FirstName = "michael";
emp.LastName = "jackson";
emp.Salary = 16000;
employees.Add(emp);
emp = new Employee();
emp.FirstName = "robert";
emp.LastName = " pattinson";
emp.Salary = 20000;
employees.Add(emp);
return employees;
}
}
public ActionResult GetView()
{
EmployeeListViewModel employeeListViewModel = new EmployeeListViewModel();
EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
List<employee> employees = empBal.GetEmployees();
List<employeeviewmodel> empViewModels = new List<employeeviewmodel>();
foreach (Employee emp in employees)
{
EmployeeViewModel empViewModel = new EmployeeViewModel();
empViewModel.EmployeeName = emp.FirstName + " " + emp.LastName;
empViewModel.Salary = emp.Salary.ToString("C");
if (emp.Salary > 15000)
{
empViewModel.SalaryColor = "yellow";
}
else
{
empViewModel.SalaryColor = "green";
}
empViewModels.Add(empViewModel);
}
employeeListViewModel.Employees = empViewModels;
employeeListViewModel.UserName = "Admin";
return View("MyView", employeeListViewModel);
}
按下F5,执行程序。
View可以作为List的强类型视图吗?
可以。
为什么单独新建EmployeeListViewModel类,不直接让View作为List的强类型视图呢?
如果我们直接使用List而不是EmployeeListViewModel,这样做有两个问题:
1.未来的展示逻辑的管理问题。
2.UserName不是跟Employee个体关联的,跟View关联的。
为什么将UserName属性从EmployeeViewModel 转移到EmployeeListViewModel中?
UserName不是Employee的属性,所以不需要再EmployeeViewModel中包含UserName属性。
我们结束了第2天的内容,在第3天我们将会把我们的工程带入另一个版本。