ASP.NET Core中两次使用PartialAsync()导致Input为null的问题
问题分析与解决方案
核心问题原因
- 模型绑定不匹配:通过
Html.PartialAsync传递registerModel给分部视图时,表单生成的字段名是Input.Email、Input.Password这类,但提交后ASP.NET Core会尝试将这些字段绑定到MainLoginModel,而非你构造注入的registerModel实例——因为注入的子PageModel不会被自动填充表单数据。 - 分部视图模型上下文错误:分部视图使用子PageModel时未设置正确的模型绑定前缀,导致提交的表单数据无法映射到子模型的
Input属性。
解决方案
方案一:重构主模型,直接包含输入模型(推荐)
将注册、登录的输入逻辑整合到MainLoginModel中,避免子PageModel的绑定问题:
后端代码(MainLogin.cshtml.cs)
public class MainLoginModel : PageModel { private readonly IMediator mediator; // 直接定义注册、登录的输入模型,并标记为可绑定 [BindProperty] public RegisterInput RegisterInput { get; set; } = new(); [BindProperty] public LoginInput LoginInput { get; set; } = new(); public bool ShowRegister { get; set; } public MainLoginModel(IMediator mediator) { this.mediator = mediator; } public async Task<IActionResult> OnGetAsync() { ShowRegister = (await mediator.Send(new CheckUsers())).UsersDontExist; return Page(); } public async Task<IActionResult> OnPostAsync() { ShowRegister = (await mediator.Send(new CheckUsers())).UsersDontExist; if (ShowRegister) { if (!ModelState.IsValid) return Page(); // 执行注册逻辑(把原RegisterModel.OnPostAsync里的代码移到这里,或封装成独立服务) await mediator.Send(new RegisterCommand(RegisterInput.Email, RegisterInput.Password)); } else { if (!ModelState.IsValid) return Page(); // 执行登录逻辑 await mediator.Send(new LoginCommand(LoginInput.Email, LoginInput.Password)); } return RedirectToPage("/Index"); } // 定义输入模型 public class RegisterInput { public string Email { get; set; } public string Password { get; set; } public string ConfirmPassword { get; set; } } public class LoginInput { public string Email { get; set; } public string Password { get; set; } } }
前端代码(MainLogin.cshtml)
修改分部视图的参数,传递对应的输入模型:
@model Path.To.MainLoginModel @if (Model.ShowRegister) { @await Html.PartialAsync("Path/To/Register.cshtml", Model.RegisterInput); }else{ @await Html.PartialAsync("Path/To/Login.cshtml", Model.LoginInput); }
同时调整分部视图的模型类型(比如Register.cshtml的模型改为RegisterInput),保持表单的asp-for绑定正常即可。
方案二:保留子PageModel,设置模型绑定前缀
如果不想重构原有子PageModel的逻辑,可通过设置视图数据前缀让表单字段与主模型的子属性匹配:
后端代码(MainLogin.cshtml.cs)
public class MainLoginModel : PageModel { private readonly IMediator mediator; private readonly IServiceProvider _serviceProvider; // 将子模型标记为可绑定属性 [BindProperty] public RegisterModel RegisterModel { get; set; } [BindProperty] public LoginModel LoginModel { get; set; } public bool ShowRegister { get; set; } public MainLoginModel(IMediator mediator, IServiceProvider serviceProvider) { this.mediator = mediator; _serviceProvider = serviceProvider; } public async Task<IActionResult> OnGetAsync() { // 初始化子模型 RegisterModel = _serviceProvider.GetRequiredService<RegisterModel>(); LoginModel = _serviceProvider.GetRequiredService<LoginModel>(); ShowRegister = (await mediator.Send(new CheckUsers())).UsersDontExist; return Page(); } public async Task<IActionResult> OnPostAsync() { ShowRegister = (await mediator.Send(new CheckUsers())).UsersDontExist; if (ShowRegister) { if (!ModelState.IsValid) return Page(); await RegisterModel.OnPostAsync(); } else { if (!ModelState.IsValid) return Page(); await LoginModel.OnPostAsync(); } return RedirectToPage("/Index"); } }
前端代码(MainLogin.cshtml)
调用分部视图时添加前缀:
@model Path.To.MainLoginModel @if (Model.ShowRegister) { @await Html.PartialAsync("Path/To/Register.cshtml", Model.RegisterModel, new ViewDataDictionary(ViewData) { Prefix = nameof(Model.RegisterModel) }); }else{ @await Html.PartialAsync("Path/To/Login.cshtml", Model.LoginModel, new ViewDataDictionary(ViewData) { Prefix = nameof(Model.LoginModel) }); }
这样分部视图生成的表单字段会自动带上RegisterModel.前缀(比如RegisterModel.Input.Email),提交后模型绑定会正确填充主模型的RegisterModel.Input属性。
内容的提问来源于stack exchange,提问作者GooseyLoosey
相关产品推荐
相关产品推荐

