Вы не можете напрямую вернуть файл для загрузки через вызов AJAX, поэтому альтернативным подходом является использование вызова AJAX для публикации связанных данных на вашем сервере. Затем вы можете использовать код на стороне сервера для создания файла Excel (я бы рекомендовал использовать для этого EPPlus или NPOI, хотя это звучит так, как будто у вас эта часть работает).
ОБНОВЛЕНИЕ Сентябрь 2016 г.
Мой первоначальный ответ (ниже) был старше 3 лет, поэтому я подумал, что обновлю, поскольку я больше не создаю файлы на сервере при загрузке файлов через AJAX, однако я оставил исходный ответ, поскольку он может быть полезен, все еще в зависимости от ваши конкретные требования.
Распространенный сценарий в моих приложениях MVC - создание отчетов через веб-страницу, на которой есть некоторые параметры отчета, настроенные пользователем (диапазоны дат, фильтры и т. Д.). Когда пользователь указал параметры, которые они отправляют на сервер, создается отчет (например, файл Excel в качестве выходных данных), а затем я сохраняю полученный файл в виде массива байтов в TempData
корзине с уникальной ссылкой. Эта ссылка возвращается как результат Json в мою функцию AJAX, которая впоследствии перенаправляет на отдельное действие контроллера для извлечения данных TempData
и загрузки в браузер конечных пользователей.
Чтобы представить это более подробно, предположим, что у вас есть представление MVC, которое имеет форму, привязанную к классу модели, позвольте вызвать модель ReportVM
.
Во-первых, для получения опубликованной модели требуется действие контроллера, например:
public ActionResult PostReportPartial(ReportVM model){
ExcelPackage workbook = new ExcelPackage();
string handle = Guid.NewGuid().ToString();
using(MemoryStream memoryStream = new MemoryStream()){
workbook.SaveAs(memoryStream);
memoryStream.Position = 0;
TempData[handle] = memoryStream.ToArray();
}
return new JsonResult() {
Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" }
};
}
Вызов AJAX, который отправляет мою форму MVC указанному выше контроллеру и получает ответ, выглядит следующим образом:
$ajax({
cache: false,
url: '/Report/PostReportPartial',
data: _form.serialize(),
success: function (data){
var response = JSON.parse(data);
window.location = '/Report/Download?fileGuid=' + response.FileGuid
+ '&filename=' + response.FileName;
}
})
Действие контроллера для обработки загрузки файла:
[HttpGet]
public virtual ActionResult Download(string fileGuid, string fileName)
{
if(TempData[fileGuid] != null){
byte[] data = TempData[fileGuid] as byte[];
return File(data, "application/vnd.ms-excel", fileName);
}
else{
return new EmptyResult();
}
}
Еще одно изменение, которое при необходимости может быть легко выполнено, - это передача MIME-типа файла в качестве третьего параметра, чтобы одно действие контроллера могло правильно обслуживать различные форматы выходных файлов.
Это устраняет необходимость в каких-либо физических файлах для создания и хранения на сервере, поэтому никаких дополнительных процедур не требуется, и, опять же, это легко для конечного пользователя.
Обратите внимание: преимущество использования TempData
вместо Session
заключается в том, что после TempData
считывания данные очищаются, поэтому использование памяти будет более эффективным при большом объеме файловых запросов. См. Рекомендации по использованию TempData .
ОРИГИНАЛЬНЫЙ ответ
Вы не можете напрямую вернуть файл для загрузки через вызов AJAX, поэтому альтернативным подходом является использование вызова AJAX для публикации связанных данных на вашем сервере. Затем вы можете использовать код на стороне сервера для создания файла Excel (я бы рекомендовал использовать для этого EPPlus или NPOI, хотя это звучит так, как будто у вас эта часть работает).
После того, как файл был создан на сервере, верните путь к файлу (или просто имя файла) в качестве возвращаемого значения для вашего вызова AJAX, а затем установите JavaScript window.location
для этого URL-адреса, который предложит браузеру загрузить файл.
С точки зрения конечных пользователей, операция загрузки файлов проходит без проблем, поскольку они никогда не покидают страницу, на которой был создан запрос.
Ниже приведен простой надуманный пример вызова ajax для достижения этой цели:
$.ajax({
type: 'POST',
url: '/Reports/ExportMyData',
data: '{ "dataprop1": "test", "dataprop2" : "test2" }',
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function (returnValue) {
window.location = '/Reports/Download?file=' + returnValue;
}
});
- urlПараметр - это метод контроллера / действия, в котором ваш код создаст файл Excel.
- Параметр data содержит данные json, которые будут извлечены из формы.
- returnValue будет именем вашего вновь созданного файла Excel.
- Команда window.location перенаправляет на метод Controller / Action, который фактически возвращает ваш файл для загрузки.
Примерный метод контроллера для действия Download:
[HttpGet]
public virtual ActionResult Download(string file)
{
string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
return File(fullPath, "application/vnd.ms-excel", file);
}
Мои 2 цента - вам не нужно хранить Excel как физический файл на сервере - вместо этого сохраните его в (сеансовом) кэше. Используйте уникально сгенерированное имя для вашей переменной Cache (в которой хранится этот файл Excel) - это будет результат вашего (начального) вызова ajax. Таким образом, вам не придется решать проблемы с доступом к файлам, управлять (удалять) файлы, когда они не нужны, и т. Д., И, имея файл в кэше, его можно быстрее получить.
источник
Недавно мне удалось сделать это в MVC (хотя не было необходимости использовать AJAX) без создания физического файла, и я решил поделиться своим кодом:
Супер простая функция JavaScript (это вызывает нажатие кнопки datatables.net):
function getWinnersExcel(drawingId) { window.location = "/drawing/drawingwinnersexcel?drawingid=" + drawingId; }
Код контроллера C #:
public FileResult DrawingWinnersExcel(int drawingId) { MemoryStream stream = new MemoryStream(); // cleaned up automatically by MVC List<DrawingWinner> winnerList = DrawingDataAccess.GetWinners(drawingId); // simple entity framework-based data retrieval ExportHelper.GetWinnersAsExcelMemoryStream(stream, winnerList, drawingId); string suggestedFilename = string.Format("Drawing_{0}_Winners.xlsx", drawingId); return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", suggestedFilename); }
В классе ExportHelper я использую сторонний инструмент ( GemBox.Spreadsheet ) для создания файла Excel, и у него есть опция «Сохранить в поток». При этом существует несколько способов создания файлов Excel, которые можно легко записать в поток памяти.
public static class ExportHelper { internal static void GetWinnersAsExcelMemoryStream(MemoryStream stream, List<DrawingWinner> winnerList, int drawingId) { ExcelFile ef = new ExcelFile(); // lots of excel worksheet building/formatting code here ... ef.SaveXlsx(stream); stream.Position = 0; // reset for future read } }
В IE, Chrome и Firefox браузер предлагает загрузить файл, но фактической навигации не происходит.
источник
Сначала создайте действие контроллера, которое создаст файл Excel.
[HttpPost] public JsonResult ExportExcel() { DataTable dt = DataService.GetData(); var fileName = "Excel_" + DateTime.Now.ToString("yyyyMMddHHmm") + ".xls"; //save the file to server temp folder string fullPath = Path.Combine(Server.MapPath("~/temp"), fileName); using (var exportData = new MemoryStream()) { //I don't show the detail how to create the Excel, this is not the point of this article, //I just use the NPOI for Excel handler Utility.WriteDataTableToExcel(dt, ".xls", exportData); FileStream file = new FileStream(fullPath, FileMode.Create, FileAccess.Write); exportData.WriteTo(file); file.Close(); } var errorMessage = "you can return the errors in here!"; //return the Excel file name return Json(new { fileName = fileName, errorMessage = "" }); }
затем создайте действие Download
[HttpGet] [DeleteFileAttribute] //Action Filter, it will auto delete the file after download, //I will explain it later public ActionResult Download(string file) { //get the temp folder and file path in server string fullPath = Path.Combine(Server.MapPath("~/temp"), file); //return the file for download, this is an Excel //so I set the file content type to "application/vnd.ms-excel" return File(fullPath, "application/vnd.ms-excel", file); }
если вы хотите удалить файл после загрузки, создайте это
public class DeleteFileAttribute : ActionFilterAttribute { public override void OnResultExecuted(ResultExecutedContext filterContext) { filterContext.HttpContext.Response.Flush(); //convert the current filter context to file and get the file path string filePath = (filterContext.Result as FilePathResult).FileName; //delete the file after download System.IO.File.Delete(filePath); } }
и, наконец, вызов ajax из вашего представления MVC Razor
//I use blockUI for loading... $.blockUI({ message: '<h3>Please wait a moment...</h3>' }); $.ajax({ type: "POST", url: '@Url.Action("ExportExcel","YourController")', //call your controller and action contentType: "application/json; charset=utf-8", dataType: "json", }).done(function (data) { //console.log(data.result); $.unblockUI(); //get the file name for download if (data.fileName != "") { //use window.location.href for redirect to download action for download the file window.location.href = "@Url.RouteUrl(new { Controller = "YourController", Action = "Download"})/?file=" + data.fileName; } });
источник
Я использовал решение, опубликованное CSL, но я бы рекомендовал вам не сохранять данные файла в сеансе в течение всего сеанса. При использовании TempData данные файла автоматически удаляются после следующего запроса (который является запросом GET для файла). Вы также можете управлять удалением данных файла в сеансе в действии загрузки.
Сеанс может потреблять много памяти / места в зависимости от хранилища SessionState и количества файлов, экспортируемых во время сеанса, и если у вас много пользователей.
Я обновил код стороны serer из CSL, чтобы вместо этого использовать TempData.
public ActionResult PostReportPartial(ReportVM model){ // Validate the Model is correct and contains valid data // Generate your report output based on the model parameters // This can be an Excel, PDF, Word file - whatever you need. // As an example lets assume we've generated an EPPlus ExcelPackage ExcelPackage workbook = new ExcelPackage(); // Do something to populate your workbook // Generate a new unique identifier against which the file can be stored string handle = Guid.NewGuid().ToString() using(MemoryStream memoryStream = new MemoryStream()){ workbook.SaveAs(memoryStream); memoryStream.Position = 0; TempData[handle] = memoryStream.ToArray(); } // Note we are returning a filename as well as the handle return new JsonResult() { Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" } }; } [HttpGet] public virtual ActionResult Download(string fileGuid, string fileName) { if(TempData[fileGuid] != null){ byte[] data = TempData[fileGuid] as byte[]; return File(data, "application/vnd.ms-excel", fileName); } else{ // Problem - Log the error, generate a blank file, // redirect to another controller action - whatever fits with your application return new EmptyResult(); } }
источник
using ClosedXML.Excel;
public ActionResult Downloadexcel() { var Emplist = JsonConvert.SerializeObject(dbcontext.Employees.ToList()); DataTable dt11 = (DataTable)JsonConvert.DeserializeObject(Emplist, (typeof(DataTable))); dt11.TableName = "Emptbl"; FileContentResult robj; using (XLWorkbook wb = new XLWorkbook()) { wb.Worksheets.Add(dt11); using (MemoryStream stream = new MemoryStream()) { wb.SaveAs(stream); var bytesdata = File(stream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "myFileName.xlsx"); robj = bytesdata; } } return Json(robj, JsonRequestBehavior.AllowGet); }
источник
источник
Принятый ответ не совсем сработал для меня, так как я получил результат 502 Bad Gateway от вызова ajax, хотя все, казалось, возвращалось с контроллера нормально.
Возможно, я достиг предела с TempData - не уверен, но я обнаружил, что если я использовал IMemoryCache вместо TempData , он работал нормально, поэтому вот моя адаптированная версия кода в принятом ответе:
public ActionResult PostReportPartial(ReportVM model){ // Validate the Model is correct and contains valid data // Generate your report output based on the model parameters // This can be an Excel, PDF, Word file - whatever you need. // As an example lets assume we've generated an EPPlus ExcelPackage ExcelPackage workbook = new ExcelPackage(); // Do something to populate your workbook // Generate a new unique identifier against which the file can be stored string handle = Guid.NewGuid().ToString(); using(MemoryStream memoryStream = new MemoryStream()){ workbook.SaveAs(memoryStream); memoryStream.Position = 0; //TempData[handle] = memoryStream.ToArray(); //This is an equivalent to tempdata, but requires manual cleanup _cache.Set(handle, memoryStream.ToArray(), new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(10))); //(I'd recommend you revise the expiration specifics to suit your application) } // Note we are returning a filename as well as the handle return new JsonResult() { Data = new { FileGuid = handle, FileName = "TestReportOutput.xlsx" } }; }
Вызов AJAX остается таким же, как и в принятом ответе (я не вносил изменений):
$ajax({ cache: false, url: '/Report/PostReportPartial', data: _form.serialize(), success: function (data){ var response = JSON.parse(data); window.location = '/Report/Download?fileGuid=' + response.FileGuid + '&filename=' + response.FileName; } })
Действие контроллера для обработки загрузки файла:
[HttpGet] public virtual ActionResult Download(string fileGuid, string fileName) { if (_cache.Get<byte[]>(fileGuid) != null) { byte[] data = _cache.Get<byte[]>(fileGuid); _cache.Remove(fileGuid); //cleanup here as we don't need it in cache anymore return File(data, "application/vnd.ms-excel", fileName); } else { // Something has gone wrong... return View("Error"); // or whatever/wherever you want to return the user } }
...
Теперь есть дополнительный код для настройки MemoryCache ...
Чтобы использовать "_cache", я ввел в конструктор контроллера вот так:
using Microsoft.Extensions.Caching.Memory; namespace MySolution.Project.Controllers { public class MyController : Controller { private readonly IMemoryCache _cache; public LogController(IMemoryCache cache) { _cache = cache; } //rest of controller code here } }
И убедитесь, что у вас есть следующее в ConfigureServices в Startup.cs:
services.AddDistributedMemoryCache();
источник
Эта ветка помогла мне создать собственное решение, которым я поделюсь здесь. Сначала я без проблем использовал запрос GET ajax, но дошел до того, что длина URL-адреса запроса была превышена, поэтому мне пришлось переключиться на POST.
В javascript используется плагин загрузки файлов JQuery, который состоит из двух последовательных вызовов. Один POST (для отправки параметров) и один GET для получения файла.
function download(result) { $.fileDownload(uri + "?guid=" + result, { successCallback: onSuccess.bind(this), failCallback: onFail.bind(this) }); } var uri = BASE_EXPORT_METADATA_URL; var data = createExportationData.call(this); $.ajax({ url: uri, type: 'POST', contentType: 'application/json', data: JSON.stringify(data), success: download.bind(this), fail: onFail.bind(this) });
Сторона сервера
[HttpPost] public string MassExportDocuments(MassExportDocumentsInput input) { // Save query for file download use var guid = Guid.NewGuid(); HttpContext.Current.Cache.Insert(guid.ToString(), input, null, DateTime.Now.AddMinutes(5), Cache.NoSlidingExpiration); return guid.ToString(); } [HttpGet] public async Task<HttpResponseMessage> MassExportDocuments([FromUri] Guid guid) { //Get params from cache, generate and return var model = (MassExportDocumentsInput)HttpContext.Current.Cache[guid.ToString()]; ..... // Document generation // to determine when file is downloaded HttpContext.Current .Response .SetCookie(new HttpCookie("fileDownload", "true") { Path = "/" }); return FileResult(memoryStream, "documents.zip", "application/zip"); }
источник
Ответ CSL был реализован в проекте, над которым я работаю, но проблема, с которой я столкнулся, заключалась в том, что масштабирование в Azure нарушило наши загрузки файлов. Вместо этого я смог сделать это с помощью одного вызова AJAX:
СЕРВЕР
[HttpPost] public FileResult DownloadInvoice(int id1, int id2) { //necessary to get the filename in the success of the ajax callback HttpContext.Response.Headers.Add("Access-Control-Expose-Headers", "Content-Disposition"); byte[] fileBytes = _service.GetInvoice(id1, id2); string fileName = "Invoice.xlsx"; return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName); }
КЛИЕНТ (измененная версия загрузки файла Handle из сообщения ajax )
$("#downloadInvoice").on("click", function() { $("#loaderInvoice").removeClass("d-none"); var xhr = new XMLHttpRequest(); var params = []; xhr.open('POST', "@Html.Raw(Url.Action("DownloadInvoice", "Controller", new { id1 = Model.Id1, id2 = Model.Id2 }))", true); xhr.responseType = 'arraybuffer'; xhr.onload = function () { if (this.status === 200) { var filename = ""; var disposition = xhr.getResponseHeader('Content-Disposition'); if (disposition && disposition.indexOf('attachment') !== -1) { var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; var matches = filenameRegex.exec(disposition); if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, ''); } var type = xhr.getResponseHeader('Content-Type'); var blob = typeof File === 'function' ? new File([this.response], filename, { type: type }) : new Blob([this.response], { type: type }); if (typeof window.navigator.msSaveBlob !== 'undefined') { // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed." window.navigator.msSaveBlob(blob, filename); } else { var URL = window.URL || window.webkitURL; var downloadUrl = URL.createObjectURL(blob); if (filename) { // use HTML5 a[download] attribute to specify filename var a = document.createElement("a"); // safari doesn't support this yet if (typeof a.download === 'undefined') { window.location = downloadUrl; } else { a.href = downloadUrl; a.download = filename; document.body.appendChild(a); a.click(); } } else { window.location = downloadUrl; } setTimeout(function() { URL.revokeObjectURL(downloadUrl); $("#loaderInvoice").addClass("d-none"); }, 100); // cleanup } } }; xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.send($.param(params)); });
источник
$.ajax({ global: false, url: SitePath + "/User/ExportTeamMembersInExcel", "data": { 'UserName': UserName, 'RoleId': RoleId, UserIds: AppraseeId }, "type": "POST", "dataType": "JSON", "success": function (result) { debugger var bytes = new Uint8Array(result.FileContents); var blob = new Blob([bytes], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }); var link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = "myFileName.xlsx"; link.click(); }, "error": function () { alert("error"); } }) [HttpPost] public JsonResult ExportTeamMembersInExcel(string UserName, long? RoleId, string[] UserIds) { MemoryStream stream = new MemoryStream(); FileContentResult robj; DataTable data = objuserservice.ExportTeamToExcel(UserName, RoleId, UserIds); using (XLWorkbook wb = new XLWorkbook()) { wb.Worksheets.Add(data, "TeamMembers"); using (stream) { wb.SaveAs(stream); } } robj = File(stream.ToArray(), System.Net.Mime.MediaTypeNames.Application.Octet, "TeamMembers.xlsx"); return Json(robj, JsonRequestBehavior.AllowGet); }
источник
Я могу показаться довольно наивным и может вызвать серьезную критику, но вот как я это сделал
( это не
ajax
касается экспорта, но и не выполняет полную обратную передачу )Спасибо за этот пост и за этот ответ.
Создайте простой контроллер
public class HomeController : Controller { /* A demo action public ActionResult Index() { return View(model); } */ [HttpPost] public FileResult ExportData() { /* An example filter var filter = TempData["filterKeys"] as MyFilter; TempData.Keep(); */ var someList = db.GetDataFromDb(/*filter*/) // filter as an example /*May be here's the trick, I'm setting my filter in TempData["filterKeys"] in an action,(GetFilteredPartial() illustrated below) when 'searching' for the data, so do not really need ajax here..to pass my filters.. */ //Some utility to convert list to Datatable var dt = Utility.ConvertToDataTable(someList); // I am using EPPlus nuget package using (ExcelPackage pck = new ExcelPackage()) { ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Sheet1"); ws.Cells["A1"].LoadFromDataTable(dt, true); using (var memoryStream = new MemoryStream()) { pck.SaveAs(memoryStream); return File(memoryStream.ToArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "ExportFileName.xlsx"); } } } //This is just a supporting example to illustrate setting up filters .. /* [HttpPost] public PartialViewResult GetFilteredPartial(MyFilter filter) { TempData["filterKeys"] = filter; var filteredData = db.GetConcernedData(filter); var model = new MainViewModel(); model.PartialViewModel = filteredData; return PartialView("_SomePartialView", model); } */ }
А вот и просмотры ..
/*Commenting out the View code, in order to focus on the imp. code @model Models.MainViewModel @{Layout...} Some code for, say, a partial View <div id="tblSampleBody"> @Html.Partial("_SomePartialView", Model.PartialViewModel) </div> */ //The actual part.. Just **posting** this bit of data from the complete View... //Here, you are not posting the full Form..or the complete View @using (Html.BeginForm("ExportData", "Home", FormMethod.Post)) { <input type="submit" value="Export Data" /> } //... //</div> /*And you may require to pass search/filter values.. as said in the accepted answer.. That can be done while 'searching' the data.. and not while we need an export..for instance:- <script> var filterData = { SkipCount: someValue, TakeCount: 20, UserName: $("#UserName").val(), DepartmentId: $("#DepartmentId").val(), } function GetFilteredData() { $("#loader").show(); filterData.SkipCount = 0; $.ajax({ url: '@Url.Action("GetFilteredPartial","Home")', type: 'POST', dataType: "html", data: filterData, success: function (dataHTML) { if ((dataHTML === null) || (dataHTML == "")) { $("#tblSampleBody").html('<tr><td>No Data Returned</td></tr>'); $("#loader").hide(); } else { $("#tblSampleBody").html(dataHTML); $("#loader").hide(); } } }); } </script>*/
Вся суть уловки кажется в том, что мы публикуем форму ( часть Razor View), для которой мы вызываем an
Action method
, который возвращает: aFileResult
, и этоFileResult
возвращаетthe Excel File
..А для публикации значений фильтра, как сказано, ( и если вам это нужно), я отправляю запрос на публикацию другого действия, как это пытались описать ..
источник
Я использую Asp.Net WebForm и просто хочу загрузить файл со стороны сервера. Есть много статей, но я не могу найти простой ответ. Я попробовал простой способ и получил его.
Это моя проблема.
Мне нужно создать много кнопок ввода динамически во время выполнения. И я хочу добавить каждую кнопку для кнопки загрузки с указанием уникального fileNumber.
Каждую кнопку я создаю так:
fragment += "<div><input type=\"button\" value=\"Create Excel\" onclick=\"CreateExcelFile(" + fileNumber + ");\" /></div>";
Каждая кнопка вызывает этот метод ajax.
$.ajax({ type: 'POST', url: 'index.aspx/CreateExcelFile', data: jsonData, contentType: 'application/json; charset=utf-8', dataType: 'json', success: function (returnValue) { window.location = '/Reports/Downloads/' + returnValue.d; } });
Затем я написал базовый простой метод.
[WebMethod] public static string CreateExcelFile2(string fileNumber) { string filePath = string.Format(@"Form_{0}.xlsx", fileNumber); return filePath; }
Я создаю эти Form_1, Form_2, Form_3 .... И я собираюсь удалить эти старые файлы с помощью другой программы. Но если есть способ просто отправить массив байтов для загрузки файла, например, с помощью Response. Я хочу его использовать.
Надеюсь, это будет полезно для всех.
источник
В форме отправки
public ActionResult ExportXls() { var filePath=""; CommonHelper.WriteXls(filePath, "Text.xls"); } public static void WriteXls(string filePath, string targetFileName) { if (!String.IsNullOrEmpty(filePath)) { HttpResponse response = HttpContext.Current.Response; response.Clear(); response.Charset = "utf-8"; response.ContentType = "text/xls"; response.AddHeader("content-disposition", string.Format("attachment; filename={0}", targetFileName)); response.BinaryWrite(File.ReadAllBytes(filePath)); response.End(); } }
источник