ดาวน์โหลดไฟล์ Excel ผ่าน AJAX MVC


93

ฉันมีรูปแบบ (ish) ขนาดใหญ่ใน MVC

ฉันต้องสามารถสร้างไฟล์ excel ที่มีข้อมูลจากชุดย่อยของแบบฟอร์มนั้นได้

สิ่งที่ยุ่งยากคือสิ่งนี้ไม่ควรส่งผลกระทบต่อส่วนที่เหลือของฟอร์มดังนั้นฉันจึงต้องการทำผ่าน AJAX ฉันเจอคำถามสองสามข้อเกี่ยวกับ SO ที่ดูเหมือนจะเกี่ยวข้อง แต่ฉันไม่สามารถสรุปได้ว่าคำตอบหมายถึงอะไร

อันนี้ดูเหมือนจะใกล้เคียงที่สุดกับสิ่งที่ฉันต้องการ: asp-net-mvc-download-excel - แต่ฉันไม่แน่ใจว่าฉันเข้าใจคำตอบและตอนนี้มันอายุได้สองสามปีแล้ว ฉันเจอบทความอื่น (หาไม่ได้อีกแล้ว) เกี่ยวกับการใช้ iframe เพื่อจัดการกับการดาวน์โหลดไฟล์ แต่ฉันไม่แน่ใจว่าจะทำงานกับ MVC ได้อย่างไร

ไฟล์ excel ของฉันจะคืนค่าได้ดีถ้าฉันกำลังโพสต์แบบเต็ม แต่ฉันไม่สามารถใช้งานกับ AJAX ใน mvc ได้

คำตอบ:


218

คุณไม่สามารถส่งคืนไฟล์สำหรับดาวน์โหลดโดยตรงผ่านการโทร AJAX ได้อีกวิธีหนึ่งคือการใช้การโทร AJAX เพื่อโพสต์ข้อมูลที่เกี่ยวข้องไปยังเซิร์ฟเวอร์ของคุณ จากนั้นคุณสามารถใช้รหัสฝั่งเซิร์ฟเวอร์เพื่อสร้างไฟล์ Excel (ฉันขอแนะนำให้ใช้ EPPlus หรือ NPOI สำหรับสิ่งนี้แม้ว่าจะฟังดูเหมือนว่าคุณมีส่วนนี้ทำงานอยู่ก็ตาม)

อัพเดทกันยายน 2559

คำตอบเดิมของฉัน (ด้านล่าง) มีอายุมากกว่า 3 ปีฉันจึงคิดว่าฉันจะอัปเดตเนื่องจากฉันไม่ได้สร้างไฟล์บนเซิร์ฟเวอร์อีกต่อไปเมื่อดาวน์โหลดไฟล์ผ่าน AJAX อย่างไรก็ตามฉันได้ทิ้งคำตอบเดิมไว้เนื่องจากอาจยังใช้งานได้อยู่บ้างขึ้นอยู่กับ ความต้องการเฉพาะของคุณ

สถานการณ์ทั่วไปในแอปพลิเคชัน MVC ของฉันคือการรายงานผ่านหน้าเว็บที่มีผู้ใช้กำหนดค่าพารามิเตอร์รายงาน (ช่วงวันที่ตัวกรอง ฯลฯ ) เมื่อผู้ใช้ระบุพารามิเตอร์ที่พวกเขาโพสต์ไปยังเซิร์ฟเวอร์รายงานจะถูกสร้างขึ้น (เช่นไฟล์ Excel เป็นเอาต์พุต) จากนั้นฉันจะจัดเก็บไฟล์ผลลัพธ์เป็นอาร์เรย์ไบต์ในที่TempDataเก็บข้อมูลโดยมีการอ้างอิงเฉพาะ การอ้างอิงนี้ถูกส่งกลับเป็น Json Result ไปยังฟังก์ชัน AJAX ของฉันซึ่งต่อมาจะเปลี่ยนเส้นทางไปยังแอ็คชันคอนโทรลเลอร์แยกต่างหากเพื่อดึงข้อมูลจากTempDataและดาวน์โหลดไปยังเบราว์เซอร์ของผู้ใช้ปลายทาง

หากต้องการให้รายละเอียดเพิ่มเติมสมมติว่าคุณมีมุมมอง MVC ที่มีรูปแบบที่เชื่อมโยงกับคลาส Model ให้เรียก Model ReportVMดูที่มีรูปแบบที่ถูกผูกไว้กับชั้นรุ่นช่วยให้เรียกรุ่น

ขั้นแรกต้องมีการดำเนินการของคอนโทรลเลอร์เพื่อรับโมเดลที่โพสต์ตัวอย่างจะเป็น:

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

}

การเรียก 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{
        // Problem - Log the error, generate a blank file,
        //           redirect to another controller action - whatever fits with your application
        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คำสั่งเปลี่ยนเส้นทางไปที่วิธีการควบคุม / การกระทำที่จริงส่งไฟล์ของคุณสำหรับการดาวน์โหลด

วิธีการควบคุมตัวอย่างสำหรับการดำเนินการดาวน์โหลดคือ:

[HttpGet]
public virtual ActionResult Download(string file)
{   
  string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
  return File(fullPath, "application/vnd.ms-excel", file);
}

3
ดูเหมือนว่าเป็นตัวเลือกที่ดี แต่ก่อนที่จะดำเนินการต่อไม่มีทางเลือกอื่นใดที่ไม่เกี่ยวข้องกับการสร้างไฟล์บนเซิร์ฟเวอร์ก่อน?
Valuk

4
ไม่ใช่ว่าฉันรู้ - วิธีนี้ฉันเคยประสบความสำเร็จหลายครั้ง จากมุมมองของผู้ใช้มันเป็นไปอย่างราบรื่นสิ่งเดียวที่ควรคำนึงถึงคือคุณจะต้องมีกิจวัตรการดูแลทำความสะอาดเพื่อจัดระเบียบไฟล์ที่สร้างขึ้นเนื่องจากไฟล์เหล่านี้จะถูกติดตั้งเมื่อเวลาผ่านไป
connectedsoftware

7
การสร้างจุดสิ้นสุด '/ ดาวน์โหลด? file = ... ' SCREAMS มีความเสี่ยงด้านความปลอดภัยอย่างมาก - ฉันไม่ใช่ผู้เชี่ยวชาญด้านความปลอดภัย แต่ฉันคิดว่าคุณต้องการเพิ่มการตรวจสอบผู้ใช้การสุขาภิบาลการป้อนข้อมูล [ValidateAntiForgeryToken] ของ MVC และพูดถึงความปลอดภัยอื่น ๆ ที่ดีที่สุด - การปฏิบัติตามคำตอบนี้
Jimmy

2
@CSL ฉันมักจะได้รับข้อผิดพลาด 0x800a03f6 - ข้อผิดพลาดรันไทม์ของ JavaScript: อักขระไม่ถูกต้องในการตอบสนอง var = JSON.parse (ข้อมูล);
Standage

2
เยี่ยมทำไมคุณไม่ใส่คำตอบเก่าที่ด้านล่าง? และคำตอบใหม่อยู่ด้านบนเพื่อให้ทุกคนไม่เสียเวลา
goamn

19

2 เซ็นต์ของฉัน - คุณไม่จำเป็นต้องเก็บ excel เป็นไฟล์ทางกายภาพบนเซิร์ฟเวอร์ แต่ให้เก็บไว้ในแคช (เซสชัน) ใช้ชื่อที่สร้างขึ้นโดยเฉพาะสำหรับตัวแปร Cache ของคุณ (ที่เก็บไฟล์ excel นั้น) - นี่จะเป็นการกลับมาของการโทร ajax (เริ่มต้น) ของคุณ วิธีนี้ทำให้คุณไม่ต้องจัดการกับปัญหาการเข้าถึงไฟล์การจัดการ (การลบ) ไฟล์เมื่อไม่จำเป็น ฯลฯ และการมีไฟล์ในแคชจะช่วยให้ดึงข้อมูลได้เร็วขึ้น


1
คุณจะทำอย่างนั้นได้อย่างไร? ฟังดูน่าสนใจ.
Natalia

2
ตัวอย่างจะดี (ฉันหมายถึงวิธีการจัดเก็บในแคชไม่ใช่การสร้างไฟล์ excel)
ธ เดช

สิ่งนี้สามารถปรับขนาดได้แค่ไหน? หากผู้ใช้กำลังดาวน์โหลดรายงานขนาดใหญ่หลายฉบับ?
Zapnologica

หากคุณอยู่บน Azure เซสชันจะใช้งานได้จนกว่าคุณจะปิด ARRAffinity
JeeShen Lee

16

เมื่อเร็ว ๆ นี้ฉันสามารถทำสิ่งนี้ได้สำเร็จใน 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.S Spreadsheet ) เพื่อสร้างไฟล์ Excel และมีตัวเลือก Save to Stream ดังที่กล่าวมามีหลายวิธีในการสร้างไฟล์ 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 เบราว์เซอร์จะแจ้งให้ดาวน์โหลดไฟล์และไม่มีการนำทางจริงเกิดขึ้น


1
ฉันมีแนวทางที่คล้ายกัน ปัญหาคือคุณไม่รู้ว่าเมื่อการดาวน์โหลดสิ้นสุดลงเพื่อให้คุณสามารถหยุดตัวโหลดล่วงหน้าได้ :)
CătălinRădoi

8

ขั้นแรกให้สร้างแอ็คชันคอนโทรลเลอร์ที่จะสร้างไฟล์ 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 = "" });
}

จากนั้นสร้างการดำเนินการดาวน์โหลด

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

และในที่สุดก็โทรออกจากมุมมอง 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;
    }
});

7

ฉันใช้โซลูชันที่โพสต์โดย CSL แต่ฉันขอแนะนำให้คุณอย่าเก็บข้อมูลไฟล์ในเซสชันในระหว่างเซสชันทั้งหมด โดยใช้ TempData ข้อมูลไฟล์จะถูกลบออกโดยอัตโนมัติหลังจากการร้องขอถัดไป (ซึ่งเป็นคำขอ GET สำหรับไฟล์) คุณยังสามารถจัดการการลบข้อมูลไฟล์ในเซสชันในการดาวน์โหลด

เซสชันอาจใช้หน่วยความจำ / พื้นที่มากขึ้นอยู่กับพื้นที่จัดเก็บ SessionState และจำนวนไฟล์ที่ส่งออกในระหว่างเซสชันและหากคุณมีผู้ใช้จำนวนมาก

ฉันได้อัปเดตโค้ดฝั่งผู้ส่งจาก 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();
   }
}

@Nichlas ฉันเริ่มใช้ TempData ด้วยคำตอบของคุณแจ้งให้ฉันอัปเดตของฉันเพื่อสะท้อนสิ่งนี้!
connectedsoftware

5

ใช้ 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);
    }


ใน AJAX CALL Success Block, success: function (Rdata) {debugger; var bytes = Uint8Array ใหม่ (Rdata.FileContents); var blob = Blob ใหม่ ([ไบต์], {type: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"}); var link = document.createElement ('a'); link.href = window.URL.createObjectURL (หยด); link.download = "myFileName.xlsx"; link.click (); },
GVKRAO

บางคนใช้การดาวน์โหลดไฟล์ Excel ในลิงค์ด้านบนมันใช้งานได้เฉพาะกับ @ html Beginform () จากนั้นหลังจากการเปลี่ยนแปลงเล็กน้อยจำเป็นต้องใช้รหัสนั้นสำหรับ AJAX call Success Block โปรดตรวจสอบมันใช้งานได้ดีใน AJAX CALL
GVKRAO

3
$ .ajax ({
                พิมพ์: "GET",
                url: "/ หน้าแรก / Downloadexcel /",
                contentType: "application / json; charset = utf-8",
                ข้อมูล: null,
                ความสำเร็จ: function (Rdata) {
                    ดีบักเกอร์;
                    var bytes = Uint8Array ใหม่ (Rdata.FileContents); 
                    var blob = Blob ใหม่ ([ไบต์], {type: "application / vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
                    var link = document.createElement ('a');
                    link.href = window.URL.createObjectURL (หยด);
                    link.download = "myFileName.xlsx";
                    link.click ();
                },
                error: function (err) {

                }

            });

1

คำตอบที่ยอมรับไม่ค่อยได้ผลสำหรับฉันเพราะฉันได้ผลลัพธ์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();

0

ชุดข้อความนี้ช่วยฉันสร้างโซลูชันของตัวเองที่ฉันจะแบ่งปันที่นี่ ฉันใช้คำขอ GET ajax ในตอนแรกโดยไม่มีปัญหา แต่มันมาถึงจุดที่ความยาวของ URL คำขอเกินดังนั้นฉันจึงต้องเปลี่ยนเป็น POST

javascript ใช้ปลั๊กอินดาวน์โหลดไฟล์ JQuery และประกอบด้วย 2 การโทรที่สำเร็จ หนึ่ง 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");
    }

0

คำตอบของ 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 post )

$("#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));
});

0
  $.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);
    }

ไม่สามารถเปิดไฟล์ได้ excel เพิ่งเปิดและไม่ได้ปิดตัวเองฉันยังเพิ่ม stream.close () ก่อน robj แต่ไม่ทำงาน
dawncode

0

ฉันอาจฟังดูไร้เดียงสาและอาจดึงดูดคำวิจารณ์ได้มาก แต่นี่คือวิธีที่ฉันทำ
( ไม่เกี่ยวข้องกับ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>*/

จุดรวมของเคล็ดลับดูเหมือนว่าเรากำลังโพสต์แบบฟอร์ม ( ส่วนหนึ่งของมุมมองมีดโกน) ที่เรา เรียกใช้Action methodซึ่งส่งคืน: a FileResultและสิ่งนี้FileResultจะส่งกลับ the Excel File..
และสำหรับการโพสต์ค่าตัวกรองตามที่กล่าวไว้ ( และหากคุณต้องการ) ฉันกำลังตั้งกระทู้ขอให้ดำเนินการอื่นตามที่พยายามอธิบาย ..


-1

ฉันใช้ 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 ฉันอยากจะใช้มัน

ฉันหวังว่านี่จะเป็นประโยชน์สำหรับทุกคน


-1

ในการส่งแบบฟอร์ม

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();
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.