Razor Pages:如何向布局页/部分视图传递数据库数据及配置PageModel
错误的核心是布局页会继承内容页的模型:你给布局页指定了LayoutModel,但内容页(比如Index页)传递的是自身的IndexModel,类型不匹配导致抛出异常。
方案1:用ViewData/ViewBag传递数据(快速简单)
不需要修改布局页模型,直接在内容页加载数据存入ViewData,布局页读取使用:
- 在每个内容页的PageModel中加载数据:
public class IndexModel : PageModel { private readonly ApplicationDbContext _db; public IndexModel(ApplicationDbContext db) { _db = db; } public async Task OnGetAsync() { // 将项目数据存入ViewData ViewData["UserProjects"] = await _db.UserProject.ToListAsync(); } }
- 修改布局页的下拉菜单代码:
<li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown"> Projects </a> <div class="dropdown-menu"> @foreach(var p in (List<UserProject>)ViewData["UserProjects"]) { <a class="dropdown-item text-dark" asp-area="" asp-page="/Category Display/Index" asp-route-projectId="@p.Id">@p.productName</a> } </div> </li>
缺点:每个内容页都要重复编写加载数据的代码,适合小型项目。
方案2:使用ViewComponent(推荐)
ViewComponent是Razor Pages官方推荐的、用于复用带逻辑视图片段的组件,完美解决布局页/部分视图需要独立数据逻辑的场景——它自带专属的逻辑处理类,就是你想要的「类似PageModel的结构」。
步骤1:创建ViewComponent类
在项目根目录新建Components文件夹,添加ProjectsDropdownViewComponent.cs:
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; public class ProjectsDropdownViewComponent : ViewComponent { private readonly ApplicationDbContext _db; public ProjectsDropdownViewComponent(ApplicationDbContext db) { _db = db; } // InvokeAsync相当于组件的"OnGet"方法,处理数据逻辑 public async Task<IViewComponentResult> InvokeAsync() { var userProjects = await _db.UserProject.ToListAsync(); return View(userProjects); // 向视图传递数据 } }
步骤2:创建组件对应的视图
在Pages/Shared/Components/ProjectsDropdown目录下新建Default.cshtml(目录名需和组件类名去掉ViewComponent后缀一致):
@model List<UserProject> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown"> Projects </a> <div class="dropdown-menu"> @foreach(var p in Model) { <a class="dropdown-item text-dark" asp-area="" asp-page="/Category Display/Index" asp-route-projectId="@p.Id">@p.productName</a> } </div> </li>
步骤3:在布局页引用组件
替换布局页中原有的下拉菜单代码,直接调用组件:
@await Component.InvokeAsync("ProjectsDropdown")
优点:逻辑与视图完全复用,无需在每个内容页重复编写代码,扩展性强。
方案3:基础PageModel继承(不推荐)
让所有内容页的PageModel继承一个包含项目数据的基础模型,但灵活性差:
- 创建基础PageModel:
public class BasePageModel : PageModel { private readonly ApplicationDbContext _db; public List<UserProject> UserProjects { get; set; } public BasePageModel(ApplicationDbContext db) { _db = db; } public async Task OnGetBaseAsync() { UserProjects = await _db.UserProject.ToListAsync(); } }
- 内容页继承基础模型并调用基础方法:
public class IndexModel : BasePageModel { public IndexModel(ApplicationDbContext db) : base(db) { } public async Task OnGetAsync() { await OnGetBaseAsync(); } }
- 布局页指定模型为
BasePageModel:
@model BasePageModel <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown"> Projects </a> <div class="dropdown-menu"> @foreach(var p in Model.UserProjects) { <a class="dropdown-item text-dark" asp-area="" asp-page="/Category Display/Index" asp-route-projectId="@p.Id">@p.productName</a> } </div> </li>
缺点:所有内容页必须强制继承基础模型,新增页面时容易遗漏,扩展性差。
可以通过ViewComponent实现,它自带独立的依赖注入(比如注入DbContext)和逻辑处理方法(InvokeAsync),相当于给部分视图绑定了专属的逻辑类,完全满足你需要的「类似PageModel的结构」,这也是Razor Pages中最规范的实现方式。
内容的提问来源于stack exchange,提问作者DED

