Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to initialize file download from an external server?

I have an MVC controller method defined like this:

public ActionResult GetPdf(string filename)
        {
            var pdfDownload = File("~/Content/GeneratedReports/report1.pdf", "application/pdf", Server.UrlEncode("report1.pdf"));

            return pdfDownload;
        }

If i change the first parameter to the url of a server hosted on a separate cloud server then I get the error :

'MY FILE PATH' is not a valid virtual path.

I just want my client to be able to download a file. This seems so much more complex than it need be.

I have a URL that points to a PDF. I want my client to download that pdf without clicking anything. (The download will initiate upon successful service response)

Why is this so hard and how do i solve it ?

I don't care if the solution is in JS or MVC....

like image 717
BentOnCoding Avatar asked Jan 20 '12 21:01

BentOnCoding


People also ask

Can We download files from the external server network path?

We cannot download the files from the external Server network path with our Server credential. Read the article's link, given below, before reading this article because the following link helps you to learn the basics of the file download. Download Multiple Files In Compressed Format In ASP.NET MVC 5 Step By Step

Why we can’t download files from external server without credentials?

If the files are stored in the external Server without credentials, we cannot download any files. There are many classes which are important to connect the external Server, using credentials. One of the main classes is “NetworkCredential” Class.

How do I deploy an application to a file server?

Normally, we deploy our Application in the Web server but our files or documents are stored in the file Server. If need arises, connect the Web Server to the file Server, using credentials to download the files or documents through the Application because our files are stored in the file Server.

Why can't I download any files through the application?

If need arises, connect the Web Server to the file Server, using credentials to download the files or documents through the Application because our files are stored in the file Server. If the files are stored in the external Server without credentials, we cannot download any files.


2 Answers

Why is this so hard and how do i solve it ?

Actually, it's not that hard:

public ActionResult GetPdf(string filename)
{
    using (var client = new WebClient())
    {
        var buffer = client.DownloadData("http://foo.com/bar.pdf");
        return File(buffer, "application/pdf", "report1.pdf");
    }
}

Now obviously there's a serious flaw with this method as it is buferring the file in memory. While this could work great for small reports, it could be problematic with large files and even more problematic if you have lots of users impatient to put their hands on this great report.

There's also another serious flaw with the first controller action. It mixes responsibilities. It contains infrastructure code and I challenge you to unit test it in isolation.

So let's solve those 2 serious problems by writing a custom action result:

public class ReportResult : ActionResult
{
    private readonly string _filename;
    public ReportResult(string filename)
    {
        _filename = filename;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        var cd = new ContentDisposition
        {
            FileName = _filename,
            Inline = false
        };
        var response = context.HttpContext.Response;
        response.ContentType = "application/pdf";
        response.Headers["Content-Disposition"] = cd.ToString();

        using (var client = new WebClient())
        using (var stream = client.OpenRead("http://foo.com/" + _filename))
        {
            // in .NET 4.0 implementation this will process in chunks
            // of 4KB
            stream.CopyTo(response.OutputStream);
        }
    }
}

that you will use like this:

public ActionResult GetPdf(string filename)
{
    return new ReportResult(filename);
}

and in your view:

@Html.ActionLink("Download report", "GetPdf", new { filename = "report.pdf" })

Or you could totally question the usefulness of your controller action because in your view instead of:

@Html.ActionLink("Download report", "GetPdf")

you could have directly:

<a href="http://foo.com/bar.pdf">Download report</a>

assuming the client has access to this server of course.

Remark: be very careful with the filenames you are sending in the Content-Disposition header. I see in your question that you used something like Server.UrlEncode("report1.pdf"). Checkout the following question for the nightmare that this could turn into.

like image 169
Darin Dimitrov Avatar answered Oct 15 '22 11:10

Darin Dimitrov


You could just redirect the user at the remote report; if that isn't an option, you will need to proxy it:

byte[] blob;
using(var client = new WebClient()) {
    blob = client.DownloadData(remoteUrl);
}
return File(blob, "application/pdf", "report1.pdf");

the above assumed the file isn't very big; a more robust implementation would fetch and send in chunks.

like image 24
Marc Gravell Avatar answered Oct 15 '22 13:10

Marc Gravell