How to initialize file download from an external server?

前端 未结 3 1114
陌清茗
陌清茗 2020-12-29 00:16

I have an MVC controller method defined like this:

public ActionResult GetPdf(string filename)
        {
            var pdfDownload = File(\"~/Content/Gene         


        
相关标签:
3条回答
  • 2020-12-29 00:24

    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.

    0 讨论(0)
  • 2020-12-29 00:39

    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.

    0 讨论(0)
  • 2020-12-29 00:39

    I am doing a similar routine. Right now I have it accessing images from the local drive

    byte[] cimage = new WebClient().DownLoadData(System.Web.HttpContent.Server.MapPath("~/Content/koala.jpg"));

    How would I access a share on our SQL Server. I have images in a folders on the SQL Server that I want to retrieve.

    byte[] cimage = new WebClient().DownLoadData("Server share" + /Files/koala.jpg");

    0 讨论(0)
提交回复
热议问题