在Grails中通过Ajax调用控制器实现文件下载的问题
Hey there! I see you're new to Grails and stuck on getting that file download working after triggering it with jQuery. Let's break down the problem and fix it step by step.
Why Your Current Code Isn't Working
The issue here is that AJAX requests don't automatically trigger browser file downloads. When you send an AJAX POST, the browser handles the response in the background, but it won't pop up the download dialog for file streams. We need to adjust how we send the request so the browser knows to handle the response as a downloadable file.
Solution 1: Use a Hidden Form Instead of AJAX
This is the simplest approach for your scenario. We'll create a hidden form dynamically, populate it with your data, and submit it—this tells the browser to expect a file download response.
Updated jQuery Code
$("#exportAllSelectedData").click(function() { var dataToExport = $("#dataToExport").val(); // Create a hidden form to submit our data const exportForm = $('<form>', { method: 'POST', action: '/myapp/mycontroller/exportPreferences' }); // Add our data as a hidden input field $('<input>', { type: 'hidden', name: 'dataToExport', value: dataToExport }).appendTo(exportForm); // Append form to the page and submit it exportForm.appendTo('body').submit(); });
Complete Grails Controller Code
You don't even need a temporary file—we can write the data directly to the response stream, which is more efficient. Here's how to set up the response headers to trigger the download:
def exportPreferences() { String dataToWrite = params.dataToExport // Set response headers to tell browser this is a downloadable XML file response.contentType = 'application/xml' response.setHeader('Content-Disposition', 'attachment; filename="exported_preferences.xml"') // Write the data directly to the response stream response.outputStream << dataToWrite response.outputStream.flush() response.outputStream.close() // No need to return anything—we've handled the response directly return null }
Solution 2: If You Really Need AJAX (Optional)
If you must use AJAX (for example, if you need to validate data first), you can have the controller return a URL to a download endpoint, then trigger the download via window.open() or location.href. This requires storing the data temporarily (like in the session) so the download endpoint can access it:
Controller (Two Actions)
def prepareExport() { String dataToWrite = params.dataToExport session.exportData = dataToWrite // Store data in session render([downloadUrl: createLink(action: 'downloadExport')] as JSON) } def downloadExport() { String dataToWrite = session.removeAttribute('exportData') // Clean up session if (!dataToWrite) { render status: 404, text: 'No export data found' return } // Same response setup as before response.contentType = 'application/xml' response.setHeader('Content-Disposition', 'attachment; filename="exported_preferences.xml"') response.outputStream << dataToWrite response.outputStream.flush() response.outputStream.close() return null }
Updated jQuery for AJAX Approach
$("#exportAllSelectedData").click(function() { var dataToExport = $("#dataToExport").val(); $.ajax({ type: 'POST', data: { dataToExport: dataToExport }, dataType: 'json', url: '/myapp/mycontroller/prepareExport', success: function(response) { // Trigger download using the returned URL window.open(response.downloadUrl, '_self'); } }); });
Final Notes
- The first solution is recommended for your use case—it's simpler and avoids session storage overhead.
- Always set the correct
contentType(hereapplication/xml) so the browser knows how to handle the file. - The
Content-Dispositionheader tells the browser to treat the response as an attachment and sets the default filename.
内容的提问来源于stack exchange,提问作者Samos

