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

🔗 Angular + ASP.NET Core 全端整合

📌 前後端分離架構

┌─────────────────────────────────────────────────┐
│                    使用者瀏覽器                    │
│  ┌────────────────────────────────────────────┐  │
│  │         Angular SPA (TypeScript)           │  │
│  │  • 處理 UI 互動                             │  │
│  │  • 路由管理                                 │  │
│  │  • 狀態管理                                 │  │
│  │  • 透過 HttpClient 呼叫 API                │  │
│  └──────────────┬─────────────────────────────┘  │
│                 │ HTTP 請求 (JSON)               │
└─────────────────┼───────────────────────────────┘
                  │
┌─────────────────┼───────────────────────────────┐
│  ASP.NET Core   │ Web API                       │
│  ┌──────────────▼─────────────────────────────┐  │
│  │         Controllers / Minimal API          │  │
│  │  • 處理 HTTP 請求                           │  │
│  │  • 商業邏輯                                 │  │
│  │  • 資料驗證                                 │  │
│  │  • 存取資料庫                               │  │
│  └────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────┘

📌 HttpClient 呼叫 .NET API

Angular 端:建立 API Service

// api.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from '../environments/environment';

export interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
}

@Injectable({ providedIn: 'root' })
export class ApiService {
  // 根據環境切換 API 網址
  private baseUrl = environment.apiUrl;  // 例如 https://localhost:5001/api

  constructor(private http: HttpClient) { }

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(`${this.baseUrl}/products`);
  }

  getProduct(id: number): Observable<Product> {
    return this.http.get<Product>(`${this.baseUrl}/products/${id}`);
  }

  createProduct(product: Omit<Product, 'id'>): Observable<Product> {
    return this.http.post<Product>(`${this.baseUrl}/products`, product);
  }

  updateProduct(id: number, product: Product): Observable<void> {
    return this.http.put<void>(`${this.baseUrl}/products/${id}`, product);
  }

  deleteProduct(id: number): Observable<void> {
    return this.http.delete<void>(`${this.baseUrl}/products/${id}`);
  }
}

ASP.NET Core 端:API Controller

// ProductsController.cs
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly AppDbContext _db;

    public ProductsController(AppDbContext db) => _db = db;

    [HttpGet]
    public async Task<ActionResult<List<Product>>> GetAll()
    {
        return await _db.Products.ToListAsync();
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> Get(int id)
    {
        var product = await _db.Products.FindAsync(id);
        return product is null ? NotFound() : Ok(product);
    }

    [HttpPost]
    public async Task<ActionResult<Product>> Create(Product product)
    {
        _db.Products.Add(product);
        await _db.SaveChangesAsync();
        return CreatedAtAction(nameof(Get), new { id = product.Id }, product);
    }
}

📌 CORS 設定

前後端分離時,Angular(localhost:4200)和 .NET API(localhost:5001)在不同的 origin, 瀏覽器會阻擋跨域請求。需要在 .NET 端設定 CORS:

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// 註冊 CORS 政策
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAngular", policy =>
    {
        policy.WithOrigins("http://localhost:4200")  // Angular 開發伺服器
              .AllowAnyHeader()                       // 允許任何請求標頭
              .AllowAnyMethod()                       // 允許 GET, POST, PUT, DELETE
              .AllowCredentials();                    // 允許傳送 Cookie
    });
});

var app = builder.Build();

// 啟用 CORS(要放在 UseRouting 之後、UseAuthorization 之前)
app.UseCors("AllowAngular");

⚠️ 生產環境請勿使用 AllowAnyOrigin(),應該限定特定的前端網域。

📌 JWT Interceptor 攔截器

Angular 的 HTTP Interceptor 可以在每個請求自動加上 JWT Token:

// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authService = inject(AuthService);
  const token = authService.getToken();

  if (token) {
    // 複製請求並加上 Authorization 標頭
    const authReq = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
    return next(authReq);
  }

  return next(req);
};
// app.config.ts — 註冊攔截器
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { authInterceptor } from './auth.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([authInterceptor])),
    provideRouter(routes)
  ]
};

Auth Service

// auth.service.ts
@Injectable({ providedIn: 'root' })
export class AuthService {
  private tokenKey = 'jwt_token';

  constructor(private http: HttpClient, private router: Router) { }

  login(credentials: { email: string; password: string }): Observable<any> {
    return this.http.post<{ token: string }>('/api/auth/login', credentials).pipe(
      tap(response => {
        localStorage.setItem(this.tokenKey, response.token);
      })
    );
  }

  logout() {
    localStorage.removeItem(this.tokenKey);
    this.router.navigate(['/login']);
  }

  getToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  isLoggedIn(): boolean {
    const token = this.getToken();
    if (!token) return false;
    // 檢查 token 是否過期(解析 JWT payload)
    const payload = JSON.parse(atob(token.split('.')[1]));
    return payload.exp > Date.now() / 1000;
  }
}

📌 SignalR 即時通訊整合

SignalR 讓 Angular 和 .NET 之間建立雙向即時通訊

ASP.NET Core Hub

// ChatHub.cs
using Microsoft.AspNetCore.SignalR;

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

    public async Task JoinRoom(string room)
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, room);
        await Clients.Group(room).SendAsync("ReceiveMessage", "系統", $"{Context.ConnectionId} 加入了 {room}");
    }
}

Angular 端:SignalR Client

// chat.service.ts
import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { BehaviorSubject } from 'rxjs';

export interface ChatMessage {
  user: string;
  message: string;
  timestamp: Date;
}

@Injectable({ providedIn: 'root' })
export class ChatService {
  private hubConnection!: signalR.HubConnection;
  private messagesSubject = new BehaviorSubject<ChatMessage[]>([]);
  messages$ = this.messagesSubject.asObservable();

  connect(token: string) {
    this.hubConnection = new signalR.HubConnectionBuilder()
      .withUrl('https://localhost:5001/chatHub', {
        accessTokenFactory: () => token  // JWT 認證
      })
      .withAutomaticReconnect()  // 斷線自動重連
      .build();

    // 監聽伺服器發送的訊息
    this.hubConnection.on('ReceiveMessage', (user: string, message: string) => {
      const current = this.messagesSubject.value;
      this.messagesSubject.next([...current, {
        user, message, timestamp: new Date()
      }]);
    });

    // 啟動連線
    this.hubConnection.start()
      .then(() => console.log('SignalR 已連線'))
      .catch(err => console.error('SignalR 連線失敗', err));
  }

  sendMessage(user: string, message: string) {
    this.hubConnection.invoke('SendMessage', user, message);
  }

  joinRoom(room: string) {
    this.hubConnection.invoke('JoinRoom', room);
  }

  disconnect() {
    this.hubConnection.stop();
  }
}
// chat.component.ts
@Component({
  selector: 'app-chat',
  template: `
    <div class="chat-room">
      <div class="messages">
        <div *ngFor="let msg of messages$ | async" class="message">
          <strong>{{ msg.user }}:</strong>{{ msg.message }}
          <small>{{ msg.timestamp | date:'HH:mm:ss' }}</small>
        </div>
      </div>
      <div class="input-area">
        <input [(ngModel)]="newMessage" (keyup.enter)="send()" placeholder="輸入訊息...">
        <button (click)="send()">送出</button>
      </div>
    </div>
  `
})
export class ChatComponent implements OnInit, OnDestroy {
  messages$ = this.chatService.messages$;
  newMessage = '';

  constructor(
    private chatService: ChatService,
    private authService: AuthService
  ) { }

  ngOnInit() {
    const token = this.authService.getToken() ?? '';
    this.chatService.connect(token);
  }

  send() {
    if (this.newMessage.trim()) {
      this.chatService.sendMessage('我', this.newMessage);
      this.newMessage = '';
    }
  }

  ngOnDestroy() {
    this.chatService.disconnect();
  }
}

📌 Environment 設定

// environments/environment.ts(開發環境)
export const environment = {
  production: false,
  apiUrl: 'https://localhost:5001/api'
};

// environments/environment.prod.ts(生產環境)
export const environment = {
  production: true,
  apiUrl: 'https://myapp.azurewebsites.net/api'
};

📌 小結

  • Angular + ASP.NET Core 是企業級全端組合
  • 前後端透過 REST API(JSON)溝通
  • CORS 設定是前後端分離的必要步驟
  • JWT Interceptor 自動在每個請求加上認證 Token
  • SignalR 提供雙向即時通訊(聊天室、通知推播)
  • 使用 environment 檔案管理不同環境的 API 位址
  • TypeScript 讓前後端的資料結構可以保持一致(前端 interface 對應後端 DTO)

💡 大家的想法 · 0

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