Asynchronous Postbacks
An Asynchronous Postback triggers a partial update to the page without refreshing the whole page. In the early days of the web, a round trip to the server required a complete refresh of the page which made things slower and in cases where there was a significant lag between the request and response, made the process appear glitchy.
AJAX
AJAX is an acronym for Asynchronous Javascript And XML. It is a set of web development techniques that uses various web technologies on the client-side to create asynchronous web applications. With AJAX, web applications can send and retrieve data from a server asynchronously (in the background) without interfering with the display and behavior of the existing page. By decoupling the data interchange layer from the presentation layer, AJAX allows web pages and, by extension, web applications, to change content dynamically without the need to reload the entire page. In practice, modern implementations commonly utilize JSON instead of XML.
AJAX is not a technology, but rather a programming pattern. HTML and CSS can be used in combination to mark up and style information. The webpage can be modified by JavaScript to dynamically display (and allow the user to interact with) the new information. The built-in XMLHttpRequest object is used to execute AJAX on webpages, allowing websites to load content onto the screen without refreshing the page. AJAX is not a new technology, nor is it a new language. Instead, it is existing technologies used in a new way.
History
In the early-to-mid 1990s, most Websites were based on complete HTML pages. Each user action required a complete new page to be loaded from the server. This process was inefficient, as reflected by the user experience: all page content disappeared, then the new page appeared. Each time the browser reloaded a page because of a partial change, all the content had to be re-sent, even though only some of the information had changed. This placed additional load on the server and made bandwidth a limiting factor in performance.
In 1996, the iframe tag was introduced by Internet Explorer; like the object element, it can load a part of the web page asynchronously. In 1998, the Microsoft Outlook Web Access team developed the concept behind the XMLHttpRequest scripting object. It appeared as XMLHTTP in the second version of the MSXML library, which shipped with Internet Explorer 5.0 in March 1999.
The functionality of the Windows XMLHTTP ActiveX control in IE 5 was later implemented by Mozilla Firefox, Safari, Opera, Google Chrome, and other browsers as the XMLHttpRequest JavaScript Object. Microsoft adopted the native XMLHttpRequest model as of Internet Explorer 7. The ActiveX version is still supported in Internet Explorer and on "Internet Explorer mode" in Microsoft Edge. The utility of these background HTTP requests and asynchronous Web technologies remained fairly obscure until it started appearing in large scale online applications such as Outlook Web Access (2000) and Oddpost (2002).
Google made a wide deployment of standards-compliant, cross browser Ajax with Gmail (2004) and Google Maps (2005). In October 2004 Kayak.com's public beta release was among the first large-scale e-commerce uses of what their developers at that time called "the xml http thing". This increased interest in AJAX among web program developers.
The term AJAX was publicly used on 18 February 2005 by Jesse James Garrett in an article titled Ajax: A New Approach to Web Applications, based on techniques used on Google pages.
On 5 April 2006, the World Wide Web Consortium (W3C) released the first draft specification for the XMLHttpRequest Object in an attempt to create an official Web standard. The latest draft of the XMLHttpRequest Object was published on 6 October 2016, and the XMLHttpRequest specification is now a living standard.
Postback Methods
The post back, whether it is synchronous or asynchronous, is done with either GET or POST. With GET, any data that is sent is done so using name-value pairs that are delimited with an ampersand character, that are then added to the end of the page url preceeded with a question-mark character. In the following example, let's say we have a page with the url of https://www.mysite.com/login.php and that page has a form with two text boxes, username and password, along with a submit button. The following show the difference between how these are sent depending upon if the send type is GET or POST:
GET
The data is sent in the querystring of the URL.
POST
The data is sent in the body of the post and is not visible to the user.
HTML Form Element
The HTML <form> element is an optional tag that is often used in forms and form submission. It's optional because many of the attributes used to set up a form for submission can be created programatically using Javascript. Regardless, certain information should be set before a form is submitted, such as the following, all subsequently attributes of the HTML form element:
action | Where the form will be submitted to. If not set the page will submit to itself. This can easily be set programatically using javascript either with a form element or upon a postback. |
---|---|
enctype |
This stands for encryption type. This can also be set usng Javascript.
The three most common types are:
|
method |
How the form is submitted:
|
accept-charset |
Accepted character set. The most common are:
|
Ways to Submit a Form
The HTML Submit Button | The HTML <input type="submit" /> button purpose is to submit an HTML form to the server. How this is done depends upon how parameters are set in either the <form> element (dynamically or statically) or how they're set in the submit button. Both the form element and the submit button element allow the developer to set the same settings. |
---|---|
With Javascript | There are many ways a form can be submitted using Javascript. It could be an action by an HTML control. It can be done by progamatically creating a click event on a submit button. |
form | submit |
---|---|
action | formaction |
enctype | formenctype |
method | formmethod |
target | formtarget |
novalidate | formnovalidate |
How Data Is Packaged
The default way data is packaged to be sent to a server is using Name/Value pairs that are delimited with an ampersand character. This format works for many situations where the data being sent is not very complex. In situations where the data is more complex and/or hierarchical, JSON might be a better choice. A Name/Value pairs submission can be done with a submit button, but sending JSON must be done using another method. As web based applications become more complex the need to be able to post more complex data increases.
- Name/Value pairs - used for simple data.
- JSON - used for more complex heirachical data.
ASP.NET MVC Problem
One of the problems I ran into recently was trying to submit JSON data in an ASP.NET MVC form utilizing their RequestVerificationToken to prevent Cross Site Request Forgery (XSRF/CSRF) attacks. The problem is that ASP.NET MVC only considered posting standard name/value pair posting where their verification token, which is in a hidden form element, is passed along with the rest of the form element data.
Solution to MVC Problem
After searching online for a solution to this problem, I discovered I was not the only one who had to deal with this issue. The solution to this problem involved quite a few tricks. Since the data being sent was JSON, and the server side verification process involved looking for the token data in the name/value pairs, the verification process had to be modified to look for the token value in the header. This meant that the value on the client side must be placed into the header before posting to the server.
Each of the following methods posts JSON to the server with the verification token data added to the post header.
Client-Side Javascript
Server-Side C#
The following code is included in the controller for the view page. This code has the verification process look for the data in the headers instead of in the posted name/value pairs.
How Data Is Received
The form enctype, or submit formenctype property sets the way data is sent to the server. The setting for what type of response to expect is set with XMLHttpRequest using the responseType property. Using the fetch API, the setting can be set in the Response object.
Basic Response Types:
- Nothing
- Text
- HTML/XML
- JSON
The Fetch API
The Fetch API provides an interface for fetching resources (including across the network). Fetch is the modern replacement for XMLHttpRequest: unlike XMLHttpRequest, which uses callbacks, Fetch is promise-based and is integrated with features of the modern web such as service workers and Cross-Origin Resource Sharing (CORS).
With the Fetch API, you make a request by calling fetch(), which is available as a global function in both window and worker contexts. You pass it a Request object or a string containing the URL to fetch, along with an optional argument to configure the request.
The fetch() function returns a Promise which is fulfilled with a Response object representing the server's response. You can then check the request status and extract the body of the response in various formats, including text and JSON, by calling the appropriate method on the response.
Here's a minimal function that uses fetch() to retrieve some JSON data from a server:
We declare a string containing the URL and then call fetch(), passing the URL with no extra options.
The fetch() function will reject the promise on some errors, but not if the server responds with an error status like 404: so we also check the response status and throw if it is not OK.
Otherwise, we fetch the response body content as JSON by calling the json() method of Response, and log one of its values. Note that like fetch() itself, json() is asynchronous, as are all the other methods to access the response body content.
Making a Request
To make a request, call fetch(), passing in:
-
a definition of the resource to fetch. This can be any one of:
- a string containing the URL
- an object, such as an instance of URL, which has a stringifier that produces a string containing the URL
- a Request instance
- optionally, an object containing options to configure the request.
Setting the Method
By default, fetch() makes a GET request, but you can use the method option to use a different request method:
If the mode option is set to no-cors, then method must be one of GET, POST or HEAD.
Setting a Body
The request body is the payload of the request: it's the thing the client is sending to the server. You cannot include a body with GET requests, but it's useful for requests that send content to the server, such as POST or PUT requests. For example, if you want to upload a file to the server, you might make a POST request and include the file as the request body.
To set a request body, pass it as the body option:
You can supply the body as an instance of any of the following types:
- a string
- ArrayBuffer
- TypedArray
- DataView
- Blob
- File
- URLSearchParams
- FormData
- ReadableStream
Other objects are converted to strings using their toString() method. For example, you can use a URLSearchParams object to encode form data
Just like response bodies, request bodies are streams, and making the request reads the stream, so if a request contains a body, you can't make it twice:
Instead, you would need to create a clone of the request before sending it:
Setting Headers
Request headers give the server information about the request: for example, in a POST request, the Content-Type header tells the server the format of the request's body.
To set request headers, assign them to the headers option.
You can pass an object literal here containing header-name: header-value properties:
Alternatively, you can construct a Headers object, add headers to that object using Headers.append(), then assign the Headers object to the headers option:
Compared to using plain objects, the Headers object provides some additional input sanitization. For example, it normalizes header names to lowercase, strips leading and trailing whitespace from header values, and prevents certain headers from being set. Many headers are set automatically by the browser and can't be set by a script: these are called Forbidden request headers. If the mode option is set to no-cors, then the set of permitted headers is further restricted.
Creating a Request Object
The Request() constructor takes the same arguments as fetch() itself. This means that instead of passing options into fetch(), you can pass the same options to the Request() constructor, and then pass that object to fetch().
For example, we can make a POST request by passing options into fetch() using code like this:
However, we could rewrite this to pass the same arguments to the Request() constructor:
This also means that you can create a request from another request, while changing some of its properties using the second argument:
Handling the Response
As soon as the browser has received the response status and headers from the server (and potentially before the response body itself has been received), the promise returned by fetch() is fulfilled with a Response object.
Checking Response Status
The promise returned by fetch() will reject on some errors, such as a network error or a bad scheme. However, if the server responds with an error like 404, then fetch() fulfills with a Response, so we have to check the status before we can read the response body.
The Response.status property tells us the numerical status code, and the Response.ok property returns true if the status is in the 200 range.
A common pattern is to check the value of ok and throw if it is false:
Checking the Response Type
Responses have a type property that can be one of the following:
- basic: the request was a same-origin request.
- cors: the request was a cross-origin CORS request.
- opaque: the request was a cross-origin simple request made with the no-cors mode.
- opaqueredirect: the request set the redirect option to manual, and the server returned a redirect status.
The type determines the possible contents of the response, as follows:
- Basic responses exclude response headers from the Forbidden response header name list.
- CORS responses include only response headers from the CORS-safelisted response header list.
- Opaque responses and opaque redirect responses have a status of 0, an empty header list, and a null body.
Checking Headers
Just like the request, the response has a headers property which is a Headers object, and this contains any response headers that are exposed to scripts, subject to the exclusions made based on the response type.
A common use case for this is to check the content type before trying to read the body:
Reading the Response Body
The Response interface provides a number of methods to retrieve the entire body contents in a variety of different formats:
- Response.arrayBuffer()
- Response.blob()
- Response.formData()
- Response.json()
- Response.text()
These are all asynchronous methods, returning a Promise which will be fulfilled with the body content.
In this example, we fetch an image and read it as a Blob, which we can then use to create an object URL:
The method will throw an exception if the response body is not in the appropriate format: for example, if you call json() on a response that can't be parsed as JSON.
Streaming the Response Body
Request and response bodies are actually ReadableStream objects, and whenever you read them, you're streaming the content. This is good for memory efficiency, because the browser doesn't have to buffer the entire response in memory before the caller retrieves it using a method like json().
This also means that the caller can process the content incrementally as it is received.
For example, consider a GET request that fetches a large text file and processes it in some way, or displays it to the user:
If we use Response.text(), as above, we must wait until the whole file has been received before we can process any of it.
If we stream the response instead, we can process chunks of the body as they are received from the network:
In this example, we iterate asynchronously over the stream, processing each chunk as it arrives.
Note that when you access the body directly like this, you get the raw bytes of the response and must transform it yourself. In this case we call ReadableStream.pipeThrough() to pipe the response through a TextDecoderStream, which decodes the UTF-8-encoded body data as text.
Processing a Text File Line by Line
In the example below, we fetch a text resource and process it line by line, using a regular expression to look for line endings. For simplicity, we assume the text is UTF-8, and don't handle fetch errors:
Examples Using XMLHttpRequest
Examples Using Fetch API
" + rObj.ErrorMessage; };