Refit 使用紀錄II - 下載上傳檔案

Refit, HttpClient 應用簡化方案之一。WASM, Blazor WebAssembly 下載檔案、上傳檔案。

開發環境

平台:.NET6

骨架:Blazor WASM App

IDE:Visual Studio 2022

關鍵原碼紀錄-下載檔案-Client

_FileLab.razor
@using System.Net
@page "/file"
@attribute [Authorize]
@inject IFileHandleApi bizApi
@inject JSInterOpService jsTool

  <MudToolBar Class="gap-3">
    <MudButton Variant=Variant.Filled Color=Color.Primary OnClick=HandleDonloadFile>測試下載檔案</MudButton>
    <MudCheckBox @bind-Checked=f_testFail Color=Color.Warning>測試邏輯失敗</MudCheckBox>
  </MudToolBar>

[...略...]

@code {
  //## State
  string errMsg = string.Empty;
  bool f_loading = false;
  string message = string.Empty;
  bool f_testFail = false;

  async Task HandleDonloadFile()
  {
    // 模擬下載失敗狀況
    Guid id = f_testFail ? Guid.Empty : Guid.NewGuid();
    
    HttpContent content = await bizApi.DowloadFileAsync(id);
    byte[] fileBlob = await content.ReadAsByteArrayAsync();

    // 取得檔名
    string filenameU = content.Headers.GetValues("Content-Disposition").First().Split("filename*=UTF-8''")[1];
    string filename = Uri.UnescapeDataString(filenameU); // 解碼

    await jsTool.DownloadFileAsync(filename, fileBlob);
  }
}

關鍵原碼紀錄-上傳檔案-Client

上傳檔案用 FormData 封包處理。在 ASP.NET 名為 MultipartFormDataContent

_FileUploadLab.razor
@using System.Net
@using System.Net.Http.Headers
@using Microsoft.AspNetCore.Components.WebAssembly.Hosting
@page "/fileup"
@attribute [Authorize]
@inject IFileHandleApi bizApi
@inject JSInterOpService jsTool
@inject IWebAssemblyHostEnvironment env

  <MudFileUpload T="IReadOnlyList<IBrowserFile>" FilesChanged=HandleUploadFiles>
    <ButtonTemplate>
      <MudFab HtmlTag="label"
              Color="Color.Secondary"
              StartIcon=@Icons.Material.Filled.CloudUpload
              Label="選取上傳檔案"
              for="@context" />
    </ButtonTemplate>
  </MudFileUpload>

  @if (uploadResults.Count > 0)
  {
    <ul>
      @foreach (var item in uploadResults)
      {
        <li>
          Upload file: @item.FileName => @item.StoredFileName
        </li>
      }
    </ul>
  }

[...略...]  

@code {
  //## Resource
  const int maxAllowedFiles = int.MaxValue;
  const long maxFileSize = long.MaxValue;

  //## State
  List<string> fileNames = new();
  List<UploadResult> uploadResults = new();
  string errMsg = string.Empty;
  bool f_loading = false;

  Task HandleUploadFiles(IReadOnlyList<IBrowserFile> files) => CatchHandling(async () =>
  {
    using var content = new MultipartFormDataContent();

    fileNames.Clear();
    foreach (IBrowserFile file in files)
    {
      var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
      fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);

      fileNames.Add(file.Name);

      content.Add(
        content: fileContent,
        name: "\"files\"",
        fileName: file.Name);
    }

    var newUploadResults = await bizApi.UploadFileAsync(content);
    if (newUploadResults is not null)
    {
      uploadResults = uploadResults.Concat(newUploadResults).ToList();
    }
  });
}

若用 HttpClient 上傳檔案,語法如下:

using var content = new MultipartFormDataContent();
foreach (IBrowserFile file in files)
{
  var fileContent = new StreamContent(file.OpenReadStream(maxFileSize));
  fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType);
  content.Add(
    content: fileContent,
    name: "\"files\"",
    fileName: file.Name);
}

using HttpClient Http = new HttpClient();
Http.BaseAddress = new Uri(env.BaseAddress);
var resp = await Http.PostAsync("api/FileHandle/UploadFile", content);
var newUploadResults = await resp.Content.ReadFromJsonAsync<List<UploadResult>>();

定義下載上傳 Refit Client 介面

FileHandleApi.cs
using Refit;
using SmallEco.DTO;

namespace SmallEco.Client.RefitClient;

public interface IFileHandleApi
{
  [Post("/api/FileHandle/DownloadFile")]
  Task<HttpContent> DowloadFileAsync(Guid id);

  [Post("/api/FileHandle/UploadFile")]
  Task<List<UploadResult>> UploadFileAsync(MultipartFormDataContent content); 
}

關鍵原碼紀錄 - Server side

FileHanldeController.cs
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Net.Mime;
using Swashbuckle.AspNetCore.Annotations;
using SmallEco.DTO;
using SmallEco.Models;
using System.Net;

namespace SmallEco.Server.Controllers;

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class FileHandleController : ControllerBase
{
  //# for injection
  readonly ILogger<FileHandleController> _logger;
  readonly IWebHostEnvironment _env;

  public FileHandleController(ILogger<FileHandleController> logger, IWebHostEnvironment env)
  {
    _logger = logger;
    _env = env;
  }

  [HttpPost("[action]")]
  [SwaggerResponse(200, type: typeof(string))]
  public IActionResult Echo()
  {
    return Ok($"echo at {DateTime.Now:HH:mm:ss.}");
  }

  [HttpPost("[action]")]
  [SwaggerResponse(200, type: typeof(byte[]))]
  [SwaggerResponse(400, type: typeof(ErrMsg))]
  public IActionResult DownloadFile(Guid id)
  {
    // 模擬邏輯失敗!
    if (id == Guid.Empty)
    {
      return BadRequest(new ErrMsg("模擬邏輯失敗!"));
    }

    FileInfo fi = new FileInfo(@"Template\這是下載檔案.xlsx");

    if (System.IO.File.Exists(fi.FullName))
    {
      return File(System.IO.File.ReadAllBytes(fi.FullName), "application/octet-stream", fi.Name);
    }

    return NotFound();
  }

  [HttpPost("[action]")]
  [SwaggerResponse(200, type: typeof(List<UploadResult>))]
  [SwaggerResponse(400, type: typeof(ErrMsg))]
  public async Task<IActionResult> UploadFile(List<IFormFile> files)
  {
    //// 模擬邏輯失敗!
    //return BadRequest(new ErrMsg("我錯了!"));

     List<UploadResult> uploadResults = new();

    foreach (IFormFile file in files)
    {
      string trustedFileNameForDisplay = WebUtility.HtmlEncode(file.FileName);
      string trustedFileNameForStorage = Path.GetRandomFileName();
      string path = Path.Combine(_env.ContentRootPath, "uploads", trustedFileNameForStorage);
      ///※ 上傳檔將存入 uploads 目錄裡
      
      await using FileStream fs = new FileStream(path, FileMode.Create);
      await file.CopyToAsync(fs);

      uploadResults.Add(new UploadResult
      {
        FileName = trustedFileNameForDisplay,
        StoredFileName = trustedFileNameForStorage
      });
    }

    return Ok(uploadResults);
  }
}

完整原始碼

參考

Refit 使用紀錄

下載

上傳

Last updated