{"id":2170,"date":"2016-02-16T00:00:00","date_gmt":"2016-02-16T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/keeping-post-and-get-separated\/"},"modified":"2021-05-17T18:34:45","modified_gmt":"2021-05-17T18:34:45","slug":"keeping-post-and-get-separated","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/keeping-post-and-get-separated\/","title":{"rendered":"Keeping POST and GET Separated"},"content":{"rendered":"<div class=\"article-content\">\n<p class=\"start\">There are problems in server-side web development that have existed since the early days of the internet but which we as an industry have never solved in a definitive way that&#8217;s generally accepted. One of these issues is how to deal with error messages after a POST request, and more in general in how we should deal with return messages. I&#8217;m going to discuss and compare different approaches to dealing with return messages in ASP.NET MVC, but the problem exists regardless the actual platform. I&#8217;d say that the only web-based programming task unaffected by this problem is that of the entirely client-side, single-page applications where each interaction with the server is orchestrated from within the same environment and where a return or error message is the response to a synchronous or asynchronous request.<\/p>\n<h2>Formalizing the problem<\/h2>\n<p>As an example, let&#8217;s consider a user that submits a form from within a web page. From the browser&#8217;s perspective that&#8217;s a plain HTTP POST request. The payload is created and the posted data is safely stored in the body of the packet. The server receives the request and handles it. Now assume the server is an ASP.NET MVC server. The request is mapped to a controller method and the controller method typically ends by selecting a Razor template for the view engine to render, filled with some retrieved data. The user receives some HTML and feels happy. Everything works just fine, so where&#8217;s the problem?<\/p>\n<p>This is probably the most common approach. It works, but is not free of snags. In particular, there are two potential problems. One is that the URL may not reflect the specific item of information being edited or created. The other is the repetition of the last action that was tracked by the browser. <\/p>\n<p>All browsers track the last HTTP command that the user requested and will reiterate that when the user hits F5 or selects the Refresh menu item. In this case, the last request is a HTTP POST request. Repeating a post may be a dangerous action because the POST is typically an action that alters the state of the system. To be safe, the operation needs be an idempotent operation (that is, it doesn&#8217;t change the state if executed repeatedly). To warn users about the risk of refreshing after a post, all browsers display a well-known message like the Google Chrome&#8217;s window you see in Figure 1.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/2368-Fig01.jpg\" alt=\"2368-Fig01.jpg\" \/><\/p>\n<p>Such windows have existed for years and didn&#8217;t prevent the spread of the web. Even so, they&#8217;re ugly to see and still liable to cause potentially harmful and unintentional user operations. It is not as easy as it may seem to get rid of those windows, though. To eliminate the risk of getting such messages, the entire flow of server-side web operations should be revisited but this, in turn, can end up creating new types of problems.<\/p>\n<h2>The Post-Redirect-Get pattern<\/h2>\n<p>At its core, the problem here is that command and query operations-POST and GET in HTTP jargon-are not as clearly separated as they should be for the sake of software and design. Note that this problem of separating command and query is a very long-running issue in software engineering that dates back to the Eiffel language and Bertrand Meyer&#8217;s research of over two decades ago. Today, command and query separation is at the foundation of a strong and emerging architectural pattern-CQRS, short for Command and Query Responsibility Separation. As such, command and query separation is a much more general topic that goes well beyond the context of HTTP.  Yet, keeping GET and POST actions clearly separated can only help. Enter the Post-Redirect-Get (PRG) pattern.<\/p>\n<p>The PRG pattern consists of a small set of recommendations aimed at guaranteeing that each POST command actually ends with a GET. It resolves the F5 &#8216;Refresh&#8217; problem and promotes a neat separation between HTTP actions for command and for query. Let&#8217;s have a look at the some commonly-used code to display a view for a new user to register with a site.<\/p>\n<pre class=\"listing\">[HttpGet]\n[ActionName(\"register\")]\npublic ActionResult ViewRegister()\n{\n    \/\/ Display the view through which the user will register\n    return View();\n}\nTo register, the user fills out and submits the form. A new request comes in as a POST and is handed by the following code:\n[HttpPost]\n[ActionName(\"register\")]\npublic ActionResult PostRegister(RegisterInputModel input)\n{\n    \/\/ Stores the new record permanently\n    ...\n\n    return View()\n}\n<\/pre>\n<p>As written here, the      <strong>PostRegiste<\/strong>     <strong>r<\/strong>     <strong><\/strong>method lacks any validation logic and returns to the same register form once completed. In case of unexpected results, or invalid input, it&#8217;s easy for the programmer to stuff error messages right in the view either via ad-hoc      <strong>ViewBag<\/strong> entries or through a view model type. Again, everything works just fine from a purely functional perspective. The only problem left is that, if the user refreshes the page after having successfully registered, then a new attempt is made which might originate some sort of an error message. <\/p>\n<p>Just one change is required to apply the PRG pattern to this code: instead of returning the view in the POST method, you redirect the user to another page. For example, you might want to redirect to the GET method of the same action.<\/p>\n<pre class=\"listing\">[HttpPost]\n[ActionName(\"register\")]\npublic ActionResult PostRegister(RegisterInputModel input)\n{\n    \/\/ Stores the new record permanently\n    ...\n\n    return RedirectToAction(\"register\")\n}\n<\/pre>\n<p>As a result, the last tracked action is a GET and this definitely eliminates the F5 issue. But there&#8217;s more. The URL displayed on the browser&#8217;s address bar is now a lot more significant. To understand this point, let&#8217;s assume that the same form is used to add new records or to edit existing ones. This means that the GET method may be given an ID. The code becomes as follows:<\/p>\n<pre class=\"listing\">[HttpGet]\n[ActionName(\"register\")]\npublic ActionResult ViewRegister(int? id)\n{\n    if (if.HasValue)\n    {\n       var model = _service.GetRegisterViewModel(id.Value);\n       return View(model);  \n    }\n    return View();\n}\n[HttpPost]\n[ActionName(\"register\")]\npublic ActionResult PostRegister(RegisterInputModel input)\n{\n    \/\/ Stores the new record permanently\n    var newUser = ...;\n\n    return RedirectToAction(\"register\", new {id = newUser.Id));\n}\n<\/pre>\n<p>Without the PRG pattern, the URL after a new user registered is still \/controller\/register. With PRG enabled, the POST method redirects to the view method and passes the ID of the newly created record. Hence, the new displayed URL is \/controller\/register\/id. And the last action to repeat is always a GET.<\/p>\n<h2>Pros and Cons<\/h2>\n<p>Like any other pattern, PRG is not in  black and white. It&#8217;s just one common way of accomplishing some common tasks. PRG promotes the separation between commands and queries, keeps the URL cleaner and solves the long-standing problem of refreshing a page after POST. However,  PRG is not free of issues.<\/p>\n<p>By  separating HTTP, POST and GET with a redirect, you make for two distinct and unrelated requests. Unfortunately, in the context of the application, those two requests-POST and successive GET-are strictly related and should be treated as such. In particular, they should share some state information, specifically validation and error messages if any. The context of the POST action holds the results of input validation and any error messages that may have resulted from the execution of the intended task. But then the POST action redirects to refresh the view. How can you pass display information to the next GET? To help you better visualize the problem, consider a login form. If the credentials that have been provided are invalid, how would you display the canonical error message? <\/p>\n<p>In ASP.NET MVC, you have the following core options. <\/p>\n<ul>\n<li>Place any shared information in the          <strong>TempData<\/strong> dictionary.     <\/li>\n<li>Trigger the POST action via JavaScript <\/li>\n<\/ul>\n<p>The latter approach is good because it doesn&#8217;t violate the command\/query separation and allows to receive a response from the POST with possible error messages to display. The downside is that it requires JavaScript to post. Not all developers are fond of JavaScript and, although the code to post a form via script is repetitive, it may produce a result that is unfamiliar to many.<\/p>\n<p>The      <strong>TempData<\/strong> dictionary is the ASP.NET MVC tool to support the PRG pattern. It&#8217;s a very special type of a dictionary designed to hold data across two consecutive requests.      <strong>TempData<\/strong> is analogous to      <strong>Session<\/strong> except that any content that it has held  is then  removed just two requests after being placed. In other words,      <strong>TempData<\/strong> just serves the purpose of caching any data to be shared across two consecutive requests such as a POST and a successive GET. Here&#8217;s how the login post action will be: <\/p>\n<pre class=\"listing\">[HttpPost]\n[ActionName(\"login\")]\npublic ActionResult LoginPost(LoginInputModel input, string returnUrl)\n{\n    \/\/ Check credentials to ensure they're not empty or incomplete \n    if (InvalidCredentials(input))\n    {\n        ModelState.AddModelError(\"\", \"Incomplete credentials\");\n    }\n\n    \/\/ Check credentials to see if they match a known user\n    if (ModelState.IsValid)\n    {\n        if (TryAuthenticate(input))\n            return Redirect(returnUrl ?? \"\/\");\n        ModelState.AddModelError(\"\", \"Invalid credentials\");\n    }\n\n    \/\/ If here, any error occurred\n    TempData[\"ModelState\"] = ModelState;\n    return RedirectToAction(\"login\");\n}\n<\/pre>\n<p>Any error that is detected  is first collected into the      <strong>ModelState<\/strong> dictionary and then the dictionary is saved to the      <strong>TempData<\/strong> dictionary. Using the      <strong>ModelState<\/strong> dictionary is simply a design choice; writing direct entries in      <strong>TempData<\/strong> for each message would work as well. Here&#8217;s the GET side of the PRG pattern when model information is being carried from the previous request. <\/p>\n<pre class=\"listing\">[HttpGet]\n[ActionName(\"login\")]\npublic ActionResult LoginGet(string returnUrl = \"\/\")\n{\n    var state = TempData[\"ModelState\"] as ModelStateDictionary;\n    ModelState.Merge(state);\n    \/\/ Pass data to the view\n    \/\/ ViewBag or view model type\n    return View();\n}\n<\/pre>\n<p>So you now recovered any display information collected after the POST command. To display such messages you can either use fresh entries in the      <strong>ViewBag<\/strong>     <strong><\/strong>collection or use a view model type with error message string properties. <\/p>\n<h2>Inside the TempData Dictionary<\/h2>\n<p>A moment ago, I said that the      <strong>TempData<\/strong> dictionary is analogous to the more popular      <strong>Session<\/strong> dictionary. Well, to be honest the     <strong><\/strong>     <strong>TempData<\/strong> dictionary just uses the      <strong>Session<\/strong> dictionary to store any content that must survive the current request. Put it another way:  You could place any message for the view in the session state during the POST and retrieve it later from the context of the next GET.  <\/p>\n<p>The only difference between      <strong>TempData<\/strong> and      <strong>Session<\/strong>-but, look, it&#8217;s a key difference-is the lifetime of stored data.      <strong>TempData<\/strong> content is a more short-lived than any session state content. As mentioned, it automatically disappears after two requests. In the end, you should consider      <strong>TempData<\/strong> as a replacement for      <strong>Session<\/strong> just for the purpose of the PRG pattern. <\/p>\n<p>The bad news is that just because      <strong>TempData<\/strong> uses the session state internally any solution based on it is inherently brittle. Using session state, directly or indirectly, is no big deal for single server applications but in the context of web farms and web roles, the less you use the session state the easier you make your life. In other words, to use      <strong>TempData<\/strong> in a Web Farms or cloud scenario you need distributed storage of the session state in an external process or in SQL Server.  <\/p>\n<p>Using distributed session storage is an architectural decision that is part of a wider scalability plan.      <strong>TempData<\/strong> is far less of a concern than session state as a whole. Would you really bring in external processes or database storage for just the PRG pattern? At the same time, would you just sacrifice design effectiveness and even a bit of user experience in the name of session state? There&#8217;s no easy answer, I&#8217;m afraid.  <\/p>\n<p>Thankfully, the      <strong>TempData<\/strong> dictionary supports a provider mechanism that allows you to change the default storage medium. It&#8217;s easy to find examples of alternate storage for      <strong>TempData<\/strong> content. For example, the following article demonstrates and implementation of      <strong>TempData<\/strong> that saves content to cookies:      <a href=\"http:\/\/brockallen.com\/2012\/06\/11\/cookie-based-tempdata-provider\">http:\/\/brockallen.com\/2012\/06\/11\/cookie-based-         <strong>TempData<\/strong>-provider     <\/a>. The trick is that the      <strong>TempData<\/strong> dictionary implements the     <strong><\/strong>     <strong>I<\/strong>     <strong>TempData<\/strong>     <strong>Provider<\/strong> interface so all you need to do is providing an alternate implementation of the interface that uses an alternate storage medium.  <\/p>\n<p>Also note that in ASP.NET MVC      <strong>TempData<\/strong> is bound to the controller context. This means that you can have different implementations of      <strong>TempData<\/strong> per controller. It also means that in order to register a new      <strong>TempData<\/strong> provider you must override the      <strong>Create<\/strong>     <strong>TempData<\/strong>     <strong>Provider<\/strong>     <strong><\/strong>method in the base Controller class. In practice, this forces you to have a base controller in all of your solutions. No big deal, but still good to know.  Finally, when you look into custom      <strong>TempData<\/strong> implementation you should not overlook the point of removing information after two consecutive requests to avoid the proliferation of non-relevant information across requests. <\/p>\n<h2>Summary<\/h2>\n<p>It is probably good to keep command and query actions distinct over the web but it&#8217;s tricky to achieve, at least in ASP.NET MVC. The      <strong>TempData<\/strong> dictionary is helpful and functional but it is not natively designed to scale in the context of a cloud and distributed deployment setting. At the end of the day, there&#8217;s no way to show the response of say, a login page, which is free of issues. There are many ways to do that, but none is just perfect. In a future article, I&#8217;ll discuss posting via JavaScript as well as other possible workarounds for sharing response between successive POST and GET requests. <\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>The occasional problems that you can get with POST and GET are typical of the difficulties of separating any command and query operations. This separation is tricky to achieve, at least in ASP.NET MVC. Dino suggests some ways of avoiding errors and minimising the confusing warning messages.&hellip;<\/p>\n","protected":false},"author":221911,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4156,4157,5166],"coauthors":[],"class_list":["post-2170","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-asp","tag-asp-net","tag-mvc"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2170","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221911"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=2170"}],"version-history":[{"count":2,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2170\/revisions"}],"predecessor-version":[{"id":91039,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/2170\/revisions\/91039"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=2170"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=2170"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=2170"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=2170"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}