ASP.NET Core 6 MVC用TempData跨页传参遇空引用异常求助
问题分析与解决方案
为什么TempData会触发空引用异常
TempData的设计是仅保留一次请求的数据,当你从Admin/Index页面跳转到其他视图时,TempData中存储的YearList和CurrentYear会被自动清除,导致后续视图访问时为null,触发空引用错误。另外你在_ViewStart.cshtml里初始化TempData["YearList"] = null,反而会覆盖有效数据,加重问题。
用TempData实现的可行方案
如果一定要用TempData,需要手动保留数据,让它能跨多次请求:
- 在AdminController的Index方法中,存储数据后用
Keep()标记需要保留的键:
public async Task<IActionResult> Index() { var allYears = await _context.Years.ToListAsync(); List<SelectListItem> yearList = new List<SelectListItem>(); foreach (var item in allYears) { yearList.Add(new SelectListItem { Text = item.Name, Value = item.Id.ToString() }); } TempData["YearList"] = yearList; TempData["CurrentYear"] = allYears.FirstOrDefault()?.Id.ToString(); // 空值保护 // 标记需要保留的键,避免一次请求后被清除 TempData.Keep("YearList"); TempData.Keep("CurrentYear"); return View(); }
- 在_Layout.cshtml中访问TempData时,使用
Peek()获取数据(不会触发清除),同时添加空值判断:
@if (TempData.Peek("YearList") is List<SelectListItem> yearList && TempData.Peek("CurrentYear") is string currentYear) { <select class="form-control" asp-for="@currentYear"> @foreach (var item in yearList) { <option value="@item.Value" @(item.Value == currentYear ? "selected" : "")>@item.Text</option> } </select> }
- 当用户选择年份提交时,在处理提交的Action中更新TempData并再次调用
Keep():
public IActionResult SetYear(int yearId) { // 获取年份列表(可以从数据库重新获取,或者从TempData.Peek获取) var yearList = TempData.Peek("YearList") as List<SelectListItem>; TempData["CurrentYear"] = yearId.ToString(); TempData["YearList"] = yearList; TempData.Keep("YearList"); TempData.Keep("CurrentYear"); return RedirectToAction("Index", "Home"); // 跳转到目标页面 }
注意:即使这样,TempData仍然会在浏览器关闭后丢失数据,且跨标签页不共享,不是最优解。
更优方案:使用ViewComponent
ViewComponent是ASP.NET Core中用于复用视图逻辑的组件,适合处理这种全局的下拉列表:
- 创建YearSelectorViewComponent:
public class YearSelectorViewComponent : ViewComponent { private readonly YourDbContext _context; private readonly IHttpContextAccessor _httpContextAccessor; public YearSelectorViewComponent(YourDbContext context, IHttpContextAccessor httpContextAccessor) { _context = context; _httpContextAccessor = httpContextAccessor; } public async Task<IViewComponentResult> InvokeAsync() { // 从Session获取当前选中的年份,默认取第一个 var currentYearId = _httpContextAccessor.HttpContext.Session.GetString("CurrentYear"); var allYears = await _context.Years.ToListAsync(); var yearList = allYears.Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString(), Selected = y.Id.ToString() == currentYearId }).ToList(); // 如果没有选中的年份,设置默认值 if (string.IsNullOrEmpty(currentYearId) && yearList.Any()) { currentYearId = yearList.First().Value; _httpContextAccessor.HttpContext.Session.SetString("CurrentYear", currentYearId); } return View(yearList); } }
- 创建ViewComponent的视图(
Views/Shared/Components/YearSelector/Default.cshtml):
@model List<SelectListItem> <form asp-action="SetYear" asp-controller="Admin" method="post"> <select class="form-control" name="yearId" onchange="this.form.submit()"> @foreach (var item in Model) { <option value="@item.Value" @(item.Selected ? "selected" : "")>@item.Text</option> } </select> </form>
- 在_Layout.cshtml中调用ViewComponent:
@await Component.InvokeAsync("YearSelector")
- 添加SetYear的Action处理提交:
[HttpPost] public IActionResult SetYear(int yearId) { HttpContext.Session.SetString("CurrentYear", yearId.ToString()); return Redirect(Request.Headers["Referer"].ToString()); // 返回到之前的页面 }
- 确保Startup/Program中启用Session:
// Program.cs builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromHours(2); // 设置Session超时时间 }); app.UseSession(); // 放在UseRouting之后,UseAuthorization之前
其他补充建议
- 避免在
_ViewStart.cshtml中初始化TempData/Session,因为它会在每个视图渲染前执行,可能覆盖用户的选择。 - 所有需要根据年份获取数据的Action,都可以从Session中读取当前年份:
var currentYearId = HttpContext.Session.GetString("CurrentYear"); if (!string.IsNullOrEmpty(currentYearId) && int.TryParse(currentYearId, out int yearId)) { // 根据yearId查询对应数据 }
内容的提问来源于stack exchange,提问作者Dahlia
相关产品推荐
相关产品推荐

