0

I generate reports on my MVC application witch I then export to pdf and witch is then saved in my ~/Reports/Invoices/ Directory. I want to download these files one by one.

Here is my class:

public class DownloadResult : ActionResult
{

    public DownloadResult()
    {
    }

    public DownloadResult(string virtualPath)
    {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath
    {
        get;
        set;
    }

    public string FileDownloadName
    {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition",
           "attachment; filename=" + this.FileDownloadName);
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}

and here is my controller action:

 public ActionResult Download(int id)
 {

     return new DownloadResult
                {
                    VirtualPath = "~/Reports/Invoices/" + Table.Where(x => x.ID == id).FirstOrDefault().ID + ".pdf",
                    FileDownloadName = Table.Where(x => x.ID == id).FirstOrDefault().ID.ToString()
                };

 }

When I try to use this code all it does is fill the partial view with symbols like when you try to open a binary file in notepad.

Any ideas on what I'm doing wrong?

I have Tried this:

        public FileResult Download(int id)
        {
            byte[] fileBytes = System.IO.File.ReadAllBytes(Server.MapPath("~/Reports/Invoices/" + Table.Where(x => x.ID == id).FirstOrDefault().ID + ".pdf"));
            string fileName = Table.Where(x => x.ID == id).FirstOrDefault().ID.ToString();
            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, fileName);
        }

And get the same results

Invoices.GridLoadDone = function () {
$.contextMenu({
    selector: '#gbox_invoiceGrid',
    callback: function (key, options) {
        var m = "clicked: " + key;
        switch (key) {
            case "view":
                Globals.PerformAjaxFromHyperlink(null, '#/Invoice/View/' + Invoices.CurrentRow, true, Invoices.CurrentRow);
                Globals.SetUrl('#/Invoice/View/' + Invoices.CurrentRow, false);
                return true;
                break;
            case "email":
                Globals.PerformAjaxFromHyperlink(null, '/Invoice/Email/' + Invoices.CurrentRow, false);
                break;
            case "download":
                Globals.PerformAjaxFromHyperlink(null, '/Invoice/Download/' + Invoices.CurrentRow, false);
                //$("#readingsGrid").jqGrid('editRow', Readings.CurrentRow,

                break;

        }
    },
    items: {
        "view": {
            name: "View Invoice"
        },
        "email": {
            name: "Email Invoice"
        },
        "download": {
            name: "Download Invoice"
        },

    }
});
Hydro
  • 175
  • 6
  • 20
  • 9
    Why are you re-inventing the `File()` helper and `FileResult` class? – SLaks Feb 05 '14 at 14:42
  • Will you please show me how to this with the `File()` and `FileResult` class? I haven't worked with any of this before. – Hydro Feb 05 '14 at 14:45
  • Not an exact duplicate but have a look at http://stackoverflow.com/questions/3604562/download-file-of-any-type-in-asp-net-mvc-using-fileresult – Yuck Feb 05 '14 at 14:45
  • I have tried that and I end up with the same problem – Hydro Feb 05 '14 at 14:46
  • 1
    http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.file And your problem is a missing `Content-Type`. – SLaks Feb 05 '14 at 14:49
  • How do I set the content type? – Hydro Feb 06 '14 at 06:13
  • I have set the content type: `return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, fileName);` but still have the same problem. – Hydro Feb 06 '14 at 06:19
  • Where do you use this Action? Show the html code where you provide the download link? – Mat J Feb 06 '14 at 06:33
  • It is a Context menu in Java Script. You right click on a row in a JQuery grid an select Download. – Hydro Feb 06 '14 at 06:37
  • I have posted the script – Hydro Feb 06 '14 at 06:41
  • a side note, Off-Topic: You use `Table.Where(x => x.ID == id).FirstOrDefault().ID`, which will throw a `System.NullReferenceException`, if there is no matching element. If you use `Table.First(x => x.ID == id).ID` the exception will tell you that there was no matching element in the sequence, which might be of more use to you in case of an error. (Also, you already have the ID as a parameter. You could also just check if there is a matching element with `.Any(x => x.ID == id)`, and if not, throw your own, more meaning full exception.) – Dennis Feb 06 '14 at 06:50
  • I will change it accordingly. I just still don't understand why I keep on getting the what looks like a binary file opened in a text editor when I try to run this download function – Hydro Feb 06 '14 at 07:04

2 Answers2

1

IMHO, you probably want to stay away from writing physical files within your site structure.

This not only prevents you from being able to deploy the application to multiple servers on a webfarm, but it also might complicate deployment using tools such as WebDeploy.

You might want to think about storing them either on a database or on some other shared location outside of the IIS web application.

Also, for file downloads there's no need to use ajax, a standard GET will do, such as just using a regular hyperlink. However, I wouldn't recommend passing virtual paths around as parameters from client to the server. Instead, how about having in your DownloadController an action called Report, which receives only the id as parameter?

You could end up with more friendly URLs, such as: http://somewhere.com/download/report/3 or http://somewhere.com/download/report?id=3

Within that action, you can just use the method mentioned in the comments, using return File() and FileResult. With these you can either return the byte[] contents or a virtual path directly, such as: File("~/virtualpath", [content-type]), and also specify a name for the downloaded file as well.

The reason you might be seeing the binary content is because you aren't issuing a standard GET and letting the browser handle the response but using ajax instead.

Pablo Romeo
  • 11,298
  • 2
  • 30
  • 58
  • That is the standard for this project. This is a very large project and we are many developers working on it so I have to stick with the standards. The files I want to download are dynamically generated reports witch is then exported to pdf and downloaded, that is why they are not located in a Database or other directory. Is there any other way of doing a file download if this is not going to work with ajax? – Hydro Feb 06 '14 at 07:20
0

Below you wil find my latest attempt. Even if I specify the content type to application/pdf or application/octet-stream it still returns it in the view as text.

public static void TransmitFile(string url)
    {
        string ext = url.Substring(url.LastIndexOf("."), url.Length - url.LastIndexOf("."));
        string filename = url.Substring(url.LastIndexOf("/") + 1, url.Length - ext.Length - url.LastIndexOf("/") - 1).Replace(" ", "_");

        if (filename + ext == url)
        {
            filename = filename.Replace(AppDomain.CurrentDomain.BaseDirectory, "").Replace("\\", "/");
            filename = filename.Substring(filename.LastIndexOf("/") + 1, filename.Length - filename.LastIndexOf("/") - 1);
        }

        if (url.Contains("\\"))
            url = url.Replace(AppDomain.CurrentDomain.BaseDirectory, "").Replace("\\", "/");

        RegistryKey key = Registry.ClassesRoot.OpenSubKey(ext);
        string contentType = key.GetValue("Content Type").ToString();

        try
        {
            HttpContext.Current.Response.Clear();
            HttpContext.Current.Response.ContentType = contentType;
            HttpContext.Current.Response.AppendHeader("Content-Disposition", "attachment; filename=" + filename + ext);
            HttpContext.Current.Response.TransmitFile(HttpContext.Current.Server.MapPath(url));
            HttpContext.Current.Response.End();
        }
        catch { }
    }
}
Hydro
  • 175
  • 6
  • 20