ASP.NET Core 6中如何从视图向控制器传递视图模型当前状态?
问题描述
我有一个由多个不同模型组成的EventViewModel,尝试在视图首次打开时部分填充,再通过控制器中的其他操作完成完整填充。我希望能将视图模型的当前状态作为参数传递给同一控制器中的多个操作,但尝试时视图模型总是返回null。
相关代码
EventViewModel
public class EventViewModel { public Event? Event { get; set; } public Client? Client { get; set; } public Employee? SelectedEmployee { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public List<Employee>? EmployeeList { get; set; } public List<EmployeeSchedule>? EmployeeSchedules { get; set; } }
EventController
//Get: /Event/Add public IActionResult Add() { EventViewModel viewModel = new EventViewModel(); viewModel.Schedules = new List<EmployeeSchedule>(); if (_db.Employees.Any()) { viewModel.EmployeeList = _db.Employees.ToList(); } else { viewModel.EmployeeList = new List<Employee>(); } return View(viewModel); /*returns ViewModel containing Non-null Employees and Schedules lists and Add View loads with correct values */ } [HttpPost] [ValidateAntiForgeryToken] public IActionResult Add(EventViewModel viewModel) //all ViewModel properties are null here { if (ModelState.IsValid) { _db.Events.Add(viewModel.Event); //viewModel."" possible null reference warning ... _db.SaveChanges(); return RedirectToAction("Index"); } else { return View(viewModel); } } /*This method is meant to add selected employees from the employee table to the Schedule table with schedule times appended from picker. I am sure this is wrong, but I can't fix it until I can get the ViewModel back*/ public void ScheduleEmployee(EventViewModel viewModel) //all ViewModel properties null here { viewModel.EmployeeSchedule.Id = viewModel.SelectedEmployee.Id; viewModel.EmployeeSchedule.Name = viewModel.SelectedEmployee.Name; viewModel.Schedules.Add(viewModel.EmployeeSchedule); //how to return this to view? }
Event/Add.cshtml
@model MyProject.ViewModels.EventViewModel ... <!--how I am binding form input to ViewModel--> <label asp-for="Event.Location"></label> <input asp-for="Event.Location" class="form-control" /> <span asp-validation-for="Event.Location" class="text-danger"></span> ... <!--button that I need to return ViewModel to Controller Add Post--> <button type="submit" class="btn btn-primary"> Save</button> ... <!--button that needs to return ViewModel to Controller to add employee and schedule times to current state of EventViewModel.Schedules list--> <a asp-controller="Event" asp-action="ScheduleEmployee" class="btn btn-secondary"> Add To Schedule </a>
问题原因及解决办法
核心问题分析
- 请求方式不匹配:
ScheduleEmployee默认是GET请求,<a>标签发起的GET请求无法自动将页面复杂字段映射到EventViewModel,导致参数为null。 - 属性命名不一致:ViewModel中定义的是
EmployeeSchedules,但控制器里用了Schedules,直接导致赋值失败。 - 表单未保留完整状态:POST请求时,页面只提交了少数字段(比如
Event.Location),未提交的属性会被初始化为null。
分步修复
1. 修正属性命名不一致
将ViewModel中的EmployeeSchedules改为Schedules,和控制器代码对齐:
public class EventViewModel { // ... 其他属性 public List<EmployeeSchedule>? Schedules { get; set; } }
2. 修复POST请求的模型绑定(Add方法)
对于不需要用户输入但需要保留的属性(比如EmployeeList、已添加的Schedules),用隐藏字段存储,确保提交时能传递给控制器:
<!-- 存储EmployeeList的关键字段 --> @{ int empIndex = 0; } @foreach (var emp in Model.EmployeeList) { <input type="hidden" asp-for="EmployeeList[@empIndex].Id" value="@emp.Id" /> <input type="hidden" asp-for="EmployeeList[@empIndex].Name" value="@emp.Name" /> empIndex++; } <!-- 存储已添加的Schedules --> @{ int schIndex = 0; } @foreach (var schedule in Model.Schedules) { <input type="hidden" asp-for="Schedules[@schIndex].Id" value="@schedule.Id" /> <input type="hidden" asp-for="Schedules[@schIndex].Name" value="@schedule.Name" /> schIndex++; }
3. 改造ScheduleEmployee方法
- 改为POST请求,因为GET不适合传递复杂对象:
[HttpPost] [ValidateAntiForgeryToken] public IActionResult ScheduleEmployee(EventViewModel viewModel) { if (viewModel.SelectedEmployee != null) { var newSchedule = new EmployeeSchedule { Id = viewModel.SelectedEmployee.Id, Name = viewModel.SelectedEmployee.Name, StartTime = viewModel.StartTime, EndTime = viewModel.EndTime }; viewModel.Schedules ??= new List<EmployeeSchedule>(); viewModel.Schedules.Add(newSchedule); } // 重新填充EmployeeList(避免提交后丢失) viewModel.EmployeeList = _db.Employees.ToList(); return View("Add", viewModel); }
- 将页面中的
<a>标签改为表单提交:
<form asp-controller="Event" asp-action="ScheduleEmployee" method="post"> @Html.AntiForgeryToken() <!-- 选择员工的下拉框 --> <select asp-for="SelectedEmployee.Id" asp-items="@(new SelectList(Model.EmployeeList, "Id", "Name"))" class="form-control"></select> <!-- 时间选择器 --> <input asp-for="StartTime" type="datetime-local" class="form-control" /> <input asp-for="EndTime" type="datetime-local" class="form-control" /> <!-- 存储已有的Schedules --> @{ int index = 0; } @foreach (var schedule in Model.Schedules) { <input type="hidden" asp-for="Schedules[@index].Id" value="@schedule.Id" /> <input type="hidden" asp-for="Schedules[@index].Name" value="@schedule.Name" /> index++; } <button type="submit" class="btn btn-secondary">Add To Schedule</button> </form>
4. 可选:用TempData暂存ViewModel状态
如果不想用大量隐藏字段,可以把ViewModel序列化后存在TempData中:
// Get Add方法中存储 public IActionResult Add() { EventViewModel viewModel = new EventViewModel(); viewModel.Schedules = new List<EmployeeSchedule>(); viewModel.EmployeeList = _db.Employees.ToList(); TempData["EventViewModel"] = System.Text.Json.JsonSerializer.Serialize(viewModel); return View(viewModel); } // ScheduleEmployee方法中读取并更新 [HttpPost] [ValidateAntiForgeryToken] public IActionResult ScheduleEmployee(EventViewModel postedData) { var storedViewModel = System.Text.Json.JsonSerializer.Deserialize<EventViewModel>(TempData["EventViewModel"].ToString()); if (storedViewModel != null && postedData.SelectedEmployee != null) { storedViewModel.StartTime = postedData.StartTime; storedViewModel.EndTime = postedData.EndTime; storedViewModel.Schedules.Add(new EmployeeSchedule { Id = postedData.SelectedEmployee.Id, Name = postedData.SelectedEmployee.Name, StartTime = postedData.StartTime, EndTime = postedData.EndTime }); TempData["EventViewModel"] = System.Text.Json.JsonSerializer.Serialize(storedViewModel); } return View("Add", storedViewModel); }
内容的提问来源于stack exchange,提问作者user17195990
相关产品推荐
相关产品推荐

