Keeping POST and GET Separated

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.

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’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’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’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.

Formalizing the problem

As an example, let’s consider a user that submits a form from within a web page. From the browser’s perspective that’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’s the problem?

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.

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’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’s window you see in Figure 1.


Such windows have existed for years and didn’t prevent the spread of the web. Even so, they’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.

The Post-Redirect-Get pattern

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’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.

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 ‘Refresh’ problem and promotes a neat separation between HTTP actions for command and for query. Let’s have a look at the some commonly-used code to display a view for a new user to register with a site.

As written here, the PostRegiste r method lacks any validation logic and returns to the same register form once completed. In case of unexpected results, or invalid input, it’s easy for the programmer to stuff error messages right in the view either via ad-hoc ViewBag 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.

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.

As a result, the last tracked action is a GET and this definitely eliminates the F5 issue. But there’s more. The URL displayed on the browser’s address bar is now a lot more significant. To understand this point, let’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:

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.

Pros and Cons

Like any other pattern, PRG is not in black and white. It’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.

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?

In ASP.NET MVC, you have the following core options.

  • Place any shared information in the TempData dictionary.
  • Trigger the POST action via JavaScript

The latter approach is good because it doesn’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.

The TempData dictionary is the ASP.NET MVC tool to support the PRG pattern. It’s a very special type of a dictionary designed to hold data across two consecutive requests. TempData is analogous to Session except that any content that it has held is then removed just two requests after being placed. In other words, TempData just serves the purpose of caching any data to be shared across two consecutive requests such as a POST and a successive GET. Here’s how the login post action will be:

Any error that is detected is first collected into the ModelState dictionary and then the dictionary is saved to the TempData dictionary. Using the ModelState dictionary is simply a design choice; writing direct entries in TempData for each message would work as well. Here’s the GET side of the PRG pattern when model information is being carried from the previous request.

So you now recovered any display information collected after the POST command. To display such messages you can either use fresh entries in the ViewBag collection or use a view model type with error message string properties.

Inside the TempData Dictionary

A moment ago, I said that the TempData dictionary is analogous to the more popular Session dictionary. Well, to be honest the TempData dictionary just uses the Session 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.

The only difference between TempData and Session-but, look, it’s a key difference-is the lifetime of stored data. TempData 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 TempData as a replacement for Session just for the purpose of the PRG pattern.

The bad news is that just because TempData 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 TempData in a Web Farms or cloud scenario you need distributed storage of the session state in an external process or in SQL Server.

Using distributed session storage is an architectural decision that is part of a wider scalability plan. TempData 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’s no easy answer, I’m afraid.

Thankfully, the TempData dictionary supports a provider mechanism that allows you to change the default storage medium. It’s easy to find examples of alternate storage for TempData content. For example, the following article demonstrates and implementation of TempData that saves content to cookies: TempData-provider . The trick is that the TempData dictionary implements the I TempData Provider interface so all you need to do is providing an alternate implementation of the interface that uses an alternate storage medium.

Also note that in ASP.NET MVC TempData is bound to the controller context. This means that you can have different implementations of TempData per controller. It also means that in order to register a new TempData provider you must override the Create TempData Provider 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 TempData implementation you should not overlook the point of removing information after two consecutive requests to avoid the proliferation of non-relevant information across requests.


It is probably good to keep command and query actions distinct over the web but it’s tricky to achieve, at least in ASP.NET MVC. The TempData 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’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’ll discuss posting via JavaScript as well as other possible workarounds for sharing response between successive POST and GET requests.

For more articles like this, sign up to the fortnightly Simple-Talk newsletter.

Tags: , , ,


  • Rate
    [Total: 19    Average: 4.4/5]
  • Graeme Miller

    JSON Form Submit Alternative
    We used to do this but we found a better way to handle this is to never leave the page in the first place. Submit data as JSON and then only redirect on success. Return errors as JSON and update form.

    Something like this

    It ended up cleaner and more scalable than using filters to preserve and rehydrate state via TempData/Session.

  • Anonymous

    Errors are idempotent
    When doing PRG, I’ve always returned the View from the POST action if there’s an error – and only do the redirect for success. TempData comes in handy then for things like confirmation messages.

    An invalid or error’ed request is (usually) idempotent, since we’ve refused to take any action on it. If it’s an error, eg., an external system is down, the F5-to-resubmit is actually a feature as it allows a quick way to "try again".

  • Anonymous

    +1 for Errors are idempotent
    strongly agree; I think article should be updated to mention / recommend this pattern as best practice for thorough PRG implementation.

  • Dhanesh

    good Job Sir!