AI重构Razor Pages网站完成
发布时间:2026-04-16
更新时间:2026-04-16
分类:
.NET
Web开发
AI辅助开发
网站又从Blazor回归Razor Pages,本文从源码角度解读网站当前的架构设计。
1. 为什么从Blazor回归Razor Pages
网站前台之前使用 Blazor 静态 SSR 开发,随着对内容展示类网站的深入思考,认为 Razor Pages 可能更适合这类场景。
2. 网站项目结构
源码仓库:CodeWF,采用前后台分离架构:
CodeWF/src/
├── WebApp/ # 前台站点(Razor Pages)
│ ├── Pages/ # 页面文件
│ ├── Components/ # View Components
│ ├── Controllers/ # API控制器
│ └── wwwroot/ # 静态资源
│
└── CodeWF/ # 核心类库
├── Models/ # 数据模型
├── Services/ # 业务服务
└── Extensions/ # 扩展方法
3. Razor Pages核心实现
3.1 页面模型(PageModel)
以工具页面为例,展示典型的PageModel写法:
// Pages/Tool/Index.cshtml.cs
namespace WebApp.Pages.Tool;
public class IndexModel : PageModel
{
private readonly AppService _appService;
public List<ToolItem>? Tools { get; set; }
public IndexModel(AppService appService)
{
_appService = appService;
}
public async Task OnGetAsync()
{
Tools = await _appService.GetAllToolItemsAsync();
}
}
对应视图文件 Pages/Tool/Index.cshtml:
@page
@model WebApp.Pages.Tool.IndexModel
@{
ViewData["Title"] = "工具";
}
<div class="container">
<h1 class="mb-4">在线工具</h1>
<div class="row">
@foreach (var tool in Model.Tools)
{
<div class="col-md-4 mb-4">
<div class="card shadow-sm h-100">
<div class="card-body">
<h4 class="card-title">@tool.Name</h4>
@if (!string.IsNullOrEmpty(tool.Memo))
{
<p class="card-text text-muted">@tool.Memo</p>
}
@if (tool.Children != null && tool.Children.Any())
{
<ul class="list-group list-group-flush mt-3">
@foreach (var child in tool.Children)
{
<li class="list-group-item">
<a href="@child.Slug">@child.Name</a>
</li>
}
</ul>
}
</div>
</div>
</div>
}
</div>
</div>
3.2 路由参数绑定
文章详情页使用路由模板语法:
@page "/Bbs/Post/{year:int}/{month:int}/{slug}"
@model WebApp.Pages.Bbs.Post.IndexModel
对应代码后端 Post/Index.cshtml.cs:
public class IndexModel : PageModel
{
public BlogPost? Post { get; set; }
public async Task<IActionResult> OnGetAsync(int year, int month, string slug)
{
Post = await _appService.GetPostAsync(year, month, slug);
if (Post == null)
{
return NotFound();
}
return Page();
}
}
3.3 共享布局(Layout)
Layout文件定义全局页面结构:
@inject CodeWF.Services.AppService AppService
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - 码坊</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-lg navbar-dark bg-tech-nav fixed-top">
<!-- 导航栏内容 -->
</nav>
</header>
<div style="padding-top: 80px;">
@RenderBody()
</div>
@await Component.InvokeAsync("FriendLink")
<footer>
<!-- 页脚内容 -->
</footer>
</body>
</html>
4. 服务层设计
4.1 AppService核心服务
AppService.cs 是整个应用的核心服务类:
public class AppService(IOptions<SiteOption> siteOption)
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip
};
private List<BlogPost>? _blogPosts;
private List<ToolItem>? _toolItems;
private List<DocItem>? _docItems;
private List<CategoryItem>? _categoryItems;
private List<AlbumItem>? _albumItems;
public async Task<List<BlogPost>?> GetAllBlogPostsAsync()
{
if (_blogPosts?.Any() == true) return _blogPosts;
var filePath = Path.Combine(siteOption.Value.LocalAssetsDir,
"site", "blog", "posts.json");
var fileContent = await File.ReadAllTextAsync(filePath);
_blogPosts = JsonSerializer.Deserialize<List<BlogPost>>(fileContent, JsonOptions);
return _blogPosts;
}
public async Task SeedAsync()
{
// 启动时预加载所有数据
await GetAllAlbumItemsAsync();
await GetAllCategoryItemsAsync();
await GetAllBlogPostsAsync();
await GetAllFriendLinkItemsAsync();
await GetAllDocItemsAsync();
await GetAllToolItemsAsync();
}
}
4.2 数据模型
// BlogPost.cs
public class BlogPostBrief
{
public string? Title { get; set; }
public string? Slug { get; set; }
public string? Description { get; set; }
public DateTime? Date { get; set; }
public DateTime? Lastmod { get; set; }
public string? Author { get; set; }
public string? Cover { get; set; }
public List<string>? Categories { get; set; }
public List<string>? Tags { get; set; }
}
public class BlogPost : BlogPostBrief
{
public string? Content { get; set; } // 原始Markdown
public string? HtmlContent { get; set; } // 转换后的HTML
}
5. 页面请求处理流程
- 用户请求
/Timestamp - 路由系统匹配到
@page "/Timestamp" - 执行
OnGetAsync()方法(若存在) - 通过
AppService获取业务数据 - 渲染视图并返回HTML
6. Program.cs配置
var builder = WebApplication.CreateBuilder(args);
// 添加Razor Pages服务
builder.Services.AddRazorPages();
builder.Services.AddControllers();
builder.Services.AddHttpClient();
// 依赖注入AppService
builder.Services.AddSingleton<AppService>();
builder.Services.Configure<SiteOption>(builder.Configuration.GetSection("Site"));
var app = builder.Build();
// 启动时预加载数据
using (var serviceScope = app.Services.CreateScope())
{
var service = serviceScope.ServiceProvider.GetRequiredService<AppService>();
await service.SeedAsync();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.Run();
7. 在线工具示例:时间戳转换
以 Timestamp.cshtml 为例展示前端交互:
@page "/Timestamp"
@{ ViewData["Title"] = "时间戳转换工具"; }
<div class="container">
<h1 class="mb-4">时间戳转换工具</h1>
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title">时间戳 → 日期</h5>
<div class="row mb-3">
<div class="col d-flex align-items-center gap-2">
<span>时间戳:</span>
<input type="text" class="form-control" style="width: 200px;"
id="inputTimestamp" placeholder="输入时间戳" />
<select class="form-select" style="width: 100px;" id="timestampUnit">
<option value="s">秒</option>
<option value="ms">毫秒</option>
</select>
<button class="btn btn-primary btn-sm"
onclick="convertTimestampToDate()">转换为日期</button>
</div>
</div>
</div>
</div>
</div>
@section Scripts {
<script>
function convertTimestampToDate() {
const timestamp = document.getElementById('inputTimestamp').value;
const unit = document.getElementById('timestampUnit').value;
const ms = unit === 's' ? timestamp * 1000 : parseInt(timestamp);
const date = new Date(ms);
document.getElementById('outputDate').value = date.toLocaleString();
}
</script>
}
8. 总结
源码已开源,欢迎交流学习。
仓库地址:https://github.com/dotnet9/CodeWF