You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:06:20