這篇文章主要在練習如何整合前後端網站,在開發時期階段的體驗及發佈出去的方法,最終將網站容器化的一個範例
Intro#
這篇文章主要是針對 Youtube:AspNetCore Api + Vitejs + Vuejs Setup for Dev & Prod - Anton Wieslander所做的練習及心得,建議有時間的人可以花 20 分鐘看一下
example repos#
- LAB-vite: 因為喜歡在 rider 開發,因此新建一個方案檔將前後端專案都拉進來
- Lab-vite-vue-dotnetcore: 多了 dockerfile 的部分,其餘大同小異
網站開發框架的選擇#
webpack 很好沒錯,但專案變大後,不管是在產生 production build 還是在開發時期的編譯時間,都需要很久的時間才能運作,如果再加上開發較複雜的專案,可能同時主機就會需要開好幾個 IDE,有時為了做 PoC 還會需要弄個 docker 架架資料庫
在這樣的背景之下如果還要等待 webpack 是非常難以忍受的事情;同理,在先前就已經了解到了 vue2 與 vue3 的差異,並且希望能夠練習一下 vue3 所帶來的開發體驗,而我又是慣常開發 C# 的人,採用.NET6 mvc也是很自然的一件事情,因此就有了下列的技術架構
- 前端採用
vite.js+vue3.js - 後端採用
.NET6 mvc
vite.js 能幹嘛#
- 可以拿來打包程式碼
- 開發模式的時候,速度挺快 (利用 瀏覽器支援的Native ESM進行運作)
- 正式環境的時候,打包程式碼跟
webpack一樣,都是將所需要的模組全部包在一起 (為了避免因依賴鍊過長,造成瀏覽器持續的請求資源,這當中前端 JS 是停在那邊等待的,畫面會卡)
vue3.js 的好處#
相比於 vue2 的差異,最主要的感受應該是提供了 Composition API 的寫法,還有效能的提升,差異的部分網路上很多,我覺得這一篇vue3 對比 Vue2.x 差異性、注意點、整體梳理,與 React hook 比又如何挺不錯,專業的評比還是交給專業的來,我轉發一下就好
對我而言,最主要的就是一系列的效能優化改善,以及最直觀的語法上提供了 Composition API 所帶來的優點,也就是關注點分離,使邏輯、資料可以集中存放維護,也便於 reUse
前後端分離開發#
因為實際上佈署出去的環境,也會影響到開發網站的選擇,這邊因為只是練習,所以打算是將前後端 deploy 到同一個站台發佈出去,之後可以再包裝成 docker 直接容器化,感覺想法還行,那麼就是實作來驗證看看開發體驗順不順,因為是前後端分離專案,兩邊開發,之所以這麼考量,主要還是覺得前端後端其實都有其專業程度,一個專案內如果前後端包在一起,那勢必就會需要開始學習自己不熟悉的部分,有些人喜歡這樣,但也有的人不喜歡,認為為甚麼開發前端,我還要去了解後端的 razor 頁面、生命週期阿啥的東西;或者是反過來開發後端,我還需要去學習 webpack、npm、指令列那些的東西,不能就用 VS2022 IDE 介面上點一點就好了嗎?
我覺得有興趣的話,東西都放在同一個 sln 內,想看可以去看;如果不感興趣,直接開自己的頁面,前端就確定好假資料的 API 自己刻;後端因為也只提供前端呼叫,所以確認好 API 介面後自己單元測試做一做,都挺好的,反正不管怎麼說,前後端始終都有自己的技術堆疊,切割開來會是比較好的選擇
但是兩個專案跑起來兩個站台,假設前端 3000後端5000好了,前端跟後端索取資料,很自然地就會需要 fetch 給網址,像是 http://localhost:5000/Api/Test,但是最終又是會放在一起 deploy 出去的,所以 production 出去的時候,網址應該是打/Api/Test,那有沒有辦法可以解決呢
應該有吧,說不定可以弄一個設定檔之類的東西,看 deploy 出去是 development 還是 production 來切換吧?嗯,可能吧,但想想就有點累,有沒有更方便一點的?
Microsoft.AspNetCore.SpaServices.Extensions#
還真的有,這東西就叫做 Microsoft.AspNetCore.SpaServices.Extensions,但是必須要抱歉的是,我對它不熟悉,而且似乎網上的介紹很少,但藉由一些練習我大概摸索出來的用途如下
- 它能夠將發給後端的請求,代理轉發到 SPA 那邊去,所以我們必須要再判斷當前為開發時期的時候才這樣做,因為在正式環境,東西都被我們佈署在一起了,就不需要做代理這件事情
- 它能夠指定網站的
RootPath,所以當佈署在一起的時候,網站吃的首頁就是該路徑
LAB:建立前後端網站#
準備工作大概完成了,底下就列出練習的步驟及重點
建立前端網站#
建立目錄並利用官方 Getting Started的指令建立一個快速的啟動專案
mkdir Lab-vite-vue-dotnetcore
cd Lab-vite-vue-dotnetcore
npm create vite@latest接著會要求輸入專案名稱,選擇技術框架、採用的語言
完成後可以進入該目錄先安裝套件,並跑一次看看網站是否正常啟動
cd front-end
npm install
npm run dev
建立後端網站#
回到上層目錄接著開始透過 dotnet cli 建立新的 mvc 專案,完成後一樣測試看看網站是否正常啟動
dotnet new web -n back-end
cd back-end
dotnet run
LAB:前端索取資料#
這邊我們練習從 Vue Component 裡面去撈後端資料,重點在 fetch 之後,要透過 reactive 包裝,這樣資料才會響應式的隨著變動
底下就列出關鍵的部分
後端給予資料#
做一個 API 提供前端呼叫,當路由符合 /api/test就回應一個字串,因此做一個測試的 TestController
// Controller/TestController.cs
using Microsoft.AspNetCore.Mvc;
namespace back_end.Controller;
[Route("/api/[controller]")]
public class TestController : Microsoft.AspNetCore.Mvc.Controller
{
public IActionResult Index()
{
return Ok("Test Result");
}
}當然也因為測試環境是兩個站台,所以後端為了測試要允許CORS
// program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseCors(b => b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
}
app.UseRouting();
app.UseEndpoints(e => e.MapDefaultControllerRoute());
app.Run();
前端取得資料#
在前端 SFC 透過 fetch 拿資料後將結果指派回資料,經由 vue 渲染於畫面上,後端路由:https://localhost:7156/api/Test
<script setup>
// /src/components/HelloWorld.vue
import { reactive, ref } from "vue"
defineProps({
msg: String,
})
const count = ref(0)
// === new code begin
const state = reactive({
message: "empty",
})
fetch("https://localhost:7156/api/Test")
.then((r) => r.text())
.then((t) => (state.message = t))
// === new code end
</script>
LAB:Microsoft.AspNetCore.SpaServices.Extensions#
新增套件並設定#
後端加入套件Microsoft.AspNetCore.SpaServices.Extensions並設置,因為目前我採用.NET6,所以安裝的版本是 6.0.11 版
dotnet add package Microsoft.AspNetCore.SpaServices.Extensions --version 6.0.11
// program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// 此處指定 dist 是因為之後我們要佈署的時候,前端是把 build 目錄 dist 整個都複製過來
builder.Services.AddSpaStaticFiles(config => config.RootPath = "dist");
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// 因為採用套件,開發時期用 proxy 代理,已經不再需要 CORS
// app.UseCors(b => b.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
}
app.UseRouting();
app.UseEndpoints(e => e.MapDefaultControllerRoute());
// 使用 SPA 的靜態檔案
app.UseSpaStaticFiles();
app.UseSpa(b =>
{
if (app.Environment.IsDevelopment())
{
// 開發階段採用 proxy 指定前端網站
b.UseProxyToSpaDevelopmentServer("http://localhost:5173/");
}
});
app.Run();修改前端 fetch 網址#
因為請求已經被代理,所以路由就改成相對路徑
fetch("/api/Test")
.then((r) => r.text())
.then((t) => (state.message = t))接著直接開啟後端網站,就可以看到前端SPA,呼叫後端API的結果

這個時候,如果你同時有開啟 Hot Reload , HMR,前後端的程式碼修改後都可以直接看到瀏覽器會去自動更新畫面,當然後端的部分 Hot Reload 有一些條件,但到目前為止整體的開發流程無疑被改善很多了
發佈網站#
// 發布後端網站到上層的output目錄
dotnet publish -c Release -o ..\output
前端的部分直接讓他發佈到 output 就好
// vite.config.js
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
export default defineConfig({
plugins: [vue()],
build: {
outDir: "../output/dist",
},
})npm run build
測試發佈版本#
目前專案目錄如下
進入 output 目錄,這邊存放的是剛才前後端輸出的結果
直接透過 dotnet back-end.dll執行

將應用程式容器化#
建立 dockerfile#
因為不想要把建立搞得太麻煩,所以 build 都在外面先做好,image 就是直接拿 output 複製進去,所以指令變得很單純
FROM mcr.microsoft.com/dotnet/aspnet:6.0
COPY output .
EXPOSE 80
ENTRYPOINT ["dotnet", "back-end.dll"]產生 image#
在專案根目錄下執行指令建立 docker image
docker build -t demo .
產生 container#
當然還需要將 image 透過指令來產生 container,並且測試將 port:7000 指給 container
docker run -d --name=lab -p 7000:80 demo


結論#
經由這一次的練習,大概可以學到,或者說是複習了
- Vite.js + Vue3.js 的威力展示 (HMR)
- .NET6 mvc
- 前後端整合開發
- production 發佈方式
- 容器化
可以為之後日益複雜的軟體架構打一些基礎,而過去幾年的技術與現在相比,在開發時期階段更是進步了很多,但是如果舊網站跟不上技術迭代的腳步,可以想見的是維護的人力會越來越辛苦,也越來越少。像是以前的 classic asp 應該已經不會有人想要維護,而傳統的 js 開發方式也很難被現在的開發者接受,舊專案除非已經穩定沒有維護需求那就還好,技術背景停滯在開發當下的階段,如果還要求持續更新專案功能或是除錯維護,無疑是非常挑戰開發人員的一件事情,要嘛弄一些 workAround 避掉,透過增加整體架構的複雜度、犧牲可維護性;要嘛就是乖乖地想辦法把應用程式的技術棧一層層慢慢更新上去,但這其中也有很大的可能性會受限於
客戶端硬體、系統。只能說真的看運氣了,不論如何,軟體開發的演進總是越來越進步,身為開發人員也必須要能夠持續學習成長,才能說是專業從業人員啊 (技術沒有很強至少要有學習心態啊~)
補充:如果你也喜歡用 Rider#
我非常喜歡使用 Rider 來開發,所以我做了另外一個 Repo:LAB-vite,主要就是為了在 Rider 裡面可以方便的開發,這邊列出一些我碰到的問題還有解法,下面的步驟我就再做一次在新的練習專案:Lab-vite-vue-dotnetcore中
我想要利用 IDE 的搜尋功能,找到前端檔案#
首先幫前端專案複製一個專案檔過去,並修改一下,檔案命名為 front-end.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>front_end</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>根目錄下建立方案檔,並將專案加入
dotnet new sln
dotnet sln .\Lab-vite-vue-dotnetcore.sln add .\back-end\back-end.csproj
dotnet sln .\Lab-vite-vue-dotnetcore.sln add .\front-end\front-end.csproj


最後透過開啟方案檔的方式,就可以透過 Search EveryWhere 找到前端檔案了
測試前後端都要分別執行,有沒有 one click 搞定的方法#
有的,但是仍舊需要做一些前置作業,前端需要一個任務去執行 npm run dev、後端需要一個任務去執行dotnet watch,最後在透過內建的Compound執行兩個任務,
首先我們設定前端的部分,指定好前端路徑
後端的部分則透過.NET Watch Run Configuration,可以很方便的執行
如果一開始是用開啟目錄的方式打開,而不是透過開啟解決方案、專案的話,是沒有辦法設定
dotnet watch外掛的
新增 Compound 任務如下,選擇另外兩個任務就好
點一下之後等待兩個任務跑完,瀏覽器自動開出來
測試一下前端修改之後,HMR 正確執行
後端的部分修改完畢後會自動重載頁面
既然都有一鍵執行了,那一鍵發佈呢#
本質都是一樣的東西啊,把指令包裝一下就好了

再新增一個執行 output 目錄下 exe 的任務

那容器化也來一個吧,我真的懶得打指令了#
新增任務照圖設定
如果想要順便把容器也建立起來,就填一下必要的設定值

測試一下 docker container 的網站