Spring MVC - Web Forms

2,496 views

Published on

How to process request parameters with the Spring MVC framework. Namely, the presentation tackles the three primary concerns when dealing with request parameters: data binding, data buffering and data validation. To this end, the Bean Validation API (JSR-303) is discussed, and the concept of MessageSource for localized error messages is introduced. Moreover, The Post/Redirect/Get (PRG) pattern is presented along with a possible implementation strategy.

Published in: Education, Technology, Design
4 Comments
10 Likes
Statistics
Notes
No Downloads
Views
Total views
2,496
On SlideShare
0
From Embeds
0
Number of Embeds
201
Actions
Shares
0
Downloads
0
Comments
4
Likes
10
Embeds 0
No embeds

No notes for slide

Spring MVC - Web Forms

  1. 1. Spring MVC - Web forms Ilio Catallo - info@iliocatallo.it
  2. 2. Outline • First principles • Data binding • Data valida3on • The PRG pa:ern • References
  3. 3. First principles
  4. 4. Handling user input Web apps o)en have to perform some logic against one or more pieces of informa(on coming from the user User User's interaction +-------------+ information +-------------+ | | | | +--------------> Browser +-------------------> Web app | | | | | +-------------+ +-------------+
  5. 5. Request parameters This informa,on is made available through the request parameters HTTP request +-------------+ User | Request | interaction +-------------+ | parameters | +-------------+ | | +-------------+ | | +--------------> Browser +---------------------> Web app | | | | | +-------------+ +-------------+
  6. 6. Request parameters Request parameters are sent as part of the HTTP request message issued by the client HTTP request +-------------+ User | Request | interaction +-------------+ | parameters | +-------------+ | | +-------------+ | | +--------------> Browser +---------------------> Web app | | | | | +-------------+ +-------------+
  7. 7. Request parameters Request parameters can be transferred as part of • The query string • The HTTP en1ty body
  8. 8. Request parameters Request parameters can be transferred as part of • The query string • The HTTP en)ty body
  9. 9. Query string Request parameters are specified as a sequence of URL parameters appended to the request URL http://my.server/resource?par1=val1&par2=val2 <-----------------> URL parameters
  10. 10. Query string In other words, request parameters are appended to the query string http://my.server/resource?par1=val1&par2=val2 <-----------------> query string
  11. 11. HTTP request HTTP request +--------------------------------------+ | GET /webapp/greeting?name=Jon | | &surname=Snow | +--------------------------------------+ | | | Host: myserver.com | | User-Agent: ... | | Accept-Encoding: ... | | | +--------------------------------------+
  12. 12. Gree$ng by name Assume that we want our applica2on to be able to accept the name and the surname of the user to greet
  13. 13. Gree$ng by name In order to do so, we first provide an addi1onal overload of getRandomGreeting() public interface GreetingService { public String getRandomGreeting(); public String getRandomGreeting(String name, String surname); }
  14. 14. Parsing request parameters Second, we need a mechanism for parsing the HTTP request so as to extract the related request parameters HTTP request +--------------------------------------+ | GET /webapp/greeting?name=Jon | | &surname=Snow | +--------------------------------------+ | | | Host: myserver.com | | User-Agent: ... | | Accept-Encoding: ... | | | +--------------------------------------+
  15. 15. @RequestParam One such mechanism is the @RequestParam annota.on HTTP request +--------------------------------------+ | GET /webapp/greeting?name=Jon | | &surname=Snow | +--------------------------------------+ | | | Host: myserver.com | | User-Agent: ... | | Accept-Encoding: ... | | | +--------------------------------------+
  16. 16. @RequestParam The @RequestParam annota)on binds request parameters to method parameters @RequestMapping("/custom-greeting") public String getGreeting(@RequestParam("name") String name, @RequestParam("surname") String surname, Model model) { ... }
  17. 17. Request vs. method parameters The name request parameter binds to the name method parameter @RequestMapping("/custom-greeting") public String getGreeting(@RequestParam("name") String name, @RequestParam("surname") String surname, Model model) { ... }
  18. 18. Request vs. method parameters The surname request parameter binds to the surname method parameter @RequestMapping("/custom-greeting") public String getGreeting(@RequestParam("name") String name, @RequestParam("surname") String surname, Model model) { ... }
  19. 19. @RequestParam We can now add an addi+onal overload of getGreeting() and associate it with the /custom-greeting endpoint public class GreetingController { @RequestMapping("/greeting") public String getGreeting(Model model) { ... } @RequestMapping("/custom-greeting") public String getGreeting(@RequestParam("name") String name, @RequestParam("surname") String surname, Model model) { ... } }
  20. 20. getGreeting() @RequestMapping("/custom-greeting") public String getGreeting(@RequestParam("name") String name, @RequestParam("surname") String surname, Model model) { String greeting = service.getRandomGreeting(name, surname); model.addAttribute("greeting", greeting); return "greeting"; }
  21. 21. Required by default Request parameters specified using @RequestParam are required by default
  22. 22. URL templates To trigger the execu-on of getGreeting(), we should construct a URL according to the following URL template http://myserver.com/webapp/custom-greeting?name={name} &surname={surname}
  23. 23. HTML forms A possible way to construct URLs from a URL template is to use an HTML form <form action="..." method="..."> </form>
  24. 24. HTML forms HTML forms are hypermedia controls that allow users to supply data towards an applica7on endpoint <form action="..." method="..."> </form>
  25. 25. HTML forms We provide one text field per URL parameter, as well as a submit bu3on <form action="..." method="..."> <input type="text" name="name"> <input type="text" name="surname"> <button type="submit">Get your greeting!</button> </form>
  26. 26. HTML forms Since we want values to be sent as part of the query string, we specify GET as the HTTP method <form action="..." method="GET"> <input type="text" name="name"> <input type="text" name="surname"> <button type="submit">Get your greeting!</button> </form>
  27. 27. HTML forms Finally, we provide the endpoint where to send the parameters; intui6vely we would like to do the following <form action="/custom-greeting" method="GET"> <input type="text" name="name"> <input type="text" name="surname"> <button type="submit">Get your greeting!</button> </form>
  28. 28. HTML forms Since a single Web container may host several Web applica4ons, the correct endpoint is /webapp/custom-greeting1 <form action="/webapp/custom-greeting" method="GET"> <input type="text" name="name"> <input type="text" name="surname"> <button type="submit">Get your greeting!</button> </form> 1 Where webapp is the name of our Web applica4on
  29. 29. HTML forms However, we would much prefer not to hard-code the name of the Web applica7on
  30. 30. The <spring:url> tag When using JSP as view technology, we can take advantage of the <spring:url> tag <spring:url value="/custom-greeting" var="customGreeting"/>
  31. 31. The <spring:url> tag Given an URL, <spring:url> generates the corresponding context-aware URL, which is then saved into a page variable <spring:url value="/custom-greeting" var="customGreeting"/>
  32. 32. The <spring:url> tag That is, links of the form /endpoint are automa2cally transformed into links of the form /webapp/endpoint /endpoint → /webapp/endpoint
  33. 33. HTML forms The final HTML form thus reads <spring:url value="/custom-greeting" var="customGreeting"/> <form action="${customGreeting}" method="GET"> <input type="text" name="name"> <input type="text" name="surname"> <button type="submit">Get your greeting!</button> </form>
  34. 34. The Spring tag library The <spring:url> tag is part of the Spring tag library, which is in turn part of Spring MVC <spring:url value="/custom-greeting" var="customGreeting"/>
  35. 35. The Spring tag library The Spring tag library can be imported into a JSP page by means of the taglib direc8ve2 <%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %> 2 Remember that the taglib direc.ve does not actually load anything, we s.ll need to deploy the Spring tag library together with the applica.on
  36. 36. Request parameters Request parameters can be transferred as part of • The query string • The HTTP en1ty body
  37. 37. Request parameters Request parameters can be transferred as part of • The query string • The HTTP en(ty body
  38. 38. En#ty body The en&ty body is an op#onal part of HTTP messages HTTP message +----------------------------+ | Start line | +----------------------------+ | | | Headers | | | +----------------------------+ | | | Entity body | | | +----------------------------+
  39. 39. En#ty body Unlike the start line and the headers, the body can contain text or binary data HTTP message +----------------------------+ | Start line | +----------------------------+ | | | Headers | | | +----------------------------+ | | | Entity body | | | +----------------------------+
  40. 40. En#ty body While any HTTP request is allowed to contain a body, some servers will refuse to, e.g., process a GET request with a body3 3 One prominent example is Google (see references)
  41. 41. POST The POST verb is among the HTTP verbs that explicitly admit the presence of an en5ty body HTTP message +--------------------------------+ | POST /custom-greeting HTTP/1.1 | +--------------------------------+ | | | Content-Type: ... | | | +--------------------------------+ | | | Entity body | | | +--------------------------------+
  42. 42. POST As a ma&er of fact, the POST method is specifically designed to send input data to the server
  43. 43. POSTing our gree-ng +--------------------------------------------------+ | POST /webapp/custom-greeting HTTP/1.1 | +--------------------------------------------------+ | Host: myserver.com | | User-Agent: ... | | Content-Type: application/x-www-form-urlencoded | +--------------------------------------------------+ | name=Jon&surname=Snow | | | +--------------------------------------------------+
  44. 44. HTML form As before, we can use an HTML form to issue a POST request <spring:url value="/custom-greeting" var="customGreeting"/> <form action="${customGreeting}" method="POST"> <input type="text" name="name"/> <input type="text" name="surname"/> <button type="submit">Get your greeting!</button> </form>
  45. 45. HTML form Since we want our parameters to be transferred as part of the en4ty body, we specify POST as the HTTP method <spring:url value="/custom-greeting" var="customGreeting"/> <form action="${customGreeting}" method="POST"> <input type="text" name="name"/> <input type="text" name="surname"/> <button type="submit">Get your greeting!</button> </form>
  46. 46. How can we decide between GET and POST requests?
  47. 47. "...the GET and HEAD methods SHOULD NOT have the significance of taking an ac?on other than retrieval. These methods ought to be considered "safe"." (RFC 2616)
  48. 48. "This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe ac/on is being requested." (RFC 2616)
  49. 49. "The important dis0nc0on here is that the user did not request the side-effects, so therefore cannot be held accountable for them." (RFC 2616)
  50. 50. Unsafe ac)ons The user is accountable for the POST requests she executes, as they represent unsafe ac-ons on the Web applica:on
  51. 51. Unsafe ac)ons Unsafe ac)ons may alter the state of the Web applica)on. For instance, causing external informa)on sent as form data to be stored
  52. 52. Accountability That’s why the browser informs us when we try to send again a HTTP POST request
  53. 53. Data binding
  54. 54. HTML form vs. Web apps HTML forms give users a place where to enter data
  55. 55. Text parameters The browser sends the data up to the server as a list of name-value pairs +--------------------------------------------------+ | POST /webapp/custom-greeting HTTP/1.1 | +--------------------------------------------------+ | Host: myserver.com | | User-Agent: ... | | Content-Type: application/x-www-form-urlencoded | +--------------------------------------------------+ | name=Jon&surname=Snow | | | +--------------------------------------------------+
  56. 56. Text parameters Everything is going to be transferred to the Web app as text +--------------------------------------------------+ | POST /webapp/custom-greeting HTTP/1.1 | +--------------------------------------------------+ | Host: myserver.com | | User-Agent: ... | | Content-Type: application/x-www-form-urlencoded | +--------------------------------------------------+ | name=Jon&surname=Snow | | | +--------------------------------------------------+
  57. 57. HTML form vs. Web apps But, what if... • A field has to be interpreted as something different than a String (e.g., as a Date)? • The user forgets to provide a mandatory field? does she have to re-type everything from scratch? • We want to check that a field respects a given paDern?
  58. 58. HTML form vs. Web apps But, what if... • We need to perform data conversion? • We need to perform data buffering? • We need to perform data valida2on?
  59. 59. HTML form vs. Web apps HTTP/HTML does not provide a component that can buffer, validate4 and convert inputs coming from a form 4 Here, we are not considering HTML5 Constraint Valida8on API
  60. 60. HTML form vs. Web apps When trying to solve these issues, HTML and HTTP are of no use to us
  61. 61. HTML form vs. Web apps This is how HTTP and HTML work, Web apps cannot control this
  62. 62. The "Sign-up" example Let us assume that our Web applica2on requires the user to sign up for a new account
  63. 63. The "Sign-up" example To this end, we introduce: • An annotated controller named AccountController • A registra1on page
  64. 64. AccountController @Controller public class AccountController { @RequestMapping("/account") public String addAccount(...) { ... } }
  65. 65. Registra)on form <spring:url value="/account" var="account"/> <form action="${account}" method="POST"> Name: <input type="text" name="name"/> <br/> Surname: <input type="text" name="surname"/> <br/> Email: <input type="text" name="email"/> <br/> Birthday: <input type="text" name="birthday"/> <br/> <button type="submit">Sign-up</button> </form>
  66. 66. The Account bean Once we get the data, we may want to store them in a JavaBean public class Account { private String name; private String surname; private String email; private Date birthday; // getters and setters }
  67. 67. The Account bean Which is the best way of moving from the request parameters to the corresponding Account object? +------------------------------------------------+ | POST /webapp/account HTTP/1.1 | public class Account { +------------------------------------------------+ | Host: myserver.com | private String name; | User-Agent: ... | private String surname; | Content-Type: application/x-www-form-urlencoded| private String email; +------------------------------------------------+ +-----> private Date birthday; | name=Jon& | | surname=Snow& | // getters and setters | birthday=10-1-1956 | } +------------------------------------------------+
  68. 68. The naïve solu-on We could try to get each single request parameter individually @RequestMapping("/account") public String addAccount(@RequestParam("name") String name, @RequestParam("surname") String surname, @RequestParam("email") String email, @RequestParam("birthday") Date birthday, Model model) { // we manually populate the Account object with // the data coming from the user Account a = new Account(); a.setName(name); ... }
  69. 69. Method parameters in OOP “The ideal number of arguments for a func5on is zero. Next comes one, followed closely by two. Three arguments should be avoided where possible. More than three requires very special jus5fica5on - and then shouldn’t be used anyway.” (B. Mar(n)
  70. 70. The naïve solu-on By doing so, we effec/vely clu$ered the handler method signature @RequestMapping("/account") public String addAccount(@RequestParam("name") String name, @RequestParam("surname") String surname, @RequestParam("email") String email, @RequestParam("birthday") Date birthday, Model model) { Account a = new Account(); a.setName(name); ... }
  71. 71. Data binding Wouldn’t it be great if data coming from the form could be automa&cally bound to an Account object? +------------------------------------------------+ | POST /webapp/account HTTP/1.1 | public class Account { +------------------------------------------------+ | Host: myserver.com | private String name; | User-Agent: ... | private String surname; | Content-Type: application/x-www-form-urlencoded| private String email; +------------------------------------------------+ +-----> private Date birthday; | name=Jon& | | surname=Snow& | // getters and setters | birthday=10-1-1956 | } +------------------------------------------------+
  72. 72. Data binding Data binding is the process of binding the request parameters to a so called form bean public class Account { private String name; private String surname; private String email; private Date birthday; // getters and setters }
  73. 73. Data binding The form bean is also called the form-backing bean, the form object or the command object public class Account { ... }
  74. 74. Data binding It turns out that all we need to do is to declare an Account object as a method parameter @RequestMapping("/account") public String addAccount(Account account) { // account will be automatically populated // with the request parameters }
  75. 75. Data binding The following sequence of opera3ons occurs: • A new form bean is instan(ated • The form bean is added to the model • The form bean is populated from the request parameters
  76. 76. The form bean is a model a0ribute The account form bean will be automa&cally added to the model Model +----------------+ | | | account | HTTP +-------------------+ +----------------+ +--------------------+ request | | | | +--------> DispatcherServlet +----------------------> AccountController | | | | | +-------------------+ +--------------------+
  77. 77. The form bean is a model a0ribute Hence, views can access and render the form bean content Model Model +--------------+ +--------------+ | | | | | account | | account | +----------+ +--------------+ +-------------------+ +--------------+ +--------------------+ | | | | | | | View <--------------------+ DispatcherServlet <--------------------+ AccountController | | | | | | | +----------+ +-------------------+ +--------------------+
  78. 78. The form bean is a model a0ribute <html> <head> <title>Thanks</title> </head> <body> Hi, ${account.name} ${account.surname}. You have successfully registered. <br/> </body> </html>
  79. 79. Default values Assume that we want to ask the user for the permission of sending marke&ng e-mails
  80. 80. Default values To this end, we add a marketingOk property in the Account form bean public class Account { private String name; private String surname; private String email; private Date birthday; private boolean marketingOk; // getters and setters }
  81. 81. Default values By default, we would like the marketingOk property to be true public class Account { private String name; private String surname; private String email; private Date birthday; private boolean marketingOk = true; // getters and setters }
  82. 82. Prepopula)ng Account As a ma&er of fact, we are prepopula(ng the Account bean public class Account { private String name; private String surname; private String email; private Date birthday; private boolean marketingOk = true; // getters and setters }
  83. 83. Showing prepopulated data We want the registra-on page to use proper-es coming from a prepopulated Account bean +--------------------------------------+ | | | +----------------------+ | | Name: | | | | +----------------------+ | | | | +----------------------+ | | Surname: | | | +--------------+ | +----------------------+ | | | | <----------+ Account bean | | +----------------------+ | | | | Date: | | | +--------------+ | +----------------------+ | | | | +-+ | | |✓| Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  84. 84. Showing prepopulated data Recall that we can send data to the view through the model object +--------------------------------------+ | | | +----------------------+ | | Name: | | | | +----------------------+ | | | | +----------------------+ | | Surname: | | | +--------------+ | +----------------------+ | | | | <----------+ Account bean | | +----------------------+ | | | | Date: | | | +--------------+ | +----------------------+ | | | | +-+ | | |✓| Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  85. 85. Showing prepopulated data What if we let the AccountController return a prepopulated form bean as part of the model? Model Model +--------------+ +--------------+ | | | | | account | | account | +----------+ +--------------+ +-------------------+ +--------------+ +--------------------+ | | | | | | | View <--------------------+ DispatcherServlet <--------------------+ AccountController | | | | | | | +----------+ +-------------------+ +--------------------+
  86. 86. AccountController @Controller public class AccountController { @RequestMapping(value="/account/new", method=RequestMethod.GET) public String getEmptyAccount(Model model) { model.addAttribute(new Account()); return "account/new"; } @RequestMapping(value="/account/new", method=RequestMethod.POST) public String addAccount(Account account) { ... } }
  87. 87. Data binding-aware forms Once the prepopulated Account object is available in the view, we need to bind its content to the Web form +--------------------------------------+ | | | +----------------------+ | | Name: | | | | +----------------------+ | | | | +----------------------+ | | Surname: | | | +--------------+ | +----------------------+ | | | | <----------+ Account bean | | +----------------------+ | | | | Date: | | | +--------------+ | +----------------------+ | | | | +-+ | | |✓| Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  88. 88. Spring form tag library To deal with prepopulated form beans, Spring provides a set of data binding-aware tags
  89. 89. The revised registra-on form <form:form modelAttribute="account"> Name: <form:input path="name"/> <br/> Surname: <form:input path="surname"/> <br/> Email: <form:input path="email"/> <br/> Birthday: <form:input path="birthday"/> <br/> <form:checkbox path="marketingOk"/> Please send me product updates via e-mail <br/> <button type="submit">Sign-up<button/> </form:form>
  90. 90. Form tags vs. HTML tags ┌───────────────────────────────────┬───────────────────────────┐ │ Spring form tag library │ HTML │ ├───────────────────────────────────┼───────────────────────────┤ │ <form:form> │ <form> │ ├───────────────────────────────────┼───────────────────────────┤ │ <input:text> │ <input type="text"> │ ├───────────────────────────────────┼───────────────────────────┤ │ <input:password> │ <input type="password"> │ ├───────────────────────────────────┼───────────────────────────┤ │ <input:checkbox> │ <input type="checkbox"> │ └───────────────────────────────────┴───────────────────────────┘
  91. 91. Spring form tags The <form:input>, <form:password> and <form:checkbox> tags are data-binding versions of the corresponding HTML elements
  92. 92. Spring form tags The tag library does not provide anything for submit bu(ons, as there is nothing to bind to <button type="submit">Sign-up<button/>
  93. 93. Spring form tag library To use the tags from the form library, the following direc7ve needs to be added at the top of the JSP page: <%@ taglib prefix=”form" uri="http://www.springframework.org/tags/form" %>
  94. 94. Data valida)on
  95. 95. Users make mistakes No ma&er how intui/ve the registra/on form, users will accidentally fill it out with invalid informa,on +--------------------------------------+ | | | +----------------------+ | | Name: | | | | +----------------------+ | | | | +----------------------+ | | Surname: | | | | +----------------------+ | | | | +----------------------+ | | Date: | | | | +----------------------+ | | | | +-+ | | |✓| Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  96. 96. Users make mistakes When this happens, we want to explain the error in nontechnical language and help the users to overcome it +--------------------------------------+ | | | +----------------------+ | | Name: | | | | +----------------------+ | | | | +----------------------+ | | Surname: | | | | +----------------------+ | | | | +----------------------+ | | Date: | | | | +----------------------+ | | | | +-+ | | | | Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  97. 97. Data valida)on To detect user’s errors, we need to validate the form data that are encapsulated in the form bean public class Account { private String name; private String surname; private String email; private Date birthday; private boolean marketingOk = true; // getters and setters }
  98. 98. Data valida)on Example: the email property should respect the pa/ern username@provider.tld public class Account { private String name; private String surname; private String email; private Date birthday; private boolean marketingOk = true; // getters and setters }
  99. 99. Bean Valida*on API The Bean Valida*on API (JSR-303) is a specifica3on that defines a metadata model and API for JavaBean valida3on
  100. 100. Bean Valida*on API Using this API, it is possible to annotate bean proper4es with declara*ve valida*on constraints • @NotNull, @Pattern, @Size
  101. 101. Constraining Account public class Account { @Pattern(regexp="^[A-Z]{1}[a-z]+$") @Size(min=2, max=50) private String name; @Pattern(regexp="^[A-Z]{1}[a-z]+$") @Size(min=2, max=50) private String surname; @NotNull @Email private String email; @NotNull private Date birthday; }
  102. 102. Constraining Account We impose name and surname to start with a capital le2er and have at least one addi4onal lowercase le2er public class Account { @Pattern(regexp="^[A-Z]{1}[a-z]+$") @Size(min=2, max=50) private String name; @Pattern(regexp="^[A-Z]{1}[a-z]+$") @Size(min=2, max=50) private String surname; }
  103. 103. Constraining Account We impose email to respect the username@provider.tld pa.ern public class Account { @NotNull @Email private String email; }
  104. 104. Hibernate Validator As with any other Java Enterprise Edi3on API, the standard defines only the API specifica,on
  105. 105. Hibernate Validator We are going to use Hibernate Validator, which is the reference implementa5on of the JSR-303 specifica5on <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency>
  106. 106. Custom annota*ons Along with the standard constraints, Hibernate Validator provides some custom annota*ons (e.g., @Email) public class Account { @Email private String email; }
  107. 107. Custom annota*ons Remember: those annota)ons are Hibernate-specific and they will not work with any other JSR-303 implementa)on public class Account { @Email private String email; }
  108. 108. Valida&ng account Valida&on is achieved through the @Valid annota&on @RequestMapping(...) public String addAccount(@Valid Account account) { ... }
  109. 109. Valida&ng account The @Valid annota)on causes the account object to be first validated and then added to the model @RequestMapping(...) public String addAccount(@Valid Account account) { ... }
  110. 110. Valida&ng account The handler method may ask for a BindingResult object, which represent th result of the valida9on process @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult) { ... }
  111. 111. Valida&ng account We can then inspect the bindingResult object for possible valida&on errors @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult) { if (bindingResult.hasErrors()) return "account/new"; ... }
  112. 112. Data buffering Since the account bean is part of the model, it will be sent back to the view even in case of errors Model Model +--------------+ +--------------+ | | | | | account | | account | +----------+ +--------------+ +-------------------+ +--------------+ +--------------------+ | | | | | | | View <--------------------+ DispatcherServlet <--------------------+ AccountController | | | | | | | +----------+ +-------------------+ +--------------------+
  113. 113. Data buffering Hence, the user is not forced to re-enter the data from scratch when asked to correct her mistakes +--------------------------------------+ | | | +----------------------+ | | Name: | Jon | | | +----------------------+ | | | | +----------------------+ | | Surname: | 1234 | | | +----------------------+ | | | | +----------------------+ | | Date: | | | | +----------------------+ | | | | +-+ | | | | Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  114. 114. Data buffering Hence, the user can correct her mistakes, while keeping the correct data untouched +--------------------------------------+ | | | +----------------------+ | | Name: | Jon | | | +----------------------+ | | | | +----------------------+ | | Surname: | 1234 | | | +----------------------+ | | | | +----------------------+ | | Date: | | | | +----------------------+ | | | | +-+ | | | | Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  115. 115. Error messages Even if we returned the ini.al form, we s.ll have to inform the user on the reason why the data have been rejected +--------------------------------------+ | | | +----------------------+ | | Name: | Jon | | | +----------------------+ | | | | +----------------------+ | "Hey user, the | Surname: | snow <----------+ surname should | +----------------------+ | start with a | | capital letter" | +----------------------+ | | Date: | | | | +----------------------+ | | | | +-+ | | | | Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  116. 116. Error messages To this end, the BindingResult object is automa&cally inserted into the model and sent back to the view Model Model +--------------+ +--------------+ | account | | account | +--------------+ +--------------+ |bindingResult | |bindingResult | +----------+ +--------------+ +-------------------+ +--------------+ +--------------------+ | | | | | | | View <--------------------+ DispatcherServlet <--------------------+ AccountController | | | | | | | +----------+ +-------------------+ +--------------------+
  117. 117. <form:errors> Spring MVC provides the <form:errors> tag as part of the Spring’s form tag library <form:errors path="name"/>
  118. 118. <form:errors> The <form:errors> tag renders in HTML error messages taken from the BindingResult object
  119. 119. <form:errors> <form:form modelAttribute="account"> Name: <form:input path="name"/> <form:errors path="name"/> <br/> Surname: <form:input path="surname"/> <form:errors path="surname"/> <br/> Email: <form:input path="email"/> <form:errors path="email"/> <br/> ... </form:form>
  120. 120. User-centric error messages Unfortunately, most of the default Hibernate validator error messages are not par8cularly user-centric
  121. 121. User-centric error messages For instance, @Pattern's default message shows a reference to the regular expression to be respected
  122. 122. Message interpola.on Message interpola.on is the process of crea-ng error messages for violated Bean Valida-on constraints +--------------------------------------+ | | | +----------------------+ | | Name: | | | | +----------------------+ | | | | +----------------------+ | | Surname: | | | | +----------------------+ | | | | +----------------------+ | | Date: | | | | +----------------------+ | | | | +-+ | | | | Please send me product updates | | +-+ | | +-------------+ | | | Sign-up! | | | +-------------+ | +--------------------------------------+
  123. 123. Message interpola.on We can define the message descriptor of each property through the message a4ribute public class Account { @NotNull(message = "the email address cannot be empty") @Email(message = "please provide a valid e-mail address") private String email; }
  124. 124. Message interpola.on The problem with message a1ributes is that we are hard-coding the error messages public class Account { @NotNull(message = "the email address cannot be empty") @Email(message = "please provide a valid e-mail address") private String email; }
  125. 125. What if we want the messages to change according to, e.g., the user’s locale?
  126. 126. Resource bundle A be%er alterna+ve is to store the error messages in a separate file, called the resource bundle
  127. 127. Resource bundle By doing so, error messages can be updated independently of the source code (and vice versa)
  128. 128. Resource bundle We may devise the following resource bundle: NotBlank.account.email=the email address cannot be empty Email.account.email=please provide a valid e-mail address NotNull.account.birthday=The date cannot be empty
  129. 129. Resource bundle Spring’s conven.on dictates the following syntax for messages: [ConstraintName].[ClassName].[FieldName]=[Message]
  130. 130. MessageSource If we decide to externalize messages from the code, we need a source from which to get the messages, i.e., a MessageSource
  131. 131. MessageSource Specifically, in order to load messages from a resource bundle, we need a ReloadableResourceBundleMessageSource bean
  132. 132. Adding a MessageSource We therefore change frontcontroller-servlet.xml in order to have the Spring DI Container load the MessageSource5 <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="classpath:validationMessages" /> </bean> 5 Remember: the MessageSource bean must have the id equal to messageSource
  133. 133. Using message codes We can now specify the message code in lieu of the message itself public class Account { @NotBlank(message = "{NotBlank.account.email}") @Email(message = "{Email.account.email}") private String email; }
  134. 134. The PRG pa*ern
  135. 135. Duplicate submissions Most of the )mes, form data are submi2ed as POST requests +--------------+ +--------------+ | +------- POST --------> | | Browser | | Web app | | <------ 200 OK -------+ | +--------------+ +--------------+
  136. 136. Duplicate submissions What if the user presses refresh on the browser? +--------------+ +--------------+ | +------- POST --------> | | Browser | | Web app | | <------ 200 OK -------+ | +--------------+ +--------------+
  137. 137. Duplicate submissions Since refreshing means resending the latest HTTP request, it will cause the HTTP POST to be resubmi+ed +--------------+ +--------------+ +-------> +------- POST --------> | Refresh | | Browser | | Web app | +-------+ <------ 200 OK -------+ | +--------------+ +--------------+
  138. 138. Duplicate submissions Given the unsafe nature of POST, resending the latest POST request may lead to unwanted results +--------------+ +--------------+ +-------> +------- POST --------> | Refresh | | Browser | | Web app | +-------+ <------ 200 OK -------+ | +--------------+ +--------------+
  139. 139. The PRG pa*ern The Post/Redirect/Get (PRG) pa/ern solves the duplicate submission problem
  140. 140. The PRG pa*ern According to the PRG pa2ern, the Web app should not immediately return the outcome of the POST opera?ons +--------------+ +--------------+ | +------- POST --------> | | Browser | | Web app | | <------ 200 OK -------+ | +--------------+ +--------------+
  141. 141. The PRG pa*ern Instead, the Web applica1on should answer with a redirect response +--------------+ +--------------+ | +------- POST --------> | | Browser | | Web app | | <--- 3xx REDIRECT ----+ | +--------------+ +--------------+
  142. 142. The PRG pa*ern This causes the client to automa/cally issue a new GET request, to which the Web app finally answers with the actual content +--------------+ +--------------+ | +------- GET -------> | | Browser | | Web app | | <------ 200 OK -------+ | +--------------+ +--------------+
  143. 143. The PRG pa*ern Thus, if the user refreshes the page, the GET request will be send, instead of the original HTTP POST +--------------+ +--------------+ +-------> +------- GET -------> | Refresh | | Browser | | Web app | +-------+ <------ 200 OK -------+ | +--------------+ +--------------+
  144. 144. Redirec'ng In order to force a redirect, it is sufficient to return the redirect URL prefixed with the label redirect: @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult) { if (bindingResult.hasErrors()) return "account/edit"; service.saveAccount(account); return "redirect:/account/thanks"; }
  145. 145. Redirec'ng The redirect: prefix is used as a special indica+on that a redirect is needed @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult) { if (bindingResult.hasErrors()) return "account/edit"; service.saveAccount(account); return "redirect:/account/thanks"; }
  146. 146. Redirec'ng Note that what follows redirect: is used as the redirect URL @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult) { if (bindingResult.hasErrors()) return "account/edit"; service.saveAccount(account); return "redirect:/account/thanks"; }
  147. 147. Redirec'ng That is, /account/thanks has to be intended as http://myserver.com/webapp/account/thanks @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult) { if (bindingResult.hasErrors()) return "account/edit"; service.saveAccount(account); return "redirect:/account/thanks"; }
  148. 148. View controllers We need to associate a handler method with /account/thanks @Controller @RequestMapping("/account") public class AccountController { @RequestMapping("/thanks") public String getThanks() { return "thanks"; } }
  149. 149. View controllers The sole responsibility of such a handler method would be to return the /account/thanks view name @RequestMapping("/thanks") public String getThanks() { return "thanks"; }
  150. 150. View controllers Star%ng from Spring 3, it is possible to declara've set up controllers whose unique responsibility is to return a view name
  151. 151. View controllers The following declares in frontcontroller-servlet.xml a controller capable of answering to /account/thanks <mvc:view-controller path="/account/thanks” view-name="account/thanks"/>
  152. 152. Model life)me As the model contains the data to be rendered by the view, its life6me is limited by the request/response lifecycle
  153. 153. Model life)me In other words, a new model object is created for each request that hits DispatcherServlet +-------+ HTTP | Model | request +---------------------+ +-------+ +--------------+ | | | | +-------> DispatcherServlet +-------------> Controller | | | | | +---------------------+ +--------------+
  154. 154. PRG pa'ern vs. model a'ributes A redirect creates a new request, hence causing the model a3ributes to be discarded +--------------+ +--------------+ | +------- POST --------> | | Browser | | Web app | | <--- 3xx REDIRECT ----+ | +--------------+ +--------------+
  155. 155. What if we want to retain some model a1ributes?
  156. 156. The flash scope A possible solu+on is store a0ributes of interest in the flash scope
  157. 157. The flash scope The flash scope works similarly to the session scope +-----+ Request +-----+ | +----------------> | +--+-------+-+ | | | | | | | | Response | | | F | | <----------------+ | | l | | B | | W | | a | | r | Request | e | S | s | | o +----------------> b | e | h | | w | | | s | | | s | Response | a | s | | | e <----------------+ p +------ i | ------v-- | r | | p | o | | | Request | | n | | +----------------> | | | | | | | | | Response | | | | <----------------+ +---------v--- +-----+ +-----+
  158. 158. The flash scope The difference is that flash a&ributes are kept solely for the subsequent request +-----+ Request +-----+ | +----------------> | +--+-------+-+ | | | | | | | | Response | | | F | | <----------------+ | | l | | B | | W | | a | | r | Request | e | S | s | | o +----------------> b | e | h | | w | | | s | | | s | Response | a | s | | | e <----------------+ p +------ i | ------v-- | r | | p | o | | | Request | | n | | +----------------> | | | | | | | | | Response | | | | <----------------+ +---------v--- +-----+ +-----+
  159. 159. The flash scope Flash a'ributes are stored before the redirect and made available as model a'ributes a2er the redirect +-----+ Request +-----+ | +----------------> | +--+-------+-+ | | | | | | | | Response | | | F | | <----------------+ | | l | | B | | W | | a | | r | Request | e | S | s | | o +----------------> b | e | h | | w | | | s | | | s | Response | a | s | | | e <----------------+ p +------ i | ------v-- | r | | p | o | | | Request | | n | | +----------------> | | | | | | | | | Response | | | | <----------------+ +---------v--- +-----+ +-----+
  160. 160. RedirectAttributes A handler method can declare an argument of type RedirectAttributes... @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult, RedirectAttributes redirectAttributes) {...}
  161. 161. RedirectAttributes ...and use its addFlashAttribute() method to add a.ributes in the flash scope @RequestMapping(...) public String addAccount(@Valid Account account, BindingResult bindingResult, RedirectAttributes redirectAttributes) { if (bindingResult.hasErrors()) return "account/new"; redirectAttributes.addFlashAttribute("account", account); return ”redirect:/account/thanks”; }
  162. 162. The "Thank you" page account is accessible in the view, since it has been automa2cally inserted into the next request model <html> <head> <title>Thanks</title> </head> <body> Hi, ${account.name} ${account.surname}. You have been successfully registered. <br/> </body> </html>
  163. 163. Recap
  164. 164. Form beans Form beans are versa&le objects, as they play different roles at the same 1me • Data binder • Data buffer • Data validator
  165. 165. Data binder Developers can work with a trusty POJO and leave all the HTML/ HTTP issues to the framework • Binding • Coercion
  166. 166. Data buffer The form bean is not the des0na0on of the input, but a buffer that preserves the input un0l it can be validated and commi7ed to the service layer
  167. 167. Data validator Many fields must be the correct type before they can be processed by the business logic
  168. 168. References
  169. 169. References • Stackoverflow, GET vs. POST does it really ma;er? • Stackoverflow, HTTP GET with request body • R. Fielding, Introductory REST ArHcle • IETF, Hypertext Transfer Protocol – HTTP/1.1 (RFC 2616) • SpringSource, Spring Framework Reference
  170. 170. References • SpringSource, Java-doc APIs • Wikipedia, Post/Redirect/Get • Wikipedia, Bean Valida@on • Hibernate, Hibernate Validator reference guide
  171. 171. References • Craig Walls, Spring in Ac1on (3rd ed.), Manning Publica1ons • Willie Wheeler, Spring in Prac1ce, Manning Publica1ons • T. N. Husted et al., Struts 1 In Ac1on, Manning Publica1ons

×