前言:
本系列文章主要为我之前所学知识的一次微小的实践,以我学校图书馆管理系统为雏形所作。
本系列文章主要参考资料:
微软文档: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
本章内容:通过模态窗口确认是否提交表单、select 元素、表单提交数组、checkbox、关闭窗口前确认、EF 修改主键
一、外借/阅览图书信息首页
这里的实现和上一章大致一致,但这里我打算通过前面的 BookDetail 页面进入到这个页面:
首先创建一个视图模型同时保存 BookDetail 的信息并传递 IEnumerable<Book> 以便对对应 BookDetails 的外借/阅览图书信息进行展示:
1 public class BookListViewModel 2 { 3 public IEnumerable<BookDetails> BookDetails { get; set; } 4 public PagingInfo PagingInfo { get; set; } 5 }
对应 BookDetail 的外借/阅览图书信息首页:
1 [Authorize(Roles = "Admin")] 2 public IActionResult Books(string isbn) 3 { 4 BookEditModel model = new BookEditModel() 5 { 6 Books = _lendingInfoDbContext.Books.Include(b => b.Keeper).AsNoTracking().Where(b => b.ISBN == isbn), 7 BookDetails = _lendingInfoDbContext.BooksDetail.AsNoTracking().FirstOrDefault(b => b.ISBN == isbn) 8 }; 9 if (model.BookDetails == null) 10 { 11 TempData["message"] = "未找到目标书籍"; 12 return RedirectToAction("BookDetails"); 13 } 14 return View(model); 15 }
视图:
1 @using LibraryDemo.Models.DomainModels 2 @model BookEditModel 3 4 @{ 5 ViewData["Title"] = "外借/阅览书籍信息"; 6 Book temp = new Book(); 7 } 8 9 <script> 10 function confirmDelete() { 11 var barcodes = document.getElementsByName("barcodes"); 12 var message="确认删除"; 13 for (i in barcodes) { 14 if (barcodes[i].checked) { 15 var book = barcodes[i].parentElement.nextElementSibling.firstElementChild.innerHTML; 16 message=message+"《"+book+"》"; 17 } 18 } 19 message = message + "?"; 20 if (confirm(message) == true) { 21 return true; 22 } else { 23 return false; 24 } 25 } 26 </script> 27 28 <style type="text/css"> 29 tr + tr { 30 border-top: thin solid gray; 31 } 32 33 td + td { 34 padding-left: 50px; 35 } 36 37 .container { 38 width: 1200px; 39 } 40 </style> 41 42 <h2>《@Model.BookDetails.Name》</h2> 43 <br /> 44 45 @if (TempData["message"] != null) 46 { 47 <p style="font-size: large;color:red" >@TempData["message"]</p> 48 <br /> 49 <br /> 50 } 51 52 <form asp-action="RemoveBooks" method="post"> 53 <div> 54 <a class="btn btn-primary" asp-action="AddBook" asp-route-isbn="@Model.BookDetails.ISBN">添加外借书籍</a> 55 <button type="submit" class="btn btn-danger" onclick="return confirmDelete()"> 删除外借书籍</button> 56 </div> 57 <br /> 58 <input type="hidden" name="isbn" value="@Model.BookDetails.ISBN"/> 59 <table> 60 <tbody> 61 <tr> 62 <td></td> 63 <td>@Html.LabelFor(b => temp.BarCode)</td> 64 <td>@Html.LabelFor(b => temp.Bookshelf)</td> 65 <td>@Html.LabelFor(b => temp.BorrowTime)</td> 66 <td>@Html.LabelFor(b => temp.MatureTime)</td> 67 <td>@Html.LabelFor(b => temp.AppointedLatestTime)</td> 68 <td>@Html.LabelFor(b => temp.State)</td> 69 <td>@Html.LabelFor(b => temp.KeeperId)</td> 70 <td>编辑借出信息</td> 71 </tr> 72 @if (Model.Books.ToList().Count == 0) 73 { 74 <tr><td colspan="7">未有《@Model.BookDetails.Name》的外借/阅览书籍信息</td></tr> 75 } 76 @foreach (var book in Model.Books) 77 { 78 <tr> 79 <td><input type="checkbox" name="barcodes" value="@book.BarCode"/></td> 80 <td><a asp-action="EditBook" asp-route-barcode="@book.BarCode">@Html.DisplayFor(b => book.BarCode)</a></td> 81 <td>@Html.DisplayFor(b => book.BookshelfId)</td> 82 <td>@Html.DisplayFor(b => book.BorrowTime)</td> 83 <td>@Html.DisplayFor(b => book.MatureTime)</td> 84 <td>@Html.DisplayFor(b => book.AppointedLatestTime)</td> 85 <td>@Html.DisplayFor(b => book.State)</td> 86 <td>@Html.DisplayFor(b => book.Keeper.Name)</td> 87 <td><a asp-action="EditLendingInfo" asp-route-barcode="@book.BarCode">编辑</a></td> 88 </tr> 89 } 90 </tbody> 91 </table> 92 </form>
结果:
二、添加外借/阅览图书信息
在 21 行中使用了 Bind 特性,使在模型绑定过程中只绑定对应属性名的属性,防止了与其他数据的绑定。 Bind 特性中的属性名区分大小写,与表单中的 input 的 name 一一对应。
26 行使用 .AsNoTracking 告诉 EF 在查询时禁用更改跟踪提高性能,对不需要进行更改的数据的查询都可以带有该方法。
32 行对表单上传的数据进行检验以确认来自同一本书。
34 行中使用 Include 方法告诉 EF 使返回的书架包含架上书本的信息,使 bookshelf.Books 返回一个 ICollection 实例而不是空 ICollection 以将新的外借/阅览图书信息添加其中。
1 [Authorize(Roles = "Admin")] 2 public IActionResult AddBook(string isbn) 3 { 4 BookDetails bookDetails = _lendingInfoDbContext.BooksDetail.FirstOrDefault(b => b.ISBN == isbn); 5 if (bookDetails == null) 6 { 7 return RedirectToAction("BookDetails", new { isbn = isbn }); 8 } 9 Book book = new Book() 10 { 11 ISBN = bookDetails.ISBN, 12 Name = bookDetails.Name, 13 FetchBookNumber = bookDetails.FetchBookNumber 14 }; 15 return View(book); 16 } 17 18 [HttpPost] 19 [ValidateAntiForgeryToken] 20 [Authorize(Roles = "Admin")] 21 public async Task<IActionResult> AddBook([Bind("ISBN,Name,FetchBookNumber,BarCode,BookshelfId,State")]Book book) 22 { 23 if (ModelState.IsValid) 24 { 25 BookDetails bookDetails = _lendingInfoDbContext.BooksDetail.FirstOrDefault(b => b.ISBN == book.ISBN); 26 Book existBook = _lendingInfoDbContext.Books.AsNoTracking().FirstOrDefault(b => b.BarCode == book.BarCode); 27 if (existBook != null) 28 { 29 TempData["message"] = $"已有二维码为{book.BarCode}的书籍《{existBook.Name}》"; 30 return RedirectToAction("AddBook", new { isbn = book.ISBN }); 31 } 32 if (bookDetails.Name == book.Name) 33 { 34 Bookshelf bookshelf = _lendingInfoDbContext.Bookshelves.Include(b => b.Books).FirstOrDefault(b => b.BookshelfId == book.BookshelfId); 35 if (bookshelf != null) 36 { 37 book.Sort = bookshelf.Sort; 38 book.Location = bookshelf.Location; 39 bookshelf.Books.Add(book); 40 bookshelf.MaxFetchNumber = bookshelf.Books.Max(b => b.FetchBookNumber); 41 bookshelf.MinFetchNumber = bookshelf.Books.Min(b => b.FetchBookNumber); 42 } 43 await _lendingInfoDbContext.Books.AddAsync(book); 44 await _lendingInfoDbContext.SaveChangesAsync(); 45 TempData["message"] = $"《{book.Name}》 {book.BarCode} 添加成功"; 46 return RedirectToAction("Books", new { isbn = book.ISBN }); 47 } 48 } 49 return View(book); 50 }
视图:
1 @using LibraryDemo.Models.DomainModels 2 @model LibraryDemo.Models.DomainModels.Book 3 4 @{ 5 ViewData["Title"] = "AddBook"; 6 } 7 8 <script> 9 window.onload = function () { 10 $("input").addClass("form-control"); 11 } 12 window.onbeforeunload = function () { 13 return "您的数据未保存,确定退出?"; 14 } 15 function removeOnbeforeunload() { 16 window.onbeforeunload = ""; 17 } 18 </script> 19 20 <h2>@($"为《{Model.Name}》添加借阅/阅览书籍信息")</h2> 21 <br /> 22 <br /> 23 @if (TempData["message"] != null) 24 { 25 <p class="text-success">@TempData["message"]</p> 26 <br /> 27 <br /> 28 } 29 30 @Html.ValidationSummary(false, "", new { @class = "text-danger" }) 31 <form asp-action="AddBook" method="post"> 32 <div class="form-group"> 33 @Html.LabelFor(b => b.ISBN) 34 <input type="text" value="@Model.ISBN" readonly="readonly" name="@nameof(Model.ISBN)" /> 35 </div> 36 <div class="form-group"> 37 @Html.LabelFor(b => b.Name) 38 <input type="text" value="@Model.Name" readonly="readonly" name="@nameof(Model.Name)" /> 39 </div> 40 <div class="form-group"> 41 @Html.LabelFor(b => b.FetchBookNumber) 42 <input type="text" value="@Model.FetchBookNumber" readonly="readonly" name="@nameof(Model.FetchBookNumber)" /> 43 </div> 44 <div class="form-group"> 45 @Html.LabelFor(b => Model.BarCode) 46 @Html.EditorFor(b => Model.BarCode) 47 </div> 48 <div class="form-group"> 49 @Html.LabelFor(b => Model.BookshelfId) 50 @Html.EditorFor(b => Model.BookshelfId) 51 </div> 52 <div class="form-group"> 53 @Html.LabelFor(b => Model.State) 54 @Html.DropDownListFor(b=>Model.State,Enum.GetValues(typeof(BookState)).Cast<Enum>().Select(state => 55 { 56 string enumVal = Enum.GetName(typeof(BookState), state); 57 string displayVal; 58 switch (enumVal) 59 { 60 case "Normal": 61 displayVal = "可借阅"; 62 break; 63 case "Readonly": 64 displayVal = "馆内阅览"; 65 break; 66 case "Borrowed": 67 displayVal = "已借出"; 68 break; 69 case "ReBorrowed": 70 displayVal = "被续借"; 71 break; 72 case "Appointed": 73 displayVal = "被预约"; 74 break; 75 default: 76 displayVal = ""; 77 break; 78 } 79 return new SelectListItem() 80 { 81 Text=displayVal, 82 Value = enumVal 83 }; 84 })) 85 </div> 86 <div class="form-group"></div> 87 <input type="submit" class="btn-success" onclick="removeOnbeforeunload()" /> 88 </form>
三、移除外借/阅览图书信息
此处实现与之前移除书籍信息大致一致,但额外接受一个 isbn 参数用来返回原 isbn 的外借/阅览图书信息首页:
1 [Authorize(Roles = "Admin")] 2 [HttpPost] 3 [ValidateAntiForgeryToken] 4 public async Task<IActionResult> RemoveBooks(IEnumerable<string> barcodes, string isbn) 5 { 6 StringBuilder sb = new StringBuilder(); 7 foreach (var barcode in barcodes) 8 { 9 Book book = _lendingInfoDbContext.Books.First(b => b.BarCode == barcode); 10 _lendingInfoDbContext.Books.Remove(book); 11 sb.AppendLine($"{book.BarCode} 移除成功"); 12 } 13 await _lendingInfoDbContext.SaveChangesAsync(); 14 TempData["message"] = sb.ToString(); 15 return RedirectToAction("Books", new { isbn = isbn }); 16 }
四、增删总结果:
五、编辑借阅/阅览书籍信息:
在此设置 BarCode 可以被修改,由于修改主键时会导致 EF 的映射失败,因此EF 不支持直接修改主键,但是可以先将原数据删除再进行添加变相修改主键。
POST 的方法中额外接受一个 BarCode 用来保留原书籍信息。
1 [Authorize(Roles = "Admin")] 2 public IActionResult EditBook(string barcode) 3 { 4 Book book = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode == barcode); 5 if (book == null) 6 { 7 return RedirectToAction("BookDetails"); 8 } 9 return View(book); 10 } 11 12 [HttpPost] 13 [Authorize(Roles = "Admin")] 14 [ValidateAntiForgeryToken] 15 public async Task<IActionResult> EditBook(string oldBarCode, [Bind("BarCode,BookshelfId,Name,State")]Book book) 16 { 17 if (ModelState.IsValid) 18 { 19 Book oldBook = _lendingInfoDbContext.Books.FirstOrDefault(b => b.BarCode == oldBarCode); 20 if (oldBook == null) 21 { 22 TempData["message"] = $"不存在二维码为{oldBarCode}的书籍"; 23 return RedirectToAction("BookDetails"); 24 } 25 26 if (oldBook.Name == book.Name) 27 { 28 book.ISBN = oldBook.ISBN; 29 book.FetchBookNumber = oldBook.FetchBookNumber; 30 Bookshelf bookshelf = _lendingInfoDbContext.Bookshelves.Include(b => b.Books).FirstOrDefault(b => b.BookshelfId == book.BookshelfId); 31 if (bookshelf != null) 32 { 33 book.Sort = bookshelf.Sort; 34 book.Location = bookshelf.Location; 35 bookshelf.Books.Remove(oldBook); 36 bookshelf.Books.Add(book); 37 } 38 39 _lendingInfoDbContext.Books.Remove(oldBook); 40 _lendingInfoDbContext.Books.Add(book); 41 await _lendingInfoDbContext.SaveChangesAsync(); 42 TempData["message"] = "修改成功"; 43 return RedirectToAction("Books", new { isbn = oldBook.ISBN }); 44 } 45 } 46 return View(book); 47 }
1 @using LibraryDemo.Models.DomainModels 2 @model LibraryDemo.Models.DomainModels.Book 3 4 @{ 5 ViewData["Title"] = "EditBook"; 6 } 7 8 <script> 9 window.onload = function () { 10 $("input").addClass("form-control"); 11 } 12 window.onbeforeunload = function (event) { 13 return "您的数据未保存,确定退出?"; 14 } 15 function removeOnbeforeunload() { 16 window.onbeforeunload = ""; 17 } 18 </script> 19 20 <h2>编辑外借书籍</h2> 21 <br /> 22 <h3>《@Model.Name》</h3> 23 <br /> 24 <h4>原二维码:@Model.BarCode</h4> 25 <br /> 26 @Html.ValidationSummary(false,"",new{@class="text-danger"}) 27 28 <form asp-action="EditBook" method="post"> 29 <input type="hidden" name="oldBarCode" value="@Model.BarCode" /> 30 <input type="hidden" name="Name" value="@Model.Name" /> 31 <div class="form-group"> 32 @Html.LabelFor(b => b.BarCode) 33 @Html.EditorFor(b => b.BarCode) 34 </div> 35 <div class="form-group"> 36 @Html.LabelFor(b => b.BookshelfId) 37 @Html.EditorFor(b => b.BookshelfId) 38 </div> 39 <div class="form-group"> 40 @Html.LabelFor(b => b.State) 41 @Html.DropDownListFor(b => b.State, Enum.GetValues(typeof(BookState)).Cast<Enum>().Select(state => 42 { 43 string enumVal = Enum.GetName(typeof(BookState), state); 44 string displayVal; 45 switch (enumVal) 46 { 47 case "Normal": 48 displayVal = "可借阅"; 49 break; 50 case "Readonly": 51 displayVal = "馆内阅览"; 52 break; 53 case "Borrowed": 54 displayVal = "已借出"; 55 break; 56 case "ReBorrowed": 57 displayVal = "被续借"; 58 break; 59 case "Appointed": 60 displayVal = "被预约"; 61 break; 62 default: 63 displayVal = ""; 64 break; 65 } 66 return new SelectListItem() 67 { 68 Text = displayVal, 69 Value = enumVal, 70 Selected = Model.State.ToString() == enumVal 71 }; 72 })) 73 </div> 74 <div class="form-group"></div> 75 <input type="submit" class="btn-success" onclick="removeOnbeforeunload()" /> 76 </form>