☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
react 進階

🔗 React + ASP.NET Core 全端整合

📌 前後端分離架構

前後端分離架構:

  React (前端)              ASP.NET Core (後端)
  ┌──────────────┐         ┌──────────────────┐
  │  瀏覽器 SPA    │ ←JSON→ │  Web API          │
  │  localhost:5173│         │  localhost:5000   │
  │  JSX → UI     │         │  Controller/      │
  │  React Router │         │  Minimal API      │
  │  Zustand/Redux│         │  Entity Framework │
  └──────────────┘         └──────────────────┘

⚠️ React 前端和 .NET 後端是完全獨立的應用, 透過 HTTP API(JSON 格式)溝通。這就是為什麼 React 不是原生 JS—— 它需要編譯打包,而 .NET 負責資料處理和商業邏輯。


🌐 Fetch / Axios 呼叫 .NET API

使用 Fetch(原生)

// hooks/useApi.js
import { useState, useEffect } from 'react';

const API_BASE = import.meta.env.VITE_API_URL || 'https://localhost:5001';

export function useFetch(endpoint) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();  // 用於取消請求

    fetch(`${API_BASE}${endpoint}`, {
      signal: controller.signal,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${localStorage.getItem('token')}`,
      },
    })
      .then(res => {
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return res.json();
      })
      .then(setData)
      .catch(err => {
        if (err.name !== 'AbortError') setError(err);
      })
      .finally(() => setLoading(false));

    return () => controller.abort();   // 元件卸載時取消請求
  }, [endpoint]);

  return { data, loading, error };
}

// 使用
function ChapterList() {
  const { data: chapters, loading, error } = useFetch('/api/chapters');

  if (loading) return <p>載入中...</p>;
  if (error) return <p>錯誤:{error.message}</p>;

  return (
    <ul>
      {chapters.map(ch => <li key={ch.id}>{ch.title}</li>)}
    </ul>
  );
}

使用 Axios(更方便)

npm install axios
// services/api.js
import axios from 'axios';

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'https://localhost:5001',
  timeout: 10000,
});

// 請求攔截器:自動附加 Token
api.interceptors.request.use(config => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// 回應攔截器:統一錯誤處理
api.interceptors.response.use(
  response => response,
  error => {
    if (error.response?.status === 401) {
      localStorage.removeItem('token');
      window.location.href = '/login';   // Token 過期,導向登入
    }
    return Promise.reject(error);
  }
);

export default api;
// 使用 axios 實例
import api from '../services/api';

async function createChapter(data) {
  const response = await api.post('/api/chapters', data);
  return response.data;
}

🔒 CORS 設定(ASP.NET Core 端)

// Program.cs — 設定 CORS
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("ReactApp", policy =>
    {
        policy.WithOrigins("http://localhost:5173",      // Vite 開發伺服器
                           "https://your-app.vercel.app")  // 部署網址
              .AllowAnyHeader()
              .AllowAnyMethod()
              .AllowCredentials();     // 如果要帶 Cookie
    });
});

var app = builder.Build();
app.UseCors("ReactApp");             // 在 UseRouting 之後

⚠️ 什麼是 CORS? 瀏覽器安全策略:前端(localhost:5173)不能直接呼叫不同網域的 API(localhost:5001)。 CORS 設定告訴瀏覽器「這個前端是被允許的」。


🔑 JWT 認證流程

JWT 認證流程:
1. 使用者輸入帳密 → React 送 POST /api/auth/login
2. .NET 驗證成功 → 回傳 JWT Token
3. React 存到 localStorage
4. 之後每次 API 請求都帶上 Authorization: Bearer {token}
5. .NET 驗證 Token 是否有效

React 端

// context/AuthContext.jsx
import { createContext, useContext, useState, useEffect } from 'react';
import api from '../services/api';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  // 啟動時檢查 Token
  useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      api.get('/api/auth/me')
        .then(res => setUser(res.data))
        .catch(() => localStorage.removeItem('token'))
        .finally(() => setLoading(false));
    } else {
      setLoading(false);
    }
  }, []);

  const login = async (username, password) => {
    const res = await api.post('/api/auth/login', { username, password });
    const { token, user } = res.data;
    localStorage.setItem('token', token);
    setUser(user);
    return user;
  };

  const logout = () => {
    localStorage.removeItem('token');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

ASP.NET Core 端

// AuthController.cs
[ApiController]
[Route("api/auth")]
public class AuthController : ControllerBase
{
    [HttpPost("login")]
    public IActionResult Login([FromBody] LoginDto dto)
    {
        // 驗證帳密(簡化範例)
        var user = _db.Users.FirstOrDefault(
            u => u.Username == dto.Username);

        if (user == null || !VerifyPassword(dto.Password, user.PasswordHash))
            return Unauthorized("帳號或密碼錯誤");

        // 產生 JWT Token
        var token = GenerateJwtToken(user);
        return Ok(new { token, user = new { user.Id, user.Username } });
    }

    [HttpGet("me")]
    [Authorize]     // 需要有效 Token
    public IActionResult GetMe()
    {
        var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        var user = _db.Users.Find(int.Parse(userId));
        return Ok(new { user.Id, user.Username });
    }
}

⚡ SignalR 即時通訊

SignalR 讓 React 和 .NET 之間建立 WebSocket 連線,實現即時推播。

ASP.NET Core 端

// Hubs/ChatHub.cs
public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        // 廣播給所有連線的客戶端
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }

    public async Task JoinRoom(string roomName)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, roomName);
    }
}

// Program.cs
builder.Services.AddSignalR();
app.MapHub<ChatHub>("/chatHub");

React 端

npm install @microsoft/signalr
// hooks/useSignalR.js
import { useEffect, useRef, useState } from 'react';
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';

export function useSignalR() {
  const [messages, setMessages] = useState([]);
  const connectionRef = useRef(null);

  useEffect(() => {
    const connection = new HubConnectionBuilder()
      .withUrl('https://localhost:5001/chatHub', {
        accessTokenFactory: () => localStorage.getItem('token'),
      })
      .withAutomaticReconnect()
      .configureLogging(LogLevel.Information)
      .build();

    // 監聽伺服器推播
    connection.on('ReceiveMessage', (user, message) => {
      setMessages(prev => [...prev, { user, message, time: new Date() }]);
    });

    connection.start()
      .then(() => console.log('SignalR 已連線'))
      .catch(err => console.error('SignalR 連線失敗', err));

    connectionRef.current = connection;

    return () => {
      connection.stop();
    };
  }, []);

  const sendMessage = async (user, message) => {
    await connectionRef.current?.invoke('SendMessage', user, message);
  };

  return { messages, sendMessage };
}
// components/Chat.jsx
function Chat() {
  const { messages, sendMessage } = useSignalR();
  const [input, setInput] = useState('');
  const { user } = useAuth();

  const handleSend = () => {
    if (input.trim()) {
      sendMessage(user.username, input);
      setInput('');
    }
  };

  return (
    <div>
      <div style={{ height: '400px', overflowY: 'auto' }}>
        {messages.map((msg, i) => (
          <div key={i}>
            <strong>{msg.user}</strong>: {msg.message}
            <small>{msg.time.toLocaleTimeString()}</small>
          </div>
        ))}
      </div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === 'Enter' && handleSend()}
      />
      <button onClick={handleSend}>送出</button>
    </div>
  );
}

🛠️ 完整範例:React + .NET 待辦應用結構

專案結構:
├── backend/                        # ASP.NET Core
│   ├── Controllers/
│   │   ├── AuthController.cs       # 認證 API
│   │   └── TodoController.cs       # CRUD API
│   ├── Models/
│   │   └── Todo.cs
│   ├── Data/
│   │   └── AppDbContext.cs
│   └── Program.cs                  # CORS + SignalR + JWT 設定
│
├── frontend/                       # React (Vite)
│   ├── src/
│   │   ├── components/
│   │   │   ├── TodoList.jsx
│   │   │   └── Chat.jsx
│   │   ├── context/
│   │   │   └── AuthContext.jsx
│   │   ├── hooks/
│   │   │   ├── useApi.js
│   │   │   └── useSignalR.js
│   │   ├── services/
│   │   │   └── api.js              # Axios 實例
│   │   ├── pages/
│   │   │   ├── Login.jsx
│   │   │   └── Dashboard.jsx
│   │   ├── App.jsx                 # 路由設定
│   │   └── main.jsx                # 進入點
│   └── package.json

✅ 本章重點

主題 技術
API 呼叫 Fetch / Axios + 攔截器
CORS .NET 端 AddCors 設定
JWT 認證 login → 存 Token → 每次請求帶上
即時通訊 SignalR Hub + @microsoft/signalr
專案結構 frontend/ + backend/ 分離

💡 React + ASP.NET Core 是業界常見的全端組合, React 負責 UI 和使用者互動,.NET 負責 API、資料庫和商業邏輯。 兩者透過 JSON API 溝通,各自獨立開發和部署。

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊