前一篇測試了 RazorEngineCore,主要目的就是希望在 .NET 10 裡面
可以做到像以前 ASPX 一樣,前端頁面可以直接修改,不用每次都重新 Build、Publish、Deploy..
今天測試另一套套件,我個人覺得更合理,而且今年(2026) 還有再更新,所以可能更貼合我的需求
RazorEngineCore 的確可以做到,但是它本質上還是 Razor Runtime 編譯,所以多少還是有一些複雜度
因為他要去改專案檔案,這有一點麻煩..
今天測試的叫做 Scriban 目標就是 /ProdScriban?id=1 這樣可以使用,並且可以在 "執行期" 去改頁面資料
取得商品資料之後,由伺服器端直接輸出完整 HTML
也就是保留 SSR,SEO 一樣可以正常處理,但是前端模板不要再綁在 DLL 裡面
1. 先安裝 Scriban
dotnet add package Scriban2. 商品 Model 不用改跟上一篇文章,但是我這邊也貼上
namespace TestWebApp.Models;
public sealed class Product
{
public int Id { get; init; }
public string Name { get; init; } = string.Empty;
public decimal Price { get; init; }
public string Description { get; init; } = string.Empty;
public string ImageUrl { get; init; } = string.Empty;
public bool InStock { get; init; }
}這邊我也附上 DataSet ,畢竟這是測試如果要扯到資料庫有點麻煩,用記憶體資料測試就好
using TestWebApp.Models;
namespace TestWebApp.Data;
public static class DataSet
{
public static IReadOnlyDictionary<int, Product> Products { get; } =
new Dictionary<int, Product>
{
[1] = new Product
{
Id = 1,
Name = "65W USB-C 快充充電器",
Price = 990,
Description = "支援手機、平板及筆電快速充電。",
ImageUrl = "https://placehold.co/800x600?text=Product+1",
InStock = true
},
[2] = new Product
{
Id = 2,
Name = "藍牙無線喇叭",
Price = 1680,
Description = "小型輕量設計,支援藍牙連線。",
ImageUrl = "https://placehold.co/800x600?text=Product+2",
InStock = false
},
[3] = new Product
{
Id = 3,
Name = "多功能無線充電座",
Price = 1280,
Description = "可同時替手機、耳機及智慧手錶充電。",
ImageUrl = "https://placehold.co/800x600?text=Product+3",
InStock = true
}
};
public static Product? GetProduct(int id)
{
return Products.TryGetValue(id, out var product)
? product
: null;
}
}3. 建立 Pages/ProdScriban.html ,這邊就是跟 Razor 語法不同了,邊都是改用 {{ product.name }}
所以完整商品頁面完整程式碼
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1">
<title>{{ product.name }}|測試商城</title>
<meta name="description"
content="{{ product.description }}">
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background: #f4f4f4;
}
.product {
width: min(900px, calc(100% - 40px));
margin: 60px auto;
padding: 30px;
background: white;
border-radius: 16px;
}
.product img {
width: 100%;
max-width: 420px;
}
.price {
margin: 20px 0;
font-size: 28px;
font-weight: bold;
}
</style>
</head>
<body>
<main class="product">
<h1>{{ product.name }}</h1>
<img src="{{ product.image_url }}"
alt="{{ product.name }}">
<p>
{{ product.description }}
</p>
<div class="price">
NT$ {{ product.price }}
</div>
{{ if product.in_stock }}
<button type="button">
加入購物車
</button>
{{ else }}
<strong>
目前缺貨
</strong>
{{ end }}
<hr>
<small>
商品編號:{{ product.id }}
</small>
</main>
</body>
</html>這樣子就很方便 AI 去產生前端檔案,只要跟 AI 說好規則就可以當作樣板,可以在執行期處理
4. 這時候跟 RazorEngineCore 不同的就是他不用改 .csproj ,直接改 Program.cs 中建立商品的路由
app.MapGet(
"/ProdScriban",
async (
int? id,
IWebHostEnvironment environment,
CancellationToken cancellationToken) =>
{
if (id is null)
{
return Results.BadRequest("缺少商品 id。");
}
var product = DataSet.GetProduct(id.Value);
if (product is null)
{
return Results.NotFound("找不到商品。");
}
var templatePath = Path.Combine(
environment.ContentRootPath,
"Pages",
"ProdScriban.html");
if (!File.Exists(templatePath))
{
return Results.Problem(
title: "找不到商品模板",
detail: templatePath);
}
var templateContent =
await File.ReadAllTextAsync(
templatePath,
cancellationToken);
var template = Template.Parse(templateContent);
if (template.HasErrors)
{
return Results.Problem(
title: "模板解析失敗",
detail: string.Join(
Environment.NewLine,
template.Messages));
}
var scriptObject = new ScriptObject();
scriptObject.Add(
"product",
product);
var context = new TemplateContext();
context.PushGlobal(scriptObject);
var html =
await template.RenderAsync(context);
return Results.Content(
html,
"text/html; charset=utf-8");
});啟動之後,可以直接使用 /ProdScriban?id=1 讀取到資料,並且你可以動態去修改 Pages/ProdScriban.html
並且就會改變,只要確保那檔案有被 publish 出去就好
Scriban 跟 RazorEngineCore 最大的差別,就是它不會直接讓你在模板裡面寫 C#
但是反過來想,這件事情也不一定是缺點
如果核心邏輯本來就放在 .NET 10 裡面,像是商品、訂單、會員、金流、庫存這些都由後端處理,那前端其實只需要:
HTML、CSS、Data Binding 、if 、 for 就夠用了
這樣設計師或 AI Agent 可以自由修改畫面,但是不會去碰到核心程式
而且它不用 Roslyn Runtime Compile,也不用處理 .cshtml 編譯問題
目前測下來,Scriban 反而更接近一個單純的動態 Theme Template
所以這也是一個可以 AI/設計師配合的方式,筆記一下..
---
The bug existed in all possible states.
Until I ran the code.
