Файл Excel не загружается при вызове функции через метод ajax - PullRequest
6 голосов
/ 13 февраля 2020

Ситуация

Я работаю над приложением, в котором у меня может быть сетка с элементами X, и у каждого элемента есть кнопка печати. Нажатие на эту кнопку печати позволяет мне вызвать функцию ajax, которая передает идентификатор элемента сетки контроллеру. Я получаю соответствующие данные на основе этого идентификатора, а затем загружаю их в файл Excel. (Получение указанного элемента c еще не завершено)

Что у меня есть

Пока у меня есть базовый код c, который загружает файл Excel вместе с моей сеткой.

Проблема

Проблема, с которой я сталкиваюсь, заключается в том, что, если я нажимаю кнопку «Печать» ... ничего не происходит, даже с точкой останова в моих функциях 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 работает, мне было интересно, решит ли моя попытка повторить мою динамическую кнопку c.

Цените любые идеи, которые могут быть предложены.

Ответы [ 8 ]

4 голосов
/ 13 февраля 2020

Да, вы правы, у вас проблема с ajax. По сути, вам нужно снова вызвать действие контроллера из вашего ajax вызова, когда ваш первый ajax вызов вернется. Добавьте приведенный ниже фрагмент кода к вашему вызову ajax.

success: function () {

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

И вам нужно изменить метод контроллера, чтобы вернуть файл, как показано ниже

public FileResult ExportToExcelx()
{
	...............
	
	byte[] byteArray = Encoding.ASCII.GetBytes(sw.ToString());
	return File(byteArray, System.Net.Mime.MediaTypeNames.Application.Octet, "FileName.xlsx");                       
}
0 голосов
/ 22 февраля 2020

Вот как я это сделал для 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>")
});
0 голосов
/ 21 февраля 2020

Прежде всего, я бы не использовал GridView для генерации Excel. Несмотря на «простоту», он не будет генерировать настоящий файл Excel, а скорее файл html с расширением xls:

<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 error when opening html table

, что довольно раздражает (и непрофессионально). Если вы не ограничены старой версией Excel - xls - но можете использовать самый последний формат файла xlsx, я бы предпочел использовать пакет DocumentFormat.Open Xml nuget или другие пакеты / библиотеки для генерации Excel. Честно говоря, DocumentFormat.Open Xml является мощным, но немного скучным в использовании, когда у вас много столбцов и у вас просто плоский список объектов для отчета. Если вы используете. NET Framework (не Do tnet Core), вы можете попробовать пакет Cget_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, который позволяет вам загрузить DataTable (см. Это: { ссылка }).

Переходя к части AJAX, хорошо ... Я не думаю, что вам это нужно вообще: как только вы установите местоположение для нового действия ExportToExcel, он должен просто загрузить файл. Предполагая, что вы используете Bootstrap 3, для каждого вашего элемента в коллекции вы можете просто:

<a href="@Url.Action("ExportToExcel", "Profile", new {id=item.Id})" class="btn btn-info">
<i class="glyphicon glyphicon-download-alt" />
</a>
0 голосов
/ 20 февраля 2020

У меня здесь была похожая проблема, и она также разрешилась с помощью кнопки c. Я просто должен был включить 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);
0 голосов
/ 20 февраля 2020

Файл не может быть загружен до тех пор, пока не будет запущена полная запись назад. Вот как вы можете это сделать: Ваша функция ExportToExcelx будет хранить файл в объекте TempData следующим образом:

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

Вместо того, чтобы возвращать представление, возвращайте идентификатор временных данных «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
    }

Функция загрузки затем вернет файл.

Надеюсь, это поможет.

0 голосов
/ 20 февраля 2020

Данные будут преобразованы в строку запроса по запросу AJAX GET; просто сделайте это самостоятельно, используя функцию jQuery param:

$('#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;
});
0 голосов
/ 19 февраля 2020

ОТВЕТ:

Вам необходимо включить success в ваш ajax звонок.

    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;
            }
        });
    }

Для дубликатов заголовков, полученных с сервера

Повторяющихся заголовков, полученных с сервера

0 голосов
/ 17 февраля 2020

Существует несколько решений этой проблемы:

Решение 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);
}

Затем внутри вашего представления (.cs html) используйте JSONToCSVConvertor в качестве служебной функции и просто не нажимайте Прикоснитесь к нему, поскольку он преобразует массив из 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>
}

Я успешно выполнил и собрал приведенный выше код, поэтому не стесняйтесь захватывать и настройте его, как вы wi sh.

Также вот ссылка jsFiddle, благодаря его разработчику: https://jsfiddle.net/1ecj1rtz/

Решение 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 эта ветка дает вам хорошую идею: { ссылка }

Надеюсь, это помогло. :)

...