如何将ASP.NET Core的Account脚手架迁移至Controller?
解决ASP.NET Core Identity注册页面迁移至MVC视图的模型为空问题
问题根源
你直接复用Razor Page的RegisterModel是错误的,它是为Razor Page设计的PageModel子类,依赖Razor Page的上下文(如PageContext、ModelState)才能正常工作。手动实例化并调用OnGetAsync无法正确初始化这些依赖,导致视图无法识别模型,抛出空模型异常。
正确解决方案
1. 创建独立的MVC注册视图模型
不要复用Razor Page的RegisterModel,创建适配MVC的ViewModel:
using System.ComponentModel.DataAnnotations; public class RegisterViewModel { [Required] [EmailAddress] [Display(Name = "邮箱")] public string Email { get; set; } [Required] [StringLength(100, ErrorMessage = "密码长度至少为 {2} 个字符,最多为 {1} 个字符。", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "密码")] public string Password { get; set; } [DataType(DataType.Password)] [Display(Name = "确认密码")] [Compare("Password", ErrorMessage = "密码和确认密码不匹配。")] public string ConfirmPassword { get; set; } }
2. 改造AccountController的Register动作
直接在控制器中处理注册逻辑,不再依赖Razor Page的Model:
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Threading.Tasks; public class AccountController : Controller { private readonly UserManager<IdentityUser> _userManager; private readonly SignInManager<IdentityUser> _signInManager; private readonly ILogger<AccountController> _logger; private readonly IEmailSender _emailSender; public AccountController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, ILogger<AccountController> logger, IEmailSender emailSender) { _userManager = userManager; _signInManager = signInManager; _logger = logger; _emailSender = emailSender; } [HttpGet] public IActionResult Register() { return View(); } [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null) { returnUrl ??= Url.Content("~/"); if (ModelState.IsValid) { var user = new IdentityUser { UserName = model.Email, Email = model.Email }; var result = await _userManager.CreateAsync(user, model.Password); if (result.Succeeded) { _logger.LogInformation("用户创建新账号成功"); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); var callbackUrl = Url.Action(nameof(ConfirmEmail), "Account", new { userId = user.Id, code = code }, HttpContext.Request.Scheme); await _emailSender.SendEmailAsync(model.Email, "确认账号", $"请点击<a href='{callbackUrl}'>此链接</a>完成账号确认"); if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToAction(nameof(RegisterConfirmation), new { email = model.Email }); } else { await _signInManager.SignInAsync(user, isPersistent: false); return LocalRedirect(returnUrl); } } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } // 验证失败,返回表单 return View(model); } // 可选:添加账号确认动作 public IActionResult RegisterConfirmation(string email) { if (email == null) { return RedirectToAction(nameof(HomeController.Index), "Home"); } return View(new { Email = email }); } // 可选:实现邮箱确认动作 [HttpGet] public async Task<IActionResult> ConfirmEmail(string userId, string code) { if (userId == null || code == null) { return RedirectToAction(nameof(HomeController.Index), "Home"); } var user = await _userManager.FindByIdAsync(userId); if (user == null) { return NotFound($"无法找到ID为 {userId} 的用户"); } var result = await _userManager.ConfirmEmailAsync(user, code); return View(result.Succeeded ? "ConfirmEmail" : "Error"); } }
3. 创建MVC注册视图
在Views/Account/Register.cshtml中使用MVC视图语法,绑定新的ViewModel:
@model RegisterViewModel @{ ViewData["Title"] = "注册"; } <h1>@ViewData["Title"]</h1> <div class="row"> <div class="col-md-4"> <form asp-action="Register"> <h4>创建新账号</h4> <hr /> <div asp-validation-summary="All" class="text-danger"></div> <div class="form-group"> <label asp-for="Email"></label> <input asp-for="Email" class="form-control" /> <span asp-validation-for="Email" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="Password"></label> <input asp-for="Password" class="form-control" /> <span asp-validation-for="Password" class="text-danger"></span> </div> <div class="form-group"> <label asp-for="ConfirmPassword"></label> <input asp-for="ConfirmPassword" class="form-control" /> <span asp-validation-for="ConfirmPassword" class="text-danger"></span> </div> <button type="submit" class="btn btn-primary">注册</button> </form> </div> </div> @section Scripts { @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} }
关键说明
- Razor Page的PageModel和MVC的ViewModel设计逻辑完全不同,不能跨场景复用
- 手动实例化PageModel会缺失上下文依赖,导致模型状态异常
- MVC视图通过
asp-for标签助手绑定ViewModel字段,和Razor Page的语法兼容但依赖的模型类型不同
内容的提问来源于stack exchange,提问作者CitrusBoy
相关产品推荐
相关产品推荐

