103

MVC に大きな (っぽい) フォームがあります。

そのフォームのサブセットからのデータを含む Excel ファイルを生成できる必要があります。

注意が必要なのは、これがフォームの残りの部分に影響を与えるべきではないため、AJAX 経由で実行したいということです。関連していると思われるSOに関するいくつかの質問に出くわしましたが、答えが何を意味するのかよくわかりません。

これは私が求めているものに最も近いようです:asp-net-mvc-downloading-excel-しかし、私は応答を理解しているかどうか確信が持てず、現在は数年前です。iframe を使用してファイルのダウンロードを処理することに関する別の記事 (もう見つけられない) にも出会いましたが、これを MVC で動作させる方法がわかりません。

完全なポストバックを行っている場合、Excel ファイルは正常に返されますが、mvc で AJAX を使用して動作させることはできません。

4

14 に答える 14

231

AJAX 呼び出しを介してダウンロード用のファイルを直接返すことはできないため、別の方法として、AJAX 呼び出しを使用して関連データをサーバーにポストする方法があります。次に、サーバー側のコードを使用して Excel ファイルを作成できます (この部分が機能しているように聞こえますが、これには EPPlus または NPOI を使用することをお勧めします)。

2016 年 9 月の更新

私の元の回答(以下)は3年以上前のものだったので、AJAX経由でファイルをダウンロードするときにサーバー上にファイルを作成しなくなったので更新すると思っていましたが、元の回答は残っています。あなたの特定の要件。

私の MVC アプリケーションの一般的なシナリオは、ユーザーが構成したレポート パラメーター (日付範囲、フィルターなど) を持つ Web ページを介してレポートを作成することです。ユーザーがサーバーに送信するパラメーターを指定すると、レポートが生成され (たとえば、Excel ファイルを出力として)、結果のファイルをTempData一意の参照と共にバイト配列としてバケットに格納します。この参照は Json Result として AJAX 関数に返され、その後別のコントローラー アクションにリダイレクトされTempData、エンド ユーザーのブラウザーからデータが抽出されてダウンロードされます。

これをさらに詳しく説明するために、 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();
   }
}

必要に応じて簡単に対応できるもう 1 つの変更は、ファイルの MIME タイプを 3 番目のパラメーターとして渡すことです。これにより、1 つのコントローラー アクションがさまざまな出力ファイル形式を正しく処理できるようになります。

これにより、物理ファイルを作成してサーバーに保存する必要がなくなるため、ハウスキーピング ルーチンが不要になり、これもエンド ユーザーにとってシームレスになります。

を使用する利点は、データが読み取られるとクリアされるため、大量のファイル要求がある場合にメモリ使用量の点でより効率的であることに注意してTempDataくださいSessionTempData のベスト プラクティスTempDataを参照してください。

元の回答

AJAX 呼び出しを介してダウンロード用のファイルを直接返すことはできないため、別の方法として、AJAX 呼び出しを使用して関連データをサーバーにポストする方法があります。次に、サーバー側のコードを使用して Excel ファイルを作成できます (この部分が機能しているように聞こえますが、これには EPPlus または NPOI を使用することをお勧めします)。

サーバー上でファイルが作成されたら、AJAX 呼び出しへの戻り値としてファイルへのパス (またはファイル名のみ) を返し、JavaScriptwindow.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 メソッドにリダイレクトします。

ダウンロード アクションのサンプル コントローラー メソッドは次のようになります。

[HttpGet]
public virtual ActionResult Download(string file)
{   
  string fullPath = Path.Combine(Server.MapPath("~/MyFiles"), file);
  return File(fullPath, "application/vnd.ms-excel", file);
}
于 2013-05-21T12:53:25.373 に答える
19

私の2セント-Excelをサーバー上の物理ファイルとして保存する必要はありません-代わりに、(セッション)キャッシュに保存します。一意に生成されたキャッシュ変数 (その Excel ファイルを格納する) の名前を使用します。これは、(最初の) ajax 呼び出しの戻り値になります。この方法では、ファイル アクセスの問題、不要なファイルの管理 (削除) などに対処する必要がなく、ファイルをキャッシュに保持することで、より高速に取得できます。

于 2014-03-05T18:53:06.150 に答える
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 ビューからの 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;
    }
});
于 2017-06-30T19:20:35.143 に答える
7

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();
   }
}
于 2016-08-05T07:42:00.050 に答える
2
  $.ajax({
    global: false,
    url: SitePath + "/User/ExportTeamMembersInExcel",
    "data": { 'UserName': UserName, 'RoleId': RoleId, UserIds: AppraseeId },
    "type": "POST",
    "dataType": "JSON",
   "success": function (result) {
        
        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);
    }
于 2019-11-05T12:12:58.963 に答える
0

このスレッドは、ここで共有する独自のソリューションを作成するのに役立ちました。最初は問題なく GET ajax リクエストを使用していましたが、リクエスト URL の長さを超えてしまったため、POST に切り替えなければなりませんでした。

JavaScript は JQuery ファイル ダウンロード プラグインを使用し、2 つの連続する呼び出しで構成されます。1 つの POST (パラメーターを送信する) と 1 つの 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");
    }
于 2016-07-11T16:20:04.010 に答える
-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();
        }
    }
于 2017-01-07T06:08:25.960 に答える