ASP.NET Core MVC 打造一個簡單的圖書館管理系統 (修正版)(二)數據庫初始化、基本登錄頁面以及授權邏輯的建立


前言:

本系列文章主要為我之前所學知識的一次微小的實踐,以我學校圖書館管理系統為雛形所作。

本系列文章主要參考資料:

微軟文檔:https://docs.microsoft.com/zh-cn/aspnet/core/getting-started/?view=aspnetcore-2.1&tabs=windows

《Pro ASP.NET MVC 5》、《鋒利的 jQuery》

 

 

此系列皆使用 VS2017+C# 作為開發環境。如果有什么問題或者意見歡迎在留言區進行留言。 

項目 github 地址:https://github.com/NanaseRuri/LibraryDemo

 

 

本章內容:Identity 框架的配置、對賬戶進行授權的配置、數據庫的初始化方法、自定義 TagHelper

 

 

 一到四為對 Student 即 Identity框架的使用,第五節為對 Admin 用戶的配置

 

 

一、自定義賬號和密碼的限制

  在 Startup.cs 的 ConfigureServices 方法中可以對 Identity 的賬號和密碼進行限制:

 1             services.AddIdentity<Student, IdentityRole>(opts =>
 2             {
 3 
 4                 opts.User.RequireUniqueEmail = true;
 5                 opts.User.AllowedUserNameCharacters = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM0123456789";
 6                 opts.Password.RequiredLength = 6;
 7                 opts.Password.RequireNonAlphanumeric = false;
 8                 opts.Password.RequireLowercase = false;
 9                 opts.Password.RequireUppercase = false;
10                 opts.Password.RequireDigit = false;
11             }).AddEntityFrameworkStores<StudentIdentityDbContext>()
12                 .AddDefaultTokenProviders();

  RequireUniqueEmail 限制每個郵箱只能用於一個賬號。

  此處 AllowedUserNameCharacters 方法限制用戶名能夠使用的字符,需要單獨輸入每個字符。

  剩下的設置分別為限制密碼必須有符號 / 包含小寫字母 / 包含大寫字母 / 包含數字。

 

 

 

二、對數據庫進行初始化

  在此創建一個 StudentInitiator 以及一個 BookInitiator用以對數據庫進行初始化:

 1     public class StudentInitiator
 2     {
 3         public static async Task Initial(IServiceProvider serviceProvider)
 4         {
 5             UserManager<Student> userManager = serviceProvider.GetRequiredService<UserManager<Student>>();
 6             if (userManager.Users.Any())
 7             {
 8                 return;
 9             }
10             IEnumerable<Student> initialStudents = new[]
11             {
12                 new Student()
13                 {
14                     UserName = "U201600001",
15                     Name = "Nanase",
16                     Email = "Nanase@cnblog.com",
17                     PhoneNumber = "12345678910",
18                     Degree = Degrees.CollegeStudent,
19                     MaxBooksNumber = 10,
20                 },
21                 new Student()
22                 {
23                     UserName = "U201600002",
24                     Name = "Ruri",
25                     Email = "NanaseRuri@cnblog.com",
26                     PhoneNumber = "12345678911",
27                     Degree = Degrees.DoctorateDegree,
28                     MaxBooksNumber = 15
29                 },
30             };
31 
32             foreach (var student in initialStudents)
33             {
34                 await userManager.CreateAsync(student, student.UserName.Substring(student.UserName.Length - 6,6));
35             }
36         } 
37        }

 

新建 BookInitiator 用於初始化該數據庫,不知道為什么在這里會報一個 Book 重復主鍵的錯誤,但是數據確實全都插進去了:

  1     public class BookInitiator
  2     {
  3         public static async Task BookInitial(IServiceProvider serviceProvider)
  4         {
  5             LendingInfoDbContext context = serviceProvider.GetRequiredService<LendingInfoDbContext>();
  6             if (!context.Books.Any() || !context.Bookshelves.Any())
  7             {
  8                 Bookshelf[] bookshelfs = new[]
  9                 {
 10                     new Bookshelf()
 11                     {
 12                         BookshelfId = 1,
 13                         Location = "主校區",
 14                         Sort = "計算機"
 15                     },
 16                     new Bookshelf()
 17                     {
 18                         BookshelfId = 2,
 19                         Location = "主校區",
 20                         Sort = "文學"
 21                     },
 22                     new Bookshelf()
 23                     {
 24                         BookshelfId = 3,
 25                         Location = "東校區",
 26                         Sort = "計算機"
 27                     },
 28                     new Bookshelf()
 29                     {
 30                         BookshelfId = 4,
 31                         Location = "閱覽室",
 32                         Sort = "文學"
 33                     },
 34                     new Bookshelf()
 35                     {
 36                         BookshelfId = 5,
 37                         Location = "閱覽室",
 38                         Sort = "計算機"
 39                     },
 40                 };
 41 
 42                 Book[] books = new[]
 43                 {
 44                     new Book()
 45                     {
 46                         Name = "精通ASP.NET MVC 5",
 47                         BarCode = "001100987211",
 48                         ISBN = "978-7-115-41023-8",
 49                         State = BookState.Normal,
 50                         FetchBookNumber = "TP393.092 19",
 51                         Location = "主校區",
 52                         Sort = "計算機"
 53                     },
 54                     new Book()
 55                     {
 56                         Name = "精通ASP.NET MVC 5",
 57                         BarCode = "001100987212",
 58                         ISBN = "978-7-115-41023-8",
 59                         State = BookState.Normal,
 60                         FetchBookNumber = "TP393.092 19",
 61                         Location = "主校區",
 62                         Sort = "計算機"
 63                     },
 64                     new Book()
 65                     {
 66                         Name = "精通ASP.NET MVC 5",
 67                         BarCode = "001100987213",
 68                         ISBN = "978-7-115-41023-8",
 69                         State = BookState.Normal,
 70                         FetchBookNumber = "TP393.092 19",
 71                         Location = "東校區",
 72                         Sort = "計算機"
 73                     },
 74                     new Book()
 75                     {
 76                         Name = "精通ASP.NET MVC 5",
 77                         BarCode = "001100987214",
 78                         ISBN = "978-7-115-41023-8",
 79                         State = BookState.Readonly,
 80                         FetchBookNumber = "TP393.092 19",
 81                         Location = "閱覽室",
 82                         Sort = "計算機"
 83                     },
 84                     new Book()
 85                     {
 86                         Name = "Entity Framework實用精要",
 87                         BarCode = "001101279682",
 88                         ISBN = "978-7-302-48593-3",
 89                         State = BookState.Normal,
 90                         FetchBookNumber = "TP393.09 447",
 91                         Location = "主校區",
 92                         Sort = "計算機"
 93                     },
 94                     new Book()
 95                     {
 96                         Name = "Entity Framework實用精要",
 97                         BarCode = "001101279683",
 98                         ISBN = "978-7-302-48593-3",
 99                         State = BookState.Normal,
100                         FetchBookNumber = "TP393.09 447",
101                         Location = "主校區",
102                         Sort = "計算機"
103                     },
104                     new Book()
105                     {
106                         Name = "Entity Framework實用精要",
107                         BarCode = "001101279684",
108                         ISBN = "978-7-302-48593-3",
109                         State = BookState.Normal,
110                         FetchBookNumber = "TP393.09 447",
111                         Location = "東校區",
112                         Sort = "計算機"
113                     },
114                     new Book()
115                     {
116                         Name = "Entity Framework實用精要",
117                         BarCode = "001101279685",
118                         ISBN = "978-7-302-48593-3",
119                         State = BookState.Normal,
120                         FetchBookNumber = "TP393.09 447",
121                         Location = "東校區",
122                         Sort = "計算機"
123                     },
124                     new Book()
125                     {
126                         Name = "Entity Framework實用精要",
127                         BarCode = "001101279686",
128                         ISBN = "978-7-302-48593-3",
129                         State = BookState.Normal,
130                         FetchBookNumber = "TP393.09 447",
131                         Location = "閱覽室",
132                         Sort = "計算機"
133                     },
134                     new Book()
135                     {
136                         Name = "Rails 5敏捷開發",
137                         BarCode = "001101290497",
138                         ISBN = "978-7-5680-3659-7",
139                         State = BookState.Normal,
140                         FetchBookNumber = "TP393.09 448",
141                         Location = "主校區",
142                         Sort = "計算機"
143                     },
144                     new Book()
145                     {
146                         Name = "Rails 5敏捷開發",
147                         BarCode = "001101290498",
148                         ISBN = "978-7-5680-3659-7",
149                         State = BookState.Normal,
150                         FetchBookNumber = "TP393.09 448",
151                         Location = "主校區",
152                         Sort = "計算機"
153                     },
154                     new Book()
155                     {
156                         Name = "Rails 5敏捷開發",
157                         BarCode = "001101290499",
158                         ISBN = "978-7-5680-3659-7",
159                         State = BookState.Readonly,
160                         FetchBookNumber = "TP393.09 448",
161                         Location = "主校區",
162                         Sort = "計算機"
163                     },
164                     new Book()
165                     {
166                         Name = "你必須掌握的Entity Framework 6.x與Core 2.0",
167                         BarCode = "001101362986",
168                         ISBN = "978-7-5680-3659-7",
169                         State = BookState.Normal,
170                         FetchBookNumber = "TP393.09 452",
171                         Location = "主校區",
172                         Sort = "計算機"
173                     },
174                     new Book()
175                     {
176                         Name = "你必須掌握的Entity Framework 6.x與Core 2.0",
177                         BarCode = "001101362987",
178                         ISBN = "978-7-5680-3659-7",
179                         State = BookState.Readonly,
180                         FetchBookNumber = "TP393.09 452",
181                         Location = "主校區",
182                         Sort = "計算機"
183                     },
184                     new Book()
185                     {
186                         Name = "毛選. 第一卷",
187                         BarCode = "00929264",
188                         ISBN = "7-01-000922-8",
189                         State = BookState.Normal,
190                         FetchBookNumber = "A41 1:1",
191                         Location = "主校區",
192                         Sort = "文學"
193                     },
194                     new Book()
195                     {
196                         Name = "毛選. 第一卷",
197                         BarCode = "00929265",
198                         ISBN = "7-01-000922-8",
199                         State = BookState.Normal,
200                         FetchBookNumber = "A41 1:1",
201                         Location = "主校區",
202                         Sort = "文學"
203                     },
204                     new Book()
205                     {
206                         Name = "毛選. 第一卷",
207                         BarCode = "00929266",
208                         ISBN = "7-01-000922-8",
209                         State = BookState.Readonly,
210                         FetchBookNumber = "A41 1:1",
211                         Location = "閱覽室",
212                         Sort = "文學"
213                     },
214                     new Book()
215                     {
216                         Name = "毛選. 第二卷",
217                         BarCode = "00929279",
218                         ISBN = "7-01-000915-5",
219                         State = BookState.Normal,
220                         FetchBookNumber = "A41 1:2",
221                         Location = "主校區",
222                         Sort = "文學"
223                     },
224                     new Book()
225                     {
226                         Name = "毛選. 第二卷",
227                         BarCode = "00929280",
228                         ISBN = "7-01-000915-5",
229                         State = BookState.Readonly,
230                         FetchBookNumber = "A41 1:2",
231                         Location = "閱覽室",
232                         Sort = "文學"
233                     },
234                     new Book()
235                     {
236                         Name = "毛選. 第三卷",
237                         BarCode = "00930420",
238                         ISBN = "7-01-000916-3",
239                         State = BookState.Normal,
240                         FetchBookNumber = "A41 1:3",
241                         Location = "主校區",
242                         Sort = "文學"
243                     },
244                     new Book()
245                     {
246                         Name = "毛選. 第三卷",
247                         BarCode = "00930421",
248                         ISBN = "7-01-000916-3",
249                         State = BookState.Readonly,
250                         FetchBookNumber = "A41 1:3",
251                         Location = "閱覽室",
252                         Sort = "文學"
253                     },
254                     new Book()
255                     {
256                         Name = "毛選. 第四卷",
257                         BarCode = "00930465",
258                         ISBN = "7-01-000925-2",
259                         State = BookState.Normal,
260                         FetchBookNumber = "A41 1:4",
261                         Location = "主校區",
262                         Sort = "文學"
263                     },
264                     new Book()
265                     {
266                         Name = "毛選. 第四卷",
267                         BarCode = "00930466",
268                         ISBN = "7-01-000925-2",
269                         State = BookState.Readonly,
270                         FetchBookNumber = "A41 1:4",
271                         Location = "閱覽室",
272                         Sort = "文學"
273                     }
274                 };
275 
276                 BookDetails[] bookDetails = new[]
277                 {
278                     new BookDetails()
279                     {
280                         Author = "Admam Freeman",
281                         Name = "精通ASP.NET MVC 5",
282                         ISBN = "978-7-115-41023-8",
283                         Press = "人民郵電出版社",
284                         PublishDateTime = new DateTime(2016,1,1),
285                         SoundCassettes = "13, 642頁 : 圖 ; 24cm",
286                         Version = 1,
287                         FetchBookNumber = "TP393.092 19",
288                         Description = "ASP.NET MVC 5框架是微軟ASP.NET Web平台的新進展。它提供了高生產率的編程模型,結合ASP.NET的全部優勢,促成更整潔的代碼架構、測試驅動開發和強大的可擴展性。本書涵蓋ASP.NET MVC 5的所有開發優勢技術,包括用C#屬性定義路由技術及重寫過濾器技術等。且構建MVC應用程序的用戶體驗也有本質上的改進。其中書里也專一講解了用新Visual Studio 2013創建MVC應用程序時的技術和技巧。本書包括完整的開發工具介紹以及對代碼進行輔助編譯和調試的技術。本書還涉及流行的Bootstrap JavaScript庫,該庫現已被納入到MVC 5之中,為開發人員提供更廣泛的多平台CSS和HTML5選項,而不必像以前那樣去加載大量的第三方庫。"
289                     },
290                     new BookDetails()
291                     {
292                         Author = "呂高旭",
293                         Name = "Entity Framework實用精要",
294                         ISBN = "978-7-302-48593-3",
295                         Press = "清華大學出版社",
296                         PublishDateTime = new DateTime(2018,1,1),
297                         SoundCassettes = "346頁 ; 26cm",
298                         Version = 1,
299                         FetchBookNumber = "TP393.09 447",
300                         Description = "本書通過介紹Entity Framework與 LINQ 開發實戰的案例,以 Entity Framework 技術內容的討論為主線,結合關鍵的 LINQ技巧說明,提供讀者系統性學習 Entity Framework 所需的內容。本書旨在幫助讀者進入 Entity Framework的世界,建立必要的技術能力,同時希望讀者在完成本書的教學課程之后,能夠更進一步地將其運用在實際的項目開發中。"
301                     },
302                     new BookDetails()
303                     {
304                         Author = "魯比",
305                         Name = "Rails 5敏捷開發",
306                         ISBN = "978-7-5680-3659-7",
307                         Press = "華中科技大學出版社",
308                         PublishDateTime = new DateTime(2018,1,1),
309                         SoundCassettes = "xxi, 451頁 : 圖 ; 23cm",
310                         Version = 1,
311                         FetchBookNumber = "TP393.09 448",
312                         Description = "本書以講解“購書網站”案例為主線, 逐步介紹Rails的內置功能。全書分為3部分, 第一部分介紹Rails的安裝、應用程序驗證、Rails框架的體系結構, 以及Ruby語言知識; 第二部分用迭代方式構建應用程序, 然后依據敏捷開發模式開展測試, 最后用Capistrano完成部署; 第三部分補充日常實用的開發知識。本書既有直觀的示例, 又有深入的分析, 同時涵蓋了Web開發各方面的知識, 堪稱一部內容全面而又深入淺出的佳作。第5版增加了關於Rails 5和Ruby 2.2新特性和最佳實踐的內容。"
313                     },
314                     new BookDetails()
315                     {
316                         Author = "汪鵬",
317                         Name = "你必須掌握的Entity Framework 6.x與Core 2.0",
318                         ISBN = "978-7-302-50017-9",
319                         Press = "清華大學出版社",
320                         PublishDateTime = new DateTime(2018,1,1),
321                         SoundCassettes = "X, 487頁 : 圖 ; 26cm",
322                         Version = 1,
323                         FetchBookNumber = "TP393.09 452",
324                         Description = "本書分為四篇,第一篇講解Entity Framework 6.x的基礎,包括數據庫表的創建,數據的操作和數據加載方式。第二篇講解Entity Framework 6.x進階,包括基本原理和性能優化。第三篇講解跨平台Entity Framework Core 2.x的基礎知識和開發技巧。第四篇講解在Entity Framework Core 2.x中解決並發問題,並給出實戰開發案例。"
325                     },
326                     new BookDetails()
327                     {
328                         Author = "",
329                         Name = "毛選. 第一卷",
330                         ISBN = "7-01-000914-7",
331                         Press = "人民出版社",
332                         PublishDateTime = new DateTime(1991,1,1),
333                         SoundCassettes = "340頁 : 肖像 ; 19厘米",
334                         FetchBookNumber = "A41 1:1",
335                         Version = 2,
336                         Description = "《毛選》是對20世紀中國影響最大的書籍之一。"
337                     },
338                     new BookDetails()
339                     {
340                         Author = "",
341                         Name = "毛選. 第二卷",
342                         ISBN = "7-01-000915-5",
343                         Press = "人民出版社",
344                         PublishDateTime = new DateTime(1991,1,1),
345                         SoundCassettes = "343-786頁 : 肖像 ; 19厘米",
346                         Version = 2,
347                         FetchBookNumber = "A41 1:2",
348                         Description = "《毛選》是對20世紀中國影響最大的書籍之一。"
349                     },
350                     new BookDetails()
351                     {
352                         Author = "",
353                         Name = "毛選. 第三卷",
354                         ISBN = "7-01-000916-3",
355                         Press = "人民出版社",
356                         PublishDateTime = new DateTime(1991,1,1),
357                         SoundCassettes = "789ٱ1120頁 ; 20厘米",
358                         FetchBookNumber = "A41 1:3",
359                         Version = 2,
360                         Description = "《毛選》是對20世紀中國影響最大的書籍之一。"
361                     },
362                     new BookDetails()
363                     {
364                         Author = "",
365                         Name = "毛選. 第四卷",
366                         ISBN = "7-01-000925-2",
367                         Press = "人民出版社",
368                         PublishDateTime = new DateTime(1991,1,1),
369                         SoundCassettes = "1123ٱ1517頁 ; 20厘米",
370                         FetchBookNumber = "A41 1:4",
371                         Version = 2,
372                         Description = "《毛選》是對20世紀中國影響最大的書籍之一。"
373                     },
374                 };
375 
376                 var temp = from book in books
377                            from bookshelf in bookshelfs
378                            where book.Location == bookshelf.Location && book.Sort == bookshelf.Sort
379                            select new { BarCode = book.BarCode, BookshelfId = bookshelf.BookshelfId };
380 
381                 foreach (var bookshelf in bookshelfs)
382                 {
383                     bookshelf.Books=new List<Book>();
384                 }
385 
386                 foreach (var tem in temp)
387                 {
388                     Bookshelf targetShelf = bookshelfs.Single(bookshelf => bookshelf.BookshelfId == tem.BookshelfId);
389                     Book targetBook = books.Single(book => book.BarCode == tem.BarCode);
390                     targetShelf.Books.Add(targetBook);
391                 }
392 
393                 foreach (var bookshelf in bookshelfs)
394                 {
395                     bookshelf.MaxFetchNumber=bookshelf.Books.Max(b => b.FetchBookNumber);
396                     bookshelf.MinFetchNumber=bookshelf.Books.Min(b => b.FetchBookNumber);
397                 }
398 
399                 foreach (var bookshelf in bookshelfs)
400                 {
401                     await context.Bookshelves.AddAsync(bookshelf);
402                     await context.SaveChangesAsync();
403                 }
404 
405                 foreach (var bookDetail in bookDetails)
406                 {
407                     await context.BooksDetail.AddAsync(bookDetail);
408                     await context.SaveChangesAsync();
409                 }
410 
411                 foreach (var book in books)
412                 {
413                     await context.Books.AddAsync(book);
414                     await context.SaveChangesAsync();
415                 }
416             }
417         }
418     }
View Code

 

  為確保能夠進行初始化,在 Startup.cs 的 Configure 方法中調用該靜態方法:  

1              DatabaseInitiator.Initial(app.ApplicationServices).Wait();   
2              BookInitiator.BookInitial(app.ApplicationServices).Wait();

 

  Initial 方法中 serviceProvider 參數將在傳入 ConfigureServices 方法調用后的 ServiceProvider,此時在 Initial 方法中初始化的數據也會使用 ConfigureServices 中對賬號和密碼的限制。

  此處我們使用賬號的后六位作為密碼。啟動網頁后查看數據庫的數據:

 

 

 

 

 

 

 

 

 

三、建立驗證所用的控制器以及視圖

 

  首先創建一個視圖模型用於存儲賬號的信息,為了方便實現多種登錄方式,此處創建一個 LoginType 枚舉:

  [UIHint] 特性構造函數傳入一個字符串用來告知對應屬性在使用 Html.EditorFor() 時用什么模板來展示數據。

 1     public enum LoginType
 2     {
 3         UserName,
 4         Email,
 5         Phone
 6     }
 7 
 8     public class LoginModel
 9     {
10         [Required(ErrorMessage = "請輸入您的學號 / 郵箱 / 手機號碼")]
11         [Display(Name = "學號 / 郵箱 / 手機號碼")]
12         public string Account { get; set; }
13 
14         [Required(ErrorMessage = "請輸入您的密碼")]
15         [UIHint("password")]
16         [Display(Name = "密碼")]
17         public string Password { get; set; }
18 
19         [Required]
20         public LoginType LoginType { get; set; }
21     }

 

   使用支架特性創建一個 StudentAccountController

 

  StudentAccount 控制器:

  第 5 行判斷是否授權以避免多余的授權:

 1      public class StudentAccountController : Controller
 2      {
 3         public IActionResult Login(string returnUrl)
 4         {
 5             if (HttpContext.User.Identity.IsAuthenticated)
 6             {
 7                 return RedirectToAction("AccountInfo");
 8             }
 9 
10             LoginModel loginInfo = new LoginModel();
11             ViewBag.returnUrl = returnUrl;
12             return View(loginInfo);
13         }
14     }

 

  在在 Login 視圖中添加多種登錄方式,並使視圖更加清晰,創建了一個 LoginTypeTagHelper ,TagHelper 可制定自定義 HTML 標記並在最終生成視圖時轉換成標准的 HTML 標記。

 1     [HtmlTargetElement("LoginType")]
 2     public class LoginTypeTagHelper:TagHelper
 3     {
 4         public string[] LoginType { get; set; }
 5 
 6         public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
 7         {
 8             foreach (var loginType in LoginType)
 9             {
10                 switch (loginType)
11                 {
12                     case "UserName": output.Content.AppendHtml($"<option selected=\"selected/\" value=\"{loginType}\">學號</option>");
13                         break;
14                     case "Email": output.Content.AppendHtml(GetOption(loginType, "郵箱"));
15                         break;
16                     case "Phone": output.Content.AppendHtml(GetOption(loginType, "手機號碼"));
17                         break;
18                     default: break;
19                 }                
20             }            
21             return Task.CompletedTask;
22         }
23 
24         private static string GetOption(string loginType,string innerText)
25         {
26             return $"<option value=\"{loginType}\">{innerText}</option>";
27         }
28     }

 

  Login 視圖:

  25 行中使用了剛建立的 LoginTypeTagHelper:

 1 @model LoginModel
 2 
 3 @{
 4     ViewData["Title"] = "Login";
 5 }
 6 
 7 <h2>Login</h2>
 8 <br/>
 9 <div class="text-danger" asp-validation-summary="All"></div>
10 <br/>
11 <form asp-action="Login" method="post">
12     <input type="hidden" name="returnUrl" value="@ViewBag.returnUrl"/>
13     <div class="form-group">   
14         <label asp-for="Account"></label>
15         <input asp-for="Account" class="form-control" placeholder="請輸入你的學號 / 郵箱 / 手機號"/>
16     </div>
17     <div class="form-group">   
18         <label asp-for="Password"></label>
19         <input asp-for="Password" class="form-control" placeholder="請輸入你的密碼"/>
20     </div>
21     <div class="form-group">
22         <label>登錄方式</label>
23         <select asp-for="LoginType">
24             <option disabled value="">登錄方式</option>
25             <LoginType login-type="@Enum.GetNames(typeof(LoginType))"></LoginType>
26         </select>
27     </div>
28     <input type="submit" class="btn btn-primary"/>
29 </form>

 

 

  然后創建一個用於對信息進行驗證的動作方法。

 

  為了獲取數據庫的數據以及對數據進行驗證授權,需要通過 DI(依賴注入) 獲取對應的 UserManager 和 SignInManager 對象,在此針對 StudentAccountController 的構造函數進行更新。

  StudentAccountController 整體:

 1     [Authorize]
 2     public class StudentAccountController : Controller
 3     {
 4         private UserManager<Student> _userManager;
 5         private SignInManager<Student> _signInManager;
 6 
 7         public StudentAccountController(UserManager<Student> studentManager, SignInManager<Student> signInManager)
 8         {
 9             _userManager = studentManager;
10             _signInManager = signInManager;
11         }
12 
13         [AllowAnonymous]
14         public IActionResult Login(string returnUrl)
15         {
16             if (HttpContext.User.Identity.IsAuthenticated)
17             {
18                 return RedirectToAction("AccountInfo");
19             }
20 
21             LoginModel loginInfo = new LoginModel();
22             ViewBag.returnUrl = returnUrl;
23             return View(loginInfo);
24         }
25 
26         [HttpPost]
27         [ValidateAntiForgeryToken]
28         [AllowAnonymous]
29         public async Task<IActionResult> Login(LoginModel loginInfo, string returnUrl)
30         {
31             if (ModelState.IsValid)
32             {
33                 Student student =await GetStudentByLoginModel(loginInfo);
34 
35                 if (student == null)
36                 {
37                     return View(loginInfo);
38                 }
39                 SignInResult signInResult = await _signInManager.PasswordSignInAsync(student, loginInfo.Password, false, false);
40 
41                 if (signInResult.Succeeded)
42                 {
43                     return Redirect(returnUrl ?? "/StudentAccount/"+nameof(AccountInfo));
44                 }
45 
46                 ModelState.AddModelError("", "賬號或密碼錯誤");
47                             
48             }
49 
50             return View(loginInfo);
51         }
52 
53         public IActionResult AccountInfo()
54         {
55             return View(CurrentAccountData());
56         }
57 
58         Dictionary<string, object> CurrentAccountData()
59         {
60             var userName = HttpContext.User.Identity.Name;
61             var user = _userManager.FindByNameAsync(userName).Result;
62 
63             return new Dictionary<string, object>()
64             {
65                 ["學號"]=userName,
66                 ["姓名"]=user.Name,
67                 ["郵箱"]=user.Email,
68                 ["手機號"]=user.PhoneNumber,
69             };
70         }
71     }

  _userManager 以及  _signInManager 將通過 DI 獲得實例;[ValidateAntiForgeryToken] 特性用於防止 XSRF 攻擊;returnUrl 參數用於接收或返回之前正在訪問的頁面,在此處若 returnUrl 為空則返回 AccountInfo 頁面;[Authorize] 特性用於確保只有已授權的用戶才能訪問對應動作方法;CurrentAccountData 方法用於獲取當前用戶的信息以在 AccountInfo 視圖中呈現。

 

  由於未進行授權,在此直接訪問 AccountInfo 方法默認會返回 /Account/Login 頁面請求驗證,可通過在 Startup.cs 的 ConfigureServices 方法進行配置以覆蓋這一行為,讓頁面默認返回 /StudentAccount/Login :

1             services.ConfigureApplicationCookie(opts =>
2             {
3                 opts.LoginPath = "/StudentAccount/Login";
4             }

 

  為了使 [Authorize] 特性能夠正常工作,需要在 Configure 方法中使用 Authentication 中間件,如果沒有調用 app.UseAuthentication(),則訪問帶有 [Authorize] 的方法會再度要求進行驗證。中間件的順序很重要:

1             app.UseAuthentication();
2             app.UseHttpsRedirection();
3             app.UseStaticFiles();
4             app.UseCookiePolicy();

 

  直接訪問 AccountInfo 頁面:

 

  輸入賬號密碼進行驗證:

 

  驗證之后返回 /StudentAccount/AccountInfo 頁面:

 

 

 

四、創建登出網頁

  簡單地調用 SignOutAsync 用以清除當前 Cookie 中的授權信息。

 1         public async Task<IActionResult> Logout(string returnUrl)
 2         {
 3             await _signInManager.SignOutAsync();
 4             if (returnUrl == null)
 5             {
 6                 return View("Login");
 7             }
 8 
 9             return Redirect(returnUrl);
10         }

 

  同時在 AccountInfo 添加登出按鈕:

 1     @model Dictionary<string, object>
 2     @{
 3         ViewData["Title"] = "AccountInfo";
 4     }
 5     <h2>賬戶信息</h2>
 6     <ul>
 7         @foreach (var info in Model)
 8         {
 9             <li>@info.Key: @Model[info.Key]</li>
10         }
11     </ul>
12     <br />
13     <a class="btn btn-danger" asp-action="Logout">登出</a>

 

 

 

  登出后返回 Login 頁面,同時 AccountInfo 頁面需要重新進行驗證。

 

  附加使用郵箱以及手機號驗證的測試:

 

 

 

 

 

五、基於 Role 的 Identity 授權

  修改 StudentInitial 類,添加名為 admin 的學生數組並使用 AddToRoleAsync 為用戶添加身份。在添加 Role 之前需要在 RoleManager 對象中使用 Create 方法為 Role 數據庫添加特定的 Role 字段:

 1     public class StudentInitiator
 2     {
 3         public static async Task InitialStudents(IServiceProvider serviceProvider)
 4         {
 5             UserManager<Student> userManager = serviceProvider.GetRequiredService<UserManager<Student>>();
 6             RoleManager<IdentityRole> roleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
 7             if (userManager.Users.Any())
 8             {
 9                 return;
10             }
11 
12             if (await roleManager.FindByNameAsync("Admin")==null)
13             {
14                 await roleManager.CreateAsync(new IdentityRole("Admin"));
15             }
16 
17             if (await roleManager.FindByNameAsync("Student")==null)
18             {
19                 await roleManager.CreateAsync(new IdentityRole("Student"));
20             }
21 
22             IEnumerable<Student> initialStudents = new[]
23             {
24                 new Student()
25                 {
26                     UserName = "U201600001",
27                     Name = "Nanase",
28                     Email = "Nanase@cnblog.com",
29                     PhoneNumber = "12345678910",
30                     Degree = Degrees.CollegeStudent,
31                     MaxBooksNumber = 10,
32                 },
33                 new Student()
34                 {
35                     UserName = "U201600002",
36                     Name = "Ruri",
37                     Email = "NanaseRuri@cnblog.com",
38                     PhoneNumber = "12345678911",
39                     Degree = Degrees.DoctorateDegree,
40                     MaxBooksNumber = 15
41                 }
42             };
43 
44             IEnumerable<Student> initialAdmins = new[]
45             {
46                 new Student()
47                 {
48                     UserName = "A000000000",
49                     Name="Admin0000",
50                     Email = "Admin@cnblog.com",
51                     PhoneNumber = "12345678912",
52                     Degree = Degrees.CollegeStudent,
53                     MaxBooksNumber = 20
54                 },
55                 new Student()
56                 {
57                     UserName = "A000000001",
58                     Name = "Admin0001",
59                     Email = "123456789@qq.com",
60                     PhoneNumber = "12345678910",
61                     Degree = Degrees.CollegeStudent,
62                     MaxBooksNumber = 20
63                 },
64             };
65             foreach (var student in initialStudents)
66             {
67                 await userManager.CreateAsync(student, student.UserName.Substring(student.UserName.Length - 6, 6));
68             }
69             foreach (var admin in initialAdmins)
70             {
71                 await userManager.CreateAsync(admin, "zxcZXC!123");
72                 await userManager.AddToRoleAsync(admin, "Admin");
73             }
74         }
75     }

 

   對 ConfigureServices 作進一步配置,添加 Cookie 的過期時間和不滿足 Authorize 條件時返回的 Url:

1             services.ConfigureApplicationCookie(opts =>
2             {
3 opts.Cookie.HttpOnly = true; 4 opts.LoginPath = "/StudentAccount/Login"; 5 opts.AccessDeniedPath = "/StudentAccount/Login"; 6 opts.ExpireTimeSpan=TimeSpan.FromMinutes(5); 7 });

   則當 Role 不為 Admin 時將返回 /StudentAccount/Login 而非默認的 /Account/AccessDeny。

 

 

  然后新建一個用以管理學生信息的 AdminAccount 控制器,設置 [Authorize] 特性並指定 Role 屬性,使帶有特定 Role 的身份才可以訪問該控制器。

 1     [Authorize(Roles = "Admin")]
 2     public class AdminAccountController : Controller
 3     {
 4         private UserManager<Student> _userManager;
 5 
 6         public AdminAccountController(UserManager<Student> userManager)
 7         {
 8             _userManager = userManager;
 9         }
10 
11         public IActionResult Index()
12         {
13             ICollection<Student> students = _userManager.Users.ToList();
14             return View(students);
15         }
16     }

 

  Index 視圖:

  1 @using LibraryDemo.Models.DomainModels
  2 @model IEnumerable<LibraryDemo.Models.DomainModels.Student>
  3 @{
  4     ViewData["Title"] = "AccountInfo";
  5     Student stu = new Student();
  6 }
  7 <link rel="stylesheet" href="~/css/BookInfo.css" />
  8 
  9 <script>
 10     function confirmDelete() {
 11         var userNames = document.getElementsByName("userNames");
 12         var message = "確認刪除";
 13         var values = [];
 14         for (i in userNames) {
 15             if (userNames[i].checked) {
 16                 message = message + userNames[i].value+",";
 17                 values.push(userNames[i].value);
 18             }
 19         }
 20         message = message + "?";
 21         if (confirm(message)) {
 22             $.ajax({
 23                 url: "@Url.Action("RemoveStudent")",
 24                 contentType: "application/json",
 25                 method: "POST",
 26                 data: JSON.stringify(values),
 27                 success: function(students) {
 28                     updateTable(students);
 29                 }
 30             });
 31         }
 32     }
 33 
 34     function updateTable(data) {
 35         var body = $("#studentList");
 36         body.empty();
 37         for (var i = 0; i < data.length; i++) {
 38             var person = data[i];
 39             body.append(`<tr><td><input type="checkbox" name="userNames" value="${person.userName}" /></td>
 40             <td>${person.userName}</td><td>${person.name}</td><td>${person.degree}</td>
 41             <td>${person.phoneNumber}</td><td>${person.email}</td><td>${person.maxBooksNumber}</td></tr>`);
 42         }
 43     };
 44 
 45     function addStudent() {
 46         var studentList = $("#studentList");
 47         if (!document.getElementById("studentInfo")) {
 48             studentList.append('<tr id="studentInfo">' +
 49                 '<td></td>' +
 50                 '<td><input type="text" name="UserName" id="UserName" /></td>' +
 51                 '<td><input type="text" name="Name" id="Name" /></td>' +
 52                 '<td><input type="text" name="Degree" id="Degree" /></td>' +
 53                 '<td><input type="text" name="PhoneNumber" id="PhoneNumber" /></td>' +
 54                 '<td><input type="text" name="Email" id="Email" /></td>' +
 55                 '<td><input type="text" name="MaxBooksNumber" id="MaxBooksNumber" /></td>' +
 56                 '<td><button type="submit" onclick="return postAddStudent()">添加</button></td>' +
 57                 '</tr>');
 58         }
 59     }
 60     
 61     function postAddStudent() {
 62         $.ajax({
 63             url: "@Url.Action("AddStudent")",
 64             contentType: "application/json",
 65             method: "POST",
 66             data: JSON.stringify({
 67                 UserName: $("#UserName").val(),
 68                 Name: $("#Name").val(),
 69                 Degree:$("#Degree").val(),
 70                 PhoneNumber: $("#PhoneNumber").val(),
 71                 Email: $("#Email").val(),
 72                 MaxBooksNumber: $("#MaxBooksNumber").val()
 73             }),
 74             success: function (student) {
 75                 addStudentToTable(student);
 76             }
 77         });
 78     }
 79 
 80     function addStudentToTable(student) {
 81         var studentList = document.getElementById("studentList");
 82         var studentInfo = document.getElementById("studentInfo");
 83         studentList.removeChild(studentInfo);
 84 
 85         $("#studentList").append(`<tr>` +
 86             `<td><input type="checkbox" name="userNames" value="${student.userName}" /></td>` +
 87             `<td>${student.userName}</td>` +
 88             `<td>${student.name}</td>`+
 89             `<td>${student.degree}</td>` +
 90             `<td>${student.phoneNumber}</td>` +
 91             `<td>${student.email}</td>` +
 92             `<td>${student.maxBooksNumber}</td >` +
 93             `</tr>`);
 94     }
 95 </script>
 96 
 97 <h2>學生信息</h2>
 98 
 99 <div id="buttonGroup">
100     <button class="btn btn-primary" onclick="return addStudent()">添加學生</button>
101     <button class="btn btn-danger" onclick="return confirmDelete()">刪除學生</button>
102 </div>
103 
104 
105 <br />
106 <table>
107     <thead>
108         <tr>
109             <th></th>
110             <th>@Html.LabelFor(m => stu.UserName)</th>
111             <th>@Html.LabelFor(m => stu.Name)</th>
112             <th>@Html.LabelFor(m => stu.Degree)</th>
113             <th>@Html.LabelFor(m => stu.PhoneNumber)</th>
114             <th>@Html.LabelFor(m => stu.Email)</th>
115             <th>@Html.LabelFor(m => stu.MaxBooksNumber)</th>
116         </tr>
117     </thead>
118     <tbody id="studentList">
119 
120         @if (!@Model.Any())
121         {
122             <tr><td colspan="6">未有學生信息</td></tr>
123         }
124         else
125         {
126             foreach (var student in Model)
127             {
128                 <tr>
129                     <td><input type="checkbox" name="userNames" value="@student.UserName" /></td>
130                     <td>@student.UserName</td>
131                     <td>@student.Name</td>
132                     <td>@Html.DisplayFor(m => student.Degree)</td>
133                     <td>@student.PhoneNumber</td>
134                     <td>@student.Email</td>
135                     <td>@student.MaxBooksNumber</td>
136                 </tr>
137             }
138         }
139     </tbody>
140 </table>

 

  使用 Role 不是 Admin 的賬戶登錄:

 

 

   使用 Role 為 Admin 的賬戶登錄:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM