7 天玩转 ASP.NET MVC — 第 7 天
目录
0. 前言
今天是开心的一天。因为我们终于来到了系列学习的最后一节。我相信你喜欢之前的课程,并从中学到了许多。
1. Lab 32 — 让项目有组织性
这个实验确切地讲无关任何新的功能。它只是使项目更有结构性和系统化。
第一步:创建解决方案文件夹
右击解决方案,然后选择 Add -> New Solution Folder。
将文件夹的名称改为「View And Controller」。现在重复这个步骤,创建多个相似的文件夹,分别命名为「Model」,「ViewModel」,「Data Access Layer」。
第二步:创建数据访问层项目
右击 Data Access Layer 文件夹,然后创建一个新的类库项目,命名为「DataAccessLayer」。
第三步:创建业务层和业务实体层项目
在 Model 文件夹下创建两个类库项目,分别命名为「BusinessLayer」 和 「BusinessEntities」。
第四步:创建 ViewModel 项目
在 ViewModel 文件夹下创建一个新的类库项目,命名为「ViewModel」。
第五步:添加引用
首先右击每一个项目,然后选择 Add -> Reference,选择如下引用。
-
对于 DataAccessLayer,选择 BusinessEntities。
-
对于 BusinessLayer,选择 DataAccessLayer 和 BusinessEntities。
-
对于 MVC Web Application,选择 BusinessLayer,BusinessEntities 和 ViewModel。
-
对于 BusinessEntities,选择 System.ComponentModel.DataAnnotations。
第六步:设置项目
- 从 MVC 项目中的 DataAccessLayer 文件夹中复制 SalesERPDAL.cs 文件到新创建的 Data Access Layer 类库项目中。
-
从 MVC 项目中移除 DataAccessLayer 文件夹。
-
从 MVC 项目的 Model 文件夹下复制 Employee.cs,UserDetails.cs 和 UserStatus.cs 文件到新创建的 BusinessEntities 类库项目中。
-
从 MVC 项目的 Model 文件下复制 EmployeeBusinessLayer.cs 文件到新创建的 BusinessLayer 类库项目中。
-
从 MVC 项目中移除 Model 文件夹。
-
从 MVC 项目的 ViewModels 文件夹下复制所有类到新创建的 View Model 类库项目中。
-
从 MVC 项目中移除 ViewModels 文件夹。
-
将 MVC 项目,即 WebApplication1 移到「View And Controller」解决方案文件夹。
第七步:Build 项目
选择菜单栏的 Build -> Build Solution。你将会得到如下的错误信息。
第八步:解决错误
-
向 ViewModel 项目中添加 System.Web 引用。
-
在 DataAccessLayer 和 BusinessLayer 项目中运用 Nuget Manager,安装 Entity Framework。(如果你对 Nuget Manager 感到困惑,建议你看一下第 3 天的课程)
注:业务层需要引用 Entity Framework 是因为 BusinessLayer 直接与 DataAccessLayer 相关联。在一个正确的架构中,业务层不应该与数据访问层直接关联。我们可以通过 Repository 来完成这个目的。
- 从 MVC 项目中移除 EntityFramework。
步骤如下。
-
右击 MVC 项目,选择「Manage Nuge Packages」选项。
-
在左侧区域的「Manage Nuget Packages」对话框下选择「Installed Packages」。
-
右击区域将会显示之前下载过的所有包。选择 EntityFramework,然后点击 Uninstall。
第九步: Build 解决方案
你将会看到如下错误。
第十步:解决错误
现在,我们在 MVC 项目中既没有 SalesERPDAL 引用,也没有 Entity Framework 引用。添加这些引用不是一个最佳实践。作为最佳实践,控制器不应该与数据访问层直接相关联。
- 在 DataAccessLayer 项目中创建一个新的类,命名为 DatabaseSettings,它有一个静态方法,命名为 SetDatabase。
using System.Data.Entity;
using WebApplication1.DataAccessLayer;
namespace DataAccessLayer
{
public class DatabaseSettings
{
public static void SetDatabase()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<SalesERPDAL>());<saleserpdal>
}
}
}
- 在 BusinessLayer 项目中创建一个新的类,命名为 BusinessSettings,它有一个静态方法,命名为 Setbusiness。
using DataAccessLayer;
namespace BusinessLayer
{
public class BusinessSettings
{
public static void SetBusiness()
{
DatabaseSettings.SetDatabase();
}
}
}
- 在 Global.asax 中运用语句来解决错误,并且移除 Database.SetInitializer 语句。触发 BusinessSettings.SetBusiness 函数。
using BusinessLayer;
...
...
BundleConfig.RegisterBundles(BundleTable.Bundles);
BusinessSettings.SetBusiness();
再次 Bulid 应用,这一次将会成功。
Lab 32 的 Q&A
什么是解决方案文件夹?
Solution 文件夹只是逻辑文件夹。实际上,它不会在物理硬盘上被创建。它的目的只是为了让解决方案更加系统化。
2. Lab 33 — 创建单页应用 — Part 1 — 设置
现在,我们将不会对已存在的控制器和视图做出改变。我们将会为此实验创建一个全新的控制器和视图。做这些步骤的理由是:
-
使已存在的项目不受影响,因此你可以将之前的版本和单一页版本进行对比,更好地学习。
-
实现并理解 ASP.NET MVC 中的另一个概念,即 Areas。
正如我所说的,我们将会创建新的控制器,视图和视图模型。
只有以下组件会被重复利用。
-
已存在的 Business Layer。
-
已存在的 Data Access Layer。
-
已存在的 Business Entities。
-
Authentication 和异常过滤器。
-
FooterViewModel。
-
Footer.cshtml。
第一步:创建一个新的 Area
右击项目,然后选择 Add -> Area。一个对话框将会弹出,输入名称为 SPA,然后点击 Add。
它将在项目中创建一个新的文件夹架构,如下所示。
很明显,我们并不需要 Area 下的 Model 文件夹,删除即可。
什么是 Areas?
Areas 是实现 ASP.NET MVC 项目模块化的一种简单方式。
每一个项目都由多个模块组成。例如:账户模块,顾客关系模块,付款模块,等等。
在传统的应用开发风格中,我们经常使用「Folders」来达到这个目的。我们在一个单独项目中创建多个文件夹。每一个文件夹代表一个模块。我们会把各自模块的文件放在各自的文件夹中。
当使用 ASP.NET MVC 时,这种自定义文件夹将会遇到大问题。
让我们来讨论下在 ASP.NET MVC 中,运用简单的文件夹来实现模块。
-
DataAccessLayer,BusinessLayer,BusinessEntities 和 ViewModels 不会产生任何问题。他们仅仅是简单的类,所以可以被放置到任意地方。
-
我们不能将控制器随意放置。它必须放置在 Controller 文件夹下。但是这不会成为一个大问题,因为从 MVC 4 开始,控制器的位置限制就已经被舍弃了。现在我们可以将其放置到任意想要放置的地方。
-
不幸的是,对于视图是不可行的。所有的视图都必须放置在「/Views/ControllerName」或者「/Views/Shared」文件夹下。
第二步:创建所需的 ViewModels
在 ViewModel 类库项目中创建一个新的文件夹,命名为 SPA,然后创建一个 ViewModel,命名为 MainViewModel。
using WebApplication1.ViewModels;
namespace WebApplication1.ViewModels.SPA
{
public class MainViewModel
{
public string UserName { get; set; }
public FooterViewModel FooterData { get; set; }//New Property
}
}
第三步:创建 Index 行为方法
在 MainController 中引用如下语句。
using WebApplication1.ViewModels.SPA;
using OldViewModel=WebApplication1.ViewModels;
在 MainController 中创建一个新的行为方法,命名为 Index。
public ActionResult Index()
{
MainViewModel v = new MainViewModel();
v.UserName = User.Identity.Name;
v.FooterData = new OldViewModel.FooterViewModel();
v.FooterData.CompanyName = "StepByStepSchools";//Can be set to dynamic value
v.FooterData.Year = DateTime.Now.Year.ToString();
return View("Index", v);
}
正如你所见,为 WebApplication1.ViewModels 命名空间增加了一个 OldViewModels 别名。现在,我们可以使用 OldViewModel.ClassName,而不是 WebApplication1.ViewModels.ClassName。
不指定别名会导致歧义错误。在命名空间 WebApplication1.ViewModels.SPA 和 WebApplication1.ViewModels 中,存在相似的类。
第四步:创建 Index 视图
创建一个与上述 Index 方法相联系的视图。
@using WebApplication1.ViewModels.SPA
@model MainViewModel
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Employee Single Page Application</title>
第五步:执行并测试应用
按下 F5,然后执行应用。完成登录操作,然后导航到 MainController 中的 Index 行为。
Lab 33 的 Q&A
为什么在控制器名称之前需要 SPA 关键字?
当我们向 ASP.NET MVC 中添加 Area 时,Visual Studio 就会创建一个文件,命名为 [AreaName]AreaRegistration.cs,它包含一个类,这个类定义了 AreaName 属性和 RegisterArea 方法,该方法用于为 Area 注册路由信息。
在我们的例子中,你可以发现名称为 SpaAreaRegistration.cs 文件,它被放置在「~/Areas/Spa」文件夹下。SpaAreaRegistration 类的 RegisterArea 方法包含如下代码。
context.MapRoute(
"SPA_default",
"SPA/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
这就解释了为什么我们需要在控制器名称前添加 SPA 关键字。
SpaAreaRegistration 类中的 RegisterArea 方法如何被触发?
打开 Global.asax 文件,Application_Start 的第一行如下。
AreaRegistration.RegisterAllAreas();
RegisterAllAreas 方法查找到应用中所有来自于 AreaRegistration 的类型,并调用它们的每一个 RegisterArea 方法。
我们可以不使用 SPA 来触发 MainController 动作吗?
让我们简化一下问题:URL 为「localhost:8870/Main/Index」还会起作用吗?
答案是肯定的。AreaRegistration 类创建一个新的路由,但是不会删除其它路由。路由在 RouteConfig 类中定义,仍然起作用。正如我之前所说的那样,控制器的位置没有限制。因此它仍能起作用,但是输出不会被正确地呈现,因为它将不能查找到视图。我建议你执行一下应用,试一试。
3. Lab 34 — 创建单页应用 — Part 2 — 展示 Employees
第一步:为展示已存在的 Employees 创建 ViewModel
在 ViewModel 类库的 SPA 文件夹下创建两个新的 ViewModel 类,命名为 EmployeeViewModel 和 EmployeeListViewModel。
namespace WebApplication1.ViewModels.SPA
{
public class EmployeeViewModel
{
public string EmployeeName { get; set; }
public string Salary { get; set; }
public string SalaryColor { get; set; }
}
}
namespace WebApplication1.ViewModels.SPA
{
public class EmployeeListViewModel
{
public List<employeeviewmodel> Employees { get; set; }
}
}
注:两个 ViewModel 实际上都是 Non-Spa 应用的 ViewModel 复制品。唯一的区别是不需要 BaseViewModel 了。
第二步:创建 EmployeeList Index
在 MainController 下创建一个新的行为方法,命名为 EmployeeList。
public ActionResult EmployeeList()
{
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.Value.ToString("C");
if (emp.Salary > 15000)
{
empViewModel.SalaryColor = "yellow";
}
else
{
empViewModel.SalaryColor = "green";
}
empViewModels.Add(empViewModel);
}
employeeListViewModel.Employees = empViewModels;
return View("EmployeeList", employeeListViewModel);
}
注:HeaderFooterFilter 不再需要。
第三步:创建 AddNewLink 分部视图
这次我们不能运用之前的 AddNewLink 分部视图,因为之前的标签设定会导致全部的刷新。我们的目标是创建一个「Single Page Application」,因此我们不应该有全部的刷新。
在「~/Areas/Spa/Views/Main」文件夹下创建一个新的分部视图,命名为 AddNewLink.cshtml。
<a href="#" onclick="OpenAddNew();">Add New</a>
第四步:创建 AddNewLink 行为方法
在 MainController 下创建一个新的行为方法,命名为 GetAddNewLink。
public ActionResult GetAddNewLink()
{
if (Convert.ToBoolean(Session["IsAdmin"]))
{
return PartialView("AddNewLink");
}
else
{
return new EmptyResult();
}
}
第五步:创建 EmployeeList 视图
在「~/Areas/Spa/Views/Main」文件夹下创建一个新的分部视图,命名为 EmployeeList。
@using WebApplication1.ViewModels.SPA
@model EmployeeListViewModel
<div>
@{
Html.RenderAction("GetAddNewLink");
}
<table border="1" id="EmployeeTable">
<tr>
<th>Employee Name</th>
第六步:设置 EmployeeList 为初始页
在「~/Areas/Spa/Views/Main」文件夹下打开 Index.cshtml 文件,在 DivOptions div 中包含 EmployeeList 行为结果。
第七步:执行并测试
按下 F5,并执行应用。
4. Lab 35 — 创建单页应用 — Part 3 — 创建 Employee
第一步:创建 AddNew 视图模型
在 ViewModel 类库项目的 SPA 文件夹下创建一个新的视图模型,命名为 CreateEmployeeViewModel。
namespace WebApplication1.ViewModels.SPA
{
public class CreateEmployeeViewModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Salary { get; set; }
}
}
第二步:创建 AddNew 行为方法
在 MainController 中引用如下声明。
using WebApplication1.Filters;
在 MainController 下创建 AddNew 行为方法如下。
[AdminFilter]
public ActionResult AddNew()
{
CreateEmployeeViewModel v = new CreateEmployeeViewModel();
return PartialView("CreateEmployee", v);
}
第三步:创建 CreateEmployee 分部视图
在「~/Areas/Spa/Views/Main」文件夹下创建一个新的分部视图,命名为 CreateEmployee。
@using WebApplication1.ViewModels.SPA
@model CreateEmployeeViewModel
<div>
<table>
<tr>
<td>
First Name:
</td>
第四步:引入 JQuery UI
右击项目,然后选择「Manage Nuget Manager」。搜索「JQuery UI」。
安装 JQuery UI。它将会向项目中增加几个 JavaScript.js 和 Stylesheet.css 文件。
第五步:包含 JQuery UI
打开「~/Areas/Spa/Views/Main/Index.cshtml」文件,然后包含 JQuery.js,JQueryUI.js 和 All.css 文件。这些文件作为 JQuery UI 的一部分被 Nuget Manager 所添加。
<head>
<meta name="viewport" content="width=device-width" />
<script src="~/Scripts/jquery-1.8.0.js"></script>
<script src="~/Scripts/jquery-ui-1.11.4.js"></script>
<title>Employee Single Page Application</title>
<link href="~/Content/themes/base/all.css" rel="stylesheet" />
...
第六步:实现 OpenAddNew 函数
在「~/Areas/Spa/Views/Main/Index.cshtml」下创建一个新的 JavaScript 函数,命名为 OpenAddNew。
<script>
function OpenAddNew() {
$.get("/SPA/Main/AddNew").then
(
function (r) {
$("<div id='DivCreateEmployee'></div>").html(r).
dialog({
width: 'auto', height: 'auto', modal: true, title: "Create New Employee",
close: function () {
$('#DivCreateEmployee').remove();
}
});
}
);
}
</script>
第七步:执行并测试
按下 F5,执行应用。
完成登录操作,然后导航到 MainController 的 Index 动作。然后点击 Add New 超链接。
第八步:创建 ResetForm 函数
打开 CreateEmployee.cshtml 视图。在顶部创建 ResetForm 函数。
@model CreateEmployeeViewModel
<script>
function ResetForm() {
document.getElementById('TxtFName').value = "";
document.getElementById('TxtLName').value = "";
document.getElementById('TxtSalary').value = "";
}
</script>
第九步:创建 CancelSave 函数
打开 CreateEmployee.cshtml 视图。在顶部创建 CancelSave 函数。
document.getElementById('TxtSalary').value = "";
}
function CancelSave() {
$('#DivCreateEmployee').dialog('close');
}
接下来是什么?
在我们继续第十步之前,我们先要了解接下来应该做什么。
-
终端用户点击 Save Employee 按钮。
-
在客户端对控件的值进行合法性验证。
-
如果所有的值都是合法的,就被传输到服务器端。
-
在数据库中存储一个新的 Employee 记录。
-
CreateEmployee 对话框关闭。
-
Grid 更新 Employee 记录。
开始计划
- 认证
对于认证,我们可以运用在 Non-Spa 项目中使用过的认证 JavaScript 代码。
- 保存 Employee
我们将会创建一个 MVC 行为方法,用于保存 Employee,然后通过 JQuery Ajax 来触发它。
- 将数据从客户端传输到服务器端
之前通过 Form 标签和 Submit 按钮能轻松并自动的处理这件事。现在我们不能使用该方法,因为它将会带来全部的刷新。取而代之的是,我们利用 JQuery Ajax 的方式,这种方式允许我们触发服务器端的 MVC 行为方法,并且不会全部刷新页面。
现在一个重要的问题是,如果调用是手动的,那么数据是如何通过 JavaScript 传输到 MVC 行为方法中的。
找寻解决方案
理解问题
当你听到数据这个词时,首先映入你脑海的是 JavaScript,.NET,还是其它技术呢?
答案是变量。我们可以利用变量来承载临时数据,然后将其转储在持久性的仓库中,例如数据库。
在我们的例子中,我们用到了两个技术,即 JavaScript 和 ASP.NET MVC。JavaScript 是一个技术,ASP.NET MVC 是另一项技术。
它们不能交换彼此的数据,换句话说,它们不能直接与彼此交换变量。你也许会想知道,为什么它们不能?
它们都有变量。它们都支持数据格式,例如 Float,Int,Char,那么它们为什么不能将变量彼此传输呢?
答案是,它们拥有变量,但是它们不同。.NET 中的整型数据类型没有要求和其它技术的整型数据类型相同。它们也许在大小上不同,或者可能是其它属性。
举一个现实中的有趣例子。每一个人都拥有腿,手,眼睛等。同样,小狗也共同的东西。它们相同吗?显而易见的是,它们不同。人类的眼睛不能被小狗的眼睛所替代,反之亦然。
在所有的技术中,变量的概念是相似的,但是它们却不相同。再一次重复这句话,「.NET 中的整型和 Java 中的整型是有所区别的」。
解决方案 — 一个统一的数据类型
行业已经意识到了这个问题,因此考虑使用在所有技术中相同的数据类型。
String 数据类型正是如此,并且可以承载任何数据。
-
我们可以将整型数据转换为字符串类型,然后将其存储在字符串变量中。
-
我们可以将浮点型数据转换为字符串类型,然后将其存储在字符串变量中。
-
…
-
任何数据类型都可以存储在字符串变量中。
终极的解决方案是「每一次从技术1传输数据到技术2,技术1需要将数据转换为字符串类型,然后传输给技术2,因为这样可以100%确保技术2能够理解该字符串」。
目前在行业中已经形成标准。
问题 — 如何处理复杂的数据?
如果这种字符串传输方式成为标准,那么复杂的数据如何传输呢?如果我们想将 Employee 的信息从一个技术传输给另一个呢?
在 .NET 中,「类和对象」被用于呈现复杂数据。看一下如下例子。
Employee e=new Employee();
e.EmpName= "Sukesh";
e.Address= "Mumbai";
在 JavaScript 中,「JavaScript 对象」用于呈现复杂数据。看一下如下例子。
var e={
EmpName= "Sukesh",
Address= "Mumbai"
};
将复杂的数据从 .NET 传输给其它技术,意味着类对象从 .NET 传输给其它技术,将复杂数据从 JavaScript 传输给其它技术,意味着 JavaScript 对象从 JavaScript 传输给其它技术。
直接传输是不可能的。按照之前的标准,我们先要将 .NET 对象或者 JavaScript 对象转换为字符串类型,然后再发送。
解决方案:一个统一的数据格式标准
就像之前一样,行业也指出一个统一的数据格式标准。说它是统一的,意味着所有人要发送数据之前都需要呈现出他们的数据。因此 XML 应运而生。
所有技术都将数据转换为 XML 格式,然后将其发送给其它技术。就像字符串一样,XML 也被当做一种标准格式,因此每一个技术都知道它。
C# 代码创建的 Employee 对象也能通过 XML 格式这样呈现。
<employee></employee>
<Employee>
<EmpName>Sukesh</EmpName>
<Address>Mumbai</Address>
</Employee>
因此解决方案是,「技术1把复杂的数据转换为 XML 格式的字符串数据,然后将其传送给技术2」。
问题 — XML 格式的问题
XML 格式有如下的问题。
-
XML 格式增加了需要发送的字符串大小。大小越大,意味着转化需要更多的时间,即意味着更差的性能。
-
第二个原因,也是最主要的原因:XML 很难创建和解析。
让我们来探讨一下。
-
正如我们之前所说,每一个技术都需要基于数据创建 XML 字符串,然后传输这个 XML 字符串。现在利用 XML Serializers,用 C# 基于 .NET 对象来创建 XML 字符串是容易的。但是对于 JavaScript 而言呢?实际上, JavaScript 既没有序列化的概念,也没有 XML 操作库用于使用。因此当 JavaScript 传输数据给其它技术时,我们需要在 JavaScript 对象中手动创建 XML 字符串。这是一个很艰难的任务。
-
当一个技术从另一个技术那里接收到数据时,通常都是 XML 格式的字符串。现在通过 XML Deserializers,用 C# 将 XML 字符串解析并创建 .NET 对象是容易的。但是对于 JavaScript 而言呢?实际上, JavaScript 既没有反序列化的概念,也没有 XML 操作库用于使用。因此当 JavaScript 解析 XML 字符串时,这是一个很艰难的任务。
解决方案 — JSON
为了解决 XML 格式带来的问题,行业想出一个新的格式,称为 JSON。它是 「JavaScript Object Notation」的缩写。
利用 C# 来创建 Employee 对象,可以通过如下代码来呈现 JSON 格式。
{
EmpName: "Sukesh",
Address: "Mumbai"
}
JSON 格式展现的数据和 JavaScript 对象相像,因此这种格式被命名为 JSON(JavaScript Object Notation)。
-
正如你所看见的,这比之前更轻量级。
-
有一些完备的函数可用于 JavaScript,即能将 JavaScript 对象转换为 JSON 格式字符串,也能将 JSON 格式字符串解析为 JavaScript 对象。
如下代码展示了如何创建和解析 JSON 字符串。
var e={
EmpName= “Sukesh”,
Address= “Mumbai”
};
var EmployeeJsonString = JSON.stringify(e);//This EmployeeJsonString will be send to other technologies.
var EmployeeJsonString=GetFromOtherTechnology();
var e=JSON.parse(EmployeeJsonString);
alert(e.EmpName);
alert(e.Address);
关闭对话框
我们可以运用 JQuery API 来关闭 CreateEmployee 对话框。
更新 Grid
可以通过如下方式来更新 Grid。
- 通过分部视图
a. 像 CreateEmployee 设计函数一样,创建一个 Grid 的分部视图。
b. 在 EmployeeListView 中创建一个含有 Id 的 Div,然后在里面展示 Grid 的分部视图。
c. 当 Save Employee 按钮被点击时,以分部视图结果的格式更新 Grid,然后用新的 PartialViewResult 来替换内部的 Grid HTML。
我相信迄今为止我们完成的任何一个实验都能给你一个很好的思路来实现这样的目的,因此我们将把其作为一个任务。需要你来亲自完成它。
- 通过手动代码
在这个方法中,MVC 行为方法将会返回 EmployeeViewModel,而不是 EmloyeeListViewModel,这将会被 JavaScript 接收并运用 JavaScript 来创建一个新的行,然后手动插入到 Grid 中。EmployeeViewModel 将会以 JSON 字符串形式来从 MVC 行为方法中传输到 JavaScript。
回到实验
第十步:创建 SaveEmployee Action
在 MainController 中创建一个新的行为方法,命名为 SaveEmployee。
[AdminFilter]
public ActionResult SaveEmployee(Employee emp)
{
EmployeeBusinessLayer empBal = new EmployeeBusinessLayer();
empBal.SaveEmployee(emp);
EmployeeViewModel empViewModel = new EmployeeViewModel();
empViewModel.EmployeeName = emp.FirstName + " " + emp.LastName;
empViewModel.Salary = emp.Salary.Value.ToString("C");
if (emp.Salary > 15000)
{
empViewModel.SalaryColor = "yellow";
}
else
{
empViewModel.SalaryColor = "green";
}
return Json(empViewModel);
}
现在使用 JSON 的方式来讲字符串从 MVC 行为方法传输到 JavaScript。
第十一步:包含 Validation.js
在上述实验中包含 Validation.js 文件。
@using WebApplication1.ViewModels.SPA
@model CreateEmployeeViewModel
<script src="~/Scripts/Validations.js"></script>
第十二步:创建 SaveEmployee 函数
打开 CreateEmployee.cshtml 视图,在顶部创建 SaveEmployee 函数。
...
...
function SaveEmployee() {
if (IsValid()) {
var e =
{
FirstName: $('#TxtFName').val(),
LastName: $('#TxtLName').val(),
Salary: $('#TxtSalary').val()
};
$.post("/SPA/Main/SaveEmployee",e).then(
function (r) {
var newTr = $('<tr></tr>');
var nameTD = $('<td></td>');
var salaryTD = $('<td></td>');
nameTD.text(r.EmployeeName);
salaryTD.text(r.Salary);
salaryTD.css("background-color", r.SalaryColor);
newTr.append(nameTD);
newTr.append(salaryTD);
$('#EmployeeTable').append(newTr);
$('#DivCreateEmployee').dialog('close');
}
);
}
}
</script>
第十三步:执行并测试
按下 F5,执行应用。
Lab 35 的 Q&A
JSON 方法是用于做什么的?
JSONResult 是 ActionResult 的一个子类。在第六天的学习中,我们谈到了 MVC 请求周期。现在我们再来回顾一下。
在 ActionResult 类中, ExecuteResult 被声明为抽象的。ActionResult 类的所有子类都以自己的方式来定义它。在第一天的学习中,我们谈论过 ViewResult。在 ViewResult 类中,ExecuteResult 方法将会做如下事情。
-
它将会创建 ViewPageActivator 类的对象。
-
它将会选择正确的 ViewEngine,将 ViewPageActivator 对象作为参数传输给 ViewEngine 的构造器。ViewEngine 将会创建 View 类的对象。
-
它将会触发视图的 RenderView 方法,用于渲染最终 HTML 输出的响应。
当它来自于 JsonResult,ExecuteResult 方法将会:
-
设置响应内容的类型为「Application/Json」。
-
运用 JavaScript Serializer,它将会把传输的数据转换为 JSON 格式的字符串。
-
为响应流书写最终的 JSON 格式字符串。
5. Lab 36 — 创建单页应用 — Part 4 — 批量上传
第一步:创建 SpaBulkUploadController
创建一个新的 AsyncController,称作 SpaBulkUploadController。
namespace WebApplication1.Areas.SPA.Controllers
{
public class SpaBulkUploadController : AsyncController
{
}
}
第二步:创建 Index 方法
在上述的控制器中创建一个新的行为方法,称为 Index。
[AdminFilter]
public ActionResult Index()
{
return PartialView("Index");
}
第三步:创建 Index 分部视图
在「~/Areas/Spa/Views/SpaBulkUpload」中创建一个新的分部视图,称为 Index。
<div>
Select File : <input type="file" name="fileUpload" id="MyFileUploader" value="" />
<input type="submit" name="name" value="Upload" onclick="Upload();" />
</div>
第四步:创建 OpenBulkUpload 方法
在「~/Areas/Spa/Views/Main」文件夹中打开 Index.cshtml,然后创建一个 JavaScript 方法,称为 Index.cshtml。
function OpenBulkUpload() {
$.get("/SPA/SpaBulkUpload/Index").then
(
function (r) {
$("<div id='DivBulkUpload'></div>").html(r).dialog({ width: 'auto', height: 'auto', modal: true, title: "Create New Employee",
close: function () {
$('#DivBulkUpload').remove();
} });
}
);
}
</script>
</head>
<body>
<div style="text-align:right">
第五步:执行并测试
按下 F5,并执行应用。完成登录操作。导航到 Main 控制器下的 Index 行为,然后点击 BulkUpload 链接。
第六步:创建 FileUploadViewModel
在 ViewModel 类库项目的 SPA 文件夹下创建一个新的视图模型类,称为 FileUploadViewModel。
namespace WebApplication1.ViewModels.SPA
{
public class FileUploadViewModel
{
public HttpPostedFileBase fileUpload { get; set; }
}
}
第七步:创建 Upload 行为
在 SpaBulkUploadController 下创建一个新的行为方法,称为 Upload。
[AdminFilter]
public async Task<actionresult> Upload(FileUploadViewModel model)
{
int t1 = Thread.CurrentThread.ManagedThreadId;
List<employee> employees = await Task.Factory.StartNew<list<employee>>
(() => GetEmployees(model));
int t2 = Thread.CurrentThread.ManagedThreadId;
EmployeeBusinessLayer bal = new EmployeeBusinessLayer();
bal.UploadEmployees(employees);
EmployeeListViewModel vm = new EmployeeListViewModel();
vm.Employees = new List<employeeviewmodel>();
foreach (Employee item in employees)
{
EmployeeViewModel evm = new EmployeeViewModel();
evm.EmployeeName = item.FirstName + " " + item.LastName;
evm.Salary = item.Salary.Value.ToString("C");
if (item.Salary > 15000)
{
evm.SalaryColor = "yellow";
}
else
{
evm.SalaryColor = "green";
}
vm.Employees.Add(evm);
}
return Json(vm);
}
private List<employee> GetEmployees(FileUploadViewModel model)
{
List<employee> employees = new List<employee>();
StreamReader csvreader = new StreamReader(model.fileUpload.InputStream);
csvreader.ReadLine();// Assuming first line is header
while (!csvreader.EndOfStream)
{
var line = csvreader.ReadLine();
var values = line.Split(',');//Values are comma separated
Employee e = new Employee();
e.FirstName = values[0];
e.LastName = values[1];
e.Salary = int.Parse(values[2]);
employees.Add(e);
}
return employees;
}
正如你所看见的,这次我们返回的是 JsonResult,而不是重定向。
第八步:创建 Upload 函数
在「~/Areas/Spa/Views/SpaBulkUpload」文件夹下打开 Index 视图。然后创建一个 JavaScript 函数,称为 Upload。
<script>
function Upload() {
debugger;
var fd = new FormData();
var file = $('#MyFileUploader')[0];
fd.append("fileUpload", file.files[0]);
$.ajax({
url: "/Spa/SpaBulkUpload/Upload",
type: 'POST',
contentType: false,
processData: false,
data: fd
}).then(function (e) {
debugger;
for (i = 0; i < e.Employees.length; i++)
{
var newTr = $('<tr></tr>');
var nameTD = $('<td></td>');
var salaryTD = $('<td></td>');
nameTD.text(e.Employees[i].EmployeeName);
salaryTD.text(e.Employees[i].Salary);
salaryTD.css("background-color", e.Employees[i].SalaryColor);
newTr.append(nameTD);
newTr.append(salaryTD);
$('#EmployeeTable').append(newTr);
}
$('#DivBulkUpload').dialog('close');
});
}
</script>
第九步:执行并测试
创建一个文本文件,如下所示。
按下 F5,并执行应用。
6. 总结
这里,我们完成了 7 天玩转 ASP.NET MVC 的系列学习。我们已经运用 ASP.NET MVC 的功能完成了一个简单的项目。我们也在其中穿插讨论了许多详细的理论概念。
学无止境,虽然 ASP.NET MVC 的系列学习已经告终,但是后续的深入学习,还需要靠脚踏实地,不断地实践和反思。
希望每一个人都享受其中,乐于学习,不断成长。