整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          使用HTML,CSS和Javascript創建漂亮的懸停觸發的可擴展側邊欄

          天,我們將學習如何制作一個在懸停時展開和折疊的側邊欄。它看起來很漂亮,實現比看起來簡單得多。我們將逐步完成本教程,并且在此過程中我還將介紹一些值得注意的HTML / CSS技巧。

          以下是本文將涉及的提示和技巧列表:

          • 將欄放在側邊欄中
          • 在懸停時更改項目的顏色
          • 您需要了解如何修復的Google材質圖標存在問題
          • onmouseover和onmouseout事件
          • 如何將物品保持在一條線上并防止纏繞
          • 如何隱藏側邊欄中溢出的文本
          • 如何實現平滑過渡

          讓我們開始!

          第1部分:創建一個簡單的側邊欄

          可以在此筆中找到此步驟的完整代碼:https://codepen.io/dalisc/pen/rEjRWo

          只使用HTML,您的網頁將如下圖所示。一些CSS可以做出什么改變!所以我們需要用一些CSS來設置它,使其看起來像上面的gif。

          從第1部分學習的CSS技巧和竅門:

          • 將欄放在側邊欄中
          .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材質圖標存在問題

          如果您使用Google的素材圖標,您會發現一個令人沮喪的問題:默認情況下,圖標和相鄰文字沒有正確垂直對齊。

          .material-icons,
          .icon-text {
           vertical-align: middle;
          }
          .material-icons {
           padding-bottom: 3px;
           margin-right: 30px;
          }
          

          你需要做的是在CSS中垂直對齊它們(vertical-align:middle;)。即使這樣,對齊也有點偏離,所以在此之后給你的圖標一個3px垂直增強(padding-bottom:3px;)。

          第2部分:檢測您的鼠標是否懸停在側邊欄上

          現在我們將添加一些Javascript,因為我們將在側邊欄中引入一些功能。可以在此筆中找到此步驟的完整代碼

          • HTML提示:onmouseover和onmouseout

          兩個非常有用的事件是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()”>
          

          現在進行懸停并檢查控制臺以查找我們編寫的消息。它應該可以工作了!我們已經設置了告訴側邊欄是折疊還是展開所需的檢測。

          第3部分:展開/折疊側邊欄

          可以在此筆中找到此最終部分的完整代碼:https://codepen.io/dalisc/pen/qzRGxQ

          我們將折疊邊欄稱為迷你側邊欄。我們現在想要根據鼠標是否懸停在我的側邊欄上進行兩次查找,因此我們需要在javascript部分中創建一個布爾變量mini

          我們還將創建一個函數來切換側邊欄的擴展。該功能的邏輯如下:如果側邊欄處于迷你模式,將鼠標懸停在側邊欄上會將側邊欄擴展為其完整模式(并將變量mini設置為false)。如果側邊欄處于完全模式,將鼠標懸停在側邊欄上會將其折疊為迷你模式(并將變量mini設置為true)。

          因此,我們需要更改onmouseoveronmouseout事件,并相應地引入新函數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提示和技巧!

          從第3部分學習的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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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" > <script src="http://code.jquery.com/jquery-1.9.1.js"></script> <script src="http://code.jquery.com/ui/1.10.4/jquery-ui.js"></script> <link rel="stylesheet" > <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。
          • 如何在系統中啟用Identity服務。
          • UserManager與SignInManager的API介紹及使用。
          • 登錄用戶的Cookie管理。

          21.1 ASP.NET Core Identity介紹

          ASP.NET Core Identity是一個會員身份系統,早期它的名字是Membership,當然那是一段“古老”的歷史,現在我們來了解全新的Identity。它允許我們創建、讀取、更新和刪除賬戶。支持賬號驗證、身份驗證、授權、恢復密碼和SMS雙因子身份驗證。它還支持微軟、Facebook和Google等第三方登錄提供商。它提供了一個豐富的API,并且這些API還可以進行大量的擴展。我們將在本書的后面實現這些功能。

          添加ASP.NET Core Identity服務

          這里采用的是EF Core,因為要讓我們的系統支持Identity服務,所以需要安裝它的程序包。打開NuGet管理器,安裝Microsoft.AspNetCore.Identity.EntityFrameworkCore即可。

          以下是添加和配置ASP.NET Core Identity服務的步驟。

          使AppDbContext繼承類IdentityDbContext,然后引入命名空間,代碼如下。

          public class AppDbContext:IdentityDbContext
          {
             //其余代碼
          }
          • 應用程序AppDbContextDbContext類必須繼承IdentityDbContext類而不是DbContext類。
          • 因為IdentityDbContext提供了管理SQL Server中的Identity表所需的所有DbSet屬性,所以將看到ASP.NET Core Identity框架中要生成的所有數據庫表。
          • 如果瀏覽IdentityDbContext類的定義(按F12鍵可以看到),則將看到它繼承自DbContext類。因此,如果類繼承自IdentityDbContext類,那么不必顯式繼承DbContext類。

          配置ASP.NET Core Identity服務。在Startup類的ConfigureServices()方法中,添加以下代碼行。

          services.AddIdentity<IdentityUser,IdentityRole>()
                  .AddEntityFrameworkStores<AppDbContext>();
          • AddIdentity()方法是指為系統提供默認的用戶和角色類型的身份驗證系統。
          • IdentityUser類由ASP.NET Core提供,包含UserName、PasswordHash和Email等屬性。這是ASP.NET Core Identity框架默認使用的類,用于管理應用程序的注冊用戶。
          • 如果讀者希望存儲有關注冊用戶的其他信息,比如性別、城市等,則需要創建一個派生自IdentityUser的自定義類。在此自定義類中添加所需的其他屬性,然后插入此類而不是內置的IdentityUser類。我們將在后面的章節中學習如何執行此操作。
          • 同樣,IdentityRole也是ASP.NET Core Identity提供的內置類,包含角色信息。
          • 使用EntityFrameWork Core從基礎SQL Server數據庫存儲和查詢注冊用戶的角色信息。
          • 使用AddEntityFrameworkStores()方法,然后指定DbContext類作為泛型參數。

          接下來,將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 使用ASP.NET Core Identity注冊新用戶

          現在已經創建好了表的信息,接下來我們增加一個注冊功能,讓用戶能夠注冊到系統中。

          新用戶注冊視圖應如圖21.2所示。為了能夠注冊為新用戶,需要郵箱地址和密碼兩個字段。

          圖21.2

          21.2.1 RegisterViewModel視圖模型

          我們將使用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的瀏覽器中提供日期選擇器。

          21.2.2 賬戶控制器

          賬戶控制器(AccountController)是指所有與賬戶相關的CRUD(增加、讀取、更新和刪除)操作都將在此控制器中。目前我們只有Register()操作方法,可以通過向/account/register發出GET請求來實現此操作方法。

          using Microsoft.AspNetCore.Mvc;
          
          namespace MockSchoolManagement.Controllers
          {
              public class AccountController:Controller
              {
                  [HttpGet]
                  public IActionResult Register()
                  {
                      return View();
                  }
              }
          }

          21.2.3 注冊視圖中的代碼

          將此視圖放在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>

          21.2.4 添加注冊按鈕

          在布局視圖中添加注冊按鈕,我們需要在_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中創建賬戶。

          21.3 UserManager和SignInManager服務

          在本節我們學習使用ASP.NET Core Identity提供的UserManager服務創建新用戶,然后使用其提供的SignInManager服務來登錄用戶。

          UserManager <IdentityUser>類包含管理基礎數據存儲中的用戶所需的方法。比如,此類具有CreateAsync()、DeleteAsync()和UpdateAsync()等方法來創建、刪除和更新用戶,如圖21.3所示。

          圖21.3

          SignInManager <IdentityUser>類包含用戶登錄所需的方法。比如,SignInManager類具有SignInAsync()、SignOutAsync()等方法來登錄和注銷用戶,如圖21.4所示。

          • UserManager和SignInManager服務都需要使用構造函數注入AccountController,并且這兩個服務都接收泛型參數。
          • 這些服務接收泛型參數的User類。目前,我們使用內置的IdentityUser類作為泛型參數的參數。
          • 這兩個服務的通用參數User是一個擴展類。這意味著,我們可以自定義與用戶有關的信息和其他數據,來創建我們的自定義用戶。

          圖21.4

          • 我們可以聲明自己的自定義類作為泛型參數,而不是內置的IdentityUser類。

          以下是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

          21.3.1 ASP.NET Core Identity中對密碼復雜度的處理

          在剛剛注冊的時候,我們發現有兩個問題。

          • 密碼驗證機制太復雜了。
          • 它是英文的,對于我們來說支持不是很友好。

          這是因為ASP.NET Core IdentityOptions類在ASP.NET Core中用于配置密碼復雜性規則。默認情況下,ASP.NET Core身份不允許創建簡單的密碼來保護我們的應用程序免受自動暴力攻擊。

          當我們嘗試使用像abc這樣的簡單密碼注冊新賬戶時,會顯示創建失敗,讀者將看到如圖21.6所示的驗證錯誤。

          圖21.6

          我們在圖21.6中看到中文提示,后面的章節會告訴讀者如何配置。

          21.3.2 ASP.NET Core Identity密碼默認設置

          在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(略)所示。

          21.3.3 覆蓋ASP.NET Core身份中的密碼默認設置

          我們可以通過在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的配置信息,還有用戶、登錄、策略等配置信息,我們可以根據不同的場景進行靈活的配置。

          • UserOptions。
          • SignInOptions。
          • LockoutOptions。
          • TokenOptions。
          • StoreOptions。
          • ClaimsIdentityOptions。

          21.3.4 修改中文提示的錯誤信息

          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位即可。

          21.4 登錄狀態及注銷功能的實現

          在本節中我們學習如何判斷用戶是否登錄,以及注冊、登錄和注銷等功能是否可實現。

          首先來看一看如何在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,這樣會造成用戶在不知不覺中退出了賬戶。

          21.5 ASP.NET Core Identity中的登錄功能實現

          在本節中,我們將討論使用ASP.NET Core Identity的API在ASP.NET Core應用程序中實現登錄功能。要在ASP.NET Core應用程序中實現登錄功能,我們需要實現以下功能。

          • 登錄視圖模型。
          • 登錄視圖。
          • AccountController中的兩個Login()操作方法。

          21.5.1 LoginViewModel登錄視圖模型

          要在系統中登錄用戶,則需要其郵箱、用戶名、密碼以及使其選擇是否需要持久性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;}
              }

          21.5.2 登錄視圖的代碼

          登錄視圖的代碼如下。

          @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>

          21.5.3 AccountController中的Login()操作方法

          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);
                  }
          
              }
          }

          21.5.4 會話Cookie與持久性Cookie

          維基百科解釋: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的基本功能,創建一個系統用戶并完成了登錄注冊及狀態檢查。在后面的章節中,內容會逐步深入,可配合源代碼學習。

          21.6 小結

          本章介紹了ASP.NET Core Identity框架的定位及作用,并利用它提供的API完成了用戶的登錄與注銷等基本功能。在后面的章節中我們會使用更多的API將系統趨于完善。

          本文摘自《深入淺出 ASP.NET Core》

          這本書原本的計劃是描述EF Core中的知識點,帶領讀者完整地做一個管理系統。但是個人覺得這樣寫與市場上的其他圖書沒有什么區別,它就是一本概述知識點的圖書,無非多了一個較為完整的功能系統而已。對于我而言這是有落差的。有一天和朋友吃飯,他建議把ABP中那些有效的、目前市場上流行的設計理念整合進圖書,不用講解得太明白,只是告訴讀者如何用以及這么用的好處即可。

          本書的結構

          本書分為以下5個部分。

          • 第一部分(第1章~第9章)介紹ASP.NET Core的基礎知識,比如中間件、環境變量和配置信息等,簡單講解完整的ASP.NET Core的項目結構。
          • 第二部分(第10章~第20章)介紹并運用MVC模型及路由中間件,結合ASP.NET Core提供的TagHelper等新特性,完成對學生信息的增刪改查、圖片上傳;介紹簡單的倉儲模式與依賴注入的關系,為搭建管理系統做好基礎準備。
          • 第三部分(第21章~第29章) 通過搭建一個基礎管理系統,分析及處理實際業務場景中的常見問題,比如身份驗證和授權、客戶端及服務端驗證、配置信息、 EF Core數據訪問、數據分頁和統一異常處理等。
          • 第四部分(第30章~第38章) 介紹架構的作用以及意義,根據架構的思想應用設計模式,結合C#泛型特性優化倉儲模式,建立多層體系架構,通過并發、 LINQ及活用Entity Framework Core中的常用功能完成一個類似領域驅動設計的項目。
          • 第五部分(第39章~第42章)介紹簡單的Web API入門、部署ASP.NET Core項目以及從ASP .NET Core 2.2到ASP.NET Core 3.1的版本升級過程。

          主站蜘蛛池模板: 国模无码视频一区二区三区| 亚洲国产视频一区| 一区二区三区电影在线观看| 亚洲一区精品无码| 精品国产亚洲一区二区三区在线观看 | 久久久精品人妻一区二区三区四| 日韩一区二区三区电影在线观看| 精品无码一区二区三区爱欲| 国产精品亚洲产品一区二区三区 | 国产A∨国片精品一区二区| 欧美日本精品一区二区三区| 亚洲AV本道一区二区三区四区| 国产精品一区二区三区久久| 日本一区二区视频| 久久久国产精品一区二区18禁| 成人无码一区二区三区| 福利片免费一区二区三区| 亚洲日韩国产一区二区三区在线| 在线视频一区二区三区| 亚洲爆乳精品无码一区二区三区| 国产免费播放一区二区| 国产精品区AV一区二区| 精品国产一区二区三区久久蜜臀 | 国产一区二区免费在线| 久久中文字幕无码一区二区| 国产精品无码一区二区三区不卡| 亚洲AV日韩AV一区二区三曲| 蜜桃AV抽搐高潮一区二区| 无码人妻一区二区三区免费| 国产午夜精品一区二区三区极品| 97久久精品无码一区二区天美| 中文字幕一区二区三匹| 亚洲av无码一区二区三区人妖| 中文字幕日韩精品一区二区三区| 亚洲一区精品伊人久久伊人 | 久久无码人妻一区二区三区午夜| 99精品国产一区二区三区不卡| 精品一区高潮喷吹在线播放| 91在线精品亚洲一区二区| 无码人妻精品一区二区蜜桃网站| 在线观看精品视频一区二区三区|