表单验证后局部视图内Bootstrap模态框无法正常显示求助
问题描述
我有一个包含用户登录Bootstrap模态框的局部视图,当Model State验证失败时,错误信息会显示在单独的局部视图页面中,而非模态框内部。ModelState验证后的错误信息能正常显示,但位置不符合预期,求解决思路或建议。
相关代码
IdentityController.cs
public class IdentityController : MainController { private readonly IAuthenticateService _authenticationService; public IdentityController(IAuthenticateService authenticationService) { _authenticationService = authenticationService; } [HttpGet] [Route("authentication")] public IActionResult Login() { return PartialView("_UserLoginPartial"); } [HttpPost] [Route("authentication")] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(UserLogin userLogin) { if (!ModelState.IsValid) return PartialView("_UserLoginPartial", userLogin); var response = await _authenticationService.Login(userLogin); if (!ResponseContainsErrors(response.ResponseResult)) { await ExecuteLogin(response); return RedirectToAction("Index", "Home"); } TempData["Errors"] = ModelState.Values.SelectMany(v=> v.Errors.Select(e=>e.ErrorMessage)).ToList(); return RedirectToAction("Login", "Identity"); } [HttpGet] [Route("sair")] public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return RedirectToAction("Index", "Home"); } private async Task ExecuteLogin(UserResponseLogin response) { var token = GetFormattedToken(response.AccessToken); var claims = new List<Claim>(); claims.Add(new Claim("JWT", response.AccessToken)); claims.AddRange(token.Claims); var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var authProperties = new AuthenticationProperties { ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60), IsPersistent = true }; await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); } private static JwtSecurityToken GetFormattedToken(string jwtToken) { return new JwtSecurityTokenHandler().ReadToken(jwtToken) as JwtSecurityToken; } }
Index.cshtml
@{ ViewData["Title"] = "Home Page"; } <div class="modal fade" id="userRegistrationModal" tabindex="-1" role="dialog" aria-labelledby="registrationModalLabel" aria-hidden="true"> <partial name="_UserRegistrationPartial"></partial> </div> <div href="@Url.Action("Login","Identity")" class="modal fade" id="userLoginModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true"> <partial name="_UserLoginPartial"></partial> </div>
_UserLoginPartial.cshtml
@model WebApp.MVC.Models.UserLogin <link href="https://fonts.googleapis.com/css?family=Lato:300,400,700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"> <link href="~/css/style.css" rel="stylesheet" /> <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document"> <div class="modal-content"> <div class="modal-header" text-center> <h5 style="text-align:center" class="modal-title heading-section" id="exampleModalLabel">Log in</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body" id="bodyModal"> <div class="row justify-content-center"> <div class="col-md-6 col-lg-5"> <div class="login-wrap p-4 p-md-5"> <div class="icon d-flex align-items-center justify-content-center"> <span class="fa fa-user-o"></span> </div> </div> </div> </div> <form id="UserLoginForm" asp-action="Login" asp-controller="Identity" method="post" class="login-form"> <vc:summary></vc:summary> <div class="form-group d-flex"> <input type="email" asp-for="Email" placeholder="Email" class="form-control rounded-left" id="emailLogin"> <small><span asp-validation-for="Email" class="text-danger"></span></small> </div> <div class="form-group d-flex"> <input type="password" asp-for="Password" placeholder="Password" class="form-control rounded-left" id="passwordLogin"> <small><span asp-validation-for="Password" class="text-danger"></span></small> </div> <div class="modal-footer"> <button type="submit" id="submitLoginForm" name="submitLoginForm" class="btn btn-primary rounded submit p-3 px-5">Login</button> <button href="@Url.Action("Index","Home")" type="button" class="btn btn-secondary rounded submit p-3 px-5" data-dismiss="modal" id="closeLoginModal">Close</button> </div> </form> </div> <div> <p style="text-align:center">Don't have an account? <a href="@Url.Action("Register","Identity")" id="linkRegistration" data-toggle="modal" data-target="#userRegistrationModal" onclick="$('#userLoginModal').modal('hide');"><b>Sign Up</b></a></p> </div> </div> </div>
_LoginPartial.cshtml
@using WebApp.MVC.Extensions @inject IUser AppUser <ul class="navbar-nav"> @if (AppUser.IsAuthenticated()) { <li class="nav-item"> <a class="nav-link text-dark" title="Manage">Hello @AppUser.GetUserEmail()!</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-controller="Identity" asp-action="Logout">Logout</a> </li> } else { <li class="nav-item" style="padding-right: 30px;"> <a href="javascript:void(0)" class="btn btn-primary" data-toggle="modal" data-target="#userRegistrationModal" id="signUpLink">Sign Up</a> </li> <li class="nav-item"> <a href="javascript:void(0)" class="btn btn-primary" data-toggle="modal" data-target="#userLoginModal" id="loginLink">Log In</a> </li> } </ul>
_Layout.cshtml
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>@ViewData["Title"]</title> <link href="~/lib/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css" /> <link href="~/fonts/fontawesome/css/all.min.css" type="text/css" rel="stylesheet"> <link href="~/css/ui.css" rel="stylesheet" type="text/css" /> <link href="~/css/responsive.css" rel="stylesheet" /> <link href="~/css/style.css" rel="stylesheet" /> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <div class="container"> <a class="brand-wrap" asp-controller="Home" asp-action="Index"> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse"> <partial name="_LoginPartial" /> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a> </li> </ul> </div> </div> </nav> </header> <div class="container"> <main role="main" class="pb-3"> @RenderBody() </main> </div> <footer class="section-footer border-top"> <div class="container"> <section class="footer-top padding-y"> </section> <section class="footer-copyright border-top"> </section> </div> </footer> <script src="~/lib/jquery/dist/jquery.min.js" type="text/javascript"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script> <script src="~/js/site.js" type="text/javascript"></script> @RenderSection("Scripts", required: false) </body> </html>
解决思路
1. 移除重定向,直接返回带错误的局部视图
当前登录失败时使用RedirectToAction会导致浏览器跳转到单独的局部视图页面,应改为直接返回包含错误状态的局部视图:
// 替换原有TempData和RedirectToAction逻辑 foreach (var error in response.ResponseResult.Errors) { ModelState.AddModelError(string.Empty, error.Message); } return PartialView("_UserLoginPartial", userLogin);
2. 改用AJAX提交表单(推荐)
普通表单提交会刷新页面,无法保留模态框状态,改用AJAX实现无刷新提交:
在_UserLoginPartial.cshtml末尾添加脚本:
@section Scripts { <script> $("#UserLoginForm").submit(function(e) { e.preventDefault(); const form = $(this); $.ajax({ url: form.attr("action"), method: form.attr("method"), data: form.serialize(), success: function(response) { // 用返回的局部视图替换模态框内容 $("#userLoginModal .modal-dialog").html(response); // 登录成功则跳转首页 if (response.indexOf("modal-title") === -1) { window.location.href = "@Url.Action("Index", "Home")"; } }, error: function() { alert("登录失败,请检查信息后重试"); } }); }); </script> }
3. 兼容重定向场景(不推荐)
如果必须使用重定向,需在主页面读取TempData并注入到模态框:
在Index.cshtml中添加:
<script> $(document).ready(function() { const errors = @Html.Raw(Json.Serialize(TempData["Errors"])); if (errors?.length > 0) { $("#userLoginModal").modal("show"); // 生成错误提示HTML并插入到模态框 let errorHtml = '<div class="alert alert-danger">'; errors.forEach(err => errorHtml += `<p>${err}</p>`); errorHtml += '</div>'; $("#userLoginModal .login-form").prepend(errorHtml); } }); </script>
内容的提问来源于stack exchange,提问作者jo1991
相关产品推荐
相关产品推荐

