ASP.NET Core:实现可复用且绑定Razor父模型的模块化多下拉框
问题
如何在ASP.NET Core中实现模块化可复用(可在同一页面多次使用)且能绑定到Razor父页面模型的下拉框?
我了解过单个下拉框的实现方式(类似官方文档中的示例),但现在需要多个可复用的下拉框(比如新增选择大学年级的下拉框:大一、大二、大三、大四)。但C#不支持多基类继承,无法通过继承多个下拉框基类来实现。另外,就算是单个下拉框,有没有比示例更好的实现方式?
以下是官方示例中的相关代码:
// Pages/Courses/DepartmentNamePageModel.cs using ContosoUniversity.Data; using ContosoUniversity.Models; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.EntityFrameworkCore; using System.Linq; namespace ContosoUniversity.Pages.Courses { public class DepartmentNamePageModel : PageModel { public SelectList DepartmentNameSL { get; set; } public void PopulateDepartmentsDropDownList(SchoolContext _context, object selectedDepartment = null) { var departmentsQuery = from d in _context.Departments orderby d.Name // 按名称排序 select d; DepartmentNameSL = new SelectList(departmentsQuery.AsNoTracking(), nameof(Department.DepartmentID), nameof(Department.Name), selectedDepartment); } } } // Pages/Courses/Create.cshtml.cs using ContosoUniversity.Models; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; namespace ContosoUniversity.Pages.Courses { public class CreateModel : DepartmentNamePageModel { private readonly ContosoUniversity.Data.SchoolContext _context; public CreateModel(ContosoUniversity.Data.SchoolContext context) { _context = context; } public IActionResult OnGet() { PopulateDepartmentsDropDownList(_context); return Page(); } [BindProperty] public Course Course { get; set; } public async Task<IActionResult> OnPostAsync() { var emptyCourse = new Course(); if (await TryUpdateModelAsync<Course>( emptyCourse, "course", // 表单值前缀 s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits)) { _context.Courses.Add(emptyCourse); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } // 如果验证失败,重新生成下拉框并保留选中值 PopulateDepartmentsDropDownList(_context, emptyCourse.DepartmentID); return Page(); } } }
解决方案
针对多下拉框复用和单下拉框优化的需求,推荐以下几种实现方式:
方案1:工具类/服务封装下拉逻辑
把每个下拉框的数据获取逻辑封装成独立的服务类,避免使用继承,直接在PageModel中注入调用,支持多个下拉框的复用。
步骤1:创建下拉框服务类
public class DropDownService { private readonly SchoolContext _context; public DropDownService(SchoolContext context) { _context = context; } // 生成系部下拉框数据 public SelectList GetDepartmentSelectList(object selectedId = null) { var departments = _context.Departments .AsNoTracking() .OrderBy(d => d.Name); return new SelectList(departments, nameof(Department.DepartmentID), nameof(Department.Name), selectedId); } // 生成年级下拉框数据 public SelectList GetGradeSelectList(object selectedId = null) { // 模拟年级数据,实际可从数据库/配置读取 var grades = new List<SelectListItem> { new() { Value = "1", Text = "大一" }, new() { Value = "2", Text = "大二" }, new() { Value = "3", Text = "大三" }, new() { Value = "4", Text = "大四" } }; return new SelectList(grades, "Value", "Text", selectedId); } }
步骤2:注册服务
在Program.cs中注册该服务:
builder.Services.AddScoped<DropDownService>();
步骤3:在PageModel中使用
public class CreateModel : PageModel { private readonly SchoolContext _context; private readonly DropDownService _dropDownService; // 存储下拉框数据 public SelectList DepartmentSL { get; set; } public SelectList GradeSL { get; set; } [BindProperty] public Course Course { get; set; } // 绑定年级选择值 [BindProperty] public int GradeId { get; set; } public CreateModel(SchoolContext context, DropDownService dropDownService) { _context = context; _dropDownService = dropDownService; } public IActionResult OnGet() { // 初始化下拉框 DepartmentSL = _dropDownService.GetDepartmentSelectList(); GradeSL = _dropDownService.GetGradeSelectList(); return Page(); } public async Task<IActionResult> OnPostAsync() { var emptyCourse = new Course(); if (await TryUpdateModelAsync<Course>( emptyCourse, "course", s => s.CourseID, s => s.DepartmentID, s => s.Title, s => s.Credits)) { emptyCourse.GradeId = GradeId; _context.Courses.Add(emptyCourse); await _context.SaveChangesAsync(); return RedirectToPage("./Index"); } // 验证失败时,重新生成下拉框并保留选中值 DepartmentSL = _dropDownService.GetDepartmentSelectList(emptyCourse.DepartmentID); GradeSL = _dropDownService.GetGradeSelectList(GradeId); return Page(); } }
视图中渲染下拉框
<div class="form-group"> <label asp-for="Course.DepartmentID"></label> <select asp-for="Course.DepartmentID" asp-items="Model.DepartmentSL" class="form-control"></select> <span asp-validation-for="Course.DepartmentID" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="GradeId"></label> <select asp-for="GradeId" asp-items="Model.GradeSL" class="form-control"></select> <span asp-validation-for="GradeId" class="text-danger"></span> </div>
方案2:局部视图实现页面内多次复用
把下拉框封装成局部视图,通过ViewModel传递绑定字段和下拉数据,支持同一页面多次渲染不同绑定目标的下拉框。
步骤1:创建下拉框ViewModel
public class DropDownViewModel { // 绑定的父模型属性名 public string BindPropertyName { get; set; } // 下拉框选项列表 public SelectList SelectList { get; set; } }
步骤2:创建局部视图_DepartmentDropDown.cshtml
@model DropDownViewModel <div class="form-group"> <label asp-for="@Model.BindPropertyName"></label> <select asp-for="@Model.BindPropertyName" asp-items="@Model.SelectList" class="form-control"></select> <span asp-validation-for="@Model.BindPropertyName" class="text-danger"></span> </div>
步骤3:在父页面中调用
@{ // 第一个系部下拉框,绑定到Course.DepartmentID var courseDeptVm = new DropDownViewModel { BindPropertyName = "Course.DepartmentID", SelectList = Model.DepartmentSL }; // 第二个系部下拉框,绑定到其他模型的DepartmentID var anotherDeptVm = new DropDownViewModel { BindPropertyName = "AnotherModel.DepartmentID", SelectList = Model.DepartmentSL }; } <partial name="_DepartmentDropDown" model="courseDeptVm" /> <partial name="_DepartmentDropDown" model="anotherDeptVm" />
方案3:ViewComponent实现独立可复用组件
ViewComponent适合更复杂的可复用场景,自带独立的逻辑处理和视图,支持依赖注入,可在任意页面调用。
步骤1:创建ViewComponent类
public class DepartmentDropDownViewComponent : ViewComponent { private readonly DropDownService _dropDownService; public DepartmentDropDownViewComponent(DropDownService dropDownService) { _dropDownService = dropDownService; } // 调用时传入绑定属性名和选中值 public IViewComponentResult Invoke(string bindPropertyName, object selectedId = null) { var model = new DropDownViewModel { BindPropertyName = bindPropertyName, SelectList = _dropDownService.GetDepartmentSelectList(selectedId) }; return View(model); } }
步骤2:创建ViewComponent视图
在Views/Shared/Components/DepartmentDropDown/Default.cshtml中添加视图内容:
@model DropDownViewModel <div class="form-group"> <label asp-for="@Model.BindPropertyName"></label> <select asp-for="@Model.BindPropertyName" asp-items="@Model.SelectList" class="form-control"></select> <span asp-validation-for="@Model.BindPropertyName" class="text-danger"></span> </div>
步骤3:在父页面中调用
@await Component.InvokeAsync("DepartmentDropDown", new { bindPropertyName = "Course.DepartmentID", selectedId = Model.Course?.DepartmentID }) @await Component.InvokeAsync("DepartmentDropDown", new { bindPropertyName = "AnotherModel.DepartmentID", selectedId = Model.AnotherModel?.DepartmentID })
单个下拉框的优化建议
即使只需要单个下拉框,也推荐使用工具类/服务的方式替代官方示例的继承方案,优势在于:
- 避免PageModel的继承层级,代码结构更清晰
- 下拉逻辑集中在服务类,便于统一维护和单元测试
- 同一逻辑可在多个PageModel中复用,无需重复编写
内容的提问来源于stack exchange,提问作者Sam
相关产品推荐
相关产品推荐

