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).
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
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
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ố color
và resetColor
đượ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).
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:
Để ý 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).
+ 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!
dau @ co tac dung gi vay ad?
nếu cố tình muốn đặt tên biến giống Từ khóa trong C# thì gắn thêm @ vào trước.
InputString có vài phần thừa. valueColor và ResetColor đã có trong Write. Đâu cần lặp lại?
vậy nếu muốn thay đổi color khi gọi InputString thì thay đổi ntn bạn?
Trong hàm InputInt, câu lệnh
var result = int.TryParse(str, out int i); //câu này có khai báo biến i trong hàm TryParse được k ad, mình làm thì bị lỗi, phải khai báo ngoài hàm
int i;
var result =int.TryParse(str, out i); //cái này thì VS k báo lỗi
lỗi gì thế bạn
Cái này liên quan đến phiên bản C# bạn đang dùng. C# 6 trở về trước yêu cầu phải khai báo biến trước rồi mới có thể sử dụng với từ khóa out trong lời gọi hàm. C# 7 bắt đầu cho phép khai báo biên luôn trong lời gọi hàm.
sao mình nhập bất kỳ giá trị nào cho biến reading đều trả về giá trị true vậy
Bạn có thể post đoạn code của phương thức InputBool lên đây không?
Mình chưa hiểu câu lệnh sau, nhờ ad giải thích dùm:
bool reading = readingChar.KeyChar == ‘y’ || readingChar.KeyChar == ‘Y’ ? true : false;
Ký tự “||” trong câu lệnh trên có nghĩa là gi vậy?
readingChar là biến chứa kết quả của phương thức Console.ReadKey().
KeyChar là một property của object này thể hiện ký tự được nhập.
Cả biểu thức trên là một phép toán điều kiện. Giải thích bằng lời như sau:
Nếu ký tự đọc vào là ‘y’ hoặc ‘Y’ thì biến reading nhận giá trị true, nếu không reading sẽ nhận giá trị false.
Bạn có thể đọc kỹ hơn về phép toán điều kiện ở bài viết này: Các toán tử (operator) cơ bản trong C# | Tự học ICT (tuhocict.com)
Mình đã hiểu.
Cám ơn bạn!
Mình chưa hiểu logic của hàm InputInt(), nhờ ad giải thích dùm.
Theo như cấu trúc của while thì bên trong thân của while phải có lệnh làm thay đổi giá trị của biểu thức logic.
Ở đây mình không thấy được mối liên hệ giữa biểu thức logic (true) với các lệnh bên trong thân của while.
if (result == true) nó check để return i đó bạn.
nếu false thì nó lặp while tiếp thôi
method InputString() gọi tới method Write() bên trong có dùng resetColor = false.
Tức là ko reset color, nhưng ở dưới lại change thành valueColor. Vậy hàm ý dùng resetColor = false ở đây là gì ạ. Reset hay ko reset thì cũng đổi màu thành valueColor rồi?!