AI reconstruction of Razor Pages website completed

AI reconstruction of Razor Pages website completed

Returning from Blazor static SSR to Razor Pages, source code interpretation of website architecture design and core implementation

最后更新 4/16/2026 11:00 PM
沙漠尽头的狼
预计阅读 6 分钟
分类
.NET front end
标签
.NET C# ASP.NET Core Blazor Razor Pages

The website has returned from Blazor to Razor Pages. This article explains the current architecture and design of the website from the perspective of source code.

1. Why return from Blazor to Razor Pages

The front desk of the website used Blazor static SSR development before. As we thought deeply about content display websites, we thought that Razor Pages might be more suitable for such scenarios.

2. Website project structure

源码仓库:CodeWF,采用前后台分离架构:

CodeWF/src/
├── WebApp/                 # 前台站点(Razor Pages)
│   ├── Pages/             # 页面文件
│   ├── Components/        # View Components
│   ├── Controllers/      # API控制器
│   └── wwwroot/          # 静态资源
│
└── CodeWF/                # 核心类库
    ├── Models/           # 数据模型
    ├── Services/         # 业务服务
    └── Extensions/       # 扩展方法

CodeWF架构图

3. Razor Pages core implementation

3.1 Page Model

Take the tool page as an example to show a typical PageModel writing method:

// 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 Routing parameter binding

The article details page uses routing template syntax:

@page "/{year:int}/{month:int}/{slug}"
@model WebApp.Pages.Blog.Post.IndexModel

对应代码后端 Post/Index.cshtml.cs

public class IndexModel : PageModel
{
    public BlogPost? Post { get; set; }

    public async Task OnGetAsync(int year, int month, string slug)
    {
        Post = await _appService.GetPostBySlug(slug);
    }
}

3.3 Shared Layout

The Layout file defines the global page structure:

@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. Service layer design

4.1 AppService Core Services

AppService.cs 是整个应用的核心服务类:

public class AppService(IOptions<SiteOption> siteOption)
{
    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;

        _blogPosts = [];
        var endYear = DateTime.Now.Year;

        for (var start = siteOption.Value.StartYear; start <= endYear; start++)
        {
            var postDir = Path.Combine(siteOption.Value.LocalAssetsDir, start.ToString());
            if (!Directory.Exists(postDir)) continue;

            var postFiles = Directory.GetFiles(postDir, "*.md", SearchOption.AllDirectories);
            foreach (var postFile in postFiles)
            {
                var blogPost = await ReadBlogPostAsync(postFile);
                if (!blogPost.Draft)
                {
                    _blogPosts.Add(blogPost);
                }
            }
        }

        _blogPosts = _blogPosts
            .OrderByDescending(post => post.Lastmod ?? post.Date ?? DateTime.MinValue)
            .ThenByDescending(post => post.Date ?? DateTime.MinValue)
            .ToList();
        return _blogPosts;
    }

    public async Task SeedAsync()
    {
        // 启动时预加载所有数据
        await GetAllAlbumItemsAsync();
        await GetAllCategoryItemsAsync();
        await GetAllBlogPostsAsync();
        await GetAllFriendLinkItemsAsync();
        await GetAllDocItemsAsync();
        await GetAllToolItemsAsync();
    }
}

4.2 data model

// 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. Page request processing flow

页面请求处理流程

  1. 用户请求 /timestamp
  2. 路由系统匹配到 @page "/timestamp"
  3. 执行 OnGetAsync() 方法(若存在)
  4. 通过 AppService 获取业务数据
  5. Render the view and return HTML

6. Program.cs Configuration

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. Online tool example: Timestamp conversion

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. summary

The source code has been open source. Welcome to exchange and learn.

Keep Exploring

延伸阅读

更多文章
同分类 / 同标签 3/10/2025

Practical development of QR code generation tool for moving cars

This article describes how to develop a mobile QR code generation tool, including desktop versions implemented by C#and Avalonia, and online versions implemented by Blazor front-end and. NET Web API, covering requirements analysis, core code implementation, UI design and MVVM pattern applications.

继续阅读
同分类 / 同标签 6/20/2024

CodeWF.EventBus: Lightweight event bus for smoother communication

CodeWF.EventBus, a flexible event bus library that enables decoupling communication between modules. Supports multiple. NET project types, such as WPF, WinForms, ASP.NET Core, etc. Adopt concise design to easily implement command publishing and subscribing, request and response. Ensure that incidents are properly handled through orderly incident handling. Streamline your code and improve system maintainability.

继续阅读
同分类 / 同标签 1/19/2024

FluentValidation verification tutorial based on. NET

FluentValidation is a verification framework developed based on. NET. It is open source, free, and elegant. It supports chain operations. It is easy to understand and has complete functions. It can still be deeply integrated with MVC5, WebApi2 and ASP.NET CORE. It provides more than a dozen commonly used validators within the components. It is scalable, supports custom validators, and supports localized multiple languages.

继续阅读