Skip to content

3. 控制器、操作、路由

让我们来看看 ASP.NET MVC 应用程序的架构:

在本章中,我们将探讨将请求 [1] 路由到控制器以及负责处理该请求的操作 [2a] 的过程,这一机制被称为路由。我们还将介绍操作可以返回给浏览器的各种响应 [3]。这些响应可能并非视图 V [4b]。

3.1. ASP.NET MVC 项目的结构

让我们使用 Visual Studio Express 2012 创建我们的第一个 ASP.NET MVC 项目。我们将把它 [1] 添加到上一章中使用的解决方案中:

  • 在 [2] 中,新项目的名称;
  • 在 [3, 4] 中,我们选择一个基本的 ASP.NET MVC 项目。该模板为我们提供了一个空的 Web 应用程序,但其中已包含开始开发所需的所有资源(DLL、JavaScript 库等)。

生成的项目如图 [5] 所示。我们将将其设为 [6] 该解决方案的 起始项目:

请注意 [5] 中的以下几点:

  • 该项目的架构体现了其 MVC 模型:
  • C 控制器将放置在 [Controllers] 文件夹中,
  • M 数据模型将放置在 [Models] 文件夹中,
  • V 视图将放置在 [Views] 文件夹中,
  • 在 [1] 中,[Site.css] 文件将是应用程序的默认 CSS 文件;
  • 在 [2] 中,提供了若干 JavaScript 库;
  • 在 [3] 中,有三个特定视图:_ViewStart、_Layout 和 Error

[_ViewStart] 文件内容如下:


@{
    Layout = "~/Views/Shared/_Layout.cshtml";
}

  • 第 1 行:@ 字符标记了视图中 C# 代码块的开始。实际上,视图中可以包含 C# 代码;
  • 第 2 行:定义了一个 Layout 变量,用于指定所有视图的父视图。它对应于经典 ASP.NET 框架中的母版页。

文件 [ _Layout ] 内容如下:


<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    @RenderBody()
 
    @Scripts.Render("~/bundles/jquery")
    @RenderSection("scripts", required: false)
</body>
</html>

当渲染 [Views] 文件夹中的视图时,其主体内容将由上文第 11 行代码负责渲染。这意味着该视图无需包含 <html>、<head> 和 <body> 标签,这些内容由上文的文件提供。目前,该文件内容较为晦涩难懂。让我们将其简化:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>

  • 第 6 行:所有视图共用的标题;
  • 第 9 行:所有视图共用的页眉;
  • 第 10 行:显示视图特有的内容。

Image

  • [Web.config] 是 Web 应用程序的配置文件。它比较复杂。在多层架构中使用 [Spring.net] 框架时,您需要对其进行修改。
  • [Global.asax] 包含应用程序启动时执行的代码。通常,该代码会调用应用程序的各种配置文件,包括 [Web.config]。

3.2. 默认 URL 路由

[Global.asax] 中的代码目前如下:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
 
namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}

  • 第 6 行:类命名空间。它直接源自项目名称,并在项目属性中列出:

Image

我们将 [1] 设为默认命名空间。此后,该项目中创建的所有类都将默认使用该命名空间。

让我们回到 [Global.asax] 中的代码:


using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
 
namespace Exemple_01
{
 
  public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
   ...
    }
  }
}

  • 第 9 行:[MvcApplication] 类继承自 [HttpApplication] 类。[MvcApplication] 的名称可以更改;
  • 第 11 行:[Application_Start] 方法是在 Web 应用程序启动时执行的方法。该方法仅执行一次。此处用于初始化应用程序。

[Application_Start] 的代码目前如下:


    protected void Application_Start()
    {
      AreaRegistration.RegisterAllAreas();
 
      WebApiConfig.Register(GlobalConfiguration.Configuration);
      FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
      RouteConfig.RegisterRoutes(RouteTable.Routes);
      BundleConfig.RegisterBundles(BundleTable.Bundles);
}

目前,您无需理解这段代码的全部内容。第 5 至 8 行定义了 Web 应用程序接受的路由。让我们回到 ASP.NET MVC 应用程序的架构:

我们之前提到,[Front Controller] 必须将 URL 路由到负责处理它的操作。路由用于将 URL 模式与操作关联起来。这些路由由项目 [App_Start] 文件夹中的 [WebApiConfig、FilterConfig、RouteConfig、BundleConfig] 类定义:

Image

目前,我们仅关注 [RouteConfig] 类:


using System.Web.Mvc;
using System.Web.Routing;
 
namespace Exemple_01
{
  public class RouteConfig
  {
    public static void RegisterRoutes(RouteCollection routes)
    {
      routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
 
      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
      );
    }
  }
}

第 12–16 行定义了应用程序接受的 URL 格式。这被称为路由。可能存在多个路由(即多个可能的 URL 模式)。它们通过名称(第 13 行)相互区分。路由的 URL 格式在第 14 行中定义。在此,URL 将包含三个组成部分:

  • {controller}:一个从 [Controller] 派生的类名。系统将在项目的 [Controllers] 文件夹中搜索该类。按惯例,如果 URL 为 /X/Y/Z,则负责处理此 URL 的控制器将是 XController 类。URL 中出现的控制器名称后会添加 Controller 后缀;
  • {action}:上述控制器中某个方法的名称。该方法将接收随 URL 传递的参数并进行处理。该方法可返回多种结果:
    • void:该操作将自行构建返回给客户端浏览器的响应
    • String:该操作向客户端返回一个字符串;
    • ViewResult:向客户端返回一个视图;
    • PartialViewResult:返回一个部分视图;
    • EmptyResult:向客户端发送空响应;
    • RedirectResult:指示客户端重定向到某个 URL
    • RedirectToRouteResult:与上述相同,但 URL 由应用程序的路由构建而成;
    • JsonResult:发送 JSON 响应
    • JavaScriptResult:向客户端返回 JavaScript 代码;
    • ContentResult:直接向客户端返回 HTML 流,不经过视图处理;
    • FileContentResult:向客户端返回文件;
    • FileStreamResult:与上述相同,但通过不同的方法实现;
    • FilePathResult:...
  • {id}:将传递给操作的参数。要使此功能生效,该操作必须包含一个名为 id 的参数。

第 15 行定义了当 URL 没有预期格式 /{controller}/{action}/{id} 时的默认值它还表明 URL 中的 {id} 参数是可选的。以下是一组不完整的 URL 及其使用默认值补全后的 URL:

原始 URL
补全后的 URL
/
/Home/Index
/Do
/Do/Index
/Do/Something
/做/某事
/做/某事/4
/做/某事/4
/执行/某项操作/x/y/z
未路由的 URL

3.3. 创建控制器和第一个操作

让我们创建第一个控制器:

Image

  • 在 [1] 中,输入控制器名称并添加后缀 [Controller];
  • 在 [2] 中,创建一个空的 MVC 控制器;
  • 在 [3] 中,控制器已创建完成。

[FirstController] 的代码如下:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
    public class FirstController : Controller
    {
        //
        // GET: /First/
 
        public ActionResult Index()
        {
            return View();
        }
 
    }
}
  • 第 7 行:已生成一个默认命名空间
  • 第 9 行:[FirstController] 类继承自 [System.Web.Mvc.Controller] 类;
  • 第 14–17 行:默认生成了一个 [Index] 操作。它是 public 的。这一点很重要;否则将无法被找到。它返回 [ActionResult] 类型,这是一个抽象类,大多数操作通常返回的结果都继承自该类。这是一个“万能”类型,可以通过将其替换为返回类型的实际名称来指定;
  • 第 16 行:该方法不执行任何操作。它仅返回一个视图,即 [ViewResult] 类型。视图名称未指定。在这种情况下,框架会在 [Views/First] 文件夹中搜索与操作同名的视图,此处即为 [Index.cshtml]。

让我们创建 [Index.cshtml] 视图:

  • 在 [1] 中,右键单击操作代码并选择 [添加视图] 选项;
  • 在 [2] 中,向导会建议创建一个与操作同名的视图。这正是我们需要的;
  • 在 [3] 处,默认建议使用母版页 [_Layout.cshtml];
  • 在 [4] 处,确认后,向导会在 [Views] 文件夹的一个子文件夹中创建视图,该子文件夹以控制器名称(First)命名。

为 [Index] 视图生成的代码如下:


@{
    ViewBag.Title = "Index";
}
 
<h2>Index</h2>
  • 第 1–3 行:定义变量的 C# 代码;
  • 第 5 行:一个 HTML 标签。

将之前的全部代码替换为以下内容:


<strong>Vue [Index]...</strong>

总结如下:

  • 我们有一个名为 [First] 的 C# 控制器;
  • 有一个名为 [Index] 的操作,用于请求显示名为 [Index] 的视图;
  • 我们有一个名为 V [Index] 的视图。

我们可以通过两种方式调用 [Index] 操作:

  • /First/Index;
  • /First, 因为 [Index] 也是路由中的默认操作。

现在运行应用程序(CTRL-F5)。我们会看到以下页面:

Image

在 [1] 中,请求的 URL 是 http://localhost:49302。该 URL 没有路径。我们知道,路由器期望 URL 采用 /{controller}/{action}/{id} 的形式由于缺少这些元素,系统将使用默认值: 。因此,URL 变为 http://localhost:49302/Home/Index。由于 [Home] 控制器不存在,该 URL 被拒绝。

现在,让我们尝试直接在浏览器中输入 URL http://localhost:49302/First/Index

上图所示页面由 [First] 控制器的 [Index] 操作生成。该操作生成的页面是 [Index] 视图,其代码如下:


<strong>Vue [Index]...</strong>

它生成部分 [1]。另一方面,部分 [2] 来自我们之前定义的主页面 [_Layout]:


<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  @RenderBody()
</body>
</html>

第 9 行生成了页面的第 [2] 部分。然而,[Index] 视图直到第 10 行才出现。

如果查看接收到的页面的源代码,我们会发现 [Index] 页面确实被包含在 [Layout] 页面中(如下所示的第 10 行):

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width" />
  <title>Tutoriel ASP.NET MVC</title>
</head>
<body>
  <h2>Tutoriel ASP.NET MVC</h2>
  <strong>Vue [Index]...</strong>
</body>
</html>

现在让我们尝试访问 URL [/First]:

Image

URL [/First] 不完整。它被路由的默认值补全,变成了 [/First/Index]。因此,我们得到了与之前相同的结果。

3.4. 返回 [ContentResult] 类型结果的操作 - 1

让我们在 [First] 控制器中创建一个新操作:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
    // Index
    public ViewResult Index()
    {
      return View();
    }
    // Action01
    public ContentResult Action01()
    {
      return Content("<h1>Action [Action01]</h1>", "text/plain", Encoding.UTF8);
    }
  }
}

新操作在第 14–18 行中定义。它仅通过 [Controller] 类(第 6 行)的 [Content] 方法(第 16 行)返回一个字符串。该方法的参数为:

  1. 响应字符串;
  2. 发送文本类型的标识符:"text/plain"、"text/html"、"text/xml" 等。该标识符称为 MIME 类型(http://fr.wikipedia.org/wiki/Type_MIME);
  3. 第三个参数指定文本所使用的编码类型。

我们的方法并未使用抽象类型 [ActionResult],而是指定了实际的返回类型(第 9 行和第 14 行)。

让我们请求 URL [/First/Action01]。我们将获得以下页面:

Image

让我们查看收到的页面的源代码:

<h1>Action [Action01]</h1>

浏览器仅接收到了该操作发送的文本,除此之外别无其他。当您希望从 Web 服务器请求原始数据(不包含周围的 HTML 标记)时,此模式非常有用。请注意,浏览器并未解析 HTML 标签 <h1>。要理解原因,让我们查看 Chrome 中的 HTTP 交互:

浏览器发送了以下 HTTP 头部:

1
2
3
4
5
6
7
8
GET /First/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

服务器返回了以下标头:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMQ==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:22:33 GMT
Content-Length: 141
  • 第 3 行:设置文档类型。我们看到 [Action01] 方法中设置的属性。正是因为浏览器被告知文档的类型是“text/plain”而不是“text/html”,所以它没有解释接收到的文档中的 <h1> 标签。

3.5. 结果类型为 [ContentResult] 的操作 - 2

让我们考虑以下第三个操作:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class FirstController : Controller
  {
   ...
    // Action02
    public ContentResult Action02()
    {
      string data = "<action><name>Action02</name><description>renvoie un texte XML</description></action>";
      return Content(data, "text/xml", Encoding.UTF8);
    }
  }
}
  • 第 12 行:我们定义了一个 XML 文本;
  • 第 13 行:我们将该文本发送至浏览器,并指定其为 MIME 类型为 "text/xml" 的 XML。

在浏览器中,我们会看到以下页面:

Image

让我们在 Chrome 中查看服务器的 HTTP 响应:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMg==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:29:34 GMT
Content-Length: 176
  • 第 3 行:指定文档类型。此处包含在 [Action02] 方法中设置的属性;
  • 第 12 行:服务器发送的文档的字节大小。

服务器发送的文档如下(Chrome截图):

Image

3.6. 返回类型为 [JsonResult] 的操作

让我们在 [First] 控制器中添加以下操作:


    // Action03
    public JsonResult Action03()
    {
      dynamic personne = new ExpandoObject();
      personne.nom = "someone";
      personne.age = 20;
      return Json(personne,JsonRequestBehavior.AllowGet);
}
  • 第 4 行:一个动态类型的变量。在运行时,可以自由地向此类变量添加属性。该属性在初始化时同时被创建;
  • 第 5-6 行:我们初始化了两个属性,name age
  • 第 7 行:返回该对象的 JSON(JavaScript 对象表示法)表示形式。JSON 允许将对象序列化为字符串,反之亦然,将字符串反序列化为对象。它是 XML 序列化/反序列化的替代方案;
  • 第 2 行:该操作返回 [JsonResult] 类型。此类型仅可用于 POST 请求。若要在 GET 方法中返回该类型,必须将 Json 类构造函数的第二个参数(第 7 行)设置为 JsonRequestBehavior.AllowGet

当您请求 URL [/First/Action03] 时,浏览器将显示以下内容:

Image

服务器的 HTTP 响应如下:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wMw==?=
X-Powered-By: ASP.NET
Date: Mon, 23 Sep 2013 15:48:53 GMT
Content-Length: 58
  • 第 3 行:指定发送的文档为 JSON;
  • 第 10 行:发送的文档大小为 58 字节。具体内容如下:
[{"Key":"nom","Value":"someone"},{"Key":"age","Value":20}]

动态元素 [person] 在 JSON 中被视为一个字典数组,其中每个字典:

  • 对应 [person] 变量的一个字段;
  • 包含两个键:“Key”和“Value”。“Key”的关联值为字段名称,“Value”的关联值为该字段的值。

3.7. 返回 [string] 结果的操作

让我们在 [First] 控制器中添加以下操作:


    // Action04
    public string Action04()
    {
      return "<h3>Contrôleur=First, Action=Action04</h3>";
}

当我们在 Chrome 中请求 URL [/First/Action04] 时,会得到以下响应:

Image

我们可以看到 <h3> 标签已被解析。让我们查看服务器的 HTTP 响应:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 07:49:00 GMT
Content-Length: 156

以及以下文档:

<h3>Contrôleur=First, Action=Action04</h3>

如第 3 行所示,服务器表明其发送的是 HTML 格式的文本。这就是浏览器解析 <h3> 标签的原因。因此,当您想要发送纯文本时,最好返回 [ContentResult] 而不是 [string]。 [ContentResult] 允许我们指定 MIME 类型为 "text/plain",以表明我们正在发送未格式化的文本,浏览器将不会尝试解析该文本。

3.8. 类型为 [EmptyResult] 的操作

请考虑以下新操作:


    // Action05
    public EmptyResult Action05()
    {
      return new EmptyResult();
}

该操作仅返回 [EmptyResult] 类型。在此情况下,服务器会向客户端发送一个空响应,如其 HTTP 响应所示:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Cache-Control: private
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNQ==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:11:12 GMT
Content-Length: 0
  • 第 9 行:服务器通知其客户端,它正在发送一个空文档。

3.9. 结果类型为 [RedirectResult] 的操作 - 1

考虑以下新操作:


    // Action06
    public RedirectResult Action06()
    {
      return new RedirectResult("/First/Action05");
}

该操作返回 [RedirectResult] 类型。该类型允许向客户端发送重定向请求,目标 URL 为构造函数中指定的地址(第 4 行)。随后,客户端将向 [/First/Action05] 发送一个新的 GET 请求。因此,客户端总共会发出两次请求。

浏览器显示第二个请求的结果:

Image

让我们在 Chrome 中查看服务器的 HTTP 响应:

Image

上图显示了浏览器的两个请求。让我们来查看第一个请求 [Action06]。服务器的 HTTP 响应如下:

HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wNg==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:16:59 GMT
Content-Length: 132
  • 第 1 行:服务器返回了 302 Found 状态码。此前为 200 OK,这意味着请求的文档已被找到。302 代码表示请求了重定向。重定向 URL 位于第 4 行。这与我们在操作代码中指定的重定向 URL 相符;
  • 第 11 行:服务器通过其 HTTP 响应表明,它正在发送一个 132 字节(第 11 行)的 text/html 文档(第 3 行)。当我们在 Chrome 中检查对 [Action06] 请求的响应时,正如预期的那样,响应内容为空。这可能有某种解释,但我不知道具体是什么。

由于重定向,浏览器会向上述第 4 行指定的 URL 发起新的 GET 请求,如下文第 1 行所示(可在 Chrome 中查看):

1
2
3
4
5
6
7
GET /First/Action05 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4

3.10. 结果类型为 [RedirectResult] 的操作 - 2

考虑以下新操作:


    // Action07
    public RedirectResult Action07()
    {
      return new RedirectResult("/First/Action05",true);
}

在第 4 行,我们在 [RedirectResult] 构造函数中添加了第二个参数。这是一个布尔值,默认值为 false。当设置为 true 时,它会修改发送给客户端的 HTTP 响应。响应内容变为:

HTTP/1.1 301 Moved Permanently

因此,发送给客户端的响应状态码现在是 301 永久重定向。重定向过程与之前相同,但我们明确标示了此次重定向是永久性的。这使得搜索引擎能够在其结果中用新 URL 替换旧 URL。

3.11. 返回类型为 [RedirectToRouteResult] 的操作

请考虑以下新操作:


    // Action08
    public RedirectToRouteResult Action08()
    {
      return new RedirectToRouteResult("Default",new RouteValueDictionary(new {controller="First",action="Action05"}));
}
  • 第 2 行:该操作返回 [RedirectToRouteResult] 类型。该类型允许将客户端重定向到指定的 URL,不再像以前那样通过字符串,而是通过路由。

路由在 [App_Start/RouteConfig] 中定义。目前仅有一个:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
  • 在第 4 行,客户端被指示重定向到名为 [Default] 的路由,其中 controller 变量设置为 "First",action 变量设置为 "Action05"。路由系统随后将生成重定向 URL /First/Action05。这在服务器的 HTTP 响应中显示如下:
HTTP/1.1 302 Found
Cache-Control: private
Content-Type: text/html; charset=utf-8
Location: /First/Action05
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxGaXJzdFxBY3Rpb24wOA==?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 08:58:19 GMT
Content-Length: 132
  • 第 1 行:重定向;
  • 第 4 行:由 URL 路由系统生成的重定向 URL。

3.12. 返回类型为 [void] 的操作

考虑以下新操作:


    // Action09
    public void Action09()
    {
      string nom = Request.QueryString["nom"] ?? "inconnu";
      Response.AddHeader("Content-Type", "text/plain");
      Response.Write(string.Format("<h3>Action09</h3>nom={0}", nom));
}
  • 第 2 行:该操作不返回结果。它直接将内容写入发送给客户端的响应流;
  • 第 4 行:我们从请求中获取一个名为 [name] 的潜在参数。可以通过 [First] 控制器所继承的 [Controller] 类的 [Request] 属性访问该参数。在表单 [/First/Action09?name=something] 中传递的 [name] 参数可在 Request.QueryString["name"] 中获取。第 4 行的语法等同于:
string nom=Request.QueryString["nom"];
if(nom==null){
    nom="inconnu";
}
  • 第 5 行:通过 [First] 控制器继承的 [Controller] 类的 [Response] 属性,可以访问发送给客户端的响应;
  • 第 5 行:我们设置了 [Content-Type] HTTP 头部,该头部指示了服务器即将发送给客户端的文档的类型。此处的 "text/plain" 表示该文档是纯文本,不应由浏览器进行解析;
  • 第 6 行:我们将一串字符写入响应正文。我们添加了不应被浏览器解析的 HTML 标签,因为浏览器此前已接收到 HTTP 头 [Content-Type: text/plain]。这就是我们要验证的内容。

现在编译项目,并分别请求 URL [/First/Action09?name=someone ][1] 和 URL [/First/Action09 ] [2]:

现在让我们在 Chrome 中查看服务器的 HTTP 响应:

1
2
3
4
5
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
...
Content-Length: 144
  • 第 3 行:我们可以看到在操作代码中自行设置的 HTTP 头部。

3.13. 第二个控制器

让我们在项目中创建第二个控制器。我们将遵循第 3.1 节(第 36 页)中描述的方法。我们将它命名为 [Second]。

Image

生成的代码如下:


namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    //
    // GET: /Second/
 
    public ActionResult Index()
    {
      return View();
    }
 
  }
}

让我们将其修改如下:


using System.Text;
using System.Web.Mvc;
 
namespace Exemple_01.Controllers
{
  public class SecondController : Controller
  {
    // /Second/Action01
    public ContentResult Action01()
    {
      return Content("Contrôleur=Second, Action=Action01", "text/plain", Encoding.UTF8);
    }
 
  }
}

接下来,我们使用浏览器请求 URL [/Second/Action01]。我们会得到以下响应:

Image

如 Chrome 中该请求的 HTTP 日志所示,此 URL 是通过 HTTP GET 请求发出的:

GET /Second/Action01 HTTP/1.1

该 URL 也可以通过 HTTP POST 请求进行访问。为了演示这一点,让我们再次使用 [Advanced Rest Client] 应用:

  • 在 [1] 中,启动该应用程序(在新打开的 Chrome 标签页的 [Applications] 选项卡中);
  • 在 [2] 中,选择 [Request] 选项;
  • 在 [3] 中,指定要请求的 URL;
  • 在 [4] 中,指定应使用 POST 请求发送该 URL;

我们启用 Chrome 日志(CTRL-I)以查看服务器的 HTTP 响应。当我们点击 [发送] 执行上一个请求时,HTTP 交互如下:

浏览器发送了以下请求:

POST /Second/Action01 HTTP/1.1
Host: localhost:49302
Connection: keep-alive
Content-Length: 0
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
User-Agent: Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.76 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Accept-Encoding: gzip,deflate,sdch
Accept-Language: fr-FR,fr;q=0.8,en-US;q=0.6,en;q=0.4
  • 第 1 行:URL 确实是通过 POST 请求的;
  • 第 4 行:POST 元素的字节大小。此处没有数据。

服务器的 HTTP 响应如下:

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/plain; charset=utf-8
Content-Encoding: gzip
Vary: Accept-Encoding
Server: Microsoft-IIS/8.0
X-AspNetMvc-Version: 4.0
X-AspNet-Version: 4.0.30319
X-SourceFiles: =?UTF-8?B?RDpcZGF0YVxpc3RpYS0xMzE0XGFzcG5ldFxkdnBcRXhlbXBsZXNcRXhlbXBsZS0wMVxTZWNvbmRcQWN0aW9uMDE=?=
X-Powered-By: ASP.NET
Date: Tue, 24 Sep 2013 10:47:59 GMT
Content-Length: 148
  • 第 3 行:服务器发送了一个未格式化的(纯)文本文档;
  • 第 12 行:148 个字符。

发送的文档如下:

Image

我们得到的文档与GET请求时相同。

3.14. 按属性过滤的操作

让我们创建以下新操作:


    // /Second/Action02
    [HttpPost]
    public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

[Action02] 操作与 [Action01] 操作类似,但它指定该操作仅可通过 HTTP POST 请求访问(第 2 行)。还可以使用其他属性:

HttpGet
仅处理 GET 请求
HttpHead
仅处理 HEAD 请求
HttpOptions
仅支持 OPTIONS 命令
HttpPut
仅支持 PUT 命令
HttpDelete
仅用于 DELETE 命令

让我们在浏览器中直接请求 URL [/Second/Action02]。该请求将通过 GET 方式发送。随后,浏览器将显示以下响应:

Image

服务器的 HTTP 响应如下:

1
2
3
HTTP/1.1 404 Not Found
...
Content-Length: 3807
  • 第 1 行:HTTP 状态码 404 未找到表示服务器无法找到请求的文档。在此情况下,操作 [Action02] 无法处理该 GET 请求,因为它仅处理 POST 请求;
  • 第 3 行:返回文档的大小。这是浏览器显示的页面:

Image

3.15. 从路由中获取元素

在上述两个操作中,我们编写了类似以下内容:


public ContentResult Action02()
    {
      return Content("Contrôleur=Second, Action=Action02", "text/plain", Encoding.UTF8);
}

控制器和操作的名称被硬编码到了代码中。如果我们更改这些名称,代码将不再正确。我们可以按以下方式访问控制器和操作:


    // /Second/Action03
    public ContentResult Action03()
    {
      string texte = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return Content(texte, "text/plain", Encoding.UTF8);
}

在 [App_Start/RouteConfig] 中定义的路由如下:


      routes.MapRoute(
          name: "Default",
          url: "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);

第 3 行:可以通过 RouteData.Values["element"] 获取这三个路由元素,其中 element 是 [controller, action, id] 中的一个。

让我们请求 URL [http://localhost:49302/Second/Action03]:

Image

我们已成功获取了控制器名称和操作名称。