2.2处理表单提交

2.2处理表单提交

如果在视图中查看 <form> 标签,可以看到它的 method 属性被设置为POST。而且,<form> 没有声明 action 属性。这意味着在提交表单时,浏览器将收集表单中的所有数据,并通过HTTP POST请求将其发送到服务器,发送到显示表单的GET请求的同一路径 —— /design 路径。

因此,需要在该POST请求的接收端上有一个控制器处理程序方法。需要在 DesignTacoController 中编写一个新的处理程序方法来处理 /design 接口的POST请求。

在程序清单2.2中,使用 @GetMapping 注释指定 showDesignForm() 方法应该处理HTTP GET请求 /design。与 @GetMapping 处理GET请求一样,可以使用 @PostMapping 处理POST请求。为了处理玉米饼艺术家提交的设计,将以下程序清单中的 processDesign() 方法添加到 DesignTacoController 中。程序清单2.4使用@PostMapping处理POST请求。

@PostMapping
public String processDesign(Design design) {
    // Save the taco design...
    // We'll do this in chapter 3
    log.info("Processing design: " + design);
    return "redirect:/orders/current";
}

当应用到 processDesign() 方法时,@PostMapping 与类级别 @RequestMapping 相协调,以表明 processDesign() 应该处理 /design 接口的POST请求。这正是需要处理的一个玉米饼艺术家提交的作品。

提交表单时,表单中的字段被绑定到 Taco 对象的属性(其类在下一个程序清单中显示,该对象作为参数传递给 processDesign()。从这里开始,processDesign() 方法可以对 Taco 对象做任何它想做的事情。程序清单2.5域对象定义玉米饼设计。

package tacos;

import java.util.List;
import lombok.Data;

@Data
public class Taco {
    private String name;
    private List<String> ingredients;
}

Taco是一个简单的Java域对象,具有两个属性。与Ingredient类似,Taco类也使用@Data进行注释,以便在运行时自动生成基本的JavaBean方法。

如果查看程序清单2.3中的表单,将看到几个checkbox元素,它们都带有ingredients名称和一个名为name的文本输入元素。表单中的这些字段直接对应于Taco类的ingredientsname属性。

表单上的Name字段只需要捕获一个简单的文本值。因此Taconame属性的类型是String。配料复选框也有文本值,但是因为可能选择了零个或多个配料,所以它们绑定到的ingredients属性是一个 List<String>,它将捕获每个选择的配料。

目前,processDesign()方法对Taco对象没有任何作用。事实上,它什么都做不了。没关系。在第3章中,将添加一些持久性逻辑,将提交的Taco保存到数据库中。

showDesignForm()方法一样,processDesign()通过返回一个String结束。与showDesignForm()类似,返回的值指示将显示给用户的视图。但是不同的是,从processDesign()返回的值的前缀是 “redirect:”,表示这是一个重定向视图。更具体地说,它表明在processDesign()完成之后,用户的浏览器应该被重定向到相对路径/order/current

这样做的想法源于,在创建了一个玉米饼之后,用户将被重定向到一个订单表单,他们可以从该表单下订单,以交付他们的玉米饼。但是还没有一个控制器来处理/orders/current请求。

根据现在对@Controller@RequestMapping@GetMapping的了解,可以轻松地创建这样的控制器。它可能类似于下面的清单。程序清单2.6展现玉米饼订单表单的控制器。

package tacos.web;

import javax.validation.Valid;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.extern.slf4j.Slf4j;
import tacos.Order;

@Slf4j
@Controller
@RequestMapping("/orders")
public class OrderController {
    @GetMapping("/current")
    public String orderForm(Model model) {
        model.addAttribute("order", new Order());
        return "orderForm";
    }
}

同样,可以使用Lombok@Slf4j注释在运行时创建一个SLF4J Logger对象。稍后,将使用这个Logger来记录提交的订单的详细信息。

类级别的@RequestMapping指定该控制器中的任何请求处理方法都将处理路径以/orders开头的请求。当与方法级@GetMapping结合使用时,它指定orderForm()方法将处理/orders/currentHTTP GET请求。

至于orderForm()方法本身,它非常简单,只返回orderForm的逻辑视图名。在第3章中,一旦有了把创建的taco持久化到数据库的方法,将重新访问该方法并修改它,以使用taco对象的列表填充模型,这些对象将按顺序放置。

orderForm视图由一个名为orderForm.htmlThymeleaf模板提供,如下面显示的。程序清单2.7 taco订单表单视图。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
  <head>
    <title>Taco Cloud</title>
    <link rel="stylesheet" th:href="@{/styles.css}" />
  </head>

  <body>
    <form method="POST" th:action="@{/orders}" th:object="${order}">
      <h1>Order your taco creations!</h1>
      <img th:src="@{/images/TacoCloud.png}" />
      <a th:href="@{/design}" id="another">Design another taco</a><br />
      <div th:if="${#fields.hasErrors()}">
        <span class="validationError">
          Please correct the problems below and resubmit.
        </span>
      </div>
      <h3>Deliver my taco masterpieces to...</h3>

      <label for="name">Name: </label>
      <input type="text" th:field="*{name}" />
      <br />

      <label for="street">Street address: </label>
      <input type="text" th:field="*{street}" />
      <br />

      <label for="city">City: </label>
      <input type="text" th:field="*{city}" />
      <br />

      <label for="state">State: </label>
      <input type="text" th:field="*{state}" />
      <br />

      <label for="zip">Zip code: </label>
      <input type="text" th:field="*{zip}" />
      <br />

      <h3>Here's how I'll pay...</h3>

      <label for="ccNumber">Credit Card #: </label>
      <input type="text" th:field="*{ccNumber}" />
      <br />

      <label for="ccExpiration">Expiration: </label>
      <input type="text" th:field="*{ccExpiration}" />
      <br />

      <label for="ccCVV">CVV: </label>
      <input type="text" th:field="*{ccCVV}" />
      <br />

      <input type="submit" value="Submit order" />
    </form>
  </body>
</html>

在大多数情况下,orderForm.html视图是典型的HTML/Thymeleaf内容,没有什么值得注意的。但是注意,这里的

标记与程序清单2.3中使用的 标记不同,因为它还指定了一个表单操作。如果没有指定操作,表单将向呈现表单的相同URL提交HTTP POST请求。但是在这里,指定表单应该提交到/orders(使用Thymeleaf@{…} 操作符作为上下文相关路径

因此,需要添加另外一个方法到 OrderController 类中,去处理/orders接口的POST请求。在进行到下一章之前,还没有办法将订单持久化,因此在这里简化它 —— 类似于在下一个程序清单中看到的内容。程序清单2.8处理taco订单提交。

@PostMapping
public String processOrder(Order order) {
    log.info("Order submitted: " + order);
    return "redirect:/";
}

当调用processOrder()方法来处理提交的订单时,它将获得一个order对象,其属性绑定到提交的表单字段。Order非常像Taco,是一个相当简单的类,它携带订单信息。程序清单2.9 taco订单域对象。

package tacos;

import javax.validation.constraints.Digits;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.CreditCardNumber;
import org.hibernate.validator.constraints.NotBlank;
import lombok.Data;

@Data
public class Order {
    private String name;
    private String street;
    private String city;
    private String state;
    private String zip;
    private String ccNumber;
    private String ccExpiration;
    private String ccCVV;
}

现在已经开发了一个OrderControllerorder表单视图,可以开始尝试运行了。打开浏览器访问http://localhost:8080/design,为你的玉米饼选择一些原料,然后点击 Submit Your Taco 按钮。应该会看到类似于图2.3所示的表单。

![2.3 taco订单表单](E:\Document\spring-in-action-v5-translate\第一部分Spring基础\第二章 开发Web应用程序\2.3 taco订单表单.jpg)

2.3 taco订单表单

在表单中填写一些字段,然后按 Submit Order 按钮。与此同时,请密切关注应用程序日志,以查看订单信息。当我尝试它,日志条目看起来像这样(重新格式化以适应这个页面的宽度

Order submitted: Order(name=Craig Walls,street1=1234 7th Street,
    city=Somewhere, state=Who knows?, zip=zipzap, ccNumber=Who can guess?,
ccExpiration=Some day, ccCVV=See-vee-vee)

如果仔细查看来自测试订单的日志条目,可以看到,虽然processOrder()方法完成了它的工作并处理了表单提交,但是它让一些错误的信息进来了。表单中的大多数字段包含的数据可能是不正确的。需要添加一些验证,以确保提供的数据至少与所需的信息类型相似。

上一页
下一页