I was using a jQuery plugin called a ajaxfileupload to upload a file through AJAX. Technically what the plugin does isn't AJAX. It creates a hidden form and an iframe, and then submits the form using the iframe as the target. The iframe will then end up with the response from the server. This response is then read-in by the plugin and handled appropriately. In my case I was using a controller action that would return JSON (using the .action extension). The action uses Spring's MappingJacksonJSONView that returns JSON with a content type of application/json (as it should). This works perfectly in Chrome, however in both Firefox and IE, the user is presented with a dialog box that asks them to download the JSON response. This is obviously not what I wanted. The reason this is happening is because the response is being directly submitted to the iframe (and therefore, the page). That is, it's not coming through via the XMLHttpRequest object. So IE and FF don't know what to do with it and assume that it is something the user would want to download. The solution to this problem is to set the content-type to text/plain. This wasn't as straightforward as I thought it would be.
Initially I was going to call the render(...) method of MappingJacksonJsonView but that didn't work because the content-type had already been set to application/json. The solution I came up with was to duplicate some of the code (ugh) inside MappingJacksonJsonView to get the JSON as a string and to then write that to the response:
@RequestMapping public void processFileUpload(HttpServletResponse response, Model model, ...) { ... //Set the content-type and charset of the response response.setContentType("text/plain"); response.setCharacterEncoding("UTF-8"); //I need to use another OutputStream here; I cannot use the response's OutputStream because that will cause errors //later on when the JSP needs to render its content (recall that getOutputStream() can only be called exactly once //on a response). Therefore I'm writing the data to a ByteArrayOutputStream and then writing the byte array from //the ByteArrayOutputStream to the response manually. ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectMapper objectMapper = new ObjectMapper(); JsonGenerator generator = objectMapper.getJsonFactory().createJsonGenerator(byteArrayOutputStream, JsonEncoding.UTF8); //Before I can convert the data into JSON, I will need to filter some attributes out of the model (namely BindingResult) Map<String, Object> result = new HashMap<String, Object>(); for(Map.Entry<String, Object> entry : model.asMap().entrySet()) { if(!(entry.getValue() instanceof BindingResult)) { result.put(entry.getKey(), entry.getValue()); } } objectMapper.writeValue(generator, result); response.getWriter().write(new String(byteArrayOutputStream.toByteArray(), "UTF8")); }
This still seems a little hacky to me. A possible improvement is to annotate the action with @ResponseBody and return the JSON as a string without involving the response at all. If anyone has a better solution, I'm all ears!
Thanks for posting this workaround. But I followed the jsonp approach that you had posted earlier. Extend the MappingJackJsonView and override the renderMergedOutputModel method
@Override
protected void renderMergedOutputModel(Map model,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
Object value = filterModel(model);
String jsonString = this.objectMapper.writeValueAsString(value);
response.getOutputStream().write(jsonString.getBytes());
}
Seems to work but not sure if there any drawbacks
@Lakshmi Narasimhan PL: I think the only drawback is philosophical :). I don’t like having the overhead of an extra class to handle this case, but if you’re doing this a lot, then I guess you can make the argument that it would help!