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 的账户登录:

 

posted @ 2019-02-14 00:03  NanaseRuri  阅读(2996)  评论(1编辑  收藏  举报