最近在測試 .NET 10 製作網站時,遇到一個很現實的問題
以前使用 ASP.NET Web Forms,只要建立一個 product.aspx?id=1, 我就可以去更改 .aspx 裡面的內容或是樣式
設計師修改版面時,只要替換 .aspx,完全不用重新編譯整個網站
但是到了 ASP.NET Core Razor Pages,.cshtml 預設會在發布時編譯進 DLL
這邊有點麻煩,尤其是現在 agent coding ,有些我已經處理好了,但是因為設計師可能要改些東西
就需要重新 Build , Publish , Deploy
這對大量客製化網站來說有點麻煩,今天測試標很簡單,使用 .NET 10 ,保留 SSR (Server-Side Rendering )
資料在執行其取得,cshtml 可以在執行期的時候修改一些葉面上的資料,不用發佈 DLL
1. 先安裝 RazorEngineCore
dotnet add package RazorEngineCore2. 建立商品模型
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/Product.cshtml
code:
@inherits RazorEngineCore.RazorEngineTemplateBase<TestWebApp.Models.Product>
@{
var title = Model.Name + "|測試商城";
}
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8" />
<meta name="viewport"
content="width=device-width, initial-scale=1" />
<title>@title</title>
<meta name="description"
content="@Model.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>@Model.Name</h1>
<img src="@Model.ImageUrl"
alt="@Model.Name" />
<p>@Model.Description</p>
<div class="price">
NT$ @Model.Price.ToString("N0")
</div>
@if (Model.InStock)
{
<button type="button">
加入購物車
</button>
}
else
{
<strong>目前缺貨</strong>
}
<hr />
<small>
商品編號:@Model.Id
</small>
</main>
</body>
</html>這個.cshtml 不是真正傳統意義上的 Razor Page , 它只是放在 Pages 目錄中的動態模板,所以不能加入@page、@model
4. 動態載入頁面,主要要修改 Program.cs 裡面要加入這一段, 放在 app.Run(); 前,建立商品路由
app.MapGet(
"/product",
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",
"Product.cshtml");
if (!File.Exists(templatePath))
{
return Results.Problem(
title: "找不到商品模板",
detail: templatePath);
}
var templateContent =
await File.ReadAllTextAsync(
templatePath,
cancellationToken);
try
{
IRazorEngine razorEngine = new RazorEngine();
var compiledTemplate =
razorEngine.Compile<RazorEngineTemplateBase<Product>>(
templateContent);
var html = compiledTemplate.Run(
template =>
{
template.Model = product;
});
return Results.Content(
html,
"text/html; charset=utf-8");
}
catch (Exception exception)
{
return Results.Problem(
title: "Razor 模板編譯或執行失敗",
detail: exception.ToString(),
statusCode: StatusCodes.Status500InternalServerError);
}
});之後啟動網站開啟 /product?id=1 就會輸出完整 HTML
5. 做到現在你會發現,你邊議會不過,因為那cshtml 不是一個正常的 Razor Page ,所以得在 .csproj 排除 Razor SDK 編譯
並且 Release 的狀況也要設定,這邊留一段 sample code
<ItemGroup>
<Content Remove="Pages\Product.cshtml" />
<RazorGenerate Remove="Pages\Product.cshtml" />
<None Include="Pages\Product.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Always</CopyToPublishDirectory>
</None>
</ItemGroup>
之後發佈你會看到
publish/
├─ Pages/
│ └─ Product.cshtml
├─ TestWebApp.dll
└─ web.config之後你去修改, Product.cshtml 就不用重新編譯
RazorEngineCore 不是完整的 Razor Pages,也沒有 PageModel、Tag Helper 或 _Layout.cshtml 這些功能
但是如果你是需要製作一個核心資料功能,但是你希望前端是透過其他人(包含設計師、AI Agent)
會去改變風格跟布局,但是核心資料部會去更動到,我覺得這是一個比較方便的做法
可以做到像是以前 ASPX 時代去做一些騷操作..
---
The bug existed in all possible states.
Until I ran the code.
