2

I am writing a small web app and part of the functionality is to be able to download the logs from the server to the user's file system. I am able to zip the existing logs, but am failing to get the zipped folder to transmit. I have browsed many other similar questions on here, but have yet to get it to work based on any of them.

Here is the code that I am currently trying:

.Net Controller

[HttpPost]
public ActionResult DownloadLogs()
{
    string path = System.Configuration.ConfigurationManager.AppSettings["LogPath"];

    try
    {

        Log.Information("Closing current logger. AudtiLog: {auditLog}", true);
        Log.CloseAndFlush();
        string startPath = path;
        string zipPath = "C:\\my_folder\\result.zip";
        string extractPath = path + "extract";

        //deleting existing zips
        if (System.IO.File.Exists(zipPath))
            System.IO.File.Delete(zipPath);
        if (System.IO.Directory.Exists(extractPath)) 
            System.IO.Directory.Delete(extractPath, true);

        ZipFile.CreateFromDirectory(startPath, zipPath);
        ZipFile.ExtractToDirectory(zipPath, extractPath);

        FileInfo file = new FileInfo(zipPath);

        using (FileStream filestream = new FileStream(zipPath, FileMode.Open))
        {
            return File(filestream, "application/zip", "ServerLogZip.zip");
        }

    }
    catch (Exception ex)
    {
        Log.Error("Error occurred when downloading server logs. Error: {Error}; AuditLog: {auditLog}", ex.Message, true);
        return Json(new { result = "error" });
    }

}

Javascript

function DownloadLogs() {

            $.ajax({
                type: "POST",
                url: "/ManageServer/DownloadLogs",
                contentType: "application/zip",
                success: function (response) {
                    alert("Success")
                },
                error: function (response) {
                    alert("Error");
                }
            });
        }

Whenever I run it, it zips the logs into one folder, steps through the Response portion of the code successfully, but nothing happens. I've tried debugging and stepping through the code, but haven't found the answer yet. I've also tried the Response.WriteFile method as well. No luck.

Edit I've updated the code to return ActionResult and returned a File. It is currently returning a 500 error from the server.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
Justin
  • 154
  • 1
  • 17
  • 1
    You shouldn't write directly to the response in an MVC action method. And you can't write a file to the response and return JSON in a single action method. You can only do one or the other. – mason Jul 17 '18 at 20:51
  • @mason Good catch. I've updated the code, but don't have it working yet – Justin Jul 17 '18 at 21:57

1 Answers1

1

You have a problem on your code as @mason noticed. You are trying to return two things, the file and the status.

The operation status should be checked through HTTP return codes.

You can use the the IActionResult interface as return type so you can handle things right. Give the method a File as return type and if everthing is fine it will return your file with all headers needed. In case of something goes wrong, you can return a BadRequest("Error Message");

The File return type accepts as parameters a FileStream that will contain your raw data, the Mime Type of the file and the filename

To achieve that, do the following steps

  • Change the method return type to FileResult
  • Create a variable that will receive your file content as a Stream or use an using statement
  • Create a byte array to allocate the file content (If you try to return the filestream you will get an error of file closed because the using statement is closed before the retun occurs)
  • Return data like this return File(byteArray, "application/zip", "ServerLogZip.zip");

Sample

try{
    // Do things to prepare your file

    using(FileStream filestream = new FileStream(zipPath,FileMode.Open))
    {
        byte[] zipBytes= new byte[filestream.Length];
        filestream.Read(PhotoBytes, 0, PhotoBytes.Length);
        return File(zipBytes, "application/zip", "ServerLogZip.zip"); 
    }
}
catch(Exception ex){
    return BadRequest("Something gone wrong: " + ex.Message);
}

I've been burning my mind to figure out how to download this file through async request, but in the end of the day, I realized that maybe you don't need such a complex solution. You can just call the route and the file will be downloaded.

function DownloadLogs() {
    document.location = your_route;
}

For this work properly, you must also change the method decorator of your C# method from [HttpPost] to [HttpGet]

Andre.Santarosa
  • 1,150
  • 1
  • 9
  • 22
  • Thanks for the response. I've been following along with your edits. I changed the signature to ActionResult (since it didn't like `BadRequest'`). When it returns from the call, it is directing to the Error block in the ajax code. – Justin Jul 17 '18 at 21:32
  • I should add that I changed the content type in the ajax call to `application/zip` and it is still returning a 500 error – Justin Jul 17 '18 at 21:44
  • 1
    What is the content of Message in the exception thrown by .net? If you do `alert(response.data)` in the ajax you will receive the error – Andre.Santarosa Jul 17 '18 at 21:46
  • It is saying `Cannot access a closed file`. It is returning to the ajax call and not being caught in the try/catch in the controller. – Justin Jul 17 '18 at 21:57
  • 1
    Dumb error of mine... The file was being disposed before the return. I`ve updated the answer with additional steps – Andre.Santarosa Jul 17 '18 at 22:26
  • Awesome. It returns successfully now, but the file isn't being downloaded. Do I need to do something differently in the ajax call, perhaps? – Justin Jul 17 '18 at 22:57
  • That code is not ok, just a second... I'm debugging – Andre.Santarosa Jul 17 '18 at 23:30
  • I've added a simplier solution, if it is not suitable solution for you, just leave another comment and I can try to help you achieve what you need. – Andre.Santarosa Jul 18 '18 at 00:39
  • I was trying the previous solution you posted, and there was a network error downloading the file. It seemed as if the byte array was encoded wrong, so there was an error trying to read it. I converted it to a blob like this (https://stackoverflow.com/a/48769059/6668786) but it ended up downloading an empty zip. Where would I implement the latest solution you posted? – Justin Jul 18 '18 at 01:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176257/discussion-between-andre-santarosa-and-justin). – Andre.Santarosa Jul 18 '18 at 14:26