Skip to content
Coding

File Result:傳送 CSV 檔案實體和測試

File Result:傳送 CSV 檔案實體和測試

目錄

  • 傳送檔案實體:CSV
  • Unit Test

傳送檔案實體

使用者有時候需要下載資料,對我而言最簡單直接的方式是在 server 端存成檔案之後,把連結回傳給前端,讓瀏覽器另開 URL 進行下載。好處是能保存使用者的下載紀錄,壞處是會占用空間,所以想找找看有沒有不需要存成檔案的方法。

傳送實體的方式很簡單:

using System.Net.Mime;
using System.Text;
using Microsoft.AspNetCore.Mvc;

namespace download_file.Controllers;

[ApiController]
[Route("api/[controller]")]
public class DownloadController : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Download()
    {
        var mediaType = MediaTypeNames.Application.Octet;
        var buffer = await CreateContent();
    
        return File(buffer, mediaType);
    }

    private async Task<byte[]> CreateContent()
    {
        using var buffer = new MemoryStream();
        await using var sw = new StreamWriter(buffer, Encoding.UTF8);
        await sw.WriteLineAsync("Key,Value");
        await sw.WriteLineAsync("Id,123");
        await sw.FlushAsync();
        await buffer.FlushAsync();
        buffer.Position = 0;
        return buffer.ToArray();
    }
}

簡單的前端頁面接取:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>下載測試</title>
</head>
<body>
<div id="app">
    <button type="button" @click.prevent="download">下載</button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/[email protected]/dist/axios.min.js"></script>
<script>
    const app = Vue.createApp({
       methods:{
           download(){
               axios({
                   method: 'post',
                   url: './api/download'
               }).then(res => {
                   const url = window.URL
                       .createObjectURL(new Blob([res.data]));
                   console.log(res)
                   const link = document.createElement('a');
                   link.href = url;
                   link.setAttribute('download', `result.csv`);
                   document.body.appendChild(link);
                   link.click();
                   document.body.removeChild(link);
                   alert('下載成功');
               })
           },
       }, 
    });
    app.mount('#app');
</script>
</body>
</html>

下載成功的 Console.log:


Unit Test

對於最近在練習單元測試的我,這是一個很棒的挑戰:如何幫檔案下載寫一個小小的測試。其中重要也最困難的應該是如何接到檔案,以及取得檔案的內容:

這邊要注意的是第 17 行中,要將 response 轉換成 FileContentResult 的部分,他會根據你原本在 Controller 中所回傳類型而有不同:

using System.Net.Mime;
using System.Text;
using download_file.Controllers;
using Microsoft.AspNetCore.Mvc;

namespace TestProject2;

public class Tests
{
    [Test]
    public async Task download_file_success()
    {
        var content = await GivenContentStream();
        var downloadController = new DownloadController();
        var response = await downloadController.Download();
        
        var result = response as FileContentResult;
        Assert.AreEqual(MediaTypeNames.Application.Octet, result.ContentType);
        Assert.AreEqual(content, result.FileContents);
    }

    private async Task<byte[]> GivenContentStream()
    {
        using var buffer = new MemoryStream();
        await using var sw = new StreamWriter(buffer, Encoding.UTF8);
        await sw.WriteLineAsync("Key,Value");
        await sw.WriteLineAsync("Id,123");
        await sw.FlushAsync();
        await buffer.FlushAsync();
        buffer.Position = 0;
        return buffer.ToArray();
    }
}
  • new File(byte[], …) 要對應 FileContentResult
  • new File(stream, …) 要對應 FileStreamResult

References