ASP.NET Core MVC表单校验返回页面触发NullExceptionError异常
问题根因
空引用异常的原因非常直接:
- Razor Pages的执行逻辑是,POST请求只会执行对应的
OnPost方法,不会自动执行OnGetAsync里的代码。你现在的逻辑里,只有OnGetAsync会查询当前登录用户、加载对应的账户余额数据。 - 当表单提交的数据校验不通过时,
OnPost直接走return Page()返回视图,此时PageModel里的account字段根本没被赋值,是null,视图里访问Model.account.Amount必然触发空引用。 - 你现有代码还有两个隐藏的运行时异常点:一是查询收款方用户时用了
First(),如果输入的邮箱不存在,会直接抛“序列不含任何元素”的异常,根本走不到后面的null判断;二是OnGetAsync里查当前用户账户也用了First(),如果用户没有关联的账户记录,首次访问页面也会报错。
修复方案
1. 抽离公共数据加载逻辑,Post请求返回页面前也要加载基础数据
不要把页面必需的基础数据加载逻辑只写在GET方法里,抽成独立的私有方法,不管是GET访问页面,还是POST校验失败返回页面,都先把余额这类必需数据加载完成。
修改后的PageModel代码参考:
public class IndexModel : PageModel { private readonly ILogger<IndexModel> _logger; private readonly WebBankDbContext _context; private readonly UserManager<IdentityUser> userManager; private readonly SignInManager<IdentityUser> signInManager; public IdentityUser user; public Account account; [BindProperty] [DataType(DataType.EmailAddress)] public string Email { get; set; } [BindProperty] [RegularExpression("([1-9][0-9]*)", ErrorMessage = "转账金额必须为正整数")] public int AmountToSend { get; set; } public IndexModel(SignInManager<IdentityUser> signInManager, UserManager<IdentityUser> userManager, ILogger<IndexModel> logger, WebBankDbContext context) { _logger = logger; _context = context; this.userManager = userManager; this.signInManager = signInManager; } // 抽离的公共数据加载方法,所有返回页面的场景都调用 private async Task LoadCurrentUserDataAsync() { if (signInManager.IsSignedIn(User)) { user = await userManager.GetUserAsync(User); // 用FirstOrDefault代替First,无匹配记录时返回null而不是抛异常 account = _context.Accounts.FirstOrDefault(st => st.UserId == user.Id); } } public async Task<IActionResult> OnPostAsync(string returnUrl = null) { // 无论校验是否通过,先加载页面展示需要的用户、余额数据 await LoadCurrentUserDataAsync(); if (ModelState.IsValid) { // 查询收款方用户,同样用FirstOrDefault var destinationUserId = _context.Users .Where(st => st.Email == Email) .Select(st => st.Id) .FirstOrDefault(); if (destinationUserId == null) { ModelState.AddModelError(nameof(Email), "输入的邮箱未绑定有效用户"); return Page(); } // 此处补充转账核心逻辑:校验余额是否充足、开启数据库事务、扣减转出方余额、增加收款方余额、记录流水、提交事务 // 注意所有数据库操作建议用异步方法 if (string.IsNullOrEmpty(returnUrl) || returnUrl == "/") { return RedirectToPage("Index"); } return RedirectToPage(returnUrl); } // 校验失败返回页面时,account已经完成赋值,不会出现空引用 return Page(); } public async Task OnGetAsync() { await LoadCurrentUserDataAsync(); } }
2. 视图层加空值兜底
为了避免极端场景下的异常,视图里访问可能为null的字段时加空值判断,把显示余额的代码修改为:
<h5>Your balance: @(Model.account?.Amount ?? 0)$</h5>
上面的写法用了C#的null条件运算符和null合并运算符,如果account为null就默认显示0,不会直接抛出空引用错误。
其他优化建议
- 涉及数据库访问、Identity用户操作的逻辑尽量用异步方法,和框架的异步执行模型匹配,性能更好
- 所有不确定一定存在匹配数据的查询,不要用
First()、Single(),改用FirstOrDefault()、SingleOrDefault(),判断返回值为null时给出友好提示,不要让框架直接抛未处理异常 - 转账类涉及资金变动的操作必须加数据库事务,保证转出、转入两个操作的原子性,避免出现资金不一致的问题
内容的提问来源于stack exchange,提问作者dokichan
相关产品推荐
相关产品推荐

