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

表单验证后局部视图内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">&times;</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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 12:51:57