Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

ASP.NET Core 打造一个简单的图书馆管理系统(八)学生借阅/预约/查询书籍事务

NanaseRuri 2019-02-08 13:51:00 阅读数:315 评论数:0 点赞数:0 收藏数:0

前言:

本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。

本系列文章主要参考资料:

微软文档: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

修改前地址:https://github.com/NanaseRuri/LibraryDemo/tree/SomeError

 

 

本章内容:自定义布局页、自定义 EditorFor 模板、EF 多对多数据的更新

 

 

一、更新模型

折腾许久找不到同时更新具有依赖关系的两个数据库的方法,遂对原 Student 模型进行精简,并添加一个 StudentInfo 模型用来保存相应的借书信息。同时原程序中原来非登录界面对 Student 类型的引用改为对 StudentInfo 的引用。

同时由于书籍和学生存在多对多的关系——一本书可被多人预约,而一个人可以借阅多本书,因此在此更新模型使书籍与学生有多对多的关系。

此处仅展示模型的修改,控制器方面的修改请在查看源码:

 

引入中间导航类:

这里新增的 AppointingDateTime 用于将借阅的书籍以及预约的书籍进行区分。1 public classAppointmentOrLending2 {3 public Book Book { get; set; }4 public string BookId { get; set; }5 public StudentInfo Student { get; set; }6 public string StudentId { get; set; }7 public DateTime? AppointingDateTime { get; set; }8 }

 

Student 模型改动

1 public classStudent : IdentityUser2 {3 ///

4 ///学号5 /// 6 [ProtectedPersonalData]7 [RegularExpression("[UIA]\d{9}")]8 public override string UserName { get; set; }9 10 [StringLength(14, MinimumLength = 11)] public override string PhoneNumber { get; set; }11 12 public string Name { get; set; }13 public Degrees Degree { get; set; }14 public int MaxBooksNumber { get; set; }15 }View Code

 

添加新模型 StudentInfo:1 public classStudentInfo2 {3 [Key]4 public string UserName { get; set; }5 6 [Required]7 public string Name { get; set; }8 9 ///

10 ///学位,用来限制借书数目11 /// 12 [Required]13 public Degrees Degree { get; set; }14 15 /// 16 ///最大借书数目17 /// 18 [Required]19 public int MaxBooksNumber { get; set; }20 21 /// 22 ///已借图书23 /// 24 public ICollection KeepingBooks { get; set; }25 26 public string AppointingBookBarCode { get; set; }27 28 [StringLength(14, MinimumLength = 11)]29 public string PhoneNumber { get; set; }30 31 /// 32 ///罚款33 /// 34 public decimal Fine { get; set; }35 }

 

在借书信息处添加学生信息和中间类的表,同时在此指定中间类的外键——指定其外键由学生学号和书籍条形码共同组成,需要重写 DbContext 父类的 OnModelCreating 方法使其覆盖对应表格在 EF 的默认生成方式:1 public classLendingInfoDbContext:DbContext2 {3 public LendingInfoDbContext(DbContextOptions options) : base(options)4 {5 }6 7 public DbSet Books { get; set; }8 public DbSet BooksDetail { get; set; }9 public DbSet Bookshelves { get; set; }10 public DbSet RecommendedBooks { get; set; }11 public DbSet Students { get; set; }12 public DbSet AppointmentOrLendings { get; set; }13 14 protected override voidOnModelCreating(ModelBuilder modelBuilder)15 {16 base.OnModelCreating(modelBuilder);17 modelBuilder.Entity()18 .HasKey(c => new{ c.BookId, c.StudentId });19 }20 }

 

然后例行的更新数据库:cd librarydemo add-migration AddStudents -c LibraryDemo.Data.LendingInfoDbContext update-database -c LibraryDemo.Data.LendingInfoDbContext

 

在原 BookInitiator 中对 Students 表进行初始化:1 if (!context.Students.Any())2 {3 IEnumerable initialStudents = new[]4 {5 newStudentInfo()6 {7 UserName = "U201600001",8 Name = "Nanase",9 PhoneNumber = "12345678910",10 Degree =Degrees.CollegeStudent,11 MaxBooksNumber = 10,12 },13 newStudentInfo()14 {15 UserName = "U201600002",16 Name = "Ruri",17 PhoneNumber = "12345678911",18 Degree =Degrees.DoctorateDegree,19 MaxBooksNumber = 15 20 }21 };22 23 IEnumerable initialAdmins = new[]24 {25 newStudentInfo()26 {27 UserName = "A000000000",28 Name="Admin0000",29 PhoneNumber = "12345678912",30 Degree =Degrees.CollegeStudent,31 MaxBooksNumber = 20 32 },33 newStudentInfo()34 {35 UserName = "A000000001",36 Name = "Admin0001",37 PhoneNumber = "15827411963",38 Degree =Degrees.CollegeStudent,39 MaxBooksNumber = 20 40 },41 };42 foreach (var student ininitialStudents)43 {44 context.Students.Add(student);45 awaitcontext.SaveChangesAsync();46 }47 foreach (var admin ininitialAdmins)48 {49 context.Students.Add(admin);50 awaitcontext.SaveChangesAsync();51 }52 }

 

 

 

二、自定义布局页 

在 ASP.NET 中,默认将 HTML 页面的 body 元素一部分抽出来,该部分称作 RenderBody ;然后将这部分放到一个布局即大体页面框架中即可完成对同一系列的页面进行精简的布局实现。

默认布局页为 Layout.cshtml,可在视图文件夹中根目录或各个控制器视图目录的 ViewStart.cshtml 修改默认布局页,或者在每个 Razor 页面的开头中指定布局页:1 @{2 ViewData["Title"] = "EditLendingInfo";3 Layout = "_LendingLayout";4 }

 

之前一直使用的是 VS 的默认布局页,现在以该默认布局页为基础,添加自己所需要的信息:

1 @using Microsoft.AspNetCore.Http.Extensions2 @using Microsoft.AspNetCore.Authorization3 @inject IAuthorizationService AuthorizationService4 5 6 7 8 9 @ViewData["Title"] - LibraryDemo 10 11 12 13 14 15 18 19 20 21 22

59 60
61 @if (TempData["message"] != null)62 {63
64

@TempData["message"]

65 }66 @RenderBody()67
68
69 70
71
72

© 2018 - LibraryDemo

73
74
75 76 77 78 79 80 81 86 91 92 93 @RenderSection("Scripts", required: false)94 95 View Code

 

现在大体框架:

 

除了默认的 RenderBody 外,可以指定特定的部分放在页面的不同地方,在布局页中使用@RenderSection("SectionName"):1 @RenderSection("SectionName")

 

且在视图页中使用指定特定的节@section SectionName{ };1 @section SectionName{ 2 3 };

则该视图页中的 SectionName 部分会被提取出来放到布局页对应的位置。

 

 

 

三、管理员编辑借阅信息

动作方法:

在此对数据库的表格使用 Include 方法使 EF 应用其导航属性以获得 KeepingBooks 列表,否则使用 Student 对象 KeepingBooks 属性只会返回空。1 [Authorize(Roles = "Admin")]2 public IActionResult EditLendingInfo(stringbarcode)3 {4 if (barcode == null)5 {6 return RedirectToAction("BookDetails");7 }8 Book book = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode ==barcode);9 returnView(book);10 }11 12 [HttpPost]13 [Authorize(Roles = "Admin")]14 [ValidateAntiForgeryToken]15 public async Task EditLendingInfo([Bind("BarCode,ISBN,BorrowTime,KeeperId,AppointedLatestTime,State")]Book book)16 {17 if(ModelState.IsValid)18 {19 if (book.BorrowTime >DateTime.Now)20 {21 ModelState.AddModelError("", "请检查外借时间");22 returnView(book);23 }24 if(book.AppointedLatestTime.HasValue)25 {26 if (book.AppointedLatestTime s.KeepingBooks).FirstOrDefaultAsync(s => s.UserName ==book.KeeperId);40 41 Book addedBook =_lendingInfoDbContext.Books42 .Include(b => b.Keeper).ThenInclude(k =>k.KeepingBooks)43 .FirstOrDefault(b => b.BarCode ==book.BarCode);44 if (addedBook == null)45 {46 return RedirectToAction("Books", new { isbn =book.ISBN });47 }48 49 StudentInfo preStudent =addedBook.Keeper;50 AppointmentOrLending targetLending = 51 preStudent?.KeepingBooks.FirstOrDefault(b => b.BookId ==addedBook.BarCode);52 53 addedBook.AppointedLatestTime =book.AppointedLatestTime;54 addedBook.State =book.State;55 addedBook.BorrowTime =book.BorrowTime;56 addedBook.MatureTime = null;57 58 preStudent?.KeepingBooks.Remove(targetLending);59 60 if(addedBook.BorrowTime.HasValue)61 {62 if (book.KeeperId == null)63 {64 ModelState.AddModelError("", "请检查借阅者");65 returnView(book);66 }67 68 if (student == null)69 {70 ModelState.AddModelError("", "不存在该学生");71 returnView(book);72 }73 if (student != null)74 {75 if (student.KeepingBooks.Count >=student.MaxBooksNumber)76 {77 TempData["message"] = "该学生借书已超过上限";78 }79 80 addedBook.State =BookState.Borrowed;81 student.KeepingBooks.Add(newAppointmentOrLending()82 {83 BookId =addedBook.BarCode,84 StudentId =student.UserName85 });86 addedBook.Keeper =student;87 88 }89 addedBook.MatureTime = addedBook.BorrowTime + TimeSpan.FromDays(28);90 }91 92 93 TempData["message"] = "保存成功";94 await_lendingInfoDbContext.SaveChangesAsync();95 return RedirectToAction("Books", new { isbn =book.ISBN });96 }97 returnView(book);98 }

 

将 BookState 枚举提取成分部视图 _BookStatePartial:1 @using LibraryDemo.Models.DomainModels2 @model Book3

4 @Html.LabelFor(b =>b.State)5 @Html.DropDownListFor(b => b.State, Enum.GetValues(typeof(BookState)).Cast().Select(state => 6 {7 string enumVal = Enum.GetName(typeof(BookState), state);8 stringdisplayVal;9 switch(enumVal)10 {11 case "Normal":12 displayVal = "可借阅";13 break;14 case "Readonly":15 displayVal = "馆内阅览";16 break;17 case "Borrowed":18 displayVal = "已借出";19 break;20 case "ReBorrowed":21 displayVal = "被续借";22 break;23 case "Appointed":24 displayVal = "被预约";25 break;26 default:27 displayVal = "";28 break;29 }30 return newSelectListItem()31 {32 Text =displayVal,33 Value =enumVal,34 Selected = Model.State.ToString() ==enumVal35 };36 }))37

 

Html.DisplayFor 方法是 ASP.NET 内置对各种属性进行展示的方法,可以在项目的 Views 文件夹中的 Shared 文件夹创建对应类型的 Editor 模板供其使用:

在此创建一个 DateTime.cshtml,于是我们使用 Html.DisplayFor 用于展示 DateTime 数据时只会显示年份/月份/天数:1 @model DateTime? 2 3 @Model?.ToString("yyyy/M/dd")

 

视图中使用 partial TagHelper 指定其 name 为 _BookStatePartial 以应用分部视图:1 @using LibraryDemo.Models.DomainModels2 @model LibraryDemo.Models.DomainModels.Book3 @{4 ViewData["Title"] = "EditLendingInfo";5 Layout="_LendingLayout";6 }7 8

@Model.BarCode

9

@Model.Name

10
11 12 23 24 @Html.ValidationSummary(false,"",new{@class="text-danger"})25 26
27 @Html.HiddenFor(b =>b.BarCode)28 @Html.HiddenFor(b =>b.ISBN)29
30 @Html.LabelFor(b =>b.KeeperId)31 @Html.EditorFor(b =>b.KeeperId)32
33
34 @Html.LabelFor(b =>b.BorrowTime)35 @Html.EditorFor(b =>b.BorrowTime)36
37
38 @Html.LabelFor(b =>b.AppointedLatestTime)39 @Html.EditorFor(b =>b.AppointedLatestTime)40
41 42 43

 

 

结果:

 

 

 

 

 

 

 

 

四、查看个人信息

这里通过 User.Identity.Name 获取当前登录人的信息以选定特定的学生:1 [Authorize]2 public async TaskPersonalInfo()3 {4 StudentInfo student = await _lendingInfoDbContext.Students.Include(s => s.KeepingBooks).ThenInclude(k =>k.Book)5 .FirstOrDefaultAsync(s => s.UserName ==User.Identity.Name);6 decimal fine = 0;7 foreach (var book in student.KeepingBooks.Where(b => b.Book.MatureTime < DateTime.Now && !b.AppointingDateTime.HasValue))8 {9 fine += (DateTime.Now - book.Book.MatureTime.Value).Days /* (decimal)0.2;10 book.Book.State = book.Book.State == BookState.Appointed ?BookState.Appointed : BookState.Expired;11 }12 13 student.Fine =fine;14 PersonalInfoViewModel model = newPersonalInfoViewModel()15 {16 Student =student,17 BookingBook = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode ==student.AppointingBookBarCode)18 };19 returnView(model);20 }

 

 

视图:1 @model LibraryDemo.Models.PersonalInfoViewModel2 @{3 ViewData["Title"] = "PersonalInfo";4 Layout = "_LendingLayout";5 double fine = 0;6 }7 8 9

@Model.Student.Name

10
11 @if (Model.Student.KeepingBooks.Any(b => b.Book.MatureTime 14 罚款:@foreach (var matureBook in Model.Student.KeepingBooks.Where(b => !b.AppointingDateTime.HasValue&&b.Book.MatureTime 20 }21
22 23 24 25 26 27 28 29 30 31 @if (!Model.Student.KeepingBooks.Any())32 {33 34 35 36 }37 else 38 {39 foreach (var keepingBook in Model.Student.KeepingBooks.Where(b=>!b.AppointingDateTime.HasValue))40 {41 42 43 44 45 46 47 48 49 }50 }51
续借书名条形码状态到期时间索书号
未借阅书本
@keepingBook.Book.Name@keepingBook.Book.BarCode@Html.DisplayFor(b=>keepingBook.Book.State)@keepingBook.Book.MatureTime?.ToString("yyyy/MM/dd")@keepingBook.Book.FetchBookNumber
52
53 54
55 56
57 @if (Model.BookingBook != null)58 {59
60 61 62 63 64 65 66 67 68 69
书名条形码状态预约时间索书号
70
71 72 73
74 }

 

 

 

 

 

 

五、借阅书籍

由于暂时未有获取二维码的接口,仅通过直接访问 Lending 模拟借阅:1 [Authorize] 2 public async Task Lending(stringbarcode) 3 { 4 Book targetBook=await _lendingInfoDbContext.Books.Include(b=>b.Appointments).FirstOrDefaultAsync(b => b.BarCode ==barcode); 5 if (targetBook==null) 6 { 7 TempData["message"] = "请重新扫描书籍"; 8 return RedirectToAction("PersonalInfo"); 9 } 10 11 if (targetBook.Appointments.Any(a=>a.AppointingDateTime.HasValue)) 12 { 13 TempData["message"] = "此书已被预约"; 14 return RedirectToAction("PersonalInfo"); 15 } 16 17 if (targetBook.State==BookState.Readonly) 18 { 19 TempData["message"] = "此书不供外借"; 20 return RedirectToAction("PersonalInfo"); 21 } 22 23 targetBook.State =BookState.Borrowed; 24 targetBook.BorrowTime =DateTime.Now.Date; 25 targetBook.MatureTime = DateTime.Now.Date+TimeSpan.FromDays(28); 26 StudentInfo student = 27 await _lendingInfoDbContext.Students.Include(s=>s.KeepingBooks).FirstOrDefaultAsync(s => s.UserName ==User.Identity.Name); 28 student.KeepingBooks.Add(newAppointmentOrLending() 29 { 30 BookId =targetBook.BarCode, 31 StudentId =student.UserName 32 }); 33 await_lendingInfoDbContext.SaveChangesAsync(); 34 TempData["message"] = "借书成功"; 35 return RedirectToAction("PersonalInfo"); 36 }

 

 

 

 

 

 

 六、续借书籍

动作方法:1 [Authorize]2 [HttpPost]3 public async Task ReBorrow(IEnumerablebarcodes)4 {5 StringBuilder borrowSuccess = newStringBuilder();6 StringBuilder borrowFail = newStringBuilder();7 borrowSuccess.Append("成功续借书籍:");8 borrowFail.Append("续借失败书籍:");9 foreach (var barcode inbarcodes)10 {11 Book reBorrowBook = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode ==barcode);12 if (reBorrowBook != null)13 {14 if (reBorrowBook.State == BookState.Borrowed && DateTime.Now-reBorrowBook.MatureTime?.Date<=TimeSpan.FromDays(3))15 {16 reBorrowBook.State =BookState.ReBorrowed;17 reBorrowBook.BorrowTime =DateTime.Now.Date;18 reBorrowBook.MatureTime = DateTime.Now.Date+TimeSpan.FromDays(28);19 borrowSuccess.Append($"《{reBorrowBook.Name}》、");20 }21 else 22 {23 borrowFail.Append($"《{reBorrowBook.Name}》、");24 }25 }26 }27 borrowSuccess.AppendLine(borrowFail.ToString());28 await_lendingInfoDbContext.SaveChangesAsync();29 TempData["message"] =borrowSuccess.ToString();30 return RedirectToAction("PersonalInfo");31 }

 

 

 

 

 

 

七、查询书籍

修改之前的 Search 方法使其通过当前用户的身份返回不同页面,以及在 _LendingInfoLayout 中添加搜索框部分:

19 行通过短路使未授权用户不用登录。1 public async Task Search(string keyWord, stringvalue)2 {3 BookDetails bookDetails = newBookDetails();4 switch(keyWord)5 {6 case "Name":7 bookDetails = await lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefaultAsync(b => b.Name ==value);8 break;9 case "ISBN":10 bookDetails = await lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefaultAsync(b => b.ISBN ==value);11 break;12 case "FetchBookNumber":13 bookDetails = await _lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefaultAsync(b => b.FetchBookNumber ==value);14 break;15 }16 17 if (bookDetails != null)18 {19 if (User.Identity.IsAuthenticated&& User.IsInRole("Admin"))20 {21 return RedirectToAction("EditBookDetails", new { isbn =bookDetails.ISBN });22 }23 else 24 {25 return RedirectToAction("Detail", new {isbn =bookDetails.ISBN});26 }27 }28 29 TempData["message"] = "找不到该书籍";30 return RedirectToAction("BookDetails");31 }

 

视图页:1 @using Microsoft.AspNetCore.Http.Extensions2 @using Microsoft.AspNetCore.Authorization3 @inject IAuthorizationService AuthorizationService4 5 6 7 8 9 @ViewData["Title"] - LibraryDemo 10 11 12 13 14 15 18 19 20 21 22

59 @if (TempData["message"] != null)60 {61
62

@TempData["message"]

63 }64
65
66
67 @Html.DropDownList("keyword",new List()68 {69 new SelectListItem("书名","Name"),70 new SelectListItem("ISBN","ISBN"),71 new SelectListItem("索书号","FetchBookNumber"),72 })73 74 75 76
77 78
79 80 @RenderBody()81
82
83 84
85
86

© 2018 - LibraryDemo

87
88
89 90 91 92 93 94 95 100 105 106 107 @RenderSection("Scripts", required: false)108 109

 

 

版权声明
本文为[NanaseRuri]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/gokoururi/p/10344333.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;

支付宝红包,每日可领