Cải tiến view (1): NuGet, NewtonSoft, JSON

    5

    Trong bài học này chúng ta sẽ học cách sử dụng công cụ quản lý gói thư viện NuGet để cài đặt thư viện lớp của bên thứ ba. Chúng ta sẽ xem xét thư viện NewtonSoft Json để thêm chức năng xuất dữ liệu ra file.

    Trong các bài trước chúng ta đã xây dựng được một khung chương trình cơ bản đáp ứng một số yêu cầu đặt ra từ phân tích ở bài 1.

    Trong các bài còn lại của phần này, chúng ta sẽ lần lượt cải tiến các lớp giao diện sử dụng các kỹ thuật mới.

    Xuất dữ liệu ra file, JSON

    Trước hết chúng ta cải tiến các lớp giao diện bằng cách bổ sung thêm khả năng xuất dữ liệu ra file ở dạng json. Đây là yêu cầu có trong phần phân tích ca sử dụng ở bài đầu tiên (ca sử dụng số 8).

    Vì đây là một ứng dụng dạng console, mọi thông tin đều viết ra màn hình. Giao diện console không tiện lợi lắm để xem danh sách dữ liệu quá dài. Ngoài ra chúng ta có thể muốn xuất thông tin ra để sử dụng trong những chương trình khác (chẳng hạn import vào excel, import vào cơ sở dữ liệu).

    Một trong những định dạng dữ liệu được sử dụng và hỗ trợ rộng rãi hiện nay là JSON (JavaScript Object Notation).

    Các bạn có thể tham khảo thêm về định dạng JSON ở các trang web này: JSON IntroductionJSON SyntaxJSON.org.

    Chuỗi ký tự JSON được viết theo cú pháp mô tả object của ngôn ngữ JavaScript và hiện được sử dụng rất rộng rãi để lưu trữ hoặc truyền dữ liệu. JSON được sử dụng đặc biệt phổ biến trong môi trường web và để lưu trữ thông tin cấu hình của ứng dụng.

    .NET framework có hỗ trợ Json nhưng không được tiện lợi và hiệu quả như một số bộ thư viện của các bên thứ ba.

    Trong phần thực hành dưới đây chúng ta sẽ học cách sử dụng bộ thư viện JSON của NewtonSoft.

    Thực hành 1: cài đặt thư viện NewtonSoft.Json

    Bộ thư viện này cho phép chúng ta chuyển đổi một object của C# thành một chuỗi ký tự định dạng theo quy ước của JSON (JavaScript Object Notation) cũng như chuyển đổi ngược chuỗi JSON về object của C#. Đây là một trong những bộ thư viện có lượt download lớn nhất trên NuGet.

    Quá trình chuyển đổi này có tên gọi là serialization (từ object về JSON) và deserialization (từ JSON về object).

    Bước 1. Mở giao diện quản lý các gói thư viện NuGet

    Click phải vào References, chọn Manage NuGet Packages (xem hình dưới đây).

    Mở giao diện Manage NuGet Packages
    Mở giao diện Manage NuGet Packages

    Bước 2. Chọn cài gói thư viện

    Trong ô tìm kiếm ở tab Browse gõ newtonsoft, chọn gói NewtonSoft.Json và ấn Install.

    Giao diện Manage NuGet Packages
    Giao diện Manage NuGet Packages

    Sau lệnh này, Visual Studio sẽ tải gói thư viện này về và cài đặt lên project tương ứng (trong trường hợp này là BookMan.ConsoleApp).

    Kiểm tra kết quả

    Sau khi cài đặt thành công bộ thư viện này, trong danh sách References sẽ xuất hiện thêm một mục “NewtonSoft.Json”. Trong cấu trúc dự án sẽ xuất hiện thêm file “packages.config” chứa thông tin về các gói thư viện được cài đặt đặt thêm.

    Sau khi dịch chương trình (Ctrl + Shift + B) thành công, trong thư mục BinDebug của dự án sẽ xuất hiện file thư việc NewtonSoft.Json.dll.

    Khi triển khai ứng dụng cho người dùng cuối, file thư viện này cũng phải đi cùng file chương trình.

    File thư viện Newtonsoft.json.dll sau khi cài đặt
    File thư viện Newtonsoft.json.dll sau khi cài đặt

    Những các khác sử dụng NuGet Packages Manager

    Chúng ta có thể sử dụng theo một số cách khác nhau:

    1. sử dụng ứng dụng giao diện đồ họa NuGet Package Manager (như phần thực hành trên),
    2. sử dụng giao diện dòng lệnh Package Manager Console,
    3. cài đặt tự động với các file mã kịch bản.

    Cách đơn giản nhất để tìm và cài đặt các gói thư viện từ NuGet là sử dụng tiện ích mở rộng NuGet Package Manager như đã thực hiện trong phần thực hành trên.

    Sử dụng website kết hợp Package Manager Console

    Cách thứ hai là sử dụng dịch vụ tìm kiếm trên website https://www.nuget.org/packages để tìm gói thư viện phù hợp. Sau đó copy dòng lệnh paste vào Package Manager Console.

    Giao diện tìm kiếm thư viện Newtonsoft.Json trên website
    Giao diện tìm kiếm thư viện Newtonsoft.Json trên website

    Nếu không nhìn thấy tab Package Manager Console, chọn View => Other Windows => Package Manager Console, hoặc Tools => NuGet Package Manager => Package Manager Console.

    Giao diện dòng lệnh của Package Manager Console
    Giao diện dòng lệnh của Package Manager Console

    Khi sử dụng Package Manager Console lưu ý chọn tham số “Default project” là project mình cần cài đặt gói thư viện.

    Lưu ý

    Khi sử dụng các gói thư viện trên NuGet cần lưu ý xem xét kỹ sự phụ thuộc của gói thư viện cần dùng.

    Lý do là nhiều thư viện trên NuGet sử dụng lẫn nhau, cũng như được xây dựng cho các phiên bản .NET khác nhau.

    Khi cài đặt một thư viện mà nó phụ thuộc vào các thư viện khác, các thư viện kia cũng phải được cài đặt theo và phải cài đặt phiên bản mà thư viện chính có thể sử dụng được.

    Nếu các gói thư viện có phiên bản mới, NuGet cũng cho phép cập nhật phiên bản đang cài đặt trong dự án lên phiên bản mới. Tuy nhiên, cũng giống như khi cài đặt, phải lưu ý sự phụ thuộc giữa các thư viện trước khi quyết định nâng cấp. 

    Thực hành 2: thêm chức năng xuất dữ liệu ra file

    Bước 1. Bổ sung phương thức RenderToFile

    Bổ sung phương thức RenderToFile vào lớp BookListView

    public void RenderToFile(string path)
    {
        ViewHelp.WriteLine($"Saving data to file '{path}'");
        var json = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
        System.IO.File.WriteAllText(path, json);
        ViewHelp.WriteLine("Done!");
    }
    

    Tương tự, bổ sung phương thức RenderToFile vào lớp BookSingleView

    public void RenderToFile(string path)
    {
        ViewHelp.WriteLine($"Saving data to file '{path}'");
        var json = Newtonsoft.Json.JsonConvert.SerializeObject(Model);
        System.IO.File.WriteAllText(path, json);
        ViewHelp.WriteLine("Done!");
    }
    

    * để ý thấy rằng hai phương thức này giống hệt nhau

    Bước 2. Điều chỉnh các phương thức của lớp BookController

    Điều chỉnh phương thức Single và List của BookController thành dạng như sau:

    /// <summary>
    /// ghép nối dữ liệu 1 cuốn sách với giao diện hiển thị 1 cuốn sách
    /// </summary>
    /// <param name="id">mã định danh của cuốn sách</param>
    public void Single(int id, string path = "")
    {
        // lấy dữ liệu qua repository
        var model = Repository.Select(id);
        // khởi tạo view
        BookSingleView view = new BookSingleView(model);
        // gọi phương thức Render để thực sự hiển thị ra màn hình
        if (!string.IsNullOrEmpty(path)) { view.RenderToFile(path); return; }
        view.Render();
    }
    
    /// <summary>
    /// kích hoạt chức năng hiển thị danh sách
    /// </summary>
    public void List(string path = "")
    {
        // lấy dữ liệu qua repository
        var model = Repository.Select();
        // khởi tạo view
        BookListView view = new BookListView(model);
        if (!string.IsNullOrEmpty(path)) { view.RenderToFile(path); return; }
        view.Render();
    }
    

    Bước 3. Điều chỉnh lớp Program

    private static void Main(string[] args)
    {
        Console.OutputEncoding = System.Text.Encoding.UTF8;
    
        SimpleDataAccess context = new SimpleDataAccess();
        BookController controller = new BookController(context);
        
        Router r = Router.Instance;
    
        r.Register("about", About);
        r.Register("help", Help);
        r.Register(route: "create",
            action: p => controller.Create(),
            help: "[create]\r\nnhập sách mới");
        r.Register(route: "update",
            action: p => controller.Update(p["id"].ToInt()),
            help: "[update ? id = <value>]\r\ntìm và cập nhật sách");
        r.Register(route: "list",
            action: p => controller.List(),
            help: "[list]\r\nhiển thị tất cả sách");
        r.Register(route: "single",
            action: p => controller.Single(p["id"].ToInt()),
            help: "[single ? id = < value >]\r\nhiển thị một cuốn sách theo id");
    
        r.Register(route: "list file",
            action: p => controller.List(p["path"]),
            help: "[list file ? path = <value>]\r\nhiển thị tất cả sách");
        r.Register(route: "single file",
            action: p => controller.Single(p["id"].ToInt(), p["path"]),
            help: "[single file ? id = <value> & path = <value>]");
    
        while (true)
        {               
            ViewHelp.Write("# Request >>> ", ConsoleColor.Green);
            string request = Console.ReadLine();        
            Router.Instance.Forward(request);        
                        
            Console.WriteLine();
        }
    }
    

    Bước 4. Dịch và chạy thử chương trình

    Dịch và chạy thử chương trình với hai lệnh mới

    List file ? path = list.json
    Single file ? id = 1 & path = single1.json
    Kết quả chạy chương trình
    Kết quả chạy chương trình

    Khi đó trong cùng thư mục với file chạy xuất hiện hai file: list.json và single1.json với nội dung lần lượt như sau:

    [{"Id":1,"Authors":"Unknown author","Title":"A new book 1","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":2,"Authors":"Unknown author","Title":"A new book 2","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":3,"Authors":"Unknown author","Title":"A new book 3","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":4,"Authors":"Unknown author","Title":"A new book 4","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":5,"Authors":"Unknown author","Title":"A new book 5","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":6,"Authors":"Unknown author","Title":"A new book 6","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":7,"Authors":"Unknown author","Title":"A new book 7","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":8,"Authors":"Unknown author","Title":"A new book 8","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null},{"Id":9,"Authors":"Unknown author","Title":"A new book 9","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null}]

    {"Id":1,"Authors":"Unknown author","Title":"A new book 1","Publisher":"Unknown publisher","Year":2018,"Edition":1,"Isbn":null,"Tags":null,"Description":"A new book","Rating":1,"Reading":false,"File":null,"FileName":null}

    Đây là hai file văn bản định dạng theo kiểu JSON do chương trình tạo ra theo lệnh của người dùng.

    Nếu mang đoạn văn bản trên vào file mã nguồn của JavaScript, đoạn văn bản đó là mã nguồn chạy được vì đây chính là các đoạn code để tạo object của ngôn ngữ này!

    Đến đây chúng ta có thể thấy, việc bổ sung thêm các tính năng mới cho ứng dụng trở nên rất đơn giản theo quy trình: điều chỉnh/ tạo mới lớp giao diện => điều chỉnh lớp điều khiển => bổ sung phần tử mới cho routes.

    Tuy nhiên, trong việc thêm tính năng xuất dữ liệu ra file chúng ta lại gặp một vấn đề: lặp code.

    Trong bài trước, chúng ta đã giải quyết tình trạng lặp code bằng cách nhóm các phương thức có chức năng liên quan vào một class mới và chuyển chúng thành các phương thức tĩnh. Tuy nhiên, giải pháp này chỉ áp dụng tốt với các phương thức không sử dụng biến thành viên (tức là không có liên quan đến trạng thái của object).

    Đối với các lớp giao diện xuất hiện tình trạng lặp code khác. Hai phương thức trùng nhau RenderToFile ở trên lại bắt buộc phải sử dụng biến thành viên Model.

    Ngoài hai phương thức bị lặp này, chúng ta cũng nhận thấy, trong tất cả các lớp giao diện, các thành viên Model, phương thức Render và hàm tạo đều rất tương đồng nhau.

    Ở bài tiếp theo chúng ta sẽ học một giải pháp khác để tái sử dụng code giữa các class: kế thừa.

    Kết luận

    Trong bài học này chúng ta đã học cách sử dụng công cụ NuGet Package Manager để cài đặt bộ thư viện của hãng thứ ba. Chúng ta đã cài đặt thư viện NewtonSoft Json để thực hiện chức năng xuất dữ liệu ra file.

    Trong bài tiếp theo chúng ta sẽ xem xét sử dụng công cụ kế thừa để tái sử dụng code.

    + 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!

    Subscribe
    Notify of
    guest
    5 Thảo luận
    Oldest
    Newest
    Inline Feedbacks
    View all comments
    Trinh Le

    Gửi Admin! Trong class Router, phương thức public void Register(string router, ControllerAction action, string help = “”){ …} có 3 đối số là router, action và help. trong khi đó tai bước 3, điều chỉnh lớp Program khi gọi phương thức cho tính năng “Single file”” qua lời gọi hàm r.Register  r.Register(router: “single file”,        action: p => controller.Single(p[“id”].ToInt()), p[“path”],        help: “[single file ? id = <value> & path = <value>]\r\n hiển thị một cuốn sách theo id”); lời gọi hàm này truyền vào register tới 4 thông số, nên bị lỗi. nhờ add xem lại và chỉ giáo giúp để… Read more »

    Nhật Linh

    Xin lỗi, mình chưa hiểu ý ạ. Lời gọi hàm Register ở đây vẫn chỉ có 3 tham số mà bạn:

    1. router: “single file”
    2. action: p => controller.Single(p[“id”].ToInt()), p[“path”]
    3. help: “[single file ? id = <value> & path = <value>]\r\n hiển thị một cuốn sách theo id”
    

    Tất cả lời gọi hàm Register ở bước 3 đều cung cấp 3 tham số.

    Nhật Linh

    A, mình hiểu vấn đề của bạn rồi. Tham số action bạn đang viết nhầm. Nó cần viết như sau:

    action: p => controller.Single(p["id"].ToInt(), p["path"]),
    

    So với cách bạn viết:

    action: p => controller.Single(p[“id”].ToInt()), p[“path”],
    

    Bạn có thấy sự khác biệt không ạ?
    Ở đây p[“path”] là tham số thứ hai của Single. Tuy nhiên bạn lại gọi Single với một tham số p[“id”].ToInt(), và tách p[“path”] ra khỏi lời gọi Single, và biến nó thành tham số của Register.

    Trinh Le

    Cảm ơn bạn nhiều.

    Linh

    Gửi Admin
    Mình đã cài NewtonSoft như hướng dẫn.
    Nhưng sau khi viết hàm RenderToFile thì gặp lỗi.
    Biến json không thể chuyển đổi từ string sang System.Text.Encoding.
    Mong bạn hướng dẫn.