iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0
Software Development

全端工程師團隊的養成計畫系列 第 27

Day27 前後端分離的魔鬼與細節(一)

  • 分享至 

  • xImage
  •  

今天來聊聊前後端分離的細節。雖然分離後商業邏輯的核心可以完全集中在後端,前端只需要專心負責呈現,再加上透過 SPA 的方式,也能更容易更新畫面並達到動態渲染的效果。
但實際上還是有許多細節需要注意,以下就來分享一些成員們曾經踩過的雷與必須留意的地方。

後端與前端 Dto 的介接

命名策略差異

需特別注意前後端 Dto 的欄位名稱必須保持一致(包含大小寫),否則前端很容易出現欄位無法對應而導致資料為空的情況。

  • 後端 C# 傳統習慣:PascalCase
  • ASP.NET Core (3.0+) 預設:會將 C# 的 PascalCase 自動轉換為 camelCase
//C# 原生 Dto
public class OptionDto
{
  public string? Text { get; set; }
  public string? Value { get; set; }
  public OptionProps? Props { get; set; }
}

前端收到的原生 JSON 會長這樣

{
  "text": "...",
  "value": "...",
  "props": {
    "disabled": false
  }
}

解決方式 1:全域設定命名策略
如果想要關閉預設轉換,或改用其他命名策略,可以在 Program.csStartup.cs 中進行設定:

var builder = WebApplication.CreateBuilder(args);
builder.Services
  .AddControllers()
  .AddJsonOptions(opts =>
  {
    // 關閉自動 camelCase(改回跟 C# 一樣的 PascalCase)
    opts.JsonSerializerOptions.PropertyNamingPolicy = null;
  });

var app = builder.Build();
app.MapControllers();
app.Run();

解決方式 2:在屬性上標註 JsonPropertyName
如果不想調整全域命名策略,也可以在 Dto 物件的屬性上直接加入 [JsonPropertyName("SomeName")],序列化時就會以指定的名稱輸出。

/C# 原生 Dto
public class OptionDto
{
  [JsonPropertyName("Text")]
  public string? Text { get; set; }

  [JsonPropertyName("Value")]
  public string? Value { get; set; }

 [JsonPropertyName("Props ")]
  public OptionProps? Props { get; set; }
}

欄位型別與必填限制不一致

另一個常見的坑是欄位型別或必填限制不一致,在 Controller 類別上加上 [ApiController] 屬性時,框架會自動處理以下事項,並在失敗時回傳 400 Bad Request

  1. Model binding 失敗(例如 JSON 反序列化時的型別不符)
  2. Model 驗證失敗(例如 DataAnnotation 標籤驗證未通過)
    這些錯誤會直接回傳 400 Bad Request,而且程式不會進入你的 Action。
using Microsoft.AspNetCore.Mvc;
  using Core.Models.Dto;

    [ApiController]
    [Route("api/[controller]")]
    public class TestController : ControllerBase
    {   
       [HttpPost]
       public IActionResult Post([FromBody] TestDto req)
       {
        // 如果 HostName 型別不符(如傳數字),
        // 框架在這裡之前就已回 400 了。
        return Ok(new { message = "Success" });
       }
    }
 public class TestDto
  {
    [JsonPropertyName("HostName")]
    public string HostName { get; set; }

  }

假設前端不小心傳了錯誤型別(int 而不是 string):

{ "HostName": 123 } 

這種情況在初期開發很常見,尤其是欄位型別與必填限制不一致時,容易卡關。

HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
  "type": "...",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "req.HostName": [
      "The JSON value could not be converted to System.String. Path: $.HostName ..."
    ]
  }
}

Ending Remark

前後端分離能帶來清晰的職責劃分與靈活的畫面更新,但細節處理不能掉以輕心。

命名策略:保持前後端 Dto 欄位一致,避免大小寫或格式差異。
型別驗證:善用 [ApiController] 與 DataAnnotation,自動攔截錯誤。
掌握這些原則,就能減少踩雷次數,讓開發更順暢,整理以下常見的錯誤與對應結果

錯誤情境 範例請求 / 狀況 後端處理結果
欄位名稱不一致 前端傳 { "text": "AAA" } 後端期待 Text 前端無法正確綁定 → 資料為空或 null
大小寫不符 前端傳 { "Hostname": "ABC" } 後端期待 HostName 無法對應 → 值為 null
型別錯誤 前端傳 { "HostName": 123 } 後端期待 string ApiController 自動攔截 → 回 400 Bad Request
必填欄位缺漏 前端傳 { },缺少 HostName DataAnnotation 驗證失敗 → 回 400 Bad Request
傳遞額外欄位(後端無此欄位) 前端傳 { "HostName": "ABC", "Ip": "127.0.0.1" } 額外欄位會被忽略,僅綁定已定義的屬性
全部正確 前端傳 { "HostName": "ABC" } 成功綁定 → 進入 Controller 邏輯

上一篇
Day26 前端偵錯方式與建議
下一篇
Day28 前後端分離的魔鬼與細節(二)
系列文
全端工程師團隊的養成計畫30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言