使用 Twitter Bootstrap 模态对话框获得更顺畅的登录体验






4.77/5 (9投票s)
本文介绍如何修改默认的 ASP.NET MVC 项目,使用 Twitter Bootstrap 模态对话框作为登录对话框。已包含对非 JavaScript 浏览器的优雅降级处理。
引言
本文介绍如何使用 Twitter Boostrap 框架的 Modal Dialog jQuery 插件,创建更流畅的用户登录体验。此解决方案可以轻松地集成到由默认 Visual Studio 2013 ASP.NET MVC 项目模板创建的 ASP.NET MVC 项目中。当用户点击“登录”导航项时,不再重定向到登录页面,而是将登录表单以模态对话框的形式打开。使用 Twitter Boostrap jQuery 模态对话框插件,可以将该对话框很好地集成到网站的整体 Boostrap 设计中。此解决方案带来了更流畅的用户登录体验。当用户禁用 JavaScript 时,用户将被重定向到登录页面,应用程序将正常运行。
贡献
本项目是在以下贡献者的支持下完成的:
- 在 ASP.NET MVC 中实现 AJAX 登录
- Forloop HtmlHelpers 帮助将脚本块或脚本文件引用放置到正确位置的 HtmlHelpers。可作为 NuGet 包安装到您的 Web 项目中。请参阅文档,了解如何在您的项目中添加和使用它。
背景
Visual Studio ASP.NET MVC 项目模板提供了一个登录解决方案,当用户点击“登录”导航项时,会将用户重定向到单独的登录视图。当今的 Web 用户期望更流畅的体验,这通常通过 DHTML、AJAX 和 JavaScript 实现。许多网站将登录体验实现为一个小型弹出窗口,该窗口会出现在调用页面之前,并在用户成功登录后将用户重定向到目标页面。
这就是本文的初衷,即提供更流畅的登录体验,尽可能多地利用 ASP.NET MVC 项目模板的默认功能,并遵循基于 Twitter Bootstrap 的默认 ASP.NET MVC 设计。有关 Twitter Bootstrap 的文档可以在 getbootstrap.com 上找到。有关 Bootstrap Modal 插件的文档可以在 此处找到。
模态对话框不会提交登录表单,直到用户成功登录。登录本身是通过对 Account 控制器上的一个附加 Action
方法进行的 AJAX 调用来启动的,而不是通过表单提交。成功和失败的结果在客户端处理,而不是像默认那样在服务器端处理。因为模态登录在页面打开时是不可见的,所以当托管模态登录表单的页面提交时,模态登录表单将被关闭。
Using the Code
代码包含在示例项目中。本项目基于 Visual Studio 2013 默认的 ASP.NET MVC 项目模板。
准备工作
也许您已经有一个 MVC 项目,并且想尝试将模态登录添加到您现有的项目中。或者,您可以在 Visual Studio 中创建一个新的 ASP.NET MVC 网站,并将必要的代码添加到该项目中。您可以将示例项目作为您自己 Web 项目的起点。它几乎与 Visual Studio 的默认项目模板相同。如前所述,默认的 ASP.NET MVC 项目模板包含 Twitter Bootstrap 资源(脚本和样式),因此在创建 ASP.NET MVC 项目后,您可以立即开始以下步骤。
在开始编写或复制代码到您的 Web 项目之前,建议您从 NuGet 安装 Forloop.HtmlHelpers
到您的项目。这个小助手可以帮助您在 `<body>` 标签的末尾添加脚本或脚本引用。安装助手后,您需要在现有的代码文件中添加以下两行代码:App_Start/BundleConfig.cs
在所有 bundle 配置之后添加以下代码行
ScriptContext.ScriptPathResolver = System.Web.Optimization.Scripts.Render;
Views/Shared/_Layout.cshtml
添加一个对 Forloop HtmlHelper
方法 RenderScripts()
的调用
...
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/jqueryval")
@RenderSection("scripts", required: false)
@Html.RenderScripts()
</body>
</html>
渲染 jQuery 验证脚本引用。其脚本 bundle 已在 BundleConfig.cs 中创建。因此,您只需要一个命令即可将该 bundle 的引用渲染到 Views/Shared/_Layout.cshtml。这确保了客户端验证,并避免提交表单来验证登录表单。将其添加到布局视图可以将其从基于该布局的视图中移除。
...
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/bundles/jqueryval")
@RenderSection("scripts", required: false)
@Html.RenderScripts()
</body>
</html>
创建模态登录视图
Twitter Bootstrap Modal 插件基于 jQuery 插件。它已集成到 Bootstrap 提供的默认 bootstrap.js 脚本资源中,并且由默认的 Visual Studio 2013 ASP.NET MVC 项目模板提供,因此您无需添加任何额外资源。
Bootstrap Modal 对话框由特定的 HTML 结构构成,并带有规定的类名,这在创建基于 Twitter Bootstrap 的网站时是众所周知的。构成 Modal 对话框外观和功能的 JavaScript 代码使用了这种结构。要将登录设置为模态对话框,我们需要做五件事。
- 基于 Twitter Bootstrap 创建模态对话框的 MVC 布局视图
- 将登录表单从 Login.cshtml 分离到一个部分视图,以便我们可以在模态对话框和登录页面中使用它
- 基于部分登录视图和第一步创建的模态布局,创建模态登录视图
- 在 Login.cshtml 视图中添加部分登录视图的引用
- 将模态登录表单添加到默认页面布局视图
为模态对话框创建 MVC 布局
在您的 Visual Studio 项目的 Views/Shared 文件夹中创建一个“MVC 5 布局页面”。例如,命名为 _LayoutModal.cshtml。此布局视图包含以下代码
@* Layout Page for Twitter Bootstrap Modal Dialogs *@
<div class='modal fade' id='@ViewBag.ModalId' tabindex='-1' role='dialog'
aria-labelledby='@ViewBag.ModalId@Html.Raw(' label')' aria-hidden='true'>
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-header'>
<button type='button' class='close' data-dismiss='modal'
aria-label='Close'><span aria-hidden='true'>×</span></button>
<h4 class='modal-title' id='@ViewBag.ModalId@Html.Raw(' label')'>
@ViewBag.ModalTitle</h4>
</div>
<div class='modal-body'>
@RenderBody()
</div>
<div class='modal-footer'>
<button type='button' class='btn btn-default' data-dismiss='modal'>Close</button>
</div>
</div>
</div>
</div>
将登录表单代码分离到一个部分视图
为了在模态对话框和默认登录页面中使用登录表单,我们在 Views/Shared 文件夹中创建一个 _LoginPartialForm.cshtml 部分视图。在创建视图时,选择“创建为部分视图”选项。
接下来,将 Login.cshtml 中的登录表单剪切并粘贴到新的部分视图 _LoginPartialForm.cshtml 中。我在 Login.cshtml 中使用了未经编辑的默认 Razor 标记。它看起来是这样的
@using Sample.Web.ModalLogin.Models
@model LoginViewModel
<div class='row'>
<div class='col-md-8'>
<section id='loginForm'>
@using (Html.BeginForm('Login', 'Account', new { ReturnUrl = ViewBag.ReturnUrl },
FormMethod.Post, new { @class = 'form-horizontal', role = 'form' }))
{
@Html.AntiForgeryToken()
<h4>Use a local account to log in.</h4>
<hr />
<div class='alert alert-danger' role='alert' style='display: none' id='alertBox'></div>
@Html.ValidationSummary(true, '', new { @class = 'text-danger' })
<div class='form-group'>
...
}
</section>
</div>
</div>
我们添加了一个 Bootstrap Alert
元素来显示登录错误和服务器端异常。我们编辑了一些 Bootstrap Grid 的 CSS 类,使其在模态对话框中看起来更好。
创建模态登录视图
下一步是创建一个视图,该视图使用第一步创建的 _LayoutModal.cshtml 并包含第二步创建的部分视图。
为此,请在 Views/Shared 下创建一个视图,并将其命名为,例如,LoginModal.cshtml。创建视图时,选择“使用布局页面”选项,然后选择 _LayoutModal.cshtml 布局视图。在视图中添加部分视图 _LoginPartialForm.cshtml。例如,在视图末尾添加对部分登录表单的引用。@Html.Partial("_LoginPartialForm")
以下代码片段显示了 LoginModal.cshtml 的内容。它将 _LoginPartialForm.cshtml 登录表单渲染为正文。有两个 ViewBag
属性需要设置:模态对话框的 id
属性和模态对话框的 title
标签。这是为了区分页面上是否存在多个模态对话框。视图开头的代码行确保包含我们模态登录客户端功能的 JavaScript 文件放置在 <body>
节点之后。这通过后面描述的 Forloop HtmlHelpers
库来实现。LoginModal.js 文件本身将在下面描述。
@using Forloop.HtmlHelpers
@using (Html.BeginScriptContext()){
Html.AddScriptFile("~/Scripts/app/views/LoginModal.js");
}
@{
Layout = "~/Views/Shared/_LayoutModal.cshtml";
ViewBag.ModalTitle = "Login";
ViewBag.ModalId = "ModalLogin";
}
@Html.Partial("_LoginPartialForm")
在 Login.cshtml
视图中添加部分登录视图的引用
与 LoginModal.cshtml 视图一样,您需要通过将 @Html.Partial("_LoginPartialForm")
添加到 Login.cshtml 来添加一个命令来渲染部分视图 _LoginPartialForm.cshtml。
将模态登录表单添加到默认页面布局
要打开模态登录对话框,我们需要将其添加到 _Layout.cshtml 布局视图中。
<body>
@if (!Request.IsAuthenticated)
{
@Html.Partial("LoginModal")
}
...
模态对话框需要位于 <body>
标签下的 DOM 树的最顶层。当然,只有在未进行身份验证时,才需要渲染模态登录表单及其脚本资源。
让模态登录表单工作起来
创建了模态登录表单本身之后,我们需要编辑“登录”导航项,以便在启用了 JavaScript 时显示模态登录表单,而不是重定向到默认的登录视图。这可以通过在页面加载时通过 JavaScript 操作“登录”导航链接的 DOM 来实现。以下代码片段可以在下面描述的 LoginModal.js 文件中找到。该链接仅在启用了 JavaScript 时进行编辑。否则,什么都不会发生,“登录”链接将像默认一样运行,并将用户重定向到登录页面。
$(document).ready(function () {
var loginLink = $("a[id='loginLink']");
loginLink.attr({ "href": "#", "data-toggle": "modal", "data-target": "#ModalLogin" })
});
为模态登录表单添加登录功能
如前所述,登录时我们不能提交模态登录表单,因为在这种情况下,即使登录不成功,模态登录也会关闭。更好的用户体验是让登录对话框保持打开状态,直到用户成功登录。因此,我们通过 AJAX 调用一个新的 Controller
方法来处理登录,该方法返回 JsonResult
而不是 ActionResult
。当用户通过点击“登录”按钮提交表单时,将通过 AJAX 请求调用此方法。
以下代码显示了这个 Action
方法。
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<jsonresult> LoginJson(string username, string password, bool rememberme)
{
var result = await SignInManager.PasswordSignInAsync
(username, password, rememberme, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return Json(true);
case SignInStatus.LockedOut:
case SignInStatus.RequiresVerification:
case SignInStatus.Failure:
return Json(false);
default:
break;
}
return Json(false);
}
这个示例使用了最简单的方法。它在没有额外验证的情况下进行登录,并在用户成功登录时简单地返回 true
,登录失败时返回 false
。
现在,我们需要处理客户端部分。为了更好地分离关注点(将功能与视图特定代码分开),我们可以将功能拆分为两个 JavaScript 文件。对于这个简单的示例,我将代码添加到一个文件中。以下是您可以在 Scripts/app/LoginModal.js 文件中找到的客户端脚本。
第一部分包含视图相关功能。我们处理表单的提交事件,通过 AJAX 调用 Account
控制器上的新 Action
方法 LoginJson
,并抑制表单提交。成功登录后,用户将被重定向到同一页面。发生错误或登录失败时(例如,由于提供了错误的凭据),会显示错误,但表单会保持打开状态。
与用户提供的凭据一起,我们必须将反伪造令牌(防止 CSRF 攻击)发布到 Action
方法。在 ASP.NET MVC 视图中,此令牌值可以在名为 __RequestVerificationToken
的隐藏输入字段中找到。
我们处理两个 Modal
事件 hidden.bs.modal
和 shown.bs.modal
,以便在用户关闭模态框时重置表单,并在模态框打开时将焦点设置到第一个输入字段。
$(document).ready(function () {
var loginLink = $("a[id='loginLink']");
loginLink.attr({ "href": "#", "data-toggle": "modal", "data-target": "#ModalLogin" });
$("#loginform").submit(function (event) {
if ($("#loginform").valid()) {
var username = $("#Email").val();
var password = $("#Password").val();
var rememberme = $("#RememberMe").val();
var antiForgeryToken = Sample.Web.ModalLogin.Views.Common.getAntiForgeryValue();
Sample.Web.ModalLogin.Identity.LoginIntoStd
(username, password, rememberme, antiForgeryToken,
Sample.Web.ModalLogin.Views.LoginModal.loginSuccess,
Sample.Web.ModalLogin.Views.LoginModal.loginFailure);
}
return false;
});
$("#ModalLogin").on("hidden.bs.modal", function (e) {
Sample.Web.ModalLogin.Views.LoginModal.resetLoginForm();
});
$("#ModalLogin").on("shown.bs.modal", function (e) {
$("#Email").focus();
});
});
var Sample = Sample || {};
Sample.Web = Sample.Web || {};
Sample.Web.ModalLogin = Sample.Web.ModalLogin || {};
Sample.Web.ModalLogin.Views = Sample.Web.ModalLogin.Views || {}
Sample.Web.ModalLogin.Views.Common = {
getAntiForgeryValue: function () {
return $('input[name="__RequestVerificationToken"]').val();
}
}
Sample.Web.ModalLogin.Views.LoginModal = {
resetLoginForm: function () {
$("#loginform").get(0).reset();
$("#alertBox").css("display", "none");
},
loginFailure: function (message) {
var alertBox = $("#alertBox");
alertBox.html(message);
alertBox.css("display", "block");
},
loginSuccess: function () {
window.location.href = window.location.href;
}
}
Sample.Web.ModalLogin.Identity = {
LoginIntoStd: function (username, password, rememberme, antiForgeryToken,
successCallback, failureCallback) {
var data = { "__RequestVerificationToken": antiForgeryToken,
"username": username, "password": password, "rememberme": rememberme };
$.ajax({
url: "/Account/LoginJson",
type: "POST",
data: data
})
.done(function (loginSuccessful) {
if (loginSuccessful) {
successCallback();
}
else {
failureCallback("Invalid login attempt.");
}
})
.fail(function (jqxhr, textStatus, errorThrown) {
failureCallback(errorThrown);
});
}
}
关注点
通过 Visual Studio 2013 项目模板为 ASP.NET MVC 应用程序提供的工具,开发人员可以使用集成的 Twitter Bootstrap Modal 对话框插件来增强登录体验。
历史
- 2015 年 5 月 16 日:版本 1.0
- 2015 年 6 月 12 日:版本 1.1
- 示例项目现在包含引用的程序集。不再需要通过程序包管理器控制台重新安装。