View nhập dữ liệu: biến cục bộ, switch-case, biến đổi kiểu, tham số

Trong bài này chúng ta tiếp tục xây dựng một lớp view nữa để nhập thông tin từ người dùng. Qua bài này chúng ta sẽ tiếp xúc với một số vấn đề: biến cục bộ, nhập dữ liệu từ console, cấu trúc điều khiển, biến đổi kiểu, tham số của phương thức (tham số tùy chọn, tham số out).

NỘI DUNG CỦA BÀI Ẩn

Thực hành 1: xây dựng class giúp nhập thông tin của một cuốn sách mới

Trong các bài trước chúng ta đã xây dựng lớp view đầu tiên để hiển thị dữ liệu ra giao diện console. Chúng ta sẽ tiếp tục với lớp view thứ hai giúp nhập thông tin từ console.

Bước 1. Thêm lớp BookCreateView

Trong thư mục Views thêm file mã nguồn mới (BookCreateView.cs) cho lớp BookCreateView

Nhập code cho lớp BookCreateView như sau:

using System;

namespace BookMan.ConsoleApp.Views
{
    /// <summary>
    /// class để thêm một cuốn sách mới
    /// </summary>
    internal class BookCreateView
    {
        public BookCreateView()
        {
            
        }

        /// <summary>
        /// yêu cầu người dùng nhập từng thông tin và lưu lại thông tin đó
        /// </summary>
        public void Render()
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("CREATE A NEW BOOK");
            Console.ResetColor();

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("Title: ");
            Console.ResetColor();
            string title = Console.ReadLine(); //đọc 1 dòng và lưu vào biến title

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("Authors: ");
            Console.ResetColor();
            string authors = Console.ReadLine(); //đọc 1 dòng và lưu vào biến authors

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("Publisher: ");
            Console.ResetColor();
            string publisher = Console.ReadLine(); //đọc 1 dòng và lưu vào biến publisher

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("Year: ");
            Console.ResetColor();
            string yearString = Console.ReadLine(); //đọc 1 dòng và lưu vào biến yearString
            int year = int.Parse(yearString); //chuyển đổi chuỗi sang số nguyên

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("Edition: ");
            Console.ResetColor();
            string editionString = Console.ReadLine(); //đọc 1 dòng và lưu vào biến editionString
            int edition = int.Parse(editionString); //chuyển đổi chuỗi sang số nguyên

            Console.ForegroundColor = ConsoleColor.Magenta;
            Console.Write("Reading [y/n]: ");
            Console.ResetColor();
            ConsoleKeyInfo readingChar = Console.ReadKey(); //đọc 1 ký tự và lưu vào biến yearString
            bool reading = readingChar.KeyChar == 'y' || readingChar.KeyChar == 'Y' ?
                true : false; //chuyển sang kiểu bool dùng biểu thức điều kiện
            Console.WriteLine();

            // TODO: TẠM DỪNG Ở ĐÂY, SẼ QUAY LẠI SAU
        }
    }
}

Bước 3. Bổ sung phương thức Create cho BookController

Bổ sung phương thức Create vào cuối class BookController như sau:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BookMan.ConsoleApp.Controllers
{
    using Models; //lưu ý cách dùng using với không gian tên con
    using Views;

    /// <summary>
    /// lớp điều khiển, giúp ghép nối dữ liệu sách với giao diện
    /// </summary>
    internal class BookController
    {
        /// <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)
        {
            // khởi tạo object với property
            Book model = new Book
            {
                Id = 1,
                Authors = "Adam Freeman",
                Title = "Expert ASP.NET Web API 2 for MVC Developers (The Expert's Voice in .NET)",
                Publisher = "Apress",
                Year = 2014,
                Tags = "c#, asp.net, mvc",
                Description = "Expert insight and understanding of how to create, customize, and deploy complex, flexible, and robust HTTP web services",
                Rating = 5,
                Reading = true
            };

            // 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
            view.Render();
        }

        /// <summary>
        /// kích hoạt chức năng nhập dữ liệu cho 1 cuốn sách
        /// </summary>
        public void Create()
        {
            BookCreateView view = new BookCreateView();// khởi tạo object
            view.Render(); // hiển thị ra màn hình
        }
    }    
}

Để nhập dữ liệu từ giao diện dòng lệnh có thể sử dụng các phương thức của lớp Console, bao gồm ReadLine, ReadKey, Read. Trong đó:

  • ReadLine đọc một dòng và trả về một chuỗi ký tự.
  • ReadKey đọc một ký tự và trả về kiểu ConsoleKeyInfo.
  • Read đọc một ký tự và trả về mã ascii của ký tự đó.

Thực thi lệnh theo yêu cầu của người dùng

Bạn có thể nhận thấy một vấn đề, mặc dù chúng ta đã chế tạo lớp view thứ hai và phương thức của controller tương ứng nhưng lại chưa thể thực thi lệnh theo yêu cầu của người dùng.

Ở bài này các bài tiếp theo, chúng ta sẽ xây dựng thêm các lớp view mới để đáp ứng hết các nhu cầu tương tác với người dùng (như hiển thị danh sách, nhập dữ liệu mới, cập nhật dữ liệu, xóa dữ liệu).

Ứng với mỗi lớp view, theo quy ước của MVC chúng ta đang áp dụng, sẽ có một phương thức tương ứng của controller làm nhiệm vụ ghép nối giữa dữ liệu (model) với giao diện (view).

Như vậy, chương trình cần có khả năng tiếp nhận yêu cầu của người dùng và kích hoạt phương thức tương ứng của controller.

Trong các MVC framework (như ASP.NET MVC) đều có một công cụ làm nhiệm vụ đó và thường được gọi là router. Router cho phép ánh xạ một yêu cầu của người dùng (ví dụ, dưới dạng một chuỗi ký tự lệnh cùng tham số trong ứng dụng console, hoặc một chuỗi url đối với ứng dụng web) sang việc thực thi một phương thức tương ứng của controller.

Với kỹ thuật C# hiện tại chúng ta chưa đủ khả năng để tạo ra một router đúng nghĩa. Tuy nhiên, để giải quyết phần nào bài toán, sau đây chúng ta sẽ sử dụng cấu trúc rẽ nhiều nhánh để mô phỏng khả năng của một router.

Thực hành 2: nhận lệnh từ người dùng

Bước 1. Chỉnh sửa code của phương thức Main

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BookMan.ConsoleApp
{
    using Controllers;

    internal class Program
    {
        private static void Main(string[] args)
        {
            BookController controller = new BookController();

            while (true)
            {
                Console.Write("Request> ");
                string request = Console.ReadLine();

                switch (request.ToLower())
                {
                    case "single":
                        controller.Single(1);
                        break;

                    default:
                        Console.WriteLine("Unknown command");
                        break;
                }
            }
        }
    }
}

Bước 2. Dịch và chạy thử chương trình với lệnh single

Gõ lệnh single từ dấu nhắc lệnh

Kết quả chạy chương trình
Kết quả chạy chương trình

Nếu nhập đúng lệnh single (không phân biệt hoa thường), phương thức Single của controller sẽ được gọi. Khi nhập bất kỳ lệnh nào khác sẽ viết ra màn hình “Unknown command”.

Bước 3. Bổ sung khả năng thực hiện lệnh create

Bổ sung thêm một nhánh cho cấu trúc switch-case của Main (lớp Program)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace BookMan.ConsoleApp
{
    using Controllers;

    internal class Program
    {
        private static void Main(string[] args)
        {
            BookController controller = new BookController();

            while (true)
            {
                Console.Write("Request> ");
                string request = Console.ReadLine();

                switch (request.ToLower())
                {
                    case "single":
                        controller.Single(1);
                        break;

                    case "create":
                        controller.Create();
                        break;

                    default:
                        Console.WriteLine("Unknown command");
                        break;
                }
            }
        }
    }
}

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

Kết quả chạy chương trình với lệnh create
Kết quả chạy chương trình với lệnh create

Trong phương thức Main ở trên, chúng ta chuyển đổi chuỗi truy vấn của người dùng thành dạng chữ thường (để người dùng không phải quan tâm nhập chữ thường/chữ hoa) bằng phương thức ToLower của lớp string và cung cấp biểu thức này cho cấu trúc switch.

Trong cấu trúc này chúng ta mới xác định được một “case”: nếu giá trị chuỗi truy vấn (sau khi chuyển về chữ thường) là “single” thì sẽ gọi phương thức Single của controller. Trong các bài tiếp theo chúng ta sẽ lần lượt bổ sung thêm các “case” mới. Nếu giá trị của chuỗi truy vấn không trùng với bất kỳ “case” nào, chúng ta sẽ in ra màn hình thông báo “Unknown command”.

Thực hành 3: cải tiến BookCreateView

Bước 1. Bổ sung phương thức in ra console với màu sắc

Bổ sung thêm hai phương thức sau vào cuối lớp BookCreateView

/// <summary>
/// xuất thông tin ra console với màu sắc (WriteLine có màu)
/// </summary>
/// <param name="message">thông tin cần xuất</param>
/// <param name="color">màu chữ</param>
/// <param name="resetColor">trả lại màu mặc định hay không</param>
private void WriteLine(string message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
{
    Console.ForegroundColor = color;
    Console.WriteLine(message);
    if (resetColor)
        Console.ResetColor();
}

/// <summary>
/// xuất thông tin ra console với màu sắc (Write có màu)
/// </summary>
/// <param name="message">thông tin cần xuất</param>
/// <param name="color">màu chữ</param>
/// <param name="resetColor">trả lại màu mặc định hay không</param>
private void Write(string message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
{
    Console.ForegroundColor = color;
    Console.Write(message);
    if (resetColor)
        Console.ResetColor();
}

Bước 2. Sửa code của phương thức Render

public void Render()
{
    WriteLine("CREATE A NEW BOOK", ConsoleColor.Green);

    ConsoleColor labelColor = ConsoleColor.Magenta;

    Write("Title: ", labelColor);
    var title = Console.ReadLine(); //đọc 1 dòng và lưu vào biến title

    Write("Authors: ", labelColor);
    var authors = Console.ReadLine(); //đọc 1 dòng và lưu vào biến authors

    Write("Publisher: ", labelColor);
    var publisher = Console.ReadLine(); //đọc 1 dòng và lưu vào biến publisher

    Write("Year: ", labelColor);
    string yearString = Console.ReadLine(); //đọc 1 dòng và lưu vào biến yearString
    var year = int.Parse(yearString); //chuyển đổi chuỗi sang số nguyên

    Write("Edition: ", labelColor);
    var editionString = Console.ReadLine(); //đọc 1 dòng và lưu vào biến editionString
    var edition = int.Parse(editionString); //chuyển đổi chuỗi sang số nguyên

    Write("Reading [y/n]: ", labelColor);
    ConsoleKeyInfo readingChar = Console.ReadKey(); //đọc 1 ký tự và lưu vào biến yearString
    var reading = readingChar.KeyChar == 'y' || readingChar.KeyChar == 'Y' ?
        true : false; //chuyển sang kiểu bool dùng biểu thức điều kiện
    Console.WriteLine();

    Write("Tags: ", labelColor);
    var tags = Console.ReadLine();

    Write("Description: ", labelColor);
    var description = Console.ReadLine();

    Write("Rate: ", labelColor);
    var rateString = Console.ReadLine();
    var rating = int.Parse(rateString);

    Write("File: ", labelColor);
    var file = Console.ReadLine();
}

Trong lần cải tiến này, chúng ta viết thêm hai phương thức giúp in chữ có màu ra console và dùng hai phương thức này để giảm bớt số lượng code bị lặp trong phương thức Render. Chúng ta cũng bổ sung code để nhập nốt các dữ liệu còn lại của sách.

Trong lần cải tiến trên chúng ta xây dựng hai phương thức mới Write và WriteLine với danh sách tham số có chút khác biệt với những phương thức bình thường. Trong hai phương thức này, tham số colorresetColor được gán sẵn giá trị: color = ConsoleColor.White, và resetColor = true.

Tính năng này của C# được gọi là tham số với giá trị mặc định hoặc tham số không bắt buộc hoặc tham số tùy chọn (Optional Arguments / Parameters).

Các phương thức Write và WriteLine ở trên mặc dù được định nghĩa với 3 tham số vào nhưng hai tham số sau là tham số tùy chọn: môt tham số nhận màu sắc mặc định là White; một tham số nhận giá trị mặc định là true.

Do đó, khi gọi các phương thức này trong Render, chúng ta chỉ cung cấp giá trị cho tham số color (do chúng ta muốn viết ra chữ màu Magenta, khác với màu White mặc định) nhưng không cung cấp giá trị cho tham số thứ 3 (vì vẫn muốn dùng giá trị true, vốn là giá trị có sẵn của tham số tùy chọn này).

Cách Visual Studio hiển thị thông tin hỗ trợ của tham số không bắt buộc.
Cách Visual Studio hiển thị thông tin hỗ trợ của tham số tùy chọn.

Thực hành 4: tiếp tục cải tiến BookCreateView

Trong phần thực hành này chúng ta lại tiếp tục xây dựng thêm
các phương thức mới giúp đơn giản hóa hơn nữa việc nhập dữ liệu, đồng thời luyện
tập cách xây dựng phương thức.

Bước 1. Xây dựng phương thức nhập dữ liệu từ Console

Bổ sung các phương thức sau vào lớp BookCreateView: InputBool, InputInt, InputString

/// <summary>
/// in ra thông báo và tiếp nhận chuỗi ký tự người dùng nhập
/// rồi chuyển sang kiểu bool
/// </summary>
/// <param name="label">dòng thông báo</param>
/// <param name="labelColor">màu chữ thông báo</param>
/// <param name="valueColor">màu chữ người dùng nhập</param>
/// <returns></returns>
private bool InputBool(string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
{
    Write($"{label} [y/n]: ", labelColor);
    ConsoleKeyInfo key = Console.ReadKey(); //đọc 1 ký tự vào biến key
    Console.WriteLine();
    bool @char = key.KeyChar == 'y' || key.KeyChar == 'Y' ?
        true : false; //chuyển sang kiểu bool dùng biểu thức điều kiện
    return @char; // lưu ý cách viết tên biến @char
}

/// <summary>
/// in ra thông báo và tiếp nhận chuỗi ký tự người dùng nhập
/// rồi chuyển sang số nguyên
/// </summary>
/// <param name="label">dòng thông báo</param>
/// <param name="labelColor">màu chữ thông báo</param>
/// <param name="valueColor">màu chữ người dùng nhập</param>
/// <returns></returns>
private int InputInt(string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
{
    while (true)
    {
        var str = InputString(label, labelColor, valueColor);
        var result = int.TryParse(str, out int i);
        if (result == true)
        {
            return i;
        }
    }
}

/// <summary>
/// in ra thông báo và tiếp nhận chuỗi ký tự người dùng nhập
/// </summary>
/// <param name="label">dòng thông báo</param>
/// <param name="labelColor">màu chữ thông báo</param>
/// <param name="valueColor">màu chữ người dùng nhập</param>
/// <returns></returns>
private string InputString(string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
{
    Write($"{label}: ", labelColor, false);
    Console.ForegroundColor = valueColor;
    string value = Console.ReadLine();
    Console.ResetColor();
    return value;
}

Bước 2. Cải tiến code của phương thức Render

Sử dụng 3 phương thức mới để làm đơn giản code của phương thức Render

public void Render()
{
    WriteLine("CREATE A NEW BOOK", ConsoleColor.Green);

    var title = InputString("Title"); //đọc vào biến title            
    var authors = InputString("Authors"); //đọc vào biến authors            
    var publisher = InputString("Publisher"); //đọc vào biến publisher
    var year = InputInt("Year"); // nhập giá trị cho biến year
    var edition = InputInt("Edition"); // nhập giá trị cho biến edition
    var tags = InputString("Tags");
    var description = InputString("Description");
    var rate = InputInt("Rate");
    var reading = InputBool("Reading");
    var file = InputString("File");
}

Code đầy đủ của lớp BookCreateView như sau:

using System;

namespace BookMan.ConsoleApp.Views
{
    /// <summary>
    /// class để thêm một cuốn sách mới
    /// </summary>
    internal class BookCreateView
    {
        public BookCreateView()
        {

        }

        /// <summary>
        /// yêu cầu người dùng nhập từng thông tin và lưu lại thông tin đó
        /// </summary>
        public void Render()
        {
            WriteLine("CREATE A NEW BOOK", ConsoleColor.Green);

            var title = InputString("Title"); //đọc vào biến title            
            var authors = InputString("Authors"); //đọc vào biến authors            
            var publisher = InputString("Publisher"); //đọc vào biến publisher
            var year = InputInt("Year"); // nhập giá trị cho biến year
            var edition = InputInt("Edition"); // nhập giá trị cho biến edition
            var tags = InputString("Tags");
            var description = InputString("Description");
            var rate = InputInt("Rate");
            var reading = InputBool("Reading");
            var file = InputString("File");
        }


        /// <summary>
        /// xuất thông tin ra console với màu sắc (WriteLine có màu)
        /// </summary>
        /// <param name="message">thông tin cần xuất</param>
        /// <param name="color">màu chữ</param>
        /// <param name="resetColor">trả lại màu mặc định hay không</param>
        private void WriteLine(string message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
        {
            Console.ForegroundColor = color;
            Console.WriteLine(message);
            if (resetColor)
                Console.ResetColor();
        }

        /// <summary>
        /// xuất thông tin ra console với màu sắc (Write có màu)
        /// </summary>
        /// <param name="message">thông tin cần xuất</param>
        /// <param name="color">màu chữ</param>
        /// <param name="resetColor">trả lại màu mặc định hay không</param>
        private void Write(string message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
        {
            Console.ForegroundColor = color;
            Console.Write(message);
            if (resetColor)
                Console.ResetColor();
        }

        /// <summary>
        /// in ra thông báo và tiếp nhận chuỗi ký tự người dùng nhập
        /// rồi chuyển sang kiểu bool
        /// </summary>
        /// <param name="label">dòng thông báo</param>
        /// <param name="labelColor">màu chữ thông báo</param>
        /// <param name="valueColor">màu chữ người dùng nhập</param>
        /// <returns></returns>
        private bool InputBool(string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            Write($"{label} [y/n]: ", labelColor);
            ConsoleKeyInfo key = Console.ReadKey(); //đọc 1 ký tự vào biến key
            Console.WriteLine();
            bool @char = key.KeyChar == 'y' || key.KeyChar == 'Y' ?
                true : false; //chuyển sang kiểu bool dùng biểu thức điều kiện
            return @char; // lưu ý cách viết tên biến @char
        }

        /// <summary>
        /// in ra thông báo và tiếp nhận chuỗi ký tự người dùng nhập
        /// rồi chuyển sang số nguyên
        /// </summary>
        /// <param name="label">dòng thông báo</param>
        /// <param name="labelColor">màu chữ thông báo</param>
        /// <param name="valueColor">màu chữ người dùng nhập</param>
        /// <returns></returns>
        private int InputInt(string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            while (true)
            {
                var str = InputString(label, labelColor, valueColor);
                var result = int.TryParse(str, out int i);
                if (result == true)
                {
                    return i;
                }
            }
        }

        /// <summary>
        /// in ra thông báo và tiếp nhận chuỗi ký tự người dùng nhập
        /// </summary>
        /// <param name="label">dòng thông báo</param>
        /// <param name="labelColor">màu chữ thông báo</param>
        /// <param name="valueColor">màu chữ người dùng nhập</param>
        /// <returns></returns>
        private string InputString(string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            Write($"{label}: ", labelColor, false);
            Console.ForegroundColor = valueColor;
            string value = Console.ReadLine();
            Console.ResetColor();
            return value;
        }


    }
}

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

Chạy thử lệnh create như sau:

Kết quả chạy chương trình
Kết quả chạy chương trình

Để ý trong phương thức InputBool ở trên chúng ta sử dụng phép toán điều kiện(conditional operator).

ConsoleKeyInfo key = Console.ReadKey(); //đọc 1 ký tự vào biến key
bool @char = key.KeyChar == 'y' || key.KeyChar == 'Y' ?
     true : false; //chuyển sang kiểu bool dùng biểu thức điều kiện

Kết luận

Chúng ta đã xây dựng được một class cho phép nhập dữ liệu một cuốn sách từ giao diện console. Qua bài này chúng ta đã học cách vận dụng biến cục bộ, nhập dữ liệu từ console, cấu trúc điều khiển, biến đổi kiểu, tham số của phương thức (tham số tùy chọn, tham số out).


(ST)

Trả lời