Excel 通过 ajax 方法调用函数时未下载文件

Excel file not being downloaded when function is called via ajax method

情况

我正在开发一个应用程序,我可以在其中创建一个包含 X 个项目的网格,并且每个项目都有一个打印按钮。单击此打印按钮允许我调用 ajax 函数,该函数将网格项的 ID 传递给控制器​​。我根据该 ID 检索相关数据,然后将其下载到 excel 文件中。 (具体项目的检索尚未完成)

我目前有什么

到目前为止,我已经有了下载 excel 文件以及我的网格的基本代码。

问题

我面临的问题是,如果我单击 "Print" 按钮...没有任何反应,即使我的 exporttoexcel 函数中有一个断点也显示我已输入该函数并且我可以逐步通过它,尽管没有错误,但什么也没有发生。但是,我添加了调用相同函数的随机按钮,当我单击该按钮时,下载了 excel 文件。因此,我认为这个问题与 aJax.

有关

代码

<input type="button" value="Test" onclick="location.href='@Url.Action("ExportToExcel", "Profile")'" />

这是下载文件的代码。这是我添加的一个简单按钮。

function ExportToExcel(id) {
    $.ajax({
        type: "POST",
        url: "@Url.Action("ExportToExcel", "Profile")",
        data: { "id": id },
        dataType: "json"

    });
}

这是我想要工作的功能,但它不工作,我看不出哪里错了。

导出到 Excel 代码

public void ExportToExcelx()
{
    var products = new System.Data.DataTable("teste");
    products.Columns.Add("col1", typeof(int));
    products.Columns.Add("col2", typeof(string));

    products.Rows.Add(1, "product 1");
    products.Rows.Add(2, "product 2");
    products.Rows.Add(3, "product 3");
    products.Rows.Add(4, "product 4");
    products.Rows.Add(5, "product 5");
    products.Rows.Add(6, "product 6");
    products.Rows.Add(7, "product 7");


    var grid = new GridView();
    grid.DataSource = products;
    grid.DataBind();

    Response.ClearContent();
    Response.Buffer = true;
    Response.AddHeader("content-disposition", "attachment; filename=MyExcelFile.xls");
    Response.ContentType = "application/ms-excel";

    Response.Charset = "";
    StringWriter sw = new StringWriter();
    HtmlTextWriter htw = new HtmlTextWriter(sw);

    grid.RenderControl(htw);

    //Response.Output.Write(sw.ToString());
    //Response.Flush();
    //Response.End();
    // =============


    //Open a memory stream that you can use to write back to the response
    byte[] byteArray = Encoding.ASCII.GetBytes(sw.ToString());
    MemoryStream s = new MemoryStream(byteArray);
    StreamReader sr = new StreamReader(s, Encoding.ASCII);

    //Write the stream back to the response
    Response.Write(sr.ReadToEnd());
    Response.End();



    //  return View("MyView");
}

理论

我相信这个错误与 aJax 有某种关联,我也在这样创建控制器中的按钮。 "<button type='button' class='btn btn-warning' onclick='ExportToExcel(" + c.id + ");'>Print</button>",

由于 location.href='@Url.Action 有效,我想知道尝试重做我的动态按钮是否可以解决我的问题。

感谢您提供的任何见解。

是的,你是对的,你在使用 ajax 时遇到了问题,基本上当你第一次 ajax 调用 return 时,你必须再次调用控制器操作 ajax成功。将以下代码片段添加到您的 ajax 调用中。

success: function () {

    window.location = '@Url.Action("ExportExcel", "Profile")?id='+id;
}

并且您必须将控制器方法更改为 return 文件,如下所示

public FileResult ExportToExcelx()
{
 ...............
 
 byte[] byteArray = Encoding.ASCII.GetBytes(sw.ToString());
 return File(byteArray, System.Net.Mime.MediaTypeNames.Application.Octet, "FileName.xlsx");                       
}

这个问题有多种解决方案:

Solution 1

假设您有一个这样的产品模型:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

在你的控制器中:

[HttpPost]
public JsonResult ReportExcel(string id)
{
   // Your Logic Here: <DB operation> on input id
   // or whatsoever ...

    List<Product> list = new List<Product>() {
        new Product{  Id = 1, Name = "A"},
        new Product{  Id = 2, Name = "B"},
        new Product{  Id = 3, Name = "C"},
    };

    return Json(new { records = list }, JsonRequestBehavior.AllowGet);
}

然后在您的视图 (.cshtml) 中,使用 JSONToCSVConvertor 作为实用函数,只是不要触摸它,因为它会转换 [=38= json 个对象 的数组接收到 Excel 并提示下载。

@{
    ViewBag.Title = "View export to Excel";
}

<h2>....</h2>

@*  All Your View Content goes here *@
@* This is a sample form *@

<form>
    <div class="form-group">
        <label>Product ID</label>
        <div class="col-md-10">
            <input id="productID" name="productID" class="form-control"/>
        </div>
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input id="submit" type="submit" value="Create" class="btn btn-default" />
        </div>
    </div>
</form> 


@section scripts{
    <script>
        $('#submit').click(function (e) {
            e.preventDefault();

            var ID = $('#productID').val();

            $.ajax({
                cache: false,
                type: 'POST',
                url: '/YourControllerName/ReportExcel',
                data: {id: ID},
                success: function (data) {
                    console.log(data);                    
                    JSONToCSVConvertor(data.records, "Sample Report", true);
                }
            })
        });


        function JSONToCSVConvertor(JSONData, ReportTitle, ShowLabel) {
            //If JSONData is not an object then JSON.parse will parse the JSON string in an Object
            var arrData = typeof JSONData != 'object' ? JSON.parse(JSONData) : JSONData;

            var CSV = 'sep=,' + '\r\n\n';

            //This condition will generate the Label/Header
            if (ShowLabel) {
                var row = "";

                //This loop will extract the label from 1st index of on array
                for (var index in arrData[0]) {

                    //Now convert each value to string and comma-seprated
                    row += index + ',';
                }

                row = row.slice(0, -1);

                //append Label row with line break
                CSV += row + '\r\n';
            }

            //1st loop is to extract each row
            for (var i = 0; i < arrData.length; i++) {
                var row = "";

                //2nd loop will extract each column and convert it in string comma-seprated
                for (var index in arrData[i]) {
                    row += '"' + arrData[i][index] + '",';
                }

                row.slice(0, row.length - 1);

                //add a line break after each row
                CSV += row + '\r\n';
            }

            if (CSV == '') {
                alert("Invalid data");
                return;
            }

            //Generate a file name
            var fileName = "MyReport_";
            //this will remove the blank-spaces from the title and replace it with an underscore
            fileName += ReportTitle.replace(/ /g, "_");

            //Initialize file format you want csv or xls
            var uri = 'data:text/csv;charset=utf-8,' + escape(CSV);

            // Now the little tricky part.
            // you can use either>> window.open(uri);
            // but this will not work in some browsers
            // or you will not get the correct file extension    

            //this trick will generate a temp <a /> tag
            var link = document.createElement("a");
            link.href = uri;

            //set the visibility hidden so it will not effect on your web-layout
            link.style = "visibility:hidden";
            link.download = fileName + ".csv";

            //this part will append the anchor tag and remove it after automatic click
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    </script>
}

我 运行 并成功构建了上面的代码,所以请随意抓取并根据需要进行调整。

这里还有 jsFiddle link,感谢它的开发者:https://jsfiddle.net/1ecj1rtz/

Solution 2

通过$.ajax调用此操作方法并下载文件:

public FileResult Export(int id) 
 {
    //......... create the physical file ....//

    byte[] fileBytes = File.ReadAllBytes(filePath);
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
 }

连同解决方案 2,此线程为您提供了一个好主意:

希望这对您有所帮助。 :)

答案:

You need to include success in your ajax call.

    function ExportToExcel(id) {
        $.ajax({
            type: "POST",
            url: "@Url.Action("ExportToExcel", "Profile")",
            data: { "id": id },
            dataType: "json"
            success: function () {
                 window.location = '@Url.Action("ExportExcel", "Profile")?id='+id;
            }
        });
    }

For Duplicate headers received from the server

Duplicate headers received from server

数据将在 AJAX GET 请求中转换为查询字符串;只需使用 jQuery 参数函数自己做:

$('#excel').on('click',function(){
    var query = {
        location: $('#location').val(),
        area: $('#area').val(),
        booth: $('#booth').val()
    }

   var url = "{{URL::to('downloadExcel_location_details')}}?" + $.param(query)

   window.location = url;
});

在触发完全 post 返回之前无法下载文件。以下是您的操作方法: 您的 ExportToExcelx 函数将在 TempData 对象中保存文件,如下所示:

TempData["fileHandle"] = s.ToArray();

而不是return正在查看return临时数据标识符"fileHandle"和文件名,如下所示:

 return Json(new { fileHandle = "fileHandle", FileName = "file.xls" }, JsonRequestBehavior.AllowGet);

所以你修改后的函数是这样的:

public JsonResult ExportToExcelx()
{
    var products = new System.Data.DataTable("teste");
    products.Columns.Add("col1", typeof(int));
    products.Columns.Add("col2", typeof(string));

    products.Rows.Add(1, "product 1");
    products.Rows.Add(2, "product 2");
    products.Rows.Add(3, "product 3");
    products.Rows.Add(4, "product 4");
    products.Rows.Add(5, "product 5");
    products.Rows.Add(6, "product 6");
    products.Rows.Add(7, "product 7");


    var grid = new GridView();
    grid.DataSource = products;
    grid.DataBind();

    Response.ClearContent();
    Response.Buffer = true;
    Response.AddHeader("content-disposition", "attachment; filename=MyExcelFile.xls");
    Response.ContentType = "application/ms-excel";

    Response.Charset = "";
    StringWriter sw = new StringWriter();
    HtmlTextWriter htw = new HtmlTextWriter(sw);

    grid.RenderControl(htw);

    //Response.Output.Write(sw.ToString());
    //Response.Flush();
    //Response.End();
    // =============


    //Open a memory stream that you can use to write back to the response
    byte[] byteArray = Encoding.ASCII.GetBytes(sw.ToString());
    MemoryStream s = new MemoryStream(byteArray);

    TempData["fileHandle"] = s.ToArray();

    //StreamReader sr = new StreamReader(s, Encoding.ASCII);

    //Write the stream back to the response
    Response.Write(sr.ReadToEnd());
    Response.End();


    return Json(new { fileHandle = "fileHandle", FileName = "file.xls" }, JsonRequestBehavior.AllowGet);
    //  return View("MyView");
}

现在您需要控制器中的另一个功能来下载如下文件:

    [HttpGet]
    public virtual ActionResult Download(string fileHandle, string fileName)
    {
        if (TempData[fileHandle] != null)
        {
            byte[] data = TempData[fileHandle] as byte[];
            return File(data, "application/vnd.ms-excel", fileName);
        }
        else
        {

            return new EmptyResult();
        }
    }

成功调用 ExportToExcelx 函数后,您的 ajax 调用将调用下载函数,如下所示:

    $.ajax({
    type: 'GET',
    cache: false,
    url: '/url',      
    success: function (data) {
        window.location = '/url/Download?fileHandle=' + data.fileHandle
            + '&filename=' + data.FileName; //call download function
    },
    error: function (e) {
        //handle error
    }

下载函数然后将 return 文件。

希望对您有所帮助。

我在这里遇到过类似的问题,它也通过动态按钮解决了。我只需要在我的请求中包含一个 responseType:'blob'。 并获得对按钮的响应:

var link = document.createElement('a');
link.href = window.URL.createObjectURL(response.data);
link.download='filename.xlsx';

document.body.appendChild(link);
link.click();
document.body.removeChild(link);

我的控制器写入输出流并生成 "application/xls"

response.setContentType("application/xls");
response.setHeader("Content-disposition", "attachment;");
response.getOutputStream().write(content);

首先,我不会用GridView生成excel。尽管是 "easy",它不会生成实际的 excel 文件,而是生成一个带有 xls 扩展名的 html 文件:

<div>
    <table cellspacing="0" rules="all" border="1" style="border-collapse:collapse;">
        <tr>
            <th scope="col">col1</th><th scope="col">col2</th>
        </tr><tr>
            <td>1</td><td>product 1</td>
        </tr><tr>
            <td>2</td><td>product 2</td>
        </tr><tr>
            <td>3</td><td>product 3</td>
        </tr><tr>
            <td>4</td><td>product 4</td>
        </tr><tr>
            <td>5</td><td>product 5</td>
        </tr><tr>
            <td>6</td><td>product 6</td>
        </tr><tr>
            <td>7</td><td>product 7</td>
        </tr>
    </table>
</div>

这会生成一个文件,打开该文件会导致:

这很烦人(而且不专业)。 如果您不受限于旧 excel 版本 - xls - 但可以使用最新的文件格式 xlsx,我宁愿使用 DocumentFormat.OpenXml nuget 包或其他 packages/libraries for excel 一代。 老实说,DocumentFormat.OpenXml 功能强大但使用起来有点乏味,当您有很多列并且只有 objects 的平面列表要报告时。 如果您使用的是 .NET Framework(不是 Dotnet Core),您可以尝试 CsvHelper.Excel nuget 包。用法非常简单。您的 ExportToExcel 方法将变为:

public ActionResult ExportToExcel(string id)
{
    // TODO: Replace with correct products retrieving logic using id input
    var products = new [] {
       { col1 = 1, col2 = "product 1" },
       { col1 = 2, col2 = "product 2" },
       { col1 = 3, col2 = "product 3" },
       { col1 = 4, col2 = "product 4" },
       { col1 = 5, col2 = "product 5" },
       { col1 = 6, col2 = "product 6" },
       { col1 = 7, col2 = "product 7" },
       { col1 = 1, col2 = "product 1" },
       { col1 = 1, col2 = "product 1" },
    };

    var ms = new MemoryStream();
    var workbook = new XLWorkbook();
    using (var writer = new CsvWriter(new ExcelSerializer(workbook)))
    {
       writer.WriteRecords(products);
    }
    workbook.SaveAs(ms);
    ms.Flush();
    ms.Seek(0, SeekOrigin.Begin);
    return File(ms, MimeMapping.GetMimeMapping("file.xlsx"), $"MyExcelFile.xlsx");

}

另一个非常强大的包是 EPPlus,它允许您加载数据表(参见:https://whosebug.com/a/53957999/582792)。

来到 AJAX 部分,嗯...我认为您根本不需要它:一旦您将位置设置为新的 ExportToExcel 操作,它应该只下载文件。 假设您使用 Bootstrap 3,对于 collection 中的每个项目,您可以:

<a href="@Url.Action("ExportToExcel", "Profile", new {id=item.Id})" class="btn btn-info">
<i class="glyphicon glyphicon-download-alt" />
</a>

以下是我如何使它适用于 PDF。 Excel 下载应该差不多

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

或使用 download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});