在.Net ApiController中启动文件下载

时间:2015-01-30 23:21:33

标签: javascript c# jquery .net

我正在尝试生成一个包含HTML内容的PDF文件,该内容通过JavaScript从AJAX发送。

我正确地发送了HTML,并且使用HTML正确生成了PDF,但我一直试图弄清楚如何在用户的浏览器上启动文件下载。

这就是我目前正在尝试的。这在直接命名文件的URL时在IHttpHandler文件中有效,但在通过AJAX发布时在ApiController中不起作用。

我也尝试使用相同的结果发布到IHttpHandler文件。一切正常,直到我尝试使用BinaryWrite启动下载。

public class DownloadScheduleController : ApiController
{
    public void Post(HtmlModel data)
    {
        var htmlContent = data.html;

        var pdfBytes = (new NReco.PdfGenerator.HtmlToPdfConverter()).GeneratePdf(htmlContent);

        using (var mstream = new System.IO.MemoryStream())
        {
            HttpContext.Current.Response.ContentType = "application/pdf";
            HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=UserSchedule.pdf");
            HttpContext.Current.Response.BinaryWrite(pdfBytes);
            HttpContext.Current.Response.End();
        }
    }
}

这是Ajax请求。它使用的是Angular的$ http。

$http({
    method: 'POST',
    url: 'api/downloadSchedule',
    dataType: "json",
    contentType: "application/json; charset=utf-8",
    data: data
});

或者我可以将PDF二进制文件返回给JavaScript,但是如何使用JavaScript来保存pdf?

任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:2)

出于安全原因,浏览器无法从AJAX请求启动文件下载。它只能通过导航到发送文件的页面来完成。

通常,如果您需要从Javascript发起下载,您可以设置window.location.href = urlToFile,也可以创建指向该网址的新iframe。

在你的情况下这不会起作用,因为上面的方法只执行一个GET请求,你需要一个POST,所以我只看到两个可能的解决方案:

  • 您可以将JS修改为 - 而不是使用$ http提交请求 - 创建一个HTML表单,其中包含与您最初使用AJAX请求发布的内容相对应的字段,然后将表单附加到页面,填充字段并提交表格
  • 或者您可以更改服务器端代码以及Javascript。

如果您选择第二个解决方案,您可以遵循在服务器端使用两种方法的方法:

  • 生成文件并将其保存在缓存中的一个POST(可能有更好的解决方案使用缓存,特别是如果您在服务器场上,但让我们保持简单)

  • 一个GET,它检索缓存的内容并将其返回给用户。通过这种方式,您可以通过AJAX调用发布数据,然后通过导航到正确的URL来获取文件。

您的C#代码如下所示:

public class DownloadScheduleController : ApiController
{
    public object Post(HtmlModel data)
    {
        var htmlContent = data.html;

        var pdfBytes = (new NReco.PdfGenerator.HtmlToPdfConverter()).GeneratePdf(htmlContent);
        var policy = new CacheItemPolicy { AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(30), Priority = CacheItemPriority.NotRemovable };
        var cacheId = Guid.NewGuid().ToString();
        MemoryCache.Default.Add("pdfBytes_" + cacheId, pdfBytes, policy);
        return new { id = cacheId };
    }

    public void Get(string id)
    {
        var pdfBytes = MemoryCache.Default.Get("pdfBytes_" + id);
        MemoryCache.Default.Remove("pdfBytes_" + id);

        using (var mstream = new System.IO.MemoryStream())
        {
            HttpContext.Current.Response.ContentType = "application/pdf";
            HttpContext.Current.Response.AppendHeader("content-disposition", "attachment; filename=UserSchedule.pdf");
            HttpContext.Current.Response.BinaryWrite(pdfBytes);
            HttpContext.Current.Response.End();
        }
    }
}

你的前端可能是这样的:

$http({
    method: 'POST',
    url: 'api/downloadSchedule',
    dataType: "json",
    contentType: "application/json; charset=utf-8",
    data: data
}).success(function(response) {
    // note: the url path might be different depending on your route configuration
    window.location.href = 'api/downloadSchedule/' + response.id;
});

请记住,我的代码只是为了向您展示方法,它并不意味着在生产中使用它。例如,您应该在使用后立即清除缓存。在使用之前,您还应该在Get方法中对从缓存中检索到的内容进行完整性检查。此外,根据您的要求,您可能希望对存储和检索的数据采取其他安全预防措施。