Davide Pugliese

Software development articles, nerdy news

How can I trigger CSV download action within a REST micro-service architecture?

2019-04-30 Davide Pugliesemicroservices

springboot

Is it possible to receive something else than text via AJAX (as a redirection trigger)?

The short answer is no.

After a successful and completed call to the send method of the XMLHttpRequest, if the server response was well-formed XML and the Content-Type header sent by the server is understood by the user agent as an Internet media typefor XML, the responseXML property of the XMLHttpRequest object will contain a DOM document object. Another property, responseText  will contain the response of the server in plain text by a conforming user agent, regardless of whether or not it was understood as XML.

[1] Reference

This means that , XMLHttpRequest library, built upon a W3C standard, waits for a response being compatible with “Internet media type for XML”, id est a text. There is no status code for redirect, or possibility to trigger a download purely with AJAX. Also most of the other libraries out there are based upon XMLHttpRequest and therefore suffer of the same limitations.

Can we receive a response in a format different than text if we use form-urlencoded as a content-type in an AJAX call?

Short answer is no!

Even if you use the new fetch, you are going to receive a text anyways.

The example is pretty clear:

fetch(url, {
    method: 'post',
    headers: {
      "Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
    },
    body: 'foo=bar&lorem=ipsum'
  })
  .then(json)
  .then(function (data) {
    console.log('Request succeeded with JSON response', data);
  })
  .catch(function (error) {
    console.log('Request failed', error);
  });

You can pass in the body property your urlencoded parameters and then wait for a text as a response.

Therefore, using any JS library you will always receive a text even by sending JSON data or by sending data as form-urlencoded.

Where are the objects containing our data placed when we use POST?

Every language/library uses a different approach

This PHP example is pretty clear

Therefore, when we send some data with POST (without ajax), data are stored in POST[“something”]. instead with ajax they are placed within the POST global variable.

In theory, you would expect for data to be placed in POST[“body”], as that’s the name of the prop where you place data on the Javascript side.

As a further example, Express JS, with the help of a library, places data in request.body already parsed as a Javascript object.

In conclusion what options do we have in order to generate a CSV file with Spring and Javascript?

  • Send every parameter through a separate hidden input field: We can pass a variable as an argument, one for each input field as needed: @RequestParam whateverInput1, @RequestParam whateverInput2

    @PostMapping
    public ResponseEntity csvExport(@RequestParam whateverInput1, @RequestParam whateverInput2)  {
    ...
    }

    The HTML file would look like this:

    <form action="http://blabla/csv" method="POST">
    	<input name="whateverInput1" type="hidden" value="ceva1" />
    	<input name="whateverInput2" type="hidden" value="ceva2" />
    </form>

    Then our Javascript code would contain a call to the submit method:

    function handleSubmit(form) {
    
    	doSomethingBefore();	
    	form.submit();
    }

    Form can be a ref using ref api in React or Vue JS.

  • Use the text response to build for the second time the CSV (this time in the frontend solution)

    We might build our CSV locally and then eventually force the download action trigger as described here

    But this option looks kind of ugly as well since we are generating the CSV report twice.

  • We can use a converter

    You can create your own Converter and let Spring use it automatically where appropriate:

    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.core.convert.converter.Converter;
    import org.springframework.stereotype.Component;
    
    @Component
    class JsonToSomeFancyDTOConverter implements Converter<String, SomeFancyDTO> {
    
        private final ObjectMapper jsonMapper = new ObjectMapper();
    
        public SomeFancyDTO convert(String source) {
    	return jsonMapper.readValue(source, SomeFancyDTO.class);
        }
    }
    @PostMapping
    public ResponseEntity csvExport(@RequestParam SomeFancyDTO fancyDTO)  {
      ...
    }

The advantage, compared to the previous solution with multiple hidden inputs is that we need only a single hidden input which will contain our object serialized into JSON format.

The HTML file would look like this:

 <form action="http://blabla/csv" method="POST">
   <input name="fancyDTO" type="hidden" value="{whatever: [1,2,3,4]}" />
 </form>

Then our Javascript code would contain a call to the submit method:

 function handleSubmit(form) {

   doSomethingBefore();	
   form.submit();
 }
Loading...
Davide Pugliese - Software development articles, nerdy news

Davide Pugliese Software Engineer at Arnia | Polyglot Developer (NodeJS, Express, React, Vue, Java, Spring).