13

The setup:

The controller contains a method public ActionResult SaveFile() which returns a FileContentResult.

What works:

The view contains a form, which submits to this action. The result is this dialog:enter image description here

What doesn't work:

The view contains some javascript to do an AJAX call to the same controller action where the form would post. Rather than triggering the aforementioned dialog, or even the AJAX success function, the response triggers the AJAX error function, and the XMLHttpRequest.responseText contains the file response.

What I need to do:

Make the request for the file using AJAX, and end up with the same result as when submitting a form. How can I make the AJAX request provide the dialog that submitting a form shows?

Community
  • 1
  • 1
Rick
  • 988
  • 2
  • 13
  • 23
  • 1
    Why can't you just use non-ajax call? – LukLed Feb 11 '10 at 02:51
  • 1
    I can, but I want to do some other processing in the javaScript function once the save completes, like unblock the form, which I blocked prior to save. – Rick Feb 11 '10 at 03:08
  • OK, so you can first call ajax SaveFile. SaveFile will return true/false. When it returns true, you call non-ajax GET to get the file. – LukLed Feb 11 '10 at 04:54
  • When I call the controller's SaveFile from jquery.ajax, the browser's download dialog is not brought up. The real issue is that I want to block the UI during the save. I am able to save just fine (normal post handling for submitting the form), but then I cannot initiate the blockUI. I can catch an event (button click, or submit handler, start the block and show busy indicator, but then I cannot trigger the unblock. Is there a way to call jQuery function from controller's Action so that I can trigger unblock. I didn't notice any event that I could use. That is why I tried to call via Ajax call. – Rick Feb 11 '10 at 13:18
  • OK, you can still use ajax, but don't return FileContentResult. Return true/false and if true is returned then initialize non-ajax get to download the file. – LukLed Feb 11 '10 at 14:13
  • I appreciated your persistance in helping. I don't understand the "initial non-ajax get to download file'. So you are suggesting that I call controller's SaveFile (which actual goes to db and create a memory buffer full of comma delimited data) and instead of returning FileContentResult (which causes the browser's download dialog to be loaded), I return that buffer to the calling function and then try to bringup the dialog as save the buffer from the javascript function. If so, how do I get javascript to open the dialog and save the return buffer of data? – Rick Feb 11 '10 at 17:09

1 Answers1

17

Here's a quick example I made up. This is the concept LukLed was talking about with calling SaveFile but don't return file contents via ajax and instead redirect to the download.

Here's the view code:

<script src="../../Scripts/jquery-1.3.2.min.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function() {
        // hide form code here

        // upload to server
        $('#btnUpload').click(function() {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                url: '<%= Url.Action("SaveFile", "Home") %>',
                success: function(fileId) {
                    window.location = '<%= Url.Action("DownloadFile", "Home") %>?fileId=' + fileId;
                },
                error: function() {
                    alert('An error occurred uploading data.');
                }
            });
        });
    });
</script>

<% using (Html.BeginForm()) { %>

    <div>Field 1: <%= Html.TextBox("field1") %></div>

    <div>Field 2: <%= Html.TextBox("field2") %></div>

    <div>Field 3: <%= Html.TextBox("field3") %></div>

    <button id="btnUpload" type="button">Upload</button>

<% } %>

Here's the controller code:

[HandleError]
public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View();
    }

    public JsonResult SaveFile(string field1, string field2, string field3)
    {
        // save the data to the database or where ever
        int savedFileId = 1;

        // return the saved file id to the browser
        return Json(savedFileId);
    }

    public FileContentResult DownloadFile(int fileId)
    {
        // load file content from db or file system
        string fileContents = "field1,field2,field3";

        // convert to byte array
        // use a different encoding if needed
        var encoding = new System.Text.ASCIIEncoding();
        byte[] returnContent = encoding.GetBytes(fileContents);

        return File(returnContent, "application/CSV", "test.csv");
    }

    public ActionResult About()
    {
        return View();
    }
}
Nate Pinchot
  • 3,288
  • 24
  • 35
  • Nate, thanks for providing such a detailed example. I appreciate it and also thanks LukLed for trying diligently to explain your solution. The point that wasn't sinking-in was that the first call prepared the data to be saved, and 2nd call does the download. – Rick Feb 11 '10 at 20:02
  • I've been there before banging my head against the wall saying WTF to myself trying to understand, no worries. Glad to help. – Nate Pinchot Feb 11 '10 at 20:46