正如标题「7 天玩儿转 ASP.NET MVC」所言,这是个系列文章,所以将会向大家陆续推出 7 篇。设想一下,一天一篇,你将从一个愉快的周一开始阅读,然后在周末成为一个 ASP.NET MVC 开发者,这很酷吧!
第一天是热身运动,这篇我们将围绕 Controller 和 Views 实践两个 Labs。在每个 Lab 之中都伴随着一些 Question 和 Answer。所以文章的主体框架是 Lab 和 Q&A。
我们只需要 Visual Studio 工具就可以开始 ASP.NET MVC 之旅。你可以通过 Visual Studio 官网 下载所需版本。
许多 ASP.NET 开发者第一次接触 MVC 时会认为它是区别于 Webforms 的,会认为它是一个全新的技术。确实如此,如果说 ASP.NET Webforms 是一个创建 Web Application 的框架,那么 ASP.NET MVC 就是一个更棒的架构体系,它以一种更合理的方式来组织和放置我们的代码。
不可否认的是,ASP.NET Webforms 在过去十多年都非常流行,从 VB 开始,微软就开始传经布道 RAD 和 Visual Programming 的方法。连微软的开发工具都称作 Visual Studio,可见一斑。
通过使用 Visual Studio,开发者能通过拖拽的方式将 UI 构件放置到设计界面,Visual Studio 便自动为这些构件产生 C# 或者 VB.NET 代码。这些可被称作为「Code Behind」 ,在「Code Behind」区域内,开发者可以来写一些逻辑代码。
所以微软的 Visual RAD 方法实际上就是两件事,UI 和 Code Behind。例如 ASP.NET Webforms,有 ASPX 和 ASPX.CS;对于 WPF,有 XAML 和 XAML.CS,不一而足。
既然 ASP.NET Webform 如此成功,为什么微软还要考虑创建 ASP.NET MVC 呢?主要原因是出在 ASP.NET Webform 的性能上。可以从两个性能角度考虑:
我们来尝试解释为什么 ASP.NET Webforms 的响应时间很慢。通过一个小的负载测试,我们发现 ASP.NET MVC 比 ASP.NET Webforms 快2倍左右。
假如 ASPX 有这样一段关于 Text Box 的简单代码。
<asp:TextBox ID="TextBox1" runat="server">
然后为 Text Box 写一些后台逻辑代码,为它进行赋值和背景色的操作。
protected void Page_Load(object sender, EventArgs e)
{
TextBox1.Text = "Make it simple";
TextBox1.BackColor = Color.Aqua;
}
运行程序后,将会在 HTML 页面看到输出。
如果你查看 HTML 的源代码,它是这样的:
<input name="TextBox1" type="text" value="Make it simple" id="TextBox1" style="background-color:Aqua;" />
现在停止阅读,闭上眼睛思考片刻:
事实上,每一次请求都会有一次 Conversion 逻辑在运行,它用于转换 HTML 输出的控件。当我们输出的控件是一些 Grids 表格, Tree View 树形控件等一些复杂的 HTML 页面时,这种转换将会变得更耗时,并且非常复杂,使得等待时间更长。
为了解决这个问题,开始抛弃「Code Behind」吧,写一些纯净的 HTML。
长期从事 ASP.NET 的开发者一定非常熟悉 Viewstate,它能够自动保存 post 返回的状态并且减少了开发时间。但是正是由于这种开发时间的减少带来了巨大的代价,Viewstate 增加了页面的大小。通过负载测试,对比 ASP.NET MVC,我们发现 Viewstate 增加了近两倍的页面大小。
大小的增加是由于 Viewstate 产生了额外的字节。下图是 Viewstate 的截图快照。也许有人会反驳放弃 Viewstate 的观点,但是对于开发者而言,如果有其它选择,他们会尝试其它选择。
为了解决这个问题,开始抛弃 Server 控件吧。
因为我们都是通过 ASP.NET 控件和后台代码来编写应用,所以我们没有办法来决定什么样的 HTML 被输出,也不知道它们的效率是如何的。例如我们可以看一段 ASPX 代码,你可以试猜想一下什么样的 HTML 将被产生。
<asp:Label ID="Label1" runat="server" Text="I am label">
<asp:Literal ID="Literal1" runat="server" Text="I am a literal">
<asp:Panel ID="Panel1" runat="server" Text="I am a panel">
Label 控件会生成 DIV 还是 SPAN 标签?如果你运行一下代码会发现,Label 控件被转换为一个 SPAN,Literal 控件被转换为一个简单的 Text,而 Panel 控件则被转换为一个 DIV。
<span id="Label1">I am label</span>
I am literal
<div id="Panel1">I am panel</div>
因此与其使用 Server 控件来生成 HTML,倒不如直接手写一些 HTML 来实现一些 HTML 控件。
所以,解决方案便是,不要再使用 Server 控件,直接编写一些 HTML。
直接编写 HTML 也带来一些好处,这使得 Web 设计者能够和开发团队紧密工作。Web 设计者可以使用 DreamWeaver 或者 FrontPage 来独立设计并获取 HTML 代码。如果我们使用了服务器控件,这些设计工具便不能很好地识别。
如果你看到过专业的 ASP.NET Webform 项目,你会发现它的后台代码经常会囊括很大的代码量,并且代码真的很复杂。这些代码继承「System.Web.UI.Page」类,而这个类也不是一个常规的类,它不可以被重用或者实例化。换言之,你永远无法在一个 Webform 类里做这样的事情:
WebForm1 obj = new WebForm1(); obj.Button1_Click();
因为「WebForm」类没有「Request」和「Respnse」对象是不能够被实例化的。可以从下面的代码中看到「WebForm」的「ButtonClick」事件代码,从这段代码中你就可以看到为什么实例化会很难实现。
protected void Button1_Click(object sender, EventArgs e)
{
// The logic which you want to reuse and invoke
}
基于之前所说的后台代码无法直接实例化,所以可想而知的是,单元测试或自动化测试都是非常困难的。开发者只能自动运行应用进行手动测试。
通过之前的分析,我们发现 ASP.NET Webforms 的两个关键因素:「Code Behind」和「Server Controls」,即后台代码和服务器控件。它们影响的代价和解决方案如下图所示:
我们需要的解决方案便是将后台代码迁移到独立的简易类库,并且放弃 ASP.NET Server 控件,写一些简单的 HTML 页面。
简而言之,解决方案就像如下图的形象说明,将 Web Form「减肥」为 MVC。
正如我们之前所讨论的,后台任务和服务器控件是问题的根源。所以如果你看一下当前的 Webform 架构体系就会发现,开发者使用的几乎就是 3 层架构体系。
这 3 层架构体系包含了 UI,这个 UI 实际上包含了 ASPX 和 后台代码。
ASP.NET MVC 包含了三部分,即 Model,View 和 Controller。Controller 负责后台逻辑代码。View 是纯净的 HTML 页面。Model 是中间数据层。
这里有两个主要的改变,其一是 View 变为简单的 HTML 页面,其二是后台代码转换为简单的 .NET 类,我们称之为 Controller。
ASP.NET MVC 的请求流如下:
第一步:首先触发 Controller。
第二步:依据行为 Controller 创建 Model 对象。Model 反过来通过调用数据接口层来向 Model 对象填充数据。
第三步:填充完的 Model 对象将数据传输给 View 层,然后展示出来。
现在我们已经理解 ASP.NET MVC 的各个组件了。接下来开始深入学习每个组件,并且做一些小的 Lab。我们将从 Controller 开始,因为它是 MVC 架构体系的核心。
为了理解Controller,我们首先要理解「用户交互逻辑」的概念。
什么是交互逻辑?
你是否仔细想过,当终端用户在浏览器上敲下一个 URL 并按下回车后,会发生什么事情?
浏览器发送一个请求给服务器,服务器再做出响应。
通过这种方式的请求,客户端尝试与服务器进行交互。服务器能够返回响应是因为服务器已经有了一些逻辑来处理这些请求。这个逻辑实际上承载了用户的请求以及用户与服务器的交互行为,这便是用户交互逻辑。
存在这样一种可能,服务器返回的响应是一个 HTML 响应。这个 HTML 包含了一些输入框或者提交按钮组件。
当点击「SaveCustomer」按钮时,会发生什么事情?
如果你的回答是「一些事件处理器来处理这个按钮点击」,那么就错了。
现实是 Web 编程是没有事件的概念的。一种情况是,微软为 ASP.NET Webforms 为我们写了一些代码,并给人一种事件驱动编程的感觉。实际上,这只是一个错觉或者幻想。
当按钮被点击后,一个简单的 HTTP 请求就被发送到服务器上。这次不同的是,「Customer Name」,「Address」和「Age」的值都将伴随着请求被发送。从根本上说,如果发生了请求,那么服务器就会根据已经写好的逻辑发出返回响应,简而言之,服务器上一定存在一些用户交互逻辑。
在 ASP.NET MVC 中,最后一个字母「C」就代表的是 Controller,它是用于处理用户交互逻辑的。
打开 Visual Studio 2013 或更高版本,点击 文件 > 新建 > 项目。
选择 Web 应用,填写应用名称,选择应用路径,点击确定。
选择 MVC 模板文件
点击更改权限,在对话框中选择「无权限」。
点击确定即可。
在资源管理器中,右击「Controller」文件夹,点击 添加 > 控制器。
选择 MVC 5 控制器,点击添加。
将控制器命名为「TestController」,然后点击添加。
有一点非常重要,不要删除「Controller」这个单词,它是控制器的关键字。
打开刚刚创建的「TestController」类,你会发现里面有一个方法叫「Index」,将这个方法删除,然后创建一个新的公开方法,称为「GetString」。
public class TestController : Controller
{
public string GetString()
{
return "Hello World is old now. It's time for wassup bro ;)";
}
}
按下 F5。在地址栏中以「ControllerName/ActionName」的格式输入。但是需要注意的是,不要将控制器的名称「Controller」加上,直接写「Test」即可。
「TestController」是一个类的名称,然而「Test」确是一个控制器的名称。需要注意的是,当你在 URL 中输入控制器的名称时,不要带上单词「Controller」。
Action 方法是一个在控制器里的简单公共方法,它负责接收用户的请求,并返回一些回应。通过上述的例子,可以看到行为方法「GetString」是返回一个字符串回应。
注意:在 ASP.NET Webforms 里,默认返回的都是 HTML。有一种情形是,如果我们想返回一些其它非 HTML 类型的请求,就得要创建 HTTP 处理器,重写内容类型,然后做出回应。这并不是一个简单的任务。但是在 ASP.NET MVC 中是很容易的。你像返回「String」就返回一个「String」,而不需要返回一个完整的 HTML 页面。
查看以下代码。
namespace WebApplication1.Controllers
{
public class Customer
{
public string CustomerName { get; set; }
public string Address { get; set; }
}
public class TestController : Controller
{
public Customer GetCustomer()
{
Customer c = new Customer();
c.CustomerName = "Customer 1";
c.Address = "Address1";
return c;
}
}
}
输出 Action 方法如下。
当返回的类型是「Customer」这样的对象时,它会返回对象的实现方法 「ToString()」。方法「ToString()」 默认返回类的全名,即 「NameSpace.ClassName」这样的形式。
直接重写类的方法「ToString」即可。
public override string ToString()
{
return this.CustomerName+"|"+this.Address;
}
按下 F5,查看输出结果。
答案是肯定的,Action 方法都自动加上 Public 修饰符。
这些非 Public 方法只是类内部的非公开方法,简单理解就是它们不能被 Web 调用。
只是简单地加上 NonAction 属性即可。
[NonAction]
public string SimpleMethod()
{
return "Hi, I am not action method";
}
当我们试着调用如下 Action 方法,会得到如下的响应。
就像我们刚才理解的 Controller 是用于处理用户的请求并返回响应。大多数情况下响应的是一个 HTML 页面,浏览器能够很好地理解这种格式。 HTML 有一些图片,文本,输入控件等。一般来说,在技术领域定义用户接口设计的层称为 UI 层,在 ASP.NET MVC 中称为 View。
在 Lab1 中我们创建了一个简单的 MVC 应用,只有一些 Controller 和一些简单的返回值。现在我们为 MVC 添加 View 的部分吧。
在 TestController 中增加一个 Action 方法。
public ActionResult GetView()
{
return View("MyView");
}
右击刚才的 Action 方法,选择「Add View」。
在「Add View」对话框中添加一个视图,命名为「MyView」,取消「Use a layout」复选框,并点击「Add」。
在解决方案浏览器下,就会发现「Views/Test」文件夹增加了一个新的视图。
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>MyView</title>
</head>
<body>
Welcome to MVC 5 Step by Step learning
<body>
</html>
按下 F5,执行应用程序
在 ASP.NET MVC 中,Views 总是与特定的 Controller 相关联被放到一个特殊的文件夹中。这个特殊的文件夹将以 Controller 的名字命名并且放到 Views 文件夹中(它位于根文件夹)。对于每一个 Controller 要访问的视图而言,只有放在正确的文件夹中才是可用的。
例如:关联 TestController 的所有视图都将被放置在「~/Views/Test」下,并且 TestController 只能访问 Test 文件夹下的视图。
答案是肯定的。我们将这些视图文件放到一个指定的文件夹下,即「Shared」。
在「Shared」文件夹下的视图文件能够被所有控制器共享。
答案是肯定的。如下代码:
public ActionResult GetView()
{
if(Some_Condition_Is_Matching)
{
return View("MyView");
}
else
{
return View("YourView");
}
}
创建 ViewResult 对象,用于视图做出回应。
ActionResult 是一个抽象类,而 ViewResult 是 ActionResult 的多层子类。多层是因为 ViewResult 是 ViewResultBase 的子类,而 ViewResultBase 是 ActionResult 的子类。
这是为了实现多态,看如下例子:
public ActionResult GetView()
{
if(Some_Condition_Is_Matching)
{
return View("MyView");
}
else
{
return Content("Hi Welcome");
}
}
在上面的例子中,一些场景下,我们调用「View」函数将返回 ViewResult 类型,而一些其它场景下,我们调用「Content」函数用于放回内容结果。
ViewResult 呈现了一个完整的 HTML 响应而 ContentResult 呈现的时一个纯文本响应。就像返回一个纯 String 类型意义。所不同的是,ContentResult 是一个 ActionResult 类型 ,包装String 结果。ContentResult 也是 ActionResult 的子类。
答案是可以的。View 函数会通过当前的「ActionName」来找视图。
第 2 天中,我们将讨论 Models,Validation,Jquery 以及 Json。所以,让我们一起摇摆,一起学习吧!
原文地址:Learn MVC Project in 7 days
本文系 OneAPM 工程师编译整理。OneAPM 是中国基础软件领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客。