Trong bài học này chúng ta tiếp tục hoàn thiện các chức năng chính như đã phân tích, bao gồm: bổ sung chức năng xóa dữ liệu, lọc dữ liệu, tự động tìm sách trong thư mục, mở file pdf từ chương trình, đánh dấu (bookmark) các cuốn sách đang đọc.
Thực hành 1: thêm chức năng xóa dữ liệu
Chức năng xóa dữ liệu chưa được thực hiện ở các bài trước đây. Ở chức năng này chúng ta không xây dựng một lớp view riêng mà tận dụng khả năng của lớp MessageView
(đã xây dựng ở bài này).
Bước 1. Tạo phương thức Delete
Xây dựng phương thức Delete
trong lớp BookController
như sau:
public void Delete(int id, bool process = false) { if (process == false) { var b = Repository.Select(id); Confirm($"Do you want to delete this book ({b.Title})? ", $"do delete?id={b.Id}"); } else { Repository.Delete(id); Success("Book deleted!"); } }
Trong phương thức Delete
chúng ta vận dụng lớp Message
và MessageView
đã xây dựng ở bài trước để đưa ra các thông báo ngắn cho người dùng mà không cần xây dựng một lớp view riêng cho phương thức Delete
.
Bước 2. Bổ sung thêm route
r.Register(route: "delete", action: p => controller.Delete(p["id"].ToInt()), help: "[delete ? id = <value>"); r.Register(route: "do delete", action: p => controller.Delete(p["id"].ToInt(), true), help: "this route should be used only in code");
Bước 3. Dịch và chạy thử với lệnh delete

delete
Thực hành 2: thêm chức năng lọc dữ liệu
Ở phần thực hành này chúng ta bổ sung thêm một tính năng mới: lọc dữ liệu. Ở chức năng này, người dùng cung cấp một từ khóa bất kỳ. Chương trình sẽ tìm trong danh sách dữ liệu tất cả những cuốn sách mà tiêu đề, tác giả, nhà xuất bản, tag và mô tả có chứa từ khóa này.
Bước 1. Thêm phương thức Filter vào lớp BookController
public void Filter(string key) { var model = Repository.Select(key); if (model.Length == 0) Inform("No matched book found!"); else Render(new BookListView(model)); }
Bước 2. Bổ sung thêm route
r.Register(route: "filter", action: p => controller.Filter(p["key"]), help: "[filter ? key = <value>]rntìm sách theo từ khóa");
Bước 3. Dịch và chạy thử chương trình với lệnh filter

filter
Khi lọc dữ liệu, bạn cũng có thể muốn sắp xếp dữ liệu theo tiêu chí nào đó, ví dụ theo tựa sách, theo tác giả, theo năm xuất bản. Bạn có thể đọc thêm về các thuật toán sắp xếp để tự thực hiện.
Thực hành 3: thêm chức năng tìm sách trong thư mục
Như đã mô tả ở bài 1, đây là chức năng giúp xây dựng dữ liệu sách tự động. Người dùng cung cấp một đường dẫn tới thư mục chứa các file sách, chương trình sẽ phát hiện tất cả các file pdf có trong đó và sử dụng tên và đường dẫn của các file này để tạo ra dữ liệu cơ bản về kho sách.
Trong phần thực hành này, chúng ta sẽ tạo ra thêm một lớp điều khiển mới để thực hiện các chức năng liên quan tới file, thư mục.
Bước 1. Xây dựng lớp ShellController
Tạo mới file ShellController.cs trong thư mục Controllersdành cho lớp ShellController và viết code cho lớp ShellController
như sau:
using System.Diagnostics; using System.IO; namespace BookMan.ConsoleApp.Controllers { using DataServices; using Models; using Views; using Framework; internal class ShellController : ControllerBase { protected Repository Repository; public ShellController(SimpleDataAccess context) { Repository = new Repository(context); } public void Shell(string folder, string ext = "*.pdf") { if (!Directory.Exists(folder)) { Error("Folder not found!"); return; } var files = Directory.GetFiles(folder, ext ?? "*.pdf", SearchOption.AllDirectories); foreach (var f in files) { Repository.Insert(new Book { Title = Path.GetFileNameWithoutExtension(f), File = f }); } if (files.Length > 0) { //Render(new BookListView(Repository.Select())); Success($"{files.Length} item(s) found!"); return; } Inform("No item found!", "Sorry!"); } } }
Ở bước này chúng ta xây dựng lớp ShellController
kế thừa từ lớp ControllerBase
đã xây dựng ở các bài trước.
Bước 2. Bổ sung code cho ConfigRouter
BookController controller = new BookController(context); ShellController shell = new ShellController(context);
Bước này chúng ta chỉ bổ sung khai báo và khởi tạo một object của ShellController
. Object này dùng để thực hiện các chức năng mới liên quan đến file và thư mục.
Bước 3. Bổ sung route mới cho ConfigRouter
r.Register(route: "add shell", action: p => shell.Shell(p["path"], p["ext"]), help: "[add shell ? path = <value>]");
Bước 4. Dịch và chạy thử chương trình

add shell
Trong phần thực hành trên chúng ta lần đầu áp dụng các lớp .NET hỗ trợ làm việc với hệ thống file của windows.
Tất cả các lớp để làm việc với file trong .NET nằm trong không gian tên System.IO
. Ba class chính để làm việc với hệ thống file là Directory
(làm việc với thư mục), File
(làm việc với file), Path
(làm việc với đường dẫn).
Lớp Directory
Lớp Directory chứa hầu hết các phương thức tĩnh giúp làm việc với file và thư mục. Trong phần thực hành chúng ta đã sử dụng một số phương thức của lớp này giúp kiểm tra đường dẫn và giúp lấy danh sách file trong một thư mục.
- Phương thức tĩnh
Exists
của lớpDirectory
: kiểm tra xem một đường dẫn tới thư mục có tồn tại hoặc chính xác không.
if (!Directory.Exists(folder)) { Error("Folder not found!"); return; }
- Phương thức tĩnh
GetFiles
của lớpDirectory
: tìm tất cả các file trong thư mục có phần mở rộng là pdf:
Directory.GetFiles(folder, ext ?? "*.pdf", SearchOption.AllDirectories);
Phương thức này sử dụng ba tham số:
- đường dẫn tới thư mục;
- mẫu tìm kiếm: mẫu văn bản mà phương thức
GetFiles
sử dụng trong quá trình tìm kiếm.GetFiles
chỉ trả lại những file mà tên phù hợp với mẫu văn bản của tham số này. - phạm vi tìm kiếm: xác định xem phương thức
GetFiles
chỉ tìm trong thư mục được chỉ định (TopDirectoryOnly
) hay tìm cả trong các thư mục con của nó (AllDirectories
).
Kết quả thực hiện của phương thức này là một mảng string chứa tên đầy đủ (bao gồm cả đường dẫn) của các file tìm thấy.
Lớp Path
Lớp Path cũng chứa hầu hết các phương thức tĩnh giúp phân tích đường dẫn tới file hoặc thư mục. Ở phần thực hành chúng ta đã sử dụng một phương thức của lớp này để phân tách tên file khỏi đường dẫn và phần mở rộng.
Phương thức GetFileNameWithoutExtension
của lớp Path
trích ra phần tên của file, bỏ phần đường dẫn và phần mở rộng.
Repository.Insert(new Book { Title = Path.GetFileNameWithoutExtension(f), File = f });
Tên file này được sử dụng tạm thời làm tiêu đề của sách (vì thường sách điện tử đặt tên file trùng với tiêu đề sách).
Các phương thức của hai class Directory và Path đều tương đối dễ sử dụng. Bạn đọc có thể tự mình tìm hiểu các phương thức khác.
Thực hành 4: thêm chức năng đọc sách từ chương trình
Chức năng này cho phép mở file pdf bằng chương trình đọc pdf mặc định của windows (Acrobat Reader, Foxit Reader, v.v.). Chức này này tiện lợi cho người sử dụng vì không cần phải mở các thư mục để tìm đến file.
Bước 1. Thêm phương thức vào ShellController
public void Read(int id) { var book = Repository.Select(id); if (book == null) { Error("Book not found!"); return; } if (!File.Exists(book.File)) { Error("File not found!"); return; } Process.Start(book.File); Success($"You are reading the book '{book.Title}'"); }
Bước 2. Bổ sung route vào ConfigRouter
r.Register(route: "read", action: p => shell.Read(p["id"].ToInt()), help: "[read ? id = <value>]");
Bước 3. Dịch và chạy thử chương trình

read
Sau lệnh này, cuốn “A first course in discrete mathematics” sẽ được mở ra bằng chương trình đọc pdf mặc định trên windows.
Thực hành 5: đánh dấu những cuốn sách đang đọc
Chức năng này cho phép đánh dấu những cuốn sách đang đọc để có thể dễ dàng tìm đọc tiếp. Khi xây dựng lớp Book chúng ta có thuộc tính Reading kiểu bool dành cho chức năng này.
Bước 1. Bổ sung phương thức vào lớp Repository
public Book[] SelectMarked() { var list = new List<Book>(); foreach (var b in Books) { if (b.Reading) list.Add(b); } return list.ToArray(); }
Bước 2. Bổ sung hai phương thức vào lớp BookController
public void Mark(int id, bool read = true) { var book = Repository.Select(id); if (book == null) { Error("Book not found!"); return; } book.Reading = read; Success($"The book '{book.Title}' are marked as { (read ? "READ" : "UNREAD")}"); } public void ShowMarks() { var model = Repository.SelectMarked(); var view = new BookListView(model); Render(view); }
Bước 3. Bổ sung route vào ConfigRouter
r.Register(route: "mark", action: p => controller.Mark(p["id"].ToInt()), help: "[mark ? id = <value>]"); r.Register(route: "unmark", action: p => controller.Mark(p["id"].ToInt(), false), help: "[unmark ? id = <value>]"); r.Register(route: "show marks", action: p => controller.ShowMarks(), help: "[show marks]");
Bước 4. Dịch và chạy thử chương trình

mark
và show marks
Chức năng này tận dụng lại lớp BookListView đã xây dựng từ trước. Lớp này có thể hiển thị các cuốn sách đang đọc bằng màu Cyan, còn các cuốn sách khác hiện màu trắng.
Thực hành 6: bổ sung khả năng xóa toàn bộ dữ liệu
Bước 1. Thêm phương thức vào lớp Repository
public void Clear() { _context.Books.Clear(); }
Bước 2. Thêm phương thức Clear vào ShellController
public void Clear(bool process = false) { if (!process) { Confirm("Do you really want to clear the shell? ", "do clear"); return; } Repository.Clear(); Inform("The shell has been cleared"); }
Bước 3. Bổ sung các route
r.Register(route: "clear", action: p => shell.Clear(), help: "[clear]rnUse with care"); r.Register(route: "do clear", action: p => shell.Clear(true), help: "[clear]rnUse with care");
Bước 4. Dịch và chạy thử chương trình với lệnh clear

clear
Kết luận
Trong bài này chúng ta vận dụng các class xây dựng trong các bài trước để hoàn thiện hầu hết các chức năng đã phân tích. Qua bài này chúng ta có thể thấy được khả năng mở rộng của ứng dụng và sự rành mạch trong phân chia code. Khi cần bổ sung thêm chức năng mới, chúng ta xác định được ngay các code mới cần thêm vào đâu.
Trong các bài sau chúng ta sẽ hoàn thiện nốt chức năng cuối cùng: lưu trữ dữ liệu.
+ Nếu bạn thấy site hữu ích, trước khi rời đi hãy giúp đỡ site bằng một hành động nhỏ để site có thể phát triển và phục vụ bạn tốt hơn.
+ Nếu bạn thấy bài viết hữu ích, hãy giúp chia sẻ tới mọi người.
+ Nếu có thắc mắc hoặc cần trao đổi thêm, mời bạn viết trong phần thảo luận cuối trang.
Cảm ơn bạn!
Ad cho hỏi ngu xíu nhé. Trong Bookcontroller ở bài này có xây dựng 2 phương thức, trong đó thân Delele sử dụng phương thức Confirm và thân Filter sử dụng 1 phương thức Infirm. 2 món này chưa tồn tại chứ Ad?
Mọi người cho mình hỏi ở chức năng đọc sách mình bị lỗi như sau:
The specified executable is not a valid application for this OS platform. at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo) at System.Diagnostics.Process.Start() at System.Diagnostics.Process.Start(ProcessStartInfo startInfo) at System.Diagnostics.Process.Start(String fileName).
là do đâu ạ