practical-aspnetcore 练习三实战:用 Razor Pages + HttpClient 为菜谱 JSON 后端搭建纯服务端 Web 界面 示例工程【免费下载链接】practical-aspnetcorePractical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.项目地址https://gitcode.com/gh_mirrors/pr/practical-aspnetcore点击查看免费下载本篇指南围绕 exercises/pathway-1/exercise-3.md 展开讲解如何在前两个练习控制台菜谱应用、JSON REST API 后端的基础上用Razor Pages实现一个基于Bootstrap 5.2的 HTML 用户界面并通过HTTP ClientIHttpClientFactory与既有 JSON 后端通信。读完本文你将掌握Razor Pages 项目的搭建与路由规则、PageModel与 Handler 方法的用法、IHttpClientFactory的三种注册方式以及零 AJAX、零 JavaScript的纯服务端表单交互模型可直接照此完成该练习并迁移到自己的项目中。练习目标与整体架构Exercise 3 的技术要求非常明确共五条保留你在 Exercise 2 中创建的 JSON 后端不得重写基于 Razor Pages 实现 HTML UI使用 HTTP Client 连接 JSON 后端采用 Bootstrap 5.2 作为 CSS 框架不使用 AJAX 与 JavaScript。这实际上勾勒出一个经典的三层结构浏览器 → Razor Pages服务端渲染→ HTTP 调用 → JSON Web API → JSON 文件存储。Razor Pages 页面在服务器端完成对后端的请求、取数、拼装 HTML 并整体返回给浏览器浏览器端不需要任何fetch、XMLHttpRequest或脚本逻辑所有交互都通过标准的表单 POST / 链接 GET 触发。注意练习的附注This is not a race. Dont take on this exercise until your previous exercises are solidly implemented.——务必确保 Exercise 1控制台版菜谱管理与 Exercise 2JSON REST API 后端稳定可用后再开始本练习因为本练习的 UI 完全依赖后端 API 的可用性。第一步保留 Exercise 2 的 JSON 后端Exercise 2exercise-2.md要求把菜谱控制台应用拆分为后端REST API与控制台客户端两部分后端负责全部业务逻辑JSON 读写、新建菜谱等通过 REST API 暴露客户端只发 HTTP 请求。本练习不修改后端只新增一个与之对话的 Web 前端。仓库中 projects/minimal-api/hello-world/Program.cs 展示了最小 Web API 的极简形态——WebApplication直接以app.Run(...)响应请求。你的菜谱后端在此基础上扩充即可例如提供GET /api/recipes—— 列出全部菜谱GET /api/recipes/{id}—— 获取单个菜谱POST /api/recipes—— 新建菜谱PUT /api/recipes/{id}—— 更新菜谱GET/POST /api/categories—— 管理菜谱分类。通信格式统一为 JSON后端序列化/反序列化建议使用System.Text.Json这也是 Exercise 1 明确要求的库。本练习的 UI 将消费以上端点因此请先确认这些端点可以独立用浏览器或curl访问再继续下一步。第二步搭建 Razor Pages 前端项目项目骨架与依赖在解决方案中新建一个 Web 项目例如RecipeWeb项目文件的核心配置只需Microsoft.NET.Sdk.WebSDK。仓库中的 razor-pages-basic.csproj 是标准范例Project SdkMicrosoft.NET.Sdk.Web PropertyGroup TargetFrameworknet10.0/TargetFramework ImplicitUsingstrue/ImplicitUsings /PropertyGroup /Project说明本仓库的示例当前以net10.0为目标框架本练习在设计时面向 .NET 6 时代的 API但 Razor Pages 与IHttpClientFactory的核心用法在 .NET 6 至 .NET 11 各版本中保持一致可按你的 SDK 环境调整TargetFramework。注册 Razor Pages 服务Razor Pages 只需要两行配置。参考 projects/razor-pages/razor-pages-basic/Program.cs 与 projects/razor-pages/hello-world/Program.csvar builder WebApplication.CreateBuilder(); builder.Services.AddRazorPages(); // 注册 Razor Pages 服务 var app builder.Build(); app.MapRazorPages(); // 映射 Pages 目录下的所有页面 app.Run();Razor Pages 的默认路由约定Razor Pages 的页面统一放在项目根目录的Pages/文件夹下凡带page指令的.cshtml文件都会自动获得一条路由/Pages/Index.cshtml对应//Pages/AboutUs.cshtml对应/AboutUs。仓库 projects/razor-pages/README.md 对此有明确说明。若想自定义 URL可在page指令中直接写路由模板每个页面只能有一个page定义。参考 projects/razor-pages/routing/Pages/About.cshtml 与 projects/razor-pages/routing/Pages/Blog.cshtmlpage /AboutOurSchool !-- 该页面将通过 /AboutOurSchool 访问 -- page /Blog/2018/4/20/itsmybirthday !-- 固定路径模板 --对于菜谱界面合理的页面划分是Pages/Index.cshtml菜谱列表、Pages/Recipes/Create.cshtml新建、Pages/Recipes/Edit.cshtml编辑、Pages/Categories.cshtml分类管理。第三步通过 IHttpClientFactory 连接 JSON 后端本练习的核心要求之一是Connect to the JSON backend using HTTP Client。直接new HttpClient()会带来 socket 耗尽、DNS 变更不感知等经典问题因此 ASP.NET Core 推荐通过依赖注入使用IHttpClientFactory。仓库 projects/httpclientfactory/README.md 整理了四种用法下面逐一说明并给出与本练习的结合方式。方式一默认客户端先注册IHttpClientFactory再从容器中取出并创建客户端。参考 projects/httpclientfactory/httpclientfactory-1/Program.csvar builder WebApplication.CreateBuilder(); builder.Services.AddHttpClient(); // 注册 IHttpClientFactory var app builder.Build(); app.Run(async context { var httpClient context.RequestServices.GetServiceIHttpClientFactory(); var client httpClient.CreateClient(); // 默认客户端 var result await client.GetStringAsync(http://scripting.com/rss.xml); context.Response.Headers.Append(Content-Type, application/rssxml); await context.Response.WriteAsync(result); }); app.Run();在你的 Razor Pages 场景中更规范的做法是把这个逻辑放进PageModel的 Handler 方法见第五步而不是app.Run中间件里。方式二命名客户端当页面需要访问多个后端例如菜谱服务与分类服务时可以用AddHttpClient(name, ...)预配置各自的BaseAddress、请求头与超时再按名取用。参考 projects/httpclientfactory/httpclientfactory-2/Program.csbuilder.Services.AddHttpClient(recipe, c { c.BaseAddress new Uri(http://localhost:5000/); c.DefaultRequestHeaders.Add(Accept, application/json); });使用时httpClient.CreateClient(recipe)即可拿到这份预配置。方式三类型化客户端推荐类型化客户端把HttpClient的配置与业务方法封装进一个类Razor Pages 的PageModel只需注入这个类代码最整洁。参考 projects/httpclientfactory/httpclientfactory-3/Program.csvar builder WebApplication.CreateBuilder(); builder.Services.AddTransientRecipeApiClient(); builder.Services.AddHttpClientRecipeApiClient(); // 类型化客户端 public class RecipeApiClient { readonly HttpClient _client; public RecipeApiClient(HttpClient client) { _client client; } public Taskstring GetAll() _client.GetStringAsync(/api/recipes); }方式四基于接口的类型化客户端若想让PageModel依赖抽象而非具体类便于测试与替换可用AddHttpClientIRecipes, RecipeApiClient()。参考 projects/httpclientfactory/httpclientfactory-4/Program.csbuilder.Services.AddTransientRecipeApiClient(); builder.Services.AddHttpClientIRecipes, RecipeApiClient(); public interface IRecipes { Taskstring GetAll(); } public class RecipeApiClient : IRecipes { readonly HttpClient _client; public RecipeApiClient(HttpClient client) _client client; public Taskstring GetAll() _client.GetStringAsync(/api/recipes); }对于本练习建议采用方式三/方式四在RecipeApiClient中封装GetRecipes()、GetRecipe(id)、CreateRecipe(...)、UpdateRecipe(...)等方法JSON 序列化继续使用System.Text.Json与 Exercise 1、2 保持一致。第四步Razor Pages 的代码模型与 Handler 方法两种 code-behind 写法Razor Pages 支持把PageModel写在独立的.cs文件推荐也可内联在functions块中。分离 code-behind参考 SeparateCodebehindFile.cshtml 与 SeparateCodebehindFile.cshtml.cs// Pages/Recipes/Index.cshtml.cs using Microsoft.AspNetCore.Mvc.RazorPages; public class IndexModel : PageModel { public ListRecipe Recipes { get; set; } new(); public async Task OnGetAsync() // GET / 请求 { Recipes await _api.GetRecipes(); } }page using RecipeWeb.Pages model IndexModel h1Model.Recipes.Count 道菜谱/h1内联 code-behind参考 InlineCodebehindFile.cshtml将模型类写在functions块内适合页面很小、逻辑很少的场景functions { public class InlineCodebehindFileModel : Microsoft.AspNetCore.Mvc.RazorPages.PageModel { public string Title Page with inline codebehind file model; } }Handler 方法与表单提交Razor Pages 通过Handler 方法响应请求OnGet系列处理 GETOnPost系列处理 POST。仓库 projects/razor-pages/handler/Pages/Index.cshtml 演示了最简的 Get/Post 处理器public void OnGet() { Method this.HttpContext.Request.Method; } public IActionResult OnPost() { Method this.HttpContext.Request.Method; return Page(); // 返回当前页面重新渲染 }对应的表单通过asp-page标签助手指定提交目标同时记得启用表单验证令牌Razor Pages 模板默认会为form methodpost注入反伪造令牌form classform methodpost asp-pageIndex button typeSubmit classbtn btn-primaryPost/button /form对于菜谱界面Pages/Recipes/Edit.cshtml.cs可这样组织public class EditModel : PageModel { [BindProperty] public Recipe Recipe { get; set; } // 表单回填绑定 public async Task OnGetAsync(int id) // 进入编辑页拉取数据 { Recipe await _api.GetRecipe(id); } public async TaskIActionResult OnPostAsync() // 提交编辑写回后端 { await _api.UpdateRecipe(Recipe); return RedirectToPage(/Recipes/Index); } }关键点所有写操作都通过 Handler 方法转发给 JSON 后端由后端统一做 JSON 文件读写与业务校验——这正是练习要求Backend handles all the business logicExercise 2 的原则在本练习中的延续。表单只负责收集输入不做任何客户端校验之外的逻辑因为不允许 JavaScript。第五步接入 Bootstrap 5.2 与页面骨架练习明确指定使用Bootstrap 5.2。最直接的方式是在Pages/下建立共享的_Layout.cshtmlRazor Pages 默认通过_ViewStart.cshtml应用布局并在head中引入 Bootstrap 5.2 的 CDN 样式。仓库示例如 razor-pages-basic 的 Index.cshtml使用的是 Bootstrap 5.1.3 CDN按练习要求替换为 5.2 版本即可!doctype html html langzh-CN head meta charsetutf-8 meta nameviewport contentwidthdevice-width, initial-scale1 link hrefhttps://cdn.jsdelivr.net/npm/bootstrap5.2/dist/css/bootstrap.min.css relstylesheet title菜谱管理/title /head body div classcontainer RenderBody() /div /body /html说明Bootstrap 5.2 的 CDN 链接格式与 5.1 一致只需调整版本号路径若你的环境无法访问公网 CDN也可将 bootstrap.css 下载后放入项目的wwwroot目录通过link href~/css/bootstrap.min.css ...引用~由 Razor 自动解析到wwwroot。之后页面中的表格、按钮、导航直接使用 Bootstrap 的.table、.btn btn-primary、.navbar等类名例如菜谱列表table classtable table-striped thead trth标题/thth分类/thth操作/th/tr /thead tbody foreach (var r in Model.Recipes) { tr tdr.Title/td tdstring.Join(, , r.Categories)/td tda classbtn btn-sm btn-secondary asp-page/Recipes/Edit asp-route-idr.Id编辑/a/td /tr } /tbody /table第六步坚持零 AJAX、零 JavaScript的交互模型这是本练习最需要自律的一条约束。它意味着页面的跳转、表单提交全部使用原生a href与form methodpost页面渲染所需的全部数据都应在OnGet/OnPostHandler 中提前从后端取好通过Model输出到 HTML不得引入任何fetch、XMLHttpRequest、script逻辑或前端框架来做局部刷新。每完成一次交互浏览器都会向 Razor Pages 发起一次完整请求页面在服务端重新组装后整页返回。这个模型的好处是实现简单、可访问性好、易于调试与缓存且与后端 API 的边界非常清晰——Razor Pages 只是 JSON API 的一个服务端渲染外壳。运行与验证清单先启动 Exercise 2 的 JSON 后端例如dotnet run --project BackendProject确认GET /api/recipes在浏览器或curl下返回 JSON再启动本练习的 Razor Pages 项目dotnet run或dotnet watch run访问http://localhost:port/验证链路列表页能展示后端数据 → 新建表单提交后返回列表并能看到新条目 → 编辑表单能回显并保存 → 分类管理增删改生效确认页面上没有任何内联脚本或外部 JS 依赖样式完全来自 Bootstrap 5.2用dotnetCLI 或浏览器开发者工具确认页面发出的请求只有GET/POST即 HTML 表单与链接没有fetch请求。如果列表、新建、编辑、分类管理都能走通说明你已把REST API 后端 服务端渲染 UI这条链路完整打通可以进入 Exercise 4基于 Alpine.js Fetch API 的单页界面或继续巩固本练习。仓库参考资源汇总练习原文与相邻练习exercise-3.md、exercise-1.md、exercise-2.md、exercise-4.mdRazor Pages 入门razor-pages-basic两种 code-behind、handlerGet/Post Handler、routingpage自定义路由、razor-pages 总览HTTP Clienthttpclientfactory-1默认客户端、httpclientfactory-2命名客户端、httpclientfactory-3类型化客户端、httpclientfactory-4接口类型化客户端、总览最小 Web API 后端参考hello-world 及 minimal-api 总览赞分享示例工程【免费下载链接】practical-aspnetcorePractical samples of ASP.NET Core 11, 10, 9, 8.0, 7.0, 6.0, 5.0, 3.1, 2.2, and 2.1,projects you can use. Readme contains explanations on all projects.项目地址https://gitcode.com/gh_mirrors/pr/practical-aspnetcore点击查看免费下载相关推荐practical-aspnetcore 练习指南将 Recipe JSON API 后端改造为 gRPC 后端practical aspnetcore 练习指南将 Recipe JSON API 后端改造为 gRPC 后端 本文是 practical aspnetco示例工程practical-aspnetcore 练习指南用 HTMX 为 JWT 后端构建无刷新 UIExercise 7practical aspnetcore 练习指南用 HTMX 为 JWT 后端构建无刷新 UIExercise 7 本指南对应 practical as示例工程practical-aspnetcore 实战在 Razor Pages 应用中通过文件夹约定搭建 Areanew-routing-28practical aspnetcore 实战在 Razor Pages 应用中通过文件夹约定搭建 Areanew routing 28 在 practi示例工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考