前言:
本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。
本系列文章主要参考资料:
微软文档: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
本章内容:自定义布局页、自定义 EditorFor 模板、EF 多对多数据的更新
一、自定义布局页
在 ASP.NET 中,默认将 HTML 页面的 body 元素一部分抽出来,该部分称作 RenderBody ;然后将这部分放到一个布局即大体页面框架中即可完成对同一系列的页面进行精简的布局实现。
默认布局页为 Layout.cshtml,可在视图文件夹中根目录或各个控制器视图目录的 ViewStart.cshtml 修改默认布局页,或者在每个 Razor 页面的开头中指定布局页:@{ ViewData["Title"] = "EditLendingInfo"; Layout= "_LendingLayout"; }
之前一直使用的是 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
71 72 @if (TempData["message"] != null)73 {74
75
@TempData["message"]
76
77 }78
79
80
81 82 @RenderBody()83
84 85 86
87
90
91
92 93 94 95 96
97 102 107 108 109 @RenderSection("Scripts", required: false)110 111 View Code
现在大体框架:


除了默认的 RenderBody 外,可以指定特定的部分放在页面的不同地方,在布局页中使用@RenderSection("SectionName"):@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")
视图中第 40 行使用 partial TagHelper 指定其 name 为 _BookStatePartial 以应用分部视图:1 @model LibraryDemo.Models.DomainModels.Book2 @{3 ViewData["Title"] = "EditLendingInfo";4 Layout="_LendingLayout";5 }6 7
@Model.BarCode
8
@Model.Name
9
10 11 22 23 @Html.ValidationSummary(false,"",new{@class="text-danger"})24 25
结果:









三、查看个人信息
这里通过 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 }6 7 15 16
@Model.Student.Name
17
18 @if (Model.Student.KeepingBooks.Any(b => b.Book.MatureTime
21 22 23 过期书籍 | 24
25 26 27 书名 | 28 条形码 | 29 状态 | 30 到期时间 | 31 索书号 | 32
33 34 @foreach (var matureBook in Model.Student.KeepingBooks.Where(b => b.Book.MatureTime < DateTime.Now && !b.AppointingDateTime.HasValue))35 {36 37 @matureBook.Book.Name | 38 @matureBook.Book.BarCode | 39 @Html.DisplayFor(b => matureBook.Book.State) | 40 @matureBook.Book.MatureTime?.ToString("yyyy/MM/dd") | 41 @matureBook.Book.FetchBookNumber | 42
43 }44 罚款:@Model.Student.Fine |
45 46 }47 81 82
83 @if (Model.BookingBook != null)84 {85 100 }
结果:

四、借阅书籍
由于暂时未有获取二维码的接口,仅通过直接访问 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.UserName32 });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 }
结果:

