天,我们将学习如何制作一个在悬停时展开和折叠的侧边栏。它看起来很漂亮,实现比看起来简单得多。我们将逐步完成本教程,并且在此过程中我还将介绍一些值得注意的HTML / CSS技巧。
以下是本文将涉及的提示和技巧列表:
让我们开始!
可以在此笔中找到此步骤的完整代码:https://codepen.io/dalisc/pen/rEjRWo
只使用HTML,您的网页将如下图所示。一些CSS可以做出什么改变!所以我们需要用一些CSS来设置它,使其看起来像上面的gif。
.sidebar { height: 100%; width: 250px; position: fixed; top: 0; left: 0; background-color: #111; padding-top: 60px;
此代码为侧边栏提供250px(width:250px;)的宽度,背景颜色为深色(background-color:#111;),并使其在页面上完全垂直延伸(height:100%;)。在code pen以自定义侧边栏。
.sidebar a { padding: 8px 8px 8px 32px; text-decoration: none; font-size: 25px; color: #818181; display: block; }
在这里,我们使用“text-decoration:none;”删除了文本的下划线和蓝色,并在侧边栏项目周围添加了填充和块显示的一些空格。增强用户体验的部分是当项目颜色从灰色变为白色时,这可以通过以下代码完成:
.sidebar a:hover { color: #f1f1f1; }
此代码表示当您将鼠标悬停在类“侧栏”中用<a>标记的元素上时,元素的颜色将更改为您设置的任何颜色,在本例中为#f1f1f1。
如果您使用Google的素材图标,您会发现一个令人沮丧的问题:默认情况下,图标和相邻文字没有正确垂直对齐。
.material-icons, .icon-text { vertical-align: middle; } .material-icons { padding-bottom: 3px; margin-right: 30px; }
你需要做的是在CSS中垂直对齐它们(vertical-align:middle;)。即使这样,对齐也有点偏离,所以在此之后给你的图标一个3px垂直增强(padding-bottom:3px;)。
现在我们将添加一些Javascript,因为我们将在侧边栏中引入一些功能。可以在此笔中找到此步骤的完整代码
两个非常有用的事件是onmouseover和onmouseout,它们分别检测您的鼠标是否悬停在特定元素之上或之外。对于我们的侧边栏,我们希望检测位于侧边栏的任何部分,因此我们需要将这些事件添加到侧边栏的<div>中,如下所示:
<div id=”mySidebar” class=”sidebar” onmouseover=”somethinghappens” onmouseout=”somethinghappens”>
现在,我们可以决定鼠标悬停在侧边栏上或从边栏悬停的情况。我们需要将“ somethinghappens”替换为我们想要实际发生的事情,但首先,让我们通过向控制台发送消息来检查是否发生了检测。
现在让我们编写两个Javascript函数来确认检测到事件:
function testIn() { console.log(“hovering in sidebar”); } function testOut() { console.log(“hovering outside sidebar”); }
更新我们的侧边栏:
<div id=”mySidebar” class=”sidebar” onmouseover=”testIn()” onmouseout=”testOut()”>
现在进行悬停并检查控制台以查找我们编写的消息。它应该可以工作了!我们已经设置了告诉侧边栏是折叠还是展开所需的检测。
可以在此笔中找到此最终部分的完整代码:https://codepen.io/dalisc/pen/qzRGxQ
我们将折叠边栏称为迷你侧边栏。我们现在想要根据鼠标是否悬停在我的侧边栏上进行两次查找,因此我们需要在javascript部分中创建一个布尔变量mini。
我们还将创建一个函数来切换侧边栏的扩展。该功能的逻辑如下:如果侧边栏处于迷你模式,将鼠标悬停在侧边栏上会将侧边栏扩展为其完整模式(并将变量mini设置为false)。如果侧边栏处于完全模式,将鼠标悬停在侧边栏上会将其折叠为迷你模式(并将变量mini设置为true)。
因此,我们需要更改onmouseover和onmouseout事件,并相应地引入新函数toggleSidebar()。
更改html:
<div id=”mySidebar” class=”sidebar” onmouseover=”toggleSidebar()” onmouseout=”toggleSidebar()”>
添加到JS(我们现在可以删除testIn()和testOut()):
var mini = true; function toggleSidebar() { if (mini) { console.log(“opening sidebar”); document.getElementById(“mySidebar”).style.width = “250px”; document.getElementById(“main”).style.marginLeft = “250px”; this.mini = false; } else { console.log(“closing sidebar”); document.getElementById(“mySidebar”).style.width = “100px”; document.getElementById(“main”).style.marginLeft = “100px”; this.mini = true; } }
从功能中可以看出,它基本上都是改变侧边栏黑色块的宽度。完整模式的宽度为250px,迷你模式的宽度为85px。我们还策略性地定位文本和图标,以便在侧边栏折叠时完全隐藏文本,仅显示图标。
默认情况下,我们希望侧边栏处于迷你模式,因此我们也将侧边栏的宽度(最初未被遮挡时)更改为85px。
.sidebar { height: 100%; width: 85px; position: fixed; z-index: 1; top: 0; left: 0; background-color: #111; transition: 0.5s; padding-top: 60px; }
当前默认外观:
此时,文本溢出仍有一些问题,所以我将介绍一些CSS提示和技巧!
将“white-space:nowrap;”添加到侧栏CSS。
.sidebar { height: 100%; width: 85px; position: fixed; top: 0; left: 0; background-color: #111; padding-top: 60px; white-space: nowrap; }
即使文本大于侧边栏的宽度,这也会阻止文本换行到下一行。但正如你在下面看到的那样,虽然它现在在一行中,它会溢出,你可以看到溢出...所以我们需要找到隐藏它的方法!
s
要隐藏溢出的文本,只需将“overflow-x:hidden;”和“z-index:1;”添加到侧边栏css即可。这将隐藏任何宽于侧边栏宽度的内容。
.sidebar { height: 100%; width: 85px; position: fixed; z-index: 1; top: 0; left: 0; background-color: #111; overflow-x: hidden; padding-top: 60px; white-space: nowrap; }
现在我们的侧边栏看起来非常好!(我也改变了主要内容,但主要内容未在本教程中介绍,它包含在code pen。)
现在我们已经遇到了我们需要修复的最后一个小故障,以使侧边栏顺利移动。目前,随着侧边栏折叠和展开,没有动画添加到它,所以它看起来有点不连贯,像这样:
所以我们想要的是一个非常平滑的过渡,如第一页上的gif所示。现在,变化立即发生。为了顺利,我们需要减缓变化。首先,我们需要让侧边栏扩展得更慢,比方说0.5秒。将其添加到侧边栏CSS。
.sidebar { height: 100%; width: 85px; position: fixed; z-index: 1; top: 0; left: 0; background-color: #111; overflow-x: hidden; transition: 0.5s; padding-top: 60px; white-space: nowrap; }
我们还需要将主要部分同时推到左侧。
#main { transition: margin-left .5s; padding: 16px; margin-left: 85px; }
你有一个漂亮的侧边栏!
在GitHub存储库中找到完整的工作代码:https://github.com/dalisc/hover-collapsible-sidebar
转:https://medium.com/@9cv9official/create-a-beautiful-hover-triggered-expandable-sidebar-with-simple-html-css-and-javascript-9f5f80a908d1
一个有限的空间内显示用于呈现信息的可折叠的内容面板。
如需了解更多有关 accordion 部件的细节,请查看 API 文档 折叠面板部件(Accordion Widget)。
默认功能
点击头部展开/折叠被分为各个逻辑部分的内容,就像标签页(tabs)一样。您可以选择性地设置当鼠标悬停时是否切换各部分的打开/关闭状态。
基本的 HTML 标记是一系列的标题(H3 标签)和内容 div,因此内容不用通过 JavaScript 即可用。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 默认功能</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script> $(function() { $( "#accordion" ).accordion(); }); </script> </head> <body> <div id="accordion"> <h3>部分 1</h3> <div> <p> Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate. </p> </div> <h3>部分 2</h3> <div> <p> Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> <h3>部分 3</h3> <div> <p> Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item one</li> <li>List item two</li> <li>List item three</li> </ul> </div> <h3>部分 4</h3> <div> <p> Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p> <p> Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </div> </body> </html>
折叠内容
默认情况下,折叠面板总是保持一个部分是打开的。为了让所有部分都是折叠的,可设置 collapsible
选项为 true。点击当前打开的部分,来折叠它的内容面板。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 折叠内容</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script> $(function() { $( "#accordion" ).accordion({ collapsible: true }); }); </script> </head> <body> <div id="accordion"> <h3>部分 1</h3> <div> <p>Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.</p> </div> <h3>部分 2</h3> <div> <p>Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> <h3>部分 3</h3> <div> <p>Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item one</li> <li>List item two</li> <li>List item three</li> </ul> </div> <h3>部分 4</h3> <div> <p>Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p><p>Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </div> </body> </html>
查看演示
自定义图标
通过 icons
选项自定义标题图标,icons
选项接受标题默认的和激活的(打开的)状态的 class。使用 UI CSS 框架中的任意 class,或者使用背景图像创建自定义的 class。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 自定义图标</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script> $(function() { var icons = { header: "ui-icon-circle-arrow-e", activeHeader: "ui-icon-circle-arrow-s" }; $( "#accordion" ).accordion({ icons: icons }); $( "#toggle" ).button().click(function() { if ( $( "#accordion" ).accordion( "option", "icons" ) ) { $( "#accordion" ).accordion( "option", "icons", null ); } else { $( "#accordion" ).accordion( "option", "icons", icons ); } }); }); </script> </head> <body> <div id="accordion"> <h3>部分 1</h3> <div> <p>Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.</p> </div> <h3>部分 2</h3> <div> <p>Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> <h3>部分 3</h3> <div> <p>Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item one</li> <li>List item two</li> <li>List item three</li> </ul> </div> <h3>部分 4</h3> <div> <p>Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p><p>Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </div> <button id="toggle">切换图标</button> </body> </html>
查看演示
填充空间
由于折叠面板是由块级元素组成的,默认情况下它的宽度会填充可用的水平空间。为了填充由容器分配的垂直空间,设置 heightStyle
选项为 "fill"
,脚本会自动设置折叠面板的尺寸为父容器的高度。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 填充空间</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <style> #accordion-resizer { padding: 10px; width: 350px; height: 220px; } </style> <script> $(function() { $( "#accordion" ).accordion({ heightStyle: "fill" }); }); $(function() { $( "#accordion-resizer" ).resizable({ minHeight: 140, minWidth: 200, resize: function() { $( "#accordion" ).accordion( "refresh" ); } }); }); </script> </head> <body> <h3>重新调整外部容器:</h3> <div id="accordion-resizer"> <div id="accordion"> <h3>部分 1</h3> <div> <p>Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.</p> </div> <h3>部分 2</h3> <div> <p>Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> <h3>部分 3</h3> <div> <p>Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item one</li> <li>List item two</li> <li>List item three</li> </ul> </div> <h3>部分 4</h3> <div> <p>Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p><p>Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </div> </div> </body> </html>
非自动高度
设置 heightStyle: "content"
,让折叠面板保持它们初始的高度。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 非自动高度</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script> $(function() { $( "#accordion" ).accordion({ heightStyle: "content" }); }); </script> </head> <body> <div id="accordion"> <h3>部分 1</h3> <div> <p>Mauris mauris ante, blandit et, ultrices a, susceros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.</p> </div> <h3>部分 2</h3> <div> <p>Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> <h3>部分 3</h3> <div> <p>Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item</li> <li>List item</li> <li>List item</li> <li>List item</li> <li>List item</li> <li>List item</li> <li>List item</li> </ul> </div> </div> </body> </html>
查看演示
当悬停时打开
点击头部展开/折叠被分为各个逻辑部分的内容,就像标签页(tabs)一样。您可以选择性地设置当鼠标悬停时是否切换各部分的打开/关闭状态。
基本的 HTML 标记是一系列的标题(H3 标签)和内容 div,因此内容不用通过 JavaScript 即可用。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 当悬停时打开</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <script> $(function() { $( "#accordion" ).accordion({ event: "click hoverintent" }); }); /* * hoverIntent | Copyright 2011 Brian Cherne * http://cherne.net/brian/resources/jquery.hoverIntent.html * modified by the jQuery UI team */ $.event.special.hoverintent = { setup: function() { $( this ).bind( "mouseover", jQuery.event.special.hoverintent.handler ); }, teardown: function() { $( this ).unbind( "mouseover", jQuery.event.special.hoverintent.handler ); }, handler: function( event ) { var currentX, currentY, timeout, args = arguments, target = $( event.target ), previousX = event.pageX, previousY = event.pageY; function track( event ) { currentX = event.pageX; currentY = event.pageY; }; function clear() { target .unbind( "mousemove", track ) .unbind( "mouseout", clear ); clearTimeout( timeout ); } function handler() { var prop, orig = event; if ( ( Math.abs( previousX - currentX ) + Math.abs( previousY - currentY ) ) < 7 ) { clear(); event = $.Event( "hoverintent" ); for ( prop in orig ) { if ( !( prop in event ) ) { event[ prop ] = orig[ prop ]; } } // 防止访问原始事件,因为新事件会被异步触发,旧事件不再可用 (#6028) delete event.originalEvent; target.trigger( event ); } else { previousX = currentX; previousY = currentY; timeout = setTimeout( handler, 100 ); } } timeout = setTimeout( handler, 100 ); target.bind({ mousemove: track, mouseout: clear }); } }; </script> </head> <body> <div id="accordion"> <h3>部分 1</h3> <div> <p> Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate. </p> </div> <h3>部分 2</h3> <div> <p> Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> <h3>部分 3</h3> <div> <p> Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item one</li> <li>List item two</li> <li>List item three</li> </ul> </div> <h3>部分 4</h3> <div> <p> Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p> <p> Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </div> </body> </html>
查看演示
排序(Sortable)
拖拽标题来给面板重新排序。
<!doctype html> <html> <head> <meta charset="utf-8"> <title>jQuery UI 折叠面板(Accordion) - 排序(Sortable)</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.10.4/themes/smoothness/jquery-ui.css"> <script src="//code.jquery.com/jquery-1.9.1.js"></script> <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" href="http://jqueryui.com/resources/demos/style.css"> <style> /* 当排序时,IE 存在布局问题(查看 #5413) */ .group { zoom: 1 } </style> <script> $(function() { $( "#accordion" ) .accordion({ header: "> div > h3" }) .sortable({ axis: "y", handle: "h3", stop: function( event, ui ) { // 当排序时,IE 不能注册 blur,所以触发 focusout 处理程序来移除 .ui-state-focus ui.item.children( "h3" ).triggerHandler( "focusout" ); } }); }); </script> </head> <body> <div id="accordion"> <div> <h3>部分 1</h3> <div> <p>Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.</p> </div> </div> <div> <h3>部分 2</h3> <div> <p>Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In suscipit faucibus urna. </p> </div> </div> <div> <h3>部分 3</h3> <div> <p>Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis. Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui. </p> <ul> <li>List item one</li> <li>List item two</li> <li>List item three</li> </ul> </div> </div> <div> <h3>部分 4</h3> <div> <p>Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p><p>Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. </p> </div> </div> </div> </body> </html>
们经常使用的各类网站和App均会涉及注册、登录和修改密码等功能,登录系统后,有些功能会提示没有权限,甚至有些位置我们无法访问,这些都是系统权限和认证的体现。
我们从本章及后面的章节中,将学习在ASP.NET Core应用程序中使用ASP.NET Core Identity实现安全认证相关功能所需要掌握的知识。
本章主要向读者介绍如下内容。
ASP.NET Core Identity是一个会员身份系统,早期它的名字是Membership,当然那是一段“古老”的历史,现在我们来了解全新的Identity。它允许我们创建、读取、更新和删除账户。支持账号验证、身份验证、授权、恢复密码和SMS双因子身份验证。它还支持微软、Facebook和Google等第三方登录提供商。它提供了一个丰富的API,并且这些API还可以进行大量的扩展。我们将在本书的后面实现这些功能。
这里采用的是EF Core,因为要让我们的系统支持Identity服务,所以需要安装它的程序包。打开NuGet管理器,安装Microsoft.AspNetCore.Identity.EntityFrameworkCore即可。
以下是添加和配置ASP.NET Core Identity服务的步骤。
使AppDbContext继承类IdentityDbContext,然后引入命名空间,代码如下。
public class AppDbContext:IdentityDbContext
{
//其余代码
}
配置ASP.NET Core Identity服务。在Startup类的ConfigureServices()方法中,添加以下代码行。
services.AddIdentity<IdentityUser,IdentityRole>()
.AddEntityFrameworkStores<AppDbContext>();
接下来,将Authentication()中间件添加到请求管道,代码如下。
public void Configure(IApplicationBuilder app,IWebHostEnvironment env)
{
//如果环境是Development serve Developer Exception Page
if(env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//否则显示用户友好的错误页面
else if(env.IsStaging() || env.IsProduction() || env.IsEnvironment("UAT"))
{
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error/{0}");
}
//使用纯静态文件支持的中间件,而不使用带有终端的中间件
app.UseStaticFiles();
//添加验证中间件
app.UseAuthentication();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name:"default",
pattern:"{controller=Home}/{action=Index}/{id?}");
});
}
在Startup类的Configure()方法中,调用UseAuthentication()方法将Authentication()中间件添加到应用程序的请求处理管道中。我们希望能够在请求到达MVC中间件之前对用户进行身份验证。因此,在请求处理管道的UseRouting()中间件之前添加认证中间件。这很重要,因为我们之前讲过中间件的添加顺序不能乱。
现在开始添加身份迁移。在Visual Studio中的程序包控制台窗口执行以下命令以添加新迁移。
Add-Migration AddingIdentity
此迁移包含用于创建ASP.NET Core Identity系统所需的表的代码。
如果运行,则会出现以下错误。
The entity type'IdentityUserLogin'requires a primary key to be defined.
之前因为要封装Seed()方法,所以重写OnModelCreating()方法。出现这个错误是因为我们在DbContext类中重写了OnModelCreating()方法,但未调用基本IdentityDbContext类OnModelCreating()方法。
Identity表的键映射在IdentityDbContext类的OnModelCreating()方法中。因此,要解决这个错误,需要做的是,调用基类OnModelCreating()使用该方法的基础关键字,代码如下。
public class AppDbContext:IdentityDbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options):base(options)
{
}
public DbSet<Student> Students{get;set;}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Seed();
}
}
执行Update-Database命令以应用迁移记录并创建所需的身份表,如图21.1所示。
图21.1
现在已经创建好了表的信息,接下来我们增加一个注册功能,让用户能够注册到系统中。
新用户注册视图应如图21.2所示。为了能够注册为新用户,需要邮箱地址和密码两个字段。
图21.2
我们将使用RegisterViewModel类作为Register视图的模型,它负责将视图中的信息传递给控制器。为了验证信息是否正确,我们使用了几个ASP.NET Core验证属性。在之前的章节中详细说明过这些属性和模型验证。
using System.ComponentModel.DataAnnotations;
namespace MockSchoolManagement.ViewModels
{
public class RegisterViewModel
{
[Required]
[EmailAddress]
[Display(Name = "邮箱地址")]
public string Email{get;set;}
[Required]
[DataType(DataType.Password)]
[Display(Name = "密码")]
public string Password{get;set;}
[DataType(DataType.Password)]
[Display(Name = "确认密码")]
[Compare("Password",
ErrorMessage = "密码与确认密码不一致,请重新输入.")]
public string ConfirmPassword{get;set;}
}
}
在这里我们添加了DataType特性,它的主要作用是指定比数据库内部类型更具体的数据类型。DataType枚举提供了多种数据类型,比如日期、时间、电话号码、货币和邮箱地址等。但是请注意,DataType特性不提供任何验证,它主要服务于我们的视图文件,比如,DataType.EmailAddress可以在视图中创建mailto:链接,DataType.Date则会在支持HTML5的浏览器中提供日期选择器。
账户控制器(AccountController)是指所有与账户相关的CRUD(增加、读取、更新和删除)操作都将在此控制器中。目前我们只有Register()操作方法,可以通过向/account/register发出GET请求来实现此操作方法。
using Microsoft.AspNetCore.Mvc;
namespace MockSchoolManagement.Controllers
{
public class AccountController:Controller
{
[HttpGet]
public IActionResult Register()
{
return View();
}
}
}
将此视图放在Views/Account文件夹中,此视图的模型是我们在前面创建的Register ViewModel。
@model RegisterViewModel
@{ViewBag.Title = "用户注册";}
<h1>用户注册</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<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>
在布局视图中添加注册按钮,我们需要在_Layout.cshtml文件中找到ID为collapsibleNavbar的导航菜单栏,在下方添加注册按钮,导航到对应的视图,代码如下。
<div id="collapsibleNavbar" class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="Index">学生列表</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="Create">添加学生</a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" asp-controller="account" asp-action="register"> 注册 </a>
</li>
</ul>
</div>
运行项目后,单击注册按钮即可看到图21.2所示的效果图,接下来我们实现处理HttpPOST请求到/account/register的Register()操作方法。然后通过表单Taghelpers将数据发布到ASP.NET Core Identity中创建账户。
在本节我们学习使用ASP.NET Core Identity提供的UserManager服务创建新用户,然后使用其提供的SignInManager服务来登录用户。
UserManager <IdentityUser>类包含管理基础数据存储中的用户所需的方法。比如,此类具有CreateAsync()、DeleteAsync()和UpdateAsync()等方法来创建、删除和更新用户,如图21.3所示。
图21.3
SignInManager <IdentityUser>类包含用户登录所需的方法。比如,SignInManager类具有SignInAsync()、SignOutAsync()等方法来登录和注销用户,如图21.4所示。
图21.4
以下是AccountController的完整代码。
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MockSchoolManagement.ViewModels;
using System.Threading.Tasks;
namespace MockSchoolManagement.Controllers
{
public class AccountController:Controller
{
private UserManager<IdentityUser> _userManager;
private SignInManager<IdentityUser> _signInManager;
public AccountController(UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
this._userManager = userManager;
this._signInManager = signInManager;
}
[HttpGet]
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel model)
{
if(ModelState.IsValid)
{
//将数据从RegisterViewModel复制到IdentityUser
var user = new IdentityUser
{
UserName = model.Email,
Email = model.Email
};
//将用户数据存储在AspNetUsers数据库表中
var result = await _userManager.CreateAsync(user,model.Password);
//如果成功创建用户,则使用登录服务登录用户信息
//并重定向到HomeController的索引操作
if(result.Succeeded)
{
await _signInManager.SignInAsync(user,isPersistent:false);
return RedirectToAction("index","home");
}
//如果有任何错误,则将它们添加到ModelState对象中
//将由验证摘要标记助手显示到视图中
foreach(var error in result.Errors)
{
ModelState.AddModelError(string.Empty,error.Description);
}
}
return View(model);
}
}
}
此时,如果读者运行项目并提供有效的邮箱地址和密码,则它会在SQL Server数据库的AspNetUsers表中创建账户。读者可以从Visual Studio的SQL Server对象资源管理器中查看此数据,如图21.5所示。
图21.5
在刚刚注册的时候,我们发现有两个问题。
这是因为ASP.NET Core IdentityOptions类在ASP.NET Core中用于配置密码复杂性规则。默认情况下,ASP.NET Core身份不允许创建简单的密码来保护我们的应用程序免受自动暴力攻击。
当我们尝试使用像abc这样的简单密码注册新账户时,会显示创建失败,读者将看到如图21.6所示的验证错误。
图21.6
我们在图21.6中看到中文提示,后面的章节会告诉读者如何配置。
在ASP.NET Core Identity中,密码默认设置在PasswordOptions类中。读者可以在ASP.NET Core GitHub仓库中找到此类的源代码。只需在仓库中搜索PasswordOptions类。
代码如下。
public class PasswordOptions
{
public int RequiredLength{get;set;} = 6;
public int RequiredUniqueChars{get;set;} = 1;
public bool RequireNonAlphanumeric{get;set;} = true;
public bool RequireLowercase{get;set;} = true;
public bool RequireUppercase{get;set;} = true;
public bool RequireDigit{get;set;} = true;
}
相关参数的说明如表21.1(略)所示。
我们可以通过在Startup类的ConfigureServices()方法中使用IServiceCollection接口的Configure()方法来实现这一点。
services.Configure<IdentityOptions>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 3;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = false;
});
也可以在添加身份服务时执行此操作,代码如下。
services.AddIdentity<IdentityUser,IdentityRole>(options =>
{
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 3;
options.Password.RequireNonAlphanumeric = false;
})
.AddEntityFrameworkStores<AppDbContext>();
当然,在这里推荐使用IdentityOptions的形式进行配置,因为它可以作为一个独立服务,而不是嵌套在AddIdentity()方法中。
IdentityOptions对象中除了Password的配置信息,还有用户、登录、策略等配置信息,我们可以根据不同的场景进行灵活的配置。
Identity提供了AddErrorDescriber()方法,可方便我们进行错误内容的配置和处理。
ASP.NET Core默认提供的都是英文提示,我们可以将它们修改为中文。现在我们创建一个CustomIdentityErrorDescriber的类文件,路径为根目录下创建的CustomerMiddlewares文件夹,然后继承IdentityErrorDescriber服务,添加以下代码。
public class CustomIdentityErrorDescriber:IdentityErrorDescriber
{
public override IdentityError DefaultError()
{
return new IdentityError{Code = nameof(DefaultError),Description = $"发生了未知的故障。" };
}
public override IdentityError ConcurrencyFailure()
{
return new IdentityError{Code = nameof(ConcurrencyFailure),Description = "乐观并发失败,对象已被修改。" };
}
public override IdentityError PasswordMismatch()
{
return new IdentityError{Code = nameof(PasswordMismatch),Description = "密码错误" };
}
public override IdentityError InvalidToken()
{
return new IdentityError{Code = nameof(InvalidToken),Description = "无效的令牌." };
}
public override IdentityError LoginAlreadyAssociated()
{
return new IdentityError{Code = nameof(LoginAlreadyAssociated),Description = "具有此登录的用户已经存在." };
}
public override IdentityError InvalidUserName(string userName)
{
return new IdentityError{Code = nameof(InvalidUserName),Description = $"用户名'{userName}'无效,只能包含字母或数字." };
}
public override IdentityError InvalidEmail(string email)
{
return new IdentityError{Code = nameof(InvalidEmail),Description = $"邮箱'{email}'无效." };
}
public override IdentityError DuplicateUserName(string userName)
{
return new IdentityError{Code = nameof(DuplicateUserName),Description = $"用户名'{userName}'已被使用." };
}
public override IdentityError DuplicateEmail(string email)
{
return new IdentityError{Code = nameof(DuplicateEmail),Description = $"邮箱'{email}'已被使用." };
}
public override IdentityError InvalidRoleName(string role)
{
return new IdentityError{Code = nameof(InvalidRoleName),Description = $"角色名'{role}'无效." };
}
public override IdentityError DuplicateRoleName(string role)
{
return new IdentityError{Code = nameof(DuplicateRoleName),Description = $"角色名'{role}'已被使用." };
}
public override IdentityError UserAlreadyHasPassword()
{
return new IdentityError{Code = nameof(UserAlreadyHasPassword),Description = "该用户已设置了密码." };
}
public override IdentityError UserLockoutNotEnabled()
{
return new IdentityError{Code = nameof(UserLockoutNotEnabled),Description = "此用户未启用锁定." };
}
public override IdentityError UserAlreadyInRole(string role)
{
return new IdentityError{Code = nameof(UserAlreadyInRole),Description = $"用户已关联角色'{role}'." };
}
public override IdentityError UserNotInRole(string role)
{
return new IdentityError{Code = nameof(UserNotInRole),Description = $"用户未关联角色'{role}'." };
}
public override IdentityError PasswordTooShort(int length)
{
return new IdentityError{Code = nameof(PasswordTooShort),Description = $"密码必须至少是{length}字符." };
}
public override IdentityError PasswordRequiresNonAlphanumeric()
{
return new IdentityError
{
Code = nameof(PasswordRequiresNonAlphanumeric),
Description = "密码必须至少有一个非字母数字字符."
};
}
public override IdentityError PasswordRequiresDigit()
{
return new IdentityError{Code = nameof(PasswordRequiresDigit),Description = $"密码必须至少有一个数字('0'-'9')." };
}
public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)
{
return new IdentityError{Code = nameof(PasswordRequiresUniqueChars),Description = $"密码必须使用至少不同的{uniqueChars}字符。" };
}
public override IdentityError PasswordRequiresLower()
{
return new IdentityError{Code = nameof(PasswordRequiresLower),Description = "密码必须至少有一个小写字母('a'-'z')." };
}
public override IdentityError PasswordRequiresUpper()
{
return new IdentityError{Code = nameof(PasswordRequiresUpper),Description = "密码必须至少有一个大写字母('A'-'Z')." };
}
}
回到Startup类的ConfigureServices()方法中,在AddIdentity()服务中使用AddErrorDescriber()方法覆盖默认的错误提示内容,代码如下。
services.AddIdentity<IdentityUser,IdentityRole>().AddErrorDescriber<CustomIdentityErrorDescriber>().AddEntityFrameworkStores<AppDbContext>();
配置完成之后,提示变为中文,注册时密码长度达到6位即可。
在本节中我们学习如何判断用户是否登录,以及注册、登录和注销等功能是否可实现。
首先来看一看如何在ASP.NET Core中实现注销功能。如果用户未登录,则显示登录和注册按钮,如图21.7所示。
图21.7
如果用户已登录,请隐藏登录和注册按钮并显示注销按钮,如图21.8所示。
图21.8
我们需要在_Layout.cshtml文件中找到ID为collapsibleNavbar的导航菜单栏,修改代码如下。
在下方代码中注入了SignInManager,以便我们检查用户是否已登录,来决定显示和隐藏的内容。
@using Microsoft.AspNetCore.Identity @inject SignInManager<IdentityUser>
_signInManager
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="index">学生列表</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="home" asp-action="create">添加学生</a>
</li>
</ul>
<ul class="navbar-nav ml-auto">
@*如果用户已登录,则显示注销链接*@ @if(_signInManager.IsSignedIn(User)) {
<li class="nav-item">
<form method="post" asp-controller="account" asp-action="logout">
<button type="submit" style="width:auto"
class="nav-link btn btn-link py-0">
注销 @User.Identity.Name
</button>
</form>
</li>
}else{
<li class="nav-item">
<a class="nav-link" asp-controller="account" asp-action="register">
注册
</a>
</li>
<li class="nav-item">
<a class="nav-link" asp-controller="account" asp-action="login">
登录
</a>
</li>
}
</ul>
</div>
</IdentityUser>
然后在AccountController中添加以下Logout()方法。
[HttpPost]
public async Task<IActionResult> Logout()
{
await _signInManager.SignOutAsync();
return RedirectToAction("index","home");
}
请注意,我们使用POST请求将用户注销,而不使用GET请求,因为该方法可能会被滥用。恶意者可能会诱骗用户单击某张图片,将图片的src属性设置为应用程序注销URL,这样会造成用户在不知不觉中退出了账户。
在本节中,我们将讨论使用ASP.NET Core Identity的API在ASP.NET Core应用程序中实现登录功能。要在ASP.NET Core应用程序中实现登录功能,我们需要实现以下功能。
要在系统中登录用户,则需要其邮箱、用户名、密码以及使其选择是否需要持久性Cookie或会话Cookie。
public class LoginViewModel
{
[Required]
[EmailAddress]
public string Email{get;set;}
[Required]
[DataType(DataType.Password)]
public string Password{get;set;}
[Display(Name = "记住我")]
public bool RememberMe{get;set;}
}
登录视图的代码如下。
@model LoginViewModel
@{ViewBag.Title = "用户登录";}
<h1>用户登录</h1>
<div class="row">
<div class="col-md-12">
<form method="post">
<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">
<div class="checkbox">
<label asp-for="RememberMe">
<input asp-for="RememberMe" />
@Html.DisplayNameFor(m => m.RememberMe)
</label>
</div>
</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using MockSchoolManagement.ViewModels;
using System.Threading.Tasks;
namespace MockSchoolManagement.Controllers
{
public class AccountController:Controller
{
private UserManager<IdentityUser> _userManager;
private SignInManager<IdentityUser> _signInManager;
public AccountController(UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
this._userManager = userManager;
this._signInManager = signInManager;
}
[HttpGet]
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel model)
{
if(ModelState.IsValid)
{
var result = await _signInManager.PasswordSignInAsync(
model.Email,model.Password,model.RememberMe,false);
if(result.Succeeded)
{
return RedirectToAction("index","home");
}
ModelState.AddModelError(string.Empty,"登录失败,请重试");
}
return View(model);
}
}
}
维基百科解释:Cookie并不是它的原意“甜饼”的意思,而是一个保存在客户机中的简单的文本文件,这个文件与特定的Web文档关联在一起,保存了该客户机访问这个Web文档时的信息,当客户机再次访问这个Web文档时这些信息可供该文档使用。由于“Cookie”具有可以保存在客户机上的神奇特性,因此它可以帮助我们实现记录用户个人信息的功能,而这一切都不必使用复杂的CGI等程序。
简单来说,我们把Cookie理解为一个大小不超过4kB,便于我们在客户端保存一些用户个人信息的功能。
在ASP.NET Core Identity中,用户成功登录后,将发出Cookie,并将此Cookie随每个请求一起发送到服务器,服务器会解析此Cookie信息来了解用户是否已经通过身份验证和登录。此Cookie可以是会话Cookie或持久Cookie。
会话Cookie是指用户登录成功后,Cookie会被创建并存储在浏览器会话实例中。会话Cookie不包含过期时间,它会在浏览器窗口关闭时被永久删除。
持久Cookie是指用户登录成功后,Cookie会被创建并存储在浏览器中,因为是持久Cookie,所以在关闭浏览器窗口后,它不会被删除。但是,它通常有一个到期时间,会在到期后被删除。
在LoginViewModel.cs视图模型中,我们已经添加了一个bool类型的RememberMe属性。用户可在登录时选择记住我,选中即使用持久性Cookie,而未选中则为会话Cookie。
现在运行项目,我们可以在登录的时候选择记住我,登录成功后如图21.9所示。
图21.9
打开开发者工具(按F12键),观察图21.9框中的内容,可以发现过期时间是很长的。现在关闭浏览器,并将其再次打开,用户也依然是登录状态。这便是持久性Cookie的作用,只有在到期时间到了之后才会删除。
至于会话Cookie验证,我们在登录的时候取消选择记住我,然后看到如图21.10所示的内容。
图21.10
这里已经是一个会话了,它不包含过期时间,在关闭浏览器后,再次将其打开,系统会自动注销用户。
以上就是持久性Cookie与会话Cookie的区别了。
在本章中我们学习了Identity的基本功能,创建一个系统用户并完成了登录注册及状态检查。在后面的章节中,内容会逐步深入,可配合源代码学习。
本章介绍了ASP.NET Core Identity框架的定位及作用,并利用它提供的API完成了用户的登录与注销等基本功能。在后面的章节中我们会使用更多的API将系统趋于完善。
本文摘自《深入浅出 ASP.NET Core》
这本书原本的计划是描述EF Core中的知识点,带领读者完整地做一个管理系统。但是个人觉得这样写与市场上的其他图书没有什么区别,它就是一本概述知识点的图书,无非多了一个较为完整的功能系统而已。对于我而言这是有落差的。有一天和朋友吃饭,他建议把ABP中那些有效的、目前市场上流行的设计理念整合进图书,不用讲解得太明白,只是告诉读者如何用以及这么用的好处即可。
本书分为以下5个部分。
*请认真填写需求信息,我们会在24小时内与您取得联系。