Logo
Section 1 / 11

控制器快速入门

在本节课中,我们将学习如何使用控制器来处理 GET 和 POST 请求的基本知识。

导入

控制器快速入门

在本节课中,我们将学习如何使用控制器来处理 GET 和 POST 请求的基本知识。

在前面的章节中,我们学习了如何使用控制器来对 HTTP GET 请求生成简单的响应。这些请求通常由点击链接或在浏览器地址栏中输入 URL 触发。

在本节课中,我们将学习如何处理表单提交(即 POST 请求)以及重定向响应,以构建一个简单的应用程序。后续课程将深入探讨 MVC 模式及控制器的所有功能。

使用 POST 调用动作方法

我们已经知道,请求的 URL 会被映射到控制器和动作方法名称,然后被调用。在 GET 请求中,传递给动作方法的参数主要来自 URL 路径和查询字符串。而在 POST 请求中,我们主要关注 HTML 表单提交的各个字段的名称和值。此外,在表单提交的情况下,我们通常会使用字段值来填充复杂对象的属性,而不是将其作为独立的参数处理。

默认情况下,表单提交的数据以 POST 请求的正文形式发送到服务器。数据采用一种与查询字符串格式完全类似的编码方式。下面是一个请求示例:

1POST /MyPath HTTP/1.1 2Host: MyHost.com 3Content-Type: application/x-www-form-urlencoded 4Content-Length: 27 5 6field1=value1&field2=value2

当表单不包含 enctype 属性,或者其值为 application/x-www-form-urlencoded 时,就使用这种格式。

每个字段也可以被放置在请求正文的不同部分。只需将 enctype 设置为 multipart/form-data,如下所示:

1POST /MyPath HTTP/1.1 2Host: MyHost.com 3Content-Type: multipart/form-data;boundary=----WebKitFormBoundaryePkpFF7t 4 5----WebKitFormBoundaryePkpFF7t 6Content-Disposition: form-data; name="field1" 7 8value1 9----WebKitFormBoundaryePkpFF7t 10Content-Disposition: form-data; name="field2"; filename="example.txt" 11 12value2 13----WebKitFormBoundaryePkpFF7t 14{文件上传时的数据}

当需要上传文件时,必须使用第二种格式,因为只有它支持文件上传。

ASP.NET Core 会自动解码所有格式,并将所有的名称-值对放入 HttpContext 对象的 Request.Form 字典中,该对象贯穿整个请求处理过程。

当接收到 POST 请求时,控制器和动作方法的选择算法与 GET 请求相同。同时,URL 中包含的所有数据也会用于填充动作方法的部分参数。但在这种情况下,我们还会使用表单数据来填充复杂类型参数的属性(如果存在)。只有公共的 get 或 set 属性会被填充,因此若想通过表单数据填充属性,不能使用受保护或私有的字段。

更具体地说,MVC 引擎会尝试通过查找数据字典中是否存在名为“Property1.NestedProperty1...”的字段,来填充每个复杂类型中嵌套属性(如“Property1.NestedProperty1...”)。如果找到匹配的字段名,则会将该字段的字符串值转换为目标属性的类型。在此过程中,所需的嵌套对象会自动创建。

例如,如果某个动作方法有一个 Customer 参数,且该参数包含一个名为 DateOfBirth 的公共 DateTime 属性,那么 MVC 引擎会查找名为 DateOfBirth 的字段,如果找到,就会根据当前请求线程的文化设置解析其字符串格式,得到一个实际的 DateTime 值,并用于填充 DateOfBirth 属性。解析过程使用的是请求线程的当前文化设置,默认从浏览器声明的文化中提取。

不同的 GET 和 POST 方法

为了让用户编辑数据,我们需要两个请求:

  1. 第一个 GET 请求用于向服务器请求要编辑的数据。服务器必须返回一个包含表单的 HTML 页面,并将当前值填入表单字段。如果是新建实体,表单字段为空;如果是编辑已有实体,则字段应填入当前值。
  2. 第二个 POST 请求将编辑后的值提交给服务器。如果存在错误需要修正,服务器应返回与第 1 步相同的 HTML 页面,附带错误信息,并保留用户之前输入的内容。这使得用户可以轻松修正所有错误并重新提交表单。当所有内容正确无误后,服务器更新持久化存储,并将用户重定向到另一个页面,告知操作成功或允许用户开始下一项操作。

由于这两个步骤的操作完全不同,它们由不同的控制器动作方法执行。但由于它们属于同一操作流程,通常这两个方法会使用相同的名称。这通常是可行的,因为两个方法接受不同的参数,如下例所示:

1// GET 方法 2public async Task<IActionResult> Edit(int id) 3{ 4 ... 5} 6 7// POST 方法 8public async Task<IActionResult> Edit(Customer customer) 9{ 10 ... 11}

在上述示例中,GET 方法接收要编辑的客户 id,以便获取其数据并填充一个供用户编辑的表单。而 POST 方法则接收一个 Customer 对象,该对象将由 MVC 引擎自动创建并用用户提交的字段填充。

由于两个方法名称相同,当 URL 映射到控制器和动作名称时,ASP.NET Core 引擎无法仅凭名称判断应使用哪个方法来响应请求,除非提供额外信息。

我们可以使用 [HttpPost] 和 [HttpGet] 属性来指定 GET 请求和 POST 请求分别使用哪个方法,如下所示:

1[HttpGet] 2public async Task<IActionResult> Edit(int id) 3{ 4 ... 5} 6 7[HttpPost] 8public async Task<IActionResult> Edit(Customer customer) 9{ 10 ... 11}

[HttpPost] 和 [HttpGet] 属性并非强制要求,仅用于解决歧义。每种 HTTP 动词都有对应的 [Http...] 属性(如 [HttpPut]、[HttpDelete] 等)。

编辑已有数据

当用户需要修改已有数据时,GET 方法必须检索这些数据以填充 ViewModel:

1[HttpGet] 2public async Task<IActionResult> Edit(int id) 3{ 4 // 这里检索数据 5 .... 6 // 处理错误(客户未找到) 7 if (...) 8 return View("CustomerNotFound"); 9 10 var model = new Customer 11 { 12 ... 13 // 使用检索到的数据填充 ViewModel 14 ... 15 }; 16 return View("Edit", model); 17}

GET 方法接收要编辑的客户 id。如果未找到客户,则返回显示错误消息的视图。否则,使用检索到的数据填充 ViewModel,并返回包含表单和编辑所需字段的视图。

如果要调用的视图名称与动作方法名称相同(如上例),可以在 View 调用中省略视图名称。因此,代码中可简写为 View(model)。

POST 方法在保存编辑数据前必须验证输入是否有错误。这可以通过检查控制器的 ModelState 属性轻松实现:

1[HttpPost] 2public async Task<IActionResult> Edit(Customer customer) 3{ 4 if (ModelState.IsValid) 5 { 6 // 执行进一步检查 7 if (/* 检查通过 */) 8 { 9 // 保存用户编辑 10 .... 11 return RedirectToAction("{Action 名称}", "{Controller 名称}"); 12 } 13 else 14 { 15 ModelState.AddModelError(....); 16 return View(customer); 17 } 18 } 19 else 20 return View(customer); 21}

当 MVC 引擎填充 Customer 属性时,会自动检测错误并将它们添加到 ModelState 字典中。自动检测的错误包括日期和数字格式错误等。我们还可以添加其他检查,如属性不能为空、数值需在指定范围内、字符串长度限制等。无论何种情况,所有自动发现的错误都会被加入 ModelState 字典,我们可以通过检查 ModelState.IsValid 属性(第 4 行)来判断是否无错误。

接着,我们可以进行一些手动检查(第 7 行),并向 ModelState 添加更多错误。所有添加到 ModelState 的错误都可以在视图中自动显示。

如果存在错误,我们必须返回由 GET 方法最初调用的相同视图,以便用户能够修正所有错误。该视图必须接收与 [HttpPost] 动作方法参数相同的模型,这样用户就能在页面上看到自己之前的输入。

如果没有错误,就可以保存用户的全部修改,并将用户重定向到另一个应用 URL。这通过 RedirectToAction 实现,它向浏览器发送一个 HTTP 重定向响应。通常,浏览器的重定向 URL 是根据服务该 URL 的动作方法和控制器名称计算得出的。

插入新数据

当没有现有数据可供编辑,而用户需要创建新实体时,GET 方法无需检索数据,也不需要向视图传递任何 ViewModel。此时,GET 方法变得非常简单:

1[HttpGet] 2public async Task<IActionResult> Insert() 3{ 4 return View(); 5}

事实上,如果视图名称与动作方法名称相同,我们无需将视图名称传给 View 方法;如果传入的模型为 null,也无需显式传递 null,因为模型是可选参数。

标签助手

一个非常简单的应用程序