[c#] AJAX MVC를 통해 Excel 파일 다운로드

MVC에 큰 (ish) 형태가 있습니다.

해당 양식의 하위 집합에서 데이터를 포함하는 Excel 파일을 생성 할 수 있어야합니다.

까다로운 부분은 이것이 양식의 나머지 부분에 영향을주지 않아야한다는 것이므로 AJAX를 통해 수행하고 싶습니다. 나는 관련된 것처럼 보이는 몇 가지 질문을 보았지만 대답이 무엇을 의미하는지 잘 모르겠습니다.

이것은 내가 추구하는 것에 가장 가까운 것 같습니다 : asp-net-mvc-downloading-excel- 그러나 나는 응답을 이해하지 못하며 이제 몇 년이되었습니다. 또한 iframe을 사용하여 파일 다운로드를 처리하는 방법에 대한 다른 기사 (더 이상 찾을 수 없음)를 발견했지만 MVC에서이 작업을 수행하는 방법을 모르겠습니다.

전체 게시물을 다시 수행하는 경우 내 Excel 파일이 제대로 반환되지만 mvc에서 AJAX로 작업 할 수 없습니다.



답변

AJAX 호출을 통해 다운로드 할 파일을 직접 반환 할 수 없으므로 다른 방법은 AJAX 호출을 사용하여 관련 데이터를 서버에 게시하는 것입니다. 그런 다음 서버 측 코드를 사용하여 Excel 파일을 만들 수 있습니다 (이 부분이 작동하는 것처럼 들리지만 EPPlus 또는 NPOI를 사용하는 것이 좋습니다).

2016 년 9 월 업데이트

내 원래 답변 (아래)은 3 년 이상되었으므로 AJAX를 통해 파일을 다운로드 할 때 더 이상 서버에 파일을 만들지 않기 때문에 업데이트 할 것이라고 생각했지만 여전히 사용에 따라 원래 답변을 남겼습니다. 귀하의 특정 요구 사항.

내 MVC 애플리케이션의 일반적인 시나리오는 사용자가 구성한 보고서 매개 변수 (날짜 범위, 필터 등)가있는 웹 페이지를 통해보고하는 것입니다. 사용자가 서버에 게시하는 매개 변수를 지정하면 보고서가 생성되고 (예 : Excel 파일을 출력으로) 결과 파일을 TempData고유 한 참조 가있는 버킷에 바이트 배열로 저장합니다 . 이 참조는 TempData최종 사용자 브라우저 에서 데이터를 추출 하고 다운로드하기 위해 별도의 컨트롤러 작업으로 리디렉션되는 내 AJAX 함수에 Json 결과로 다시 전달됩니다 .

좀 더 자세히 설명하기 위해 Model 클래스에 바인딩 된 폼이있는 MVC 뷰가 있다고 가정하고 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" }
   };

}

내 MVC 양식을 위의 컨트롤러에 게시하고 응답을받는 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(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것보다 사용의 장점은 데이터가 지워 지므로 많은 양의 파일 요청이있는 경우 메모리 사용량 측면에서 더 효율적이라는 것입니다. TempData 모범 사례를 참조하십시오 .SessionTempData

원래 답변

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 파일을 생성하는 Controller / Action 메서드입니다.
  • 데이터 매개 변수에는 양식에서 추출되는 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);
}


답변

내 2 센트-엑셀을 서버에 물리적 파일로 저장할 필요가 없습니다. 대신 (세션) 캐시에 저장하십시오. 캐시 변수 (해당 엑셀 파일을 저장하는)에 대해 고유하게 생성 된 이름을 사용하십시오. 이것은 (초기) 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 = "" });
}

그런 다음 다운로드 작업을 만듭니다.

[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보기에서 ajax 호출

//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 스토리지와 세션 중에 내보내는 파일 수 및 사용자가 많은 경우에 따라 많은 메모리 / 공간을 소비 할 수 있습니다.

대신 TempData를 사용하도록 CSL에서 서버 측 코드를 업데이트했습니다.

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


답변

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 ({
                유형 : "GET",
                URL : "/ Home / Downloadexcel /",
                contentType : "application / json; charset = utf-8",
                데이터 : null,
                성공 : function (Rdata) {
                    디버거;
                    var bytes = new Uint8Array (Rdata.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 ();
                },
                오류 : function (err) {

                }

            });