0

I have a C# functoin in my MVC application that returns a JSON representation of a csv file. I am trying to catch that in javascript and open the file. Basically I want the browser do its thing and let the user decide if he want to open it, save it or cancel. I am unable to get that popup to ask me to open the file. Below are the functions

C# Function

[HttpPost]
public ActionResult ExportToCsv(string fileContents, string fileName)
{
    fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
    return Json(new { url = File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName) }); ;
}

This is the javascript where I am making the ajax call to the function

$("#btnExport").click(function (event) {
    event.preventDefault();
    var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
    $.ajax({
        url: "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv",
        type: 'Post',
        success: function (result) {
            window.open(result.url);
        }
    });
});

I know I am missing something. Can someone please help.

EDIT

After reading through all the potential answers and comments, this is what I am trying to achieve. So if my code is all horribly wrong please let me know.

I have a grid and I have an export to excel button. I have a method that converts the data i want into comma delimited text in javascript itself. I need to present this to the user as a downloadable csv file. For this I was creating the File object using the controller method. The previous incarnation was a Get method and I faced limitations due to querystring length restrictions. So I tried converting it to a POST method and it is not working.

This is the previous version of the code that works for smaller amounts of data

Javascript

   $("#btnExport").click(function (event) {
            event.preventDefault();
            var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
            window.location.href = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv";
        });

C# Function

    [HttpGet]
    public ActionResult ExportToCsv(string fileContents, string fileName)
    {
        fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
        return File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName);
    }

Hope this now gives you all more context. Basically I needed to convert my GET method to a POST method and use it from Javascript.

avsln
  • 3
  • 2
  • Assuming you aren't getting any errors, put this in your success function: console.log(JSON.stringify(result)); and then tell us what the result is. – milestyle Nov 14 '13 at 23:16
  • First, What happens when you hit the returned url directly? Secondly, are you sure what comes back is a url? Simply saying it doesn't work is nice, but it would be good to use the browser's javascript console (Chrome Web Development Tools or Firefox's Firebug plugin) to tell you that the data you get back is in fact a url. – mangr3n Nov 14 '13 at 23:16

3 Answers3

0

You can't save binary files using ajax - it's restricted due to security reasons. If you'd like the user to save the file - return a binary stream from your controller as you post the data back in JSON format (ie if the user wants to save it).

I've done a similar thing here: How to properly create and download a dynamically generated binary *.xlsx file on .net MVC4?

Community
  • 1
  • 1
silicakes
  • 6,364
  • 3
  • 28
  • 39
  • 1
    *"You can't save binary files using ajax..."* It's no more a "binary" file than any text file. – T.J. Crowder Nov 14 '13 at 23:19
  • You can't save texts either: The ajax callback will return the data - but you'll have no way of making it a binary file. if you'd like - you could return a comma separated string (i.e your CSV stringified) and use [Downloadify](https://github.com/raldenhoven/downloadify) for creating a downloadable file. Note that Downloadify relies on flash to do so (since again, pure JS can't do it) – silicakes Nov 14 '13 at 23:22
  • @ Mike: The point is that throwing the term "binary" in is irrelevant. Separately, though, you can *totally* do this without Flash, relying purely on the browser and JavaScript engine. – T.J. Crowder Nov 14 '13 at 23:24
  • 1
    Well come to think of it, if you'll maintain your file as a comma separated string, you could pull something like this: http://jsfiddle.net/VBJ9h/319/ off..it's actually a pretty good idea! – silicakes Nov 14 '13 at 23:29
  • @Mike86 provides a really intriguing possibility which NEVER needs to go to the server. I like it. – mangr3n Nov 14 '13 at 23:31
  • But has severe length restrictions in IE. (Sigh. IE. They may as well have called it "Fly" [as in "...in the ointment..."].) – T.J. Crowder Nov 14 '13 at 23:32
  • I don't think there's a client only solution to be cross browsery all the way through. Sending it to a controller will probably handle most cases – silicakes Nov 14 '13 at 23:39
  • I don't know .NET (downloading mono now to look at the library apis in a runtime. But the only way that code could possibly work above is if the File(....) converts to a string url representation via the equivalent of Java's toString method. Oh and it would need to save the content to the filesystem inline from the File() call. If he's trying to stream it in and then back out, then you would need to use Stream API's I'm assuming, not File APIs. Even so, as a file, I would think you need to massage the url turn a file system url into a "server" specific url. – mangr3n Nov 14 '13 at 23:46
  • @T.J.Crowder - Doesn't it use a server side controller? form.action = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv"; – silicakes Nov 15 '13 at 14:14
0

Maybe save it as a json file instead and load in the current page's DOM. Although I though I am confused, don't you just have a JSON response containing a URL to your CSV file is which is just that a CSV file, not a JSON representation of CSV?

$("#btnExport").click(function (event) {
        event.preventDefault();
        var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
        $.ajax({
            url: "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.json",
            type: 'Post',
            success: function (result) {
                var myDummyElement = $('<div>'); //dummy div so you can call load
                myDummyElement .load('result.url #myJson');
            }
        });
    });

<div id="myJson"></div>

[HttpPost]
    public ActionResult ExportToCsv(string fileContents, string fileName)
    {
        //change fileName to be a .json extension
        fileContents = fileContents.Replace("-CARRIAGE-", "\r\n");
        return Json(new { url = File(Encoding.UTF8.GetBytes(fileContents), "text/csv", fileName) }); ;
    }
Brian Ogden
  • 18,439
  • 10
  • 97
  • 176
0

If you use ajax, you're expected to handle the result in code. But you can't trigger a file download (directly) that way.

Instead, create a (hidden) form and post it to a (hidden) iframe (by giving the iframe a name and specifying that as the target of the form), making sure that the response specifies the header Content-Disposition: attachment. That will trigger the browser to offer to save the file. Optionally in the header you can suggest a filename for the file by adding ; filename="fname.ext" to the header value. E.g. Content-Disposition: attachment; filename="fname.ext".

The client-side looks something like this:

$("#btnExport").click(function (event) {
    event.preventDefault();
    var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes");
    var frame = $('iframe[name="formreceiver"]');
    if (!frame.length) {
        frame = $('<iframe name="formreceiver"></iframe>').appendTo(document.body).css("display", "none");
    }
    var form = $("#senderform");
    if (!form.length) {
        form = $('<form id="senderform"></form>').appendTo(document.body);
    }
    form = form[0]; // Raw DOM element rather than jQuery wrapper
    form.target = "formreceiver";
    form.action = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv";
    form.method = "POST";
    form.submit();
});

The server side is just a standard form response with the Content-Disposition header.

I've used this technique for years, with browsers from IE6 onward. My usual setup already has the iframe in the markup (rather than creating it as above), but other than that this is basically what I do.

Note that it's important you're doing this in response to a button click by the user. If you weren't, there would be popup-blocker issues.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • I think he wants to interact with the csv data as Json, I'm not sure why he needs to round trip to the server. I'm questioning why it's csv in the first place. If he needs it as json in the client, then he should simply convert it in the client, OR never create CSV in the first place. Second the service, if it needs to be one, should not convert on the filesystem it should stream in/transform and stream out, but that's not how this appears to be written. Where does the table2Csv function come from? And why isn't it table2json? – mangr3n Nov 14 '13 at 23:26
  • @mangr3n: There's nothing in the OP's quoted code suggesting he/she needs to interact with the result; that can be done entirely server-side. It's quite common to want to allow the user to download something in a convenient format. This is a fairly standard way to do that. – T.J. Crowder Nov 14 '13 at 23:29
  • Then why is he saying "return csv as json?" Json is a dataformat. What he means then is that returns a link to the csv file the user generated in the table in the page, in which case the user below is right, you could generate the link directly in the page. However, you could run into url length issues. – mangr3n Nov 14 '13 at 23:44
  • Thanks T.J.Crowder , but your method just returned me a JSON formatted blob, not the actual csv file I was looking for. – avsln Nov 15 '13 at 16:49
  • @avsln: You have to change the server-side to return what you want the browser to save. Your client-side code can't get involved in it. – T.J. Crowder Nov 15 '13 at 17:07
  • @T.J.Crowder Please see the EDIT I made to my original question. I am returning a FileContentResult object to javascript. Basically I need to know how to open it when the method is a POST method rather than a GET method. – avsln Nov 15 '13 at 17:36
  • @avsln: Again: You can't. You must return the actual file content you want the user to save as the result of the POST. Which is a trivially easy thing to do. – T.J. Crowder Nov 15 '13 at 18:12
  • @T.J.Crowder I am not sure how to do this. Could you show me an example. Actually the file contents are in the javascript variable called csv. If you look at this piece of javscript code 'code'$("#btnExport").click(function (event) { event.preventDefault(); var csv = table2csv(noteTypeTable, "full", "Table.dataTable", "noteTypes"); window.location.href = "/Admin/Admin/ExportToCsv/?fileContents=" + csv + "&fileName=NoteTypes.csv"; }); – avsln Nov 15 '13 at 19:22
  • @T.J.Crowder Thanks buddy. I figured it out. Your solution works. But for some cases, IE does not seem to like downloding the file. I simply get the "Internet Explorer cannot display the webpage". It works on Chrome though. – avsln Nov 18 '13 at 21:08