7 天玩转 ASP.NET MVC — 第 1 天

2015-07-14 16:18:52 +08:00
 OneAPM

0. 前言

正如标题「7 天玩儿转 ASP.NET MVC」所言,这是个系列文章,所以将会向大家陆续推出 7 篇。设想一下,一天一篇,你将从一个愉快的周一开始阅读,然后在周末成为一个 ASP.NET MVC 开发者,这很酷吧!

第一天是热身运动,这篇我们将围绕 Controller 和 Views 实践两个 Labs。在每个 Lab 之中都伴随着一些 Question 和 Answer。所以文章的主体框架是 Lab 和 Q&A。

1. 开始之前的准备

我们只需要 Visual Studio 工具就可以开始 ASP.NET MVC 之旅。你可以通过 Visual Studio 官网 下载所需版本。

2. MVC vs Webforms

许多 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,不一而足。

3. ASP.NET Web Forms 的问题

既然 ASP.NET Webform 如此成功,为什么微软还要考虑创建 ASP.NET MVC 呢?主要原因是出在 ASP.NET Webform 的性能上。可以从两个性能角度考虑:

  1. 响应时间:Server 端响应请求的时间是多少?
  2. 带宽消耗:多少数据被传输?

我们来尝试解释为什么 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;" />

现在停止阅读,闭上眼睛思考片刻:

  1. 这真的是一个创建 HTML 的高效方式吗?我们真的需要开始这样一个漫长的服务器响应之旅而只是为了在浏览器上显示如此简单的页面吗?
  2. 难道开发者们不能直接写 HTML 页面吗,难道很难写?

事实上,每一次请求都会有一次 Conversion 逻辑在运行,它用于转换 HTML 输出的控件。当我们输出的控件是一些 Grids 表格, Tree View 树形控件等一些复杂的 HTML 页面时,这种转换将会变得更耗时,并且非常复杂,使得等待时间更长。

为了解决这个问题,开始抛弃「Code Behind」吧,写一些纯净的 HTML。

长期从事 ASP.NET 的开发者一定非常熟悉 Viewstate,它能够自动保存 post 返回的状态并且减少了开发时间。但是正是由于这种开发时间的减少带来了巨大的代价,Viewstate 增加了页面的大小。通过负载测试,对比 ASP.NET MVC,我们发现 Viewstate 增加了近两倍的页面大小。

大小的增加是由于 Viewstate 产生了额外的字节。下图是 Viewstate 的截图快照。也许有人会反驳放弃 Viewstate 的观点,但是对于开发者而言,如果有其它选择,他们会尝试其它选择。

为了解决这个问题,开始抛弃 Server 控件吧。

自定义 HTML

因为我们都是通过 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
}

单元测试

基于之前所说的后台代码无法直接实例化,所以可想而知的是,单元测试或自动化测试都是非常困难的。开发者只能自动运行应用进行手动测试。

4. 解决方案

通过之前的分析,我们发现 ASP.NET Webforms 的两个关键因素:「Code Behind」和「Server Controls」,即后台代码和服务器控件。它们影响的代价和解决方案如下图所示:

我们需要的解决方案便是将后台代码迁移到独立的简易类库,并且放弃 ASP.NET Server 控件,写一些简单的 HTML 页面。
简而言之,解决方案就像如下图的形象说明,将 Web Form「减肥」为 MVC。

5. ASP.NET MVC 如何解决 Webforms 的问题

正如我们之前所讨论的,后台任务和服务器控件是问题的根源。所以如果你看一下当前的 Webform 架构体系就会发现,开发者使用的几乎就是 3 层架构体系。

这 3 层架构体系包含了 UI,这个 UI 实际上包含了 ASPX 和 后台代码。

ASP.NET MVC 包含了三部分,即 Model,View 和 Controller。Controller 负责后台逻辑代码。View 是纯净的 HTML 页面。Model 是中间数据层。

这里有两个主要的改变,其一是 View 变为简单的 HTML 页面,其二是后台代码转换为简单的 .NET 类,我们称之为 Controller。

ASP.NET MVC 的请求流如下:

现在我们已经理解 ASP.NET MVC 的各个组件了。接下来开始深入学习每个组件,并且做一些小的 Lab。我们将从 Controller 开始,因为它是 MVC 架构体系的核心。

6. 如何理解ASP.NET MVC 中的 Controller

为了理解Controller,我们首先要理解「用户交互逻辑」的概念。

什么是交互逻辑?

场景 1

你是否仔细想过,当终端用户在浏览器上敲下一个 URL 并按下回车后,会发生什么事情?

浏览器发送一个请求给服务器,服务器再做出响应。

通过这种方式的请求,客户端尝试与服务器进行交互。服务器能够返回响应是因为服务器已经有了一些逻辑来处理这些请求。这个逻辑实际上承载了用户的请求以及用户与服务器的交互行为,这便是用户交互逻辑。

场景2

存在这样一种可能,服务器返回的响应是一个 HTML 响应。这个 HTML 包含了一些输入框或者提交按钮组件。

当点击「SaveCustomer」按钮时,会发生什么事情?

如果你的回答是「一些事件处理器来处理这个按钮点击」,那么就错了。

现实是 Web 编程是没有事件的概念的。一种情况是,微软为 ASP.NET Webforms 为我们写了一些代码,并给人一种事件驱动编程的感觉。实际上,这只是一个错觉或者幻想。

当按钮被点击后,一个简单的 HTTP 请求就被发送到服务器上。这次不同的是,「Customer Name」,「Address」和「Age」的值都将伴随着请求被发送。从根本上说,如果发生了请求,那么服务器就会根据已经写好的逻辑发出返回响应,简而言之,服务器上一定存在一些用户交互逻辑。

在 ASP.NET MVC 中,最后一个字母「C」就代表的是 Controller,它是用于处理用户交互逻辑的。

7. Lab1 - 从简单的 MVC Hello World 来理解 Controller

创建 ASP.NET MVC 5 程序

打开 Visual Studio 2013 或更高版本,点击 文件 > 新建 > 项目。

选择 Web 应用,填写应用名称,选择应用路径,点击确定。

选择 MVC 模板文件

点击更改权限,在对话框中选择「无权限」。

点击确定即可。

创建 Controller

在资源管理器中,右击「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」即可。

8. 围绕 Lab 1 的 Q&A

TestController 和 Test 的关系?

「TestController」是一个类的名称,然而「Test」确是一个控制器的名称。需要注意的是,当你在 URL 中输入控制器的名称时,不要带上单词「Controller」。

什么是 Action「行为」方法?

Action 方法是一个在控制器里的简单公共方法,它负责接收用户的请求,并返回一些回应。通过上述的例子,可以看到行为方法「GetString」是返回一个字符串回应。

注意:在 ASP.NET Webforms 里,默认返回的都是 HTML。有一种情形是,如果我们想返回一些其它非 HTML 类型的请求,就得要创建 HTTP 处理器,重写内容类型,然后做出回应。这并不是一个简单的任务。但是在 ASP.NET MVC 中是很容易的。你像返回「String」就返回一个「String」,而不需要返回一个完整的 HTML 页面。

如果在一个 Action 方法中,返回是一个对象,会发生什么?

查看以下代码。

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 修饰符吗?

答案是肯定的,Action 方法都自动加上 Public 修饰符。

非 Public 方法如何理解?

这些非 Public 方法只是类内部的非公开方法,简单理解就是它们不能被 Web 调用。

如果我们想实现一个 Public 的非 Action 方法,该如何去做?

只是简单地加上 NonAction 属性即可。

[NonAction]
public string SimpleMethod()
{
    return "Hi, I am not action method";
}

当我们试着调用如下 Action 方法,会得到如下的响应。

9. 理解 ASP.NET MVC 中的 Views

就像我们刚才理解的 Controller 是用于处理用户的请求并返回响应。大多数情况下响应的是一个 HTML 页面,浏览器能够很好地理解这种格式。 HTML 有一些图片,文本,输入控件等。一般来说,在技术领域定义用户接口设计的层称为 UI 层,在 ASP.NET MVC 中称为 View。

10. Lab2 - 演示 Views

在 Lab1 中我们创建了一个简单的 MVC 应用,只有一些 Controller 和一些简单的返回值。现在我们为 MVC 添加 View 的部分吧。

创建一个 Action 方法

在 TestController 中增加一个 Action 方法。

public ActionResult GetView()
{
return View("MyView");
}

创建一个 View

右击刚才的 Action 方法,选择「Add View」。

在「Add View」对话框中添加一个视图,命名为「MyView」,取消「Use a layout」复选框,并点击「Add」。

在解决方案浏览器下,就会发现「Views/Test」文件夹增加了一个新的视图。

打开「MyView.cshtml」文件,将会看到内容如下。

@{
    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,执行应用程序

11. 围绕 Lab 2 的 Q&A

为什么视图被放到了 Test 文件夹下?

在 ASP.NET MVC 中,Views 总是与特定的 Controller 相关联被放到一个特殊的文件夹中。这个特殊的文件夹将以 Controller 的名字命名并且放到 Views 文件夹中(它位于根文件夹)。对于每一个 Controller 要访问的视图而言,只有放在正确的文件夹中才是可用的。

例如:关联 TestController 的所有视图都将被放置在「~/Views/Test」下,并且 TestController 只能访问 Test 文件夹下的视图。

对于多个控制器而言,可以重用一些视图吗?

答案是肯定的。我们将这些视图文件放到一个指定的文件夹下,即「Shared」。

在「Shared」文件夹下的视图文件能够被所有控制器共享。

一个 Action 方法可以引用多个 Views 吗?

答案是肯定的。如下代码:

public ActionResult GetView()
{
    if(Some_Condition_Is_Matching)
    { 
       return View("MyView");
    }
    else
    {
       return View("YourView");
    }
}

View 功能的目的是什么?

创建 ViewResult 对象,用于视图做出回应。

  1. ViewResult 内部创建 ViewPageActivator 对象
  2. ViewResult 选择正确的 ViewEngine,并将 ViewPageActivator 对象作为参数传输给 ViewEngine 构造函数。
  3. ViewEngine 创建 View 类的对象。
  4. ViewResult 调用 View 类的 RenderView 方法。

ActionResult 和 ViewResult 之间的关联?

ActionResult 是一个抽象类,而 ViewResult 是 ActionResult 的多层子类。多层是因为 ViewResult 是 ViewResultBase 的子类,而 ViewResultBase 是 ActionResult 的子类。

如果我们想要返回 ViewResult,为什么定义的返回类型是 ActionResult?

这是为了实现多态,看如下例子:

public ActionResult GetView()
{
    if(Some_Condition_Is_Matching)
    { 
        return View("MyView");
    }
    else
    {
        return Content("Hi Welcome");
    }
}

在上面的例子中,一些场景下,我们调用「View」函数将返回 ViewResult 类型,而一些其它场景下,我们调用「Content」函数用于放回内容结果。

什么是 ContentResult?

ViewResult 呈现了一个完整的 HTML 响应而 ContentResult 呈现的时一个纯文本响应。就像返回一个纯 String 类型意义。所不同的是,ContentResult 是一个 ActionResult 类型 ,包装String 结果。ContentResult 也是 ActionResult 的子类。

可以无参调用 View 函数吗?

答案是可以的。View 函数会通过当前的「ActionName」来找视图。

第 2 天我们将会学到什么?

第 2 天中,我们将讨论 Models,Validation,Jquery 以及 Json。所以,让我们一起摇摆,一起学习吧!

原文地址:Learn MVC Project in 7 days

本文系 OneAPM 工程师编译整理。OneAPM 是中国基础软件领域的新兴领军企业,能帮助企业用户和开发者轻松实现:缓慢的程序代码和 SQL 语句的实时抓取。想阅读更多技术文章,请访问 OneAPM 官方博客

2996 次点击
所在节点    .NET
4 条回复
ksupertu
2015-07-14 16:25:46 +08:00
软狗前排点赞:doge
smallerpig
2015-07-14 16:27:35 +08:00
AntiGameZ
2015-07-14 16:41:30 +08:00
东西是不错,但是这套文章有点过时了吧,ASP.NET Identity 都 release 好久了,文章里用的还是老的的 Membership。
DreamCMS
2015-07-15 13:20:34 +08:00
不错!

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/205644

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX