c# - Web Api 不会使用 jQuery Ajax 和 Basic Auth 下载文件

标签 c# asp.net asp.net-mvc-4 asp.net-web-api basic-authentication

我正在使用 ASP.NET Web API 构建 Web 服务(和站点)的原型(prototype),它具有下载文件的方法。当前端的用户按下导出按钮时, Controller 会发出并接收 jQuery ajax GET 请求,而 Controller 又会调用名为 Excel 的方法(如下所示)。该方法运行没有任何问题并结束。当我在 Chrome 中查看 header (请参阅 https://skydrive.live.com/redir?resid=2D85E5C937AC2BF9!77093 )时,它会收到带有(就我而言)所有正确 header 的响应。

我使用的是 Basic Auth,因此用户凭据是使用 http 授权 header 传输的,我已使用 Ajax 选项将其手动添加到每个 jQuery Ajax 请求中。

var excelRequest = $.ajax({
    url: 'http://localhost:59390/api/mycontroller/excel',
    cache: false,
    type: 'GET',
    data: gridString,
    dataType: 'json',
    contentType: 'application/json; charset=utf-8'
});

$.ajaxSetup({
    beforeSend: function (xhr) {
        SetAuthRequestHeader(xhr)
    }
});

function SetAuthRequestHeader(jqXHR) {
    var usr = "Gebruiker2"; // TODO: Change, this is for testing only.
    var pw = "Wachtwoord23";
    jqXHR.setRequestHeader("Authorization", "Basic " + Base64.encode(usr + ":" + pw));
}

我的原型(prototype)有一些我应该提到的特征:

  • 在授权 header 中使用基本身份验证

  • Web 服务和调用该服务的网站在不同的域中,因此我使用了 CORS 并在 web.config 中添加了以下内容

<httpProtocol>
    <customHeaders>
        <add name="Access-Control-Allow-Origin" value="*" />
        <add name="access-control-allow-headers" value="Content-Type, Authorization, Accept" />
        <add name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS" 
    </customHeaders>
</httpProtocol>

下面显示的是整个 Excel 方法。

    [HttpGet]
    // Get api/myController/excel 
    public HttpResponseMessage Excel(string sidx, string sord, int page, int rows, string Depot, string PDID, string User, string Property, string Value)
    {
        if (AuthHelper.AuthService.HasValidCredentials(Request))
        {
            var gridResult = this.GetDataJqGridFormat( sidx,  sord,  page,  rows,  Depot,  PDID,  User,  Property,  Value);

            // Generate a HTML table.
            StringBuilder builder = new StringBuilder();
            // We create a html table:
            builder.Append("<table border=1>");
            builder.Append("<tr><td>DEPOT</td>");
            builder.Append("<td>PDID</td>");
            builder.Append("<td>USER</td>");
            builder.Append("<td>PROPERTY</td>");
            builder.Append("<td>VALUE</td></tr>");
            // Create response from anonymous type            
            foreach (var item in gridResult.rows)
            {
                builder.Append("</tr>");
                builder.Append("<tr>");
                builder.Append("<td>" + item.cell[0] + "</td>");
                builder.Append("<td>" + item.cell[2] + "</td>");
                builder.Append("<td>" + item.cell[3] + "</td>");
                builder.Append("<td>" + item.cell[4] + "</td>");
                builder.Append("<td>" + item.cell[5] + "</td>");
            }
            builder.Append("</table>");

            HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
            result.Content = new StringContent(builder.ToString());
            result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");                                
            result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            result.Content.Headers.ContentDisposition.FileName = "file.xls";

            return result;
        }
        else
        {
            throw ForbiddenResponseMessage();
        }

    }

这是还应返回文件的 header : https://skydrive.live.com/redir?resid=2D85E5C937AC2BF9!77093

我想要的是在调用指向 excel 方法的 url 时下载文件。我不明白为什么它不会下载。甚至可以通过这种方式下载文件吗?

最佳答案

好吧,我明白了。为了节省您的阅读时间,不可能按照我想要的方式进行。

首先,我无法使用 jQuery Ajax 下载文件。正如我已经预料(或担心)的那样,无法使用 Ajax 下载文件。这是出于安全考虑。 参见 Why threre is no way to download file using ajax request?

但我仍然需要下载文件。有了以上知识,解决方案/解决方法似乎很简单。

我将执行 ajax 调用的 javascript 函数更改为以下内容:

    var gridInfo = {
    sidx: "",
    sord: "asc",
    nd: 1371046879480,
    rows: 50,
    page: 1
}
var payLoad = "?" + PrepareSearchQueryString() + "&" + serialize(gridInfo);

window.location= "http://site:59390/api/mycontroller/excel" + payLoad

但这只是解决了部分问题,因为我需要设置授权 header 。

感谢 Stackoverflow 用户 SLaks 回答了我的相关问题 (Set headers using javascript) 我能够找到用户名和密码类似于 https://user:password@domain.com/path?query 的网址可能是一个解决方案。可悲的是,它没有用。它不被 IE 接受,在使用它的 Chrome 中找不到授权 header 。

因为我也在使用 jqGrid,所以我能够使用 jqGrid 设置授权头。这在使用站点上的搜索功能时非常有效。我的 Javascript 导出函数现在看起来像这样:

    var sUrl = "http://localhost:59390/api/Wmssettings/excel" + "?" + PrepareSearchQueryString() ;
$("#gridOverview").jqGrid('excelExport', { url: sUrl });

但是在查看请求时我注意到,与使用搜索功能时不同,没有传递授权 header 。我确实找到了原因。在查看 jqGrid 源代码时,我注意到 Grid 只是在执行 window.location 以指向下载位置。并且这样做时无法传递基本的身份验证信息。

所以我唯一要走的路就是我试图避免的路。我更改了我的 Controller 方法以返回一个包含指向该文件的 url 的 json,然后我使用 javascript 进行重定向到一个名为 downloadcontroller 的新 Controller 。

Controller Excel方法

        [HttpGet]
    // Get api/wmssettings/excel 
    public HttpResponseMessage Excel(string sidx, string sord, int page, int rows, string Depot, string PDID, string User, string Property, string Value)
    {
        if (AuthHelper.AuthService.HasValidCredentials(Request))
        {
            var gridResult = this.GetDataJqGridFormat(sidx, sord, page, rows, Depot, PDID, User, Property, Value);

            // Generate a HTML table.
            StringBuilder builder = new StringBuilder();
            // We create a html table:
            builder.Append("<table border=1>");
            builder.Append("<tr><td>DEPOT</td>");
            builder.Append("<td>PDID</td>");
            builder.Append("<td>USER</td>");
            builder.Append("<td>PROPERTY</td>");
            builder.Append("<td>VALUE</td></tr>");
            // Create response from anonymous type            
            foreach (var item in gridResult.rows)
            {
                builder.Append("</tr>");
                builder.Append("<tr>");
                builder.Append("<td>" + item.cell[0] + "</td>");
                builder.Append("<td>" + item.cell[2] + "</td>");
                builder.Append("<td>" + item.cell[3] + "</td>");
                builder.Append("<td>" + item.cell[4] + "</td>");
                builder.Append("<td>" + item.cell[5] + "</td>");
            }
            builder.Append("</table>");

            // Put all in a file and return the url:
            string fileName = "export" + "_" + Guid.NewGuid().ToString() + ".xls";
            using (StreamWriter writer = new StreamWriter(HttpContext.Current.Server.MapPath("~/Downloads" + fileName)))
            {
                writer.Write(builder.ToString());
                writer.Flush();                    
            }
            HttpResponseMessage result = Request.CreateResponse(HttpStatusCode.OK, fileName);
            return result;
        }
        else
        {
            throw ForbiddenResponseMessage();
        }
    }

JavaScript 导出方法

    var gridParams = {
    //"nd": Math.floor((Math.random() * 10000000) + 1),
    sidx: "",
    sord: "asc",
    page: "1",
    rows: "50"        
}   

payLoad = PrepareSearchQueryString() + "&" + serialize(gridParams);

var excelRequest = $.ajax({
    url: 'http://localhost:59390/api/Wmssettings/excel',
    cache: false,
    type: 'GET',
    data: payLoad,
    dataType: 'json',
    contentType: 'application/json; charset=utf-8'
});

excelRequest.success(function (data, code, jqXHR) {
    if (data == null) {
        alert('sd');
        ShowErrorMessage("There was no file created.", "", "Title");
    } else {
        window.location = 'http://localhost:59390/api/download/?fileName=' + data;
    }
});

我在 WebApiConfig.cs 中添加了以下行

            config.Routes.MapHttpRoute("Downloadcontroller", "api/{controller}/{action}", 
             new { action = "Get"}, new { httpMethod = new HttpMethodConstraint(allowedVerbsGet), controller="download"});    

最后是下载 Controller :

    public class DownloadController : ApiController
{
    [HttpGet]
    public HttpResponseMessage Get(string fileName)
    {
        HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
        string fileLocation = HttpContext.Current.Server.MapPath("~/Downloads" + fileName);

        if (!File.Exists(fileLocation))
        {
            throw new HttpResponseException(HttpStatusCode.OK);
        }

        Stream fileStream = File.Open(fileLocation, FileMode.Open);
        result.Content = new StreamContent(fileStream);

        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.ms-excel");
        result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        return result;
    }
}

可以总结出以下几点:

  • 无法使用 Ajax 下载文件,我必须重定向到精确的文件位置或另一个 Controller 。

  • 将用户名和密码放在 url 中在 IE 中是不允许的,并且在其他浏览器中似乎会产生问题。例如,在 Chrome 中,授权 header 保持为空。

  • 在执行 window.location 时,无法传递其他 header ,例如基本身份验证所需的授权 header 。

嗯,就是这样。答案是我想做的事情不可能按照我喜欢的方式去做。现在工作正常。

关于c# - Web Api 不会使用 jQuery Ajax 和 Basic Auth 下载文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17084231/

相关文章:

c# - IE9 中带有 Repeater Control 的 Ghost Cell

javascript - 如何从 ASP.Net 发布然后重定向到外部 URL?

c# - 使用 HttpClient 时记录请求/响应消息

c# - 没有 html 标记的 ASP.NET MVC 3/4 ValidationMessageFor

c# - MVC5 Razor html.dropdownlistfor 当值在数组中时设置选择

C# Rest API 返回动态对象

c# - Facebook json 消息的编码/解码问题。 C#解析

c# - 在 MeasureOverride(WPF 面板)中,当我没有固有的所需大小时,如何处理 availableSize 中的无穷大值?

asp.net - HTML Agility 包删除了中断标记 close

c# - Unity 3 和错误 "The type name or alias "xxxxx“无法解析。请检查您的配置文件并验证此类型名称。”