0

Inside my controller I am using ServiceStack.Text to serialize a List and wish to return it to the client.

I am passing json that gets passed and manipulated into a list of objects that I then want to return as a csv. I can confirm this part is working. The client reports the request was successful but no file is available for download. if i console.log the response it prints the csv string.

I actually receive a list of json strings that I deserialize and append to a single list.Again, I can confirm this is working as expected.

Here is the Code:

[ValidateInput(false)]
[HttpPost]
public FileContentResult DownloadCSV(List<string> json, string delimiter, string method)
    {
        var respCSV ="";

        if (method == "combine")
        {
            List<AgmtToCSV> comb = new List<AgmtToCSV>();

            foreach (var i in json)
            {
            var d = Newtonsoft.Json.JsonConvert.DeserializeObject<List<AgmtToCSV>>(i);
                foreach (var u in d)
                {
                    comb.Add(u);
                }

            }

            var csv = CsvSerializer.SerializeToCsv(comb);


            respCSV = csv;
        }



        return File(new System.Text.UTF8Encoding().GetBytes(respCSV), "text/csv", "ExpirationReport.csv");
            //return File(respCSV);
        }

EDIT

Here is what the response look like:

Cache-Control:private
Content-Disposition:attachment; filename=ExpirationReport.csv
Content-Encoding:gzip
Content-Length:3117
Content-Type:text/csv
Date:Thu, 20 Jul 2017 17:42:16 GMT
Server:Microsoft-IIS/8.0
Vary:Accept-Encoding
X-AspNet-Version:4.0.30319
X-AspNetMvc-Version:5.2
X-Powered-By:ASP.NET
X-SourceFiles:=?UTF-8?B?SDpcZGV2ZWxvcG1lbnRcQWdyZWVtZW50LVZpZXdlclxBViAxLjEuMyBkZXZcQVZcQVZcSG9tZVxEb3dubG9hZENTVg==?=

Here is the jquery request and how it handles the successful response..

$.post('@Url.Action("DownloadCSV", "Home")', { "json":dta, "delimiter":del, "method":"combine"}, function (r) {
    console.log("Success");
    console.log(r);

});

UPDATE - Solution

Was able to get this to work with the accepted answer: The success function was the key here. It creates a link that points to the download and then initiates it.

$.post('@Url.Action("DownloadCSV", "Home")', { "json":dta, "delimiter":del, "method":"combine"}, function (r) {
   console.log("Success");
   var link = document.createElement("a");
   link.id = "lnkDwnldLnk";
   var converted = r;

   document.body.appendChild(link);
   var csv = converted;
   blob = new Blob([csv], { type: 'text/csv' });
   window.URL = window.URL || window.webkitURL;
   var csvUrl = window.URL.createObjectURL(blob);
   var filename = 'ExpirationReport.csv';
   $("#lnkDwnldLnk")
        .attr({
        'download': filename,
        'href': csvUrl
        });
    var ua = window.navigator.userAgent;
    var msie = ua.indexOf("MSIE ");

                if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./))  // If Internet Explorer, return version number
                {
                    window.navigator.msSaveOrOpenBlob(blob, filename);
                }
                else {
                    $('#lnkDwnldLnk')[0].click();
                }


                document.body.removeChild(link);

            }); 
LCaraway
  • 1,257
  • 3
  • 20
  • 48
  • What's returned has nothing to do with ServiceStack. It all boils down to your `return File` line. However, including the filename parameter should set the Content-Disposition to `attachment` (i.e. a download), so there's nothing wrong with the code you have currently. Given that you have commented return that *would* actually return inline, are you sure that you have rebuilt your solution since you made this change? – Chris Pratt Jul 20 '17 at 16:10
  • Yes, I have rebuilt and see the same results. – LCaraway Jul 20 '17 at 16:15
  • Why download the link via AJAX request? AJAX requests are known to be behave for downloading files. Just use normal `@Html.ActionLink()` - your page will not reload if all you do is file download. – trailmax Jul 20 '17 at 18:05
  • Is it possible to fire that inside of a click event. I have some functions that are triggered at the same time to prepare a request. How would I do that and then hit the controller. – LCaraway Jul 20 '17 at 18:10
  • Went ahead and kept this change as I like it better, it produces the same result however. – LCaraway Jul 20 '17 at 19:19
  • The issue does seem to be the AJAX – LCaraway Jul 20 '17 at 19:24

1 Answers1

1

I'm not exactly sure that my answer will do exactly as you want but still. I have this code that I'm using that basically takes in a JSON object, transforms it in a CSV and downloads it in the browser.

The controller code that goes to a service to get a list of objects and returns it as JSON.

        public ActionResult DownLoadExcel(long BatchNumber)
    {
        Context context = new Context();
        IQueryable<string> ICodeList = context.Codes.OrderByDescending(c => c.CreatedDateTime).Where(c => c.BatchNumber == BatchNumber).Select(c => c.Id); ;
        var codeList = ICodeList.ToList();


        return Json(codeList, JsonRequestBehavior.AllowGet);
    }

The AJAX call that gets the JSON list from the controller and transforms it into a CSV. Then it creates and fake a anchor and simulates a click on it to trigger the download.:

    <script type="text/javascript">
    function getExcel(batchNumber)
    {
        $.ajax({
            type: 'GET',
            url: '@Url.Action("DownloadExcel", "BatchCode")',
            data: { BatchNumber: batchNumber },
            cache: false,
            success: function (result) {
                var converted = ConvertToCSV(result);
                //this trick will generate a temp "a" tag
                var link = document.createElement("a");
                link.id = "lnkDwnldLnk";

                //this part will append the anchor tag and remove it after automatic click
                document.body.appendChild(link);
                var csv = converted;
                blob = new Blob([csv], { type: 'text/csv' });
                window.URL = window.URL || window.webkitURL;
                var csvUrl = window.URL.createObjectURL(blob);
                var filename = 'file.csv';
                $("#lnkDwnldLnk")
                .attr({
                    'download': filename,
                    'href': csvUrl
                });
                var ua = window.navigator.userAgent;
                var msie = ua.indexOf("MSIE ");

                if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./))  // If Internet Explorer, return version number
                {
                    window.navigator.msSaveOrOpenBlob(blob, filename);
                }
                else{
                    $('#lnkDwnldLnk')[0].click();
                }


                document.body.removeChild(link);
            }
        });
    }


function ConvertToCSV(objArray) {
    var array = typeof objArray != 'object' ? JSON.parse(objArray) : objArray;
    var str = '';

    for (var i = 0; i < array.length; i++) {
        var line = array[i];
        str += line + '\r\n';
    }

    return str;
}
</script>

And finally you need and empty a in your HTML

<a hidden="hidden"></a>
Helpha
  • 452
  • 6
  • 17
  • I like this and may repurpose, the issue does seem to be ajax not handling the response very well... – LCaraway Jul 20 '17 at 19:22
  • 1
    Got this to work for me. I simply just used your success function and substituted my response for converted as mine was already in a string. Ill add the solution to my original question Thanks. – LCaraway Jul 20 '17 at 19:55
  • Glad to help :D – Helpha Jul 20 '17 at 20:00