Console MVC Library cho .NET (3): xuất nhập, view, controller

Lớp view cơ sở, hỗ trợ xuất nhập dữ liệu với console

0

Trong bài này chúng ta sẽ xây dựng các class quan trọng hỗ trợ làm việc với giao diện console. Chúng ta cũng sẽ xây dựng các class cơ sở chính cho MVC. Các lớp cơ sở này hỗ trợ cho view, controller, và hỗ trợ thông báo từ controller. Cuối cùng chúng ta sẽ xây dựng một lớp giúp đơn giản hóa Entry point cho console.

Đây là bài thứ ba trong loạt bài:

Loạt bài Xây dựng thư viện hỗ trợ ứng dụng Console:
Phần 1 – Giới thiệu chung
Phần 2 – Truy vấn, xây dựng router
Phần 3 – Hỗ trợ I/O với console, view, controller
Phần 4 – Ví dụ minh họa

Hỗ trợ nhập xuất cho console

Trước hết chúng ta sẽ xây dựng một class hỗ trợ nhập xuất dữ liệu với console. Như đã biết, console chỉ làm việc với văn bản, do đó khi đọc bất kỳ giá trị nào ta chỉ thu được văn bản. Sau đó, phải biến đổi văn bản về kiểu dữ liệu cần thiết.

Để tăng tính thẩm mỹ cho console, chúng ta cũng muốn in text với màu sắc và màu nền. Tuy console khá giới hạn trong việc lựa chọn màu văn bản và màu nền, một màn hình console có nhiều màu sắc có thể dễ đọc và dễ làm việc hơn.

Xây dựng lớp ViewHelper

Tạo một file mã nguồn ViewHelper.cs trực thuộc project Framework dành cho lớp tĩnh ViewHelper như sau:

using System;
namespace Framework
{
    public static class ViewHelper
    {
        /// <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>
        public static void Write(this object message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
        {
            Console.ForegroundColor = color;
            Console.Write(message);
            if (resetColor)
                Console.ResetColor();
        }
        /// <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>
        public static void WriteLine(this object message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
        {
            Write(message, color, resetColor);
            Console.WriteLine();
        }
        /// <summary>
        /// Biến đổi value thành kiểu T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T To<T>(this string value)
        {
            return (T)Convert.ChangeType(value, typeof(T));
        }
        /// <summary>
        /// Biến đổi value thành kiểu T qua tham số out
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool To<T>(this string value, out T result)
        {
            var ok = false;
            result = default(T);
            try
            {
                result = value.To<T>();
                ok = true;
            }
            catch (Exception)
            {
            }
            return ok;
        }
        /// <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>
        public static string Read(this 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;
        }
        /// <summary>
        /// Đọc một chuỗi và chuyển về kiểu cơ sở T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="label"></param>
        /// <param name="labelColor"></param>
        /// <param name="valueColor"></param>
        /// <returns></returns>
        public static T Read<T>(this string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            var ok = false;
            T result = default(T);
            while (true)
            {
                var str = Read(label, labelColor, valueColor);
                try
                {
                    result = str.To<T>();
                    ok = true;
                }
                catch (Exception)
                {
                }
                if (ok) break;
            }
            return result;
        }
        /// <summary>
        /// cập nhật giá trị kiểu string. Nếu ấn enter mà không nhập dữ liệu sẽ trả lại giá trị cũ.
        /// </summary>
        /// <param name="label">dòng thông báo</param>
        /// <param name="oldValue">giá trị gốc</param>
        /// <param name="labelColor">màu chữ thông báo</param>
        /// <param name="valueColor">màu chữ dữ liệu</param>
        /// <returns></returns>
        public static string Update(this string label, string oldValue, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            Write($"{label}: ", labelColor);
            WriteLine(oldValue, ConsoleColor.Yellow);
            Write(" >> ", ConsoleColor.Green);
            Console.ForegroundColor = valueColor;
            string newValue = Console.ReadLine();
            return string.IsNullOrEmpty(newValue.Trim()) ? oldValue : newValue;
        }
        /// <summary>
        /// Cập nhật giá trị có kiểu cơ bản T bất kỳ
        /// </summary>
        /// <param name="label"></param>
        /// <param name="oldValue"></param>
        /// <param name="labelColor"></param>
        /// <param name="valueColor"></param>
        /// <returns></returns>
        public static T Update<T>(this string label, T oldValue, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            Write($"{label}: ", labelColor);
            WriteLine($"{oldValue}", ConsoleColor.Yellow);
            Write(" >> ", ConsoleColor.Green);
            Console.ForegroundColor = valueColor;
            string str = Console.ReadLine();
            if (string.IsNullOrEmpty(str)) return oldValue;
            if (str.To<T>(out T i)) return i; //sử dụng phương thức mở rộng ToInt
            return oldValue;
        }
    }
}

Trong lớp ViewHelper chúng ta xây dựng các phương thức sau:

  • Write/WriteLine: viết ra các dòng văn bản có màu sắc, giúp dễ đọc thông tin trên giao diện console.
  • Read và Read<T>: in ra dòng thông báo, chờ người dùng nhập dữ liệu và ấn Enter để hoàn tất. Read trả lại một chuỗi, còn Read<T> sẽ biến đổi chuỗi về kiểu T (nếu chuỗi nhập vào phù hợp với kiểu T).
  • Update và Update<T>: in ra dòng thông báo và giá trị cũ của biến. Nếu người dùng ấn Enter luôn (bỏ qua nhập dữ liệu) thì trả lại giá trị cũ. Nếu nhập giá trị mới thì trả lại giá trị này. Update trả lại chuỗi, còn Update<T> biến đổi chuỗi nhận được về kiểu T.
  • To<T>: biến đổi chuỗi về kiểu T nếu chuỗi phù hợp.

Thử nghiệm ViewHelper

Để kiểm tra hoạt động của class ViewHelper vừa xây dựng, sử dụng project SampleApp đã tạo trong bài trước. Lưu ý đặt SampleApp làm startup project và tham chiếu SampleApp sang thư viện Framework.

Solution với hai project: thư viện Framework và console app SampleApp (startup project). SampleApp tham chiếu sang thư viện Framework.
Solution với hai project: thư viện Framework và console app SampleApp (startup project). SampleApp tham chiếu sang thư viện Framework.

Xóa bỏ code cũ và viết lại code cho Program.cs như sau:

using System;
namespace BookMan.ConsoleApp
{
    using Framework;
    internal class Program
    {
        private static void Main()
        {
            Console.Title = "Sample App";
            Console.OutputEncoding = System.Text.Encoding.UTF8;
            ViewHelper.WriteLine("This message is printed in green", ConsoleColor.Green, true);
            ViewHelper.WriteLine("The color has been reset to default");
            var name = ViewHelper.Read("Enter your name");
            $"Your name is {name}".WriteLine(ConsoleColor.Yellow);
            name = "Now change your name".Update(name);
            $"Your new name is {name}".WriteLine(ConsoleColor.Yellow);
            Console.ReadLine();
        }
    }
}

Xây dựng các lớp view cơ sở

Trong phần này chúng ta sẽ xây dựng hai class, ViewBase và ViewBase<T>, là hai class cha của tất cả các lớp view do người dùng định nghĩa về sau.

Tạo file mã nguồn ViewBase.cs trực thuộc project Framework và viết code như sau:

namespace Framework
{
    public abstract class ViewBase
    {        
        protected Router Router = Router.Instance;
        public ViewBase() { }
        public abstract void Render();
    }
    public abstract class ViewBase<T> : ViewBase
    {
        protected T Model;
        public ViewBase(T model) => Model = model;        
    }
}

Cả hai class này đều là abstract class, nghĩa là người dùng không thể sử dụng trực tiếp để tạo ra object. Hai class này đưa ra quy tắc chung về các lớp view của framework này:

  • Mọi lớp view đều phải chứa và thực thi phương thức Render. Render là phương thức dùng để xuất thông tin ra màn hình.
  • Nếu view không nhận dữ liệu từ controller thì cần kế thừa ViewBase; Nếu view nhận dữ liệu từ controller thì phải kế thừa ViewBase<T>.
  • Nếu view kế thừa ViewBase<T> thì bắt buộc phải có hàm tạo chứa 1 tham số kiểu T (chính là model cần hiển thị).

Message – hỗ trợ thông báo

Tạo file mã nguồn mới Message.cs trong Framework cho lớp Message và thêm code như sau:

using System;
namespace Framework
{
    public enum MessageType { Success, Information, Error, Confirmation }
    public class Message
    {
        public MessageType Type { get; set; } = MessageType.Success;
        public string Label { get; set; }
        public string Text { get; set; } = "Your action has completed successfully";
        public string BackRoute { get; set; }
    }
    public class MessageView : ViewBase<Message>
    {
        public MessageView(Message model) : base(model)
        {
        }
        public override void Render()
        {
            switch (Model.Type)
            {
                case MessageType.Success:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "SUCCESS", ConsoleColor.Green);
                    break;
                case MessageType.Error:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "ERROR!", ConsoleColor.Red);
                    break;
                case MessageType.Information:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "INFORMATION!", ConsoleColor.Yellow);
                    break;
                case MessageType.Confirmation:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "CONFIRMATION", ConsoleColor.Cyan);
                    break;
            }
            if (Model.Type != MessageType.Confirmation)
                ViewHelper.WriteLine(Model.Text, ConsoleColor.White);
            else
            {
                ViewHelper.Write(Model.Text, ConsoleColor.Magenta);
                var answer = Console.ReadLine().ToLower();
                if (answer == "y" || answer == "yes")
                    Router.Forward(Model.BackRoute);
            }
        }
    }
}

Trong quá trình sử dụng giao diện, mỗi hành động thành công hoặc thất bại đều phải được thông báo trở lại cho người dùng. Ví dụ, khi cập nhật dữ liệu thành công sẽ phải thông báo trở lại cho người dùng, trước khi xóa dữ liệu phải hỏi ý kiến người dùng, v.v..

Nhiệm vụ của lớp Message và MessageView là gửi các thông báo ngắn từ controller tới người dùng. Thông báo bao gồm 4 loại:

  • Success: báo thành công, in ra mới màu green, label mặc định là SUCCESS
  • Error: báo lỗi, in ra với màu read và label mặc định là ERROR
  • Information: thông báo chung, in ra với màu yellow, label INFORMATION
  • Confirmation: yêu cầu người dùng xác nhận hành động, in ra với màu magenta và label CONFIRMATION. Riêng loại thông báo này yêu cầu người dùng nhập “y” hoặc “yes” để xác nhận hành động. Nếu người dùng xác nhận, một truy vấn (đã thiết lập từ trước) sẽ được kích hoạt.

Cơ chế thông báo này giúp hạn chế phải viết những lớp view nhỏ lẻ chỉ để hiển thị một vài thông báo.

Xây dựng lớp ControllerBase

ControllerBase là lớp cha của tất cả các lớp controller do người dùng định nghĩa về sau. Lớp này chỉ định nghĩa một số phương thức tắt để thuận tiện cho các lớp kế thừa.

Tạo file mã nguồn ControllerBase.cs trực thuộc project Framework và viết code như sau:

namespace Framework
{
    public class ControllerBase
    {
        public virtual void Render(ViewBase view) => view.Render();
        public virtual void Render<T>(ViewBase<T> view) => view.Render();
        public virtual void Render(Message message)
        {
            Render(new MessageView(message));
        }
        public virtual void Success(string text, string label = "SUCCESS")
        {
            Render(new Message { Type = MessageType.Success, Text = text, Label = label });
        }
        public virtual void Inform(string text, string label = "INFORMATION")
        {
            Render(new Message { Type = MessageType.Information, Text = text, Label = label });
        }
        public virtual void Error(string text, string label = "ERROR!")
        {
            Render(new Message { Type = MessageType.Error, Text = text, Label = label });
        }
        public virtual void Confirm(string text, string route, string label = "CONFIRMATION")
        {
            Render(new Message { Type = MessageType.Confirmation, Text = text, Label = label, BackRoute = route });
        }
    }
}

Lớp này chủ yếu chứa các phương thức để hỗ trợ “render” – gọi phương thức Render của các lớp view, hoặc gửi đi các thông báo tới giao diện.

Lớp Application

Application là một class đặc biệt chứa các phương thức giúp thực hiện vòng lặp truy vấn – phản hồi của client và gọi phương thức trợ giúp.

using System;
namespace Framework
{
    public class Application
    {
        public Action Config { get; set; }
        public ControllerAction Help { get; set; } = DefaultHelp;
        public string Prompt { get; set; } = "# Request > ";
        public ConsoleColor Color { get; set; } = ConsoleColor.Green;
        public void Run()
        {
            Config();
            Router.Instance.Register("help", Help);
            while (true)
            {
                ViewHelper.Write(Prompt, Color);
                var request = Console.ReadLine();
                try
                {
                    Router.Instance.Forward(request);
                }
                catch (Exception e)
                {
                    ViewHelper.WriteLine($"ERROR: {e.Message}", ConsoleColor.Red);
                }
                finally
                {
                    Console.WriteLine();
                }
            }
        }
        private static void DefaultHelp(Parameter p)
        {
            if (p == null)
            {
                ViewHelper.WriteLine("SUPPORTED COMMANDS:", ConsoleColor.Magenta);
                ViewHelper.WriteLine(Router.Instance.GetRoutes(), ConsoleColor.Yellow);
                Console.WriteLine("type: help ? cmd= <command> to get command details");
                return;
            }
            Console.BackgroundColor = ConsoleColor.DarkBlue;
            var command = p["cmd"].ToLower();
            Console.WriteLine(Router.Instance.GetHelp(command));
            Console.ResetColor();
        }
    }
}

Phương thức Run của Application sẽ được gọi ở Entry point (static void Main) của ứng dụng. Sau khi gọi Run, chương trình sẽ luôn hiển thị một dấu nhắc lệnh riêng. Người dùng nhập lệnh + tham số và ấn Enter. Lệnh sẽ tự động đẩy vào router và phương thức tương ứng sẽ được kích hoạt.

Việc đăng ký các truy vấn được thực hiện trong một phương thức mà biến delegate Config trỏ tới.

DefaultHelp là một phương thức hỗ trợ đơn giản được xây dựng sẵn. Nhiệm vụ của DefaultHelp là đưa ra thông tin về các truy vấn đã được đăng ký (trong delegate Config) khi người dùng gõ lệnh help và help ? cmd = <…>.

Mã nguồn đầy đủ của Framework

using System;
using System.Collections.Generic;
namespace Framework
{
    /// <summary>
    /// lưu các cặp khóa-giá trị người dùng nhập;
    /// chuỗi tham số cần viết ở dạng khóa=giá trị;
    /// nếu có nhiều tham số thì viết tách nhau bằng ký tự &
    /// </summary>
    public class Parameter
    {
        private readonly Dictionary<string, string> _pairs = new Dictionary<string, string>();
        /// <summary>
        /// nạp chồng phép toán indexing []; cho phép truy xuất giá trị theo kiểu biến[khóa] = giá_trị;
        /// </summary>
        /// <param name="key">khóa</param>
        /// <returns>giá trị tương ứng</returns>
        public string this[string key] // để nạp chồng phép toán indexing phải viết hai phương thức get,set
        {
            get {
                if (_pairs.ContainsKey(key))
                    return _pairs[key];
                else return null;
            } // phương thức get trả lại giá trị từ dictionary
            set => _pairs[key] = value; // phương thức set gán giá trị cho dictionary
        }
        /// <summary>
        /// Kiểm tra xem một khóa có trong danh sách tham số không
        /// </summary>
        /// <param name="key">khóa cần kiểm tra</param>
        /// <returns></returns>
        public bool ContainsKey(string key)
        {
            return _pairs.ContainsKey(key);
        }
        /// <summary>
        /// nhận chuỗi ký tự và phân tích, chuyển thành các cặp khóa-giá trị
        /// </summary>
        /// <param name="parameter">chuỗi ký tự theo quyt tắc khóa_1=giá_trị_1&khóa-2=giá_trị2</param>
        public Parameter(string parameter)
        {
            // cắt chuỗi theo mốc là ký tự &
            // kết quả của phép toán này là một mảng, mỗi phần tử là một chuỗi có dạng khóa = giá_trị
            var pairs = parameter.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries);
            foreach (var pair in pairs)
            {
                var p = pair.Split('='); // cắt mỗi phần tử lấy mốc là ký tự =
                if (p.Length == 2) // một cặp khóa = giá_trị đúng sau khi cắt sẽ phải có 2 phần
                {
                    var key = p[0].Trim(); // phần tử thứ nhất là khóa
                    var value = p[1].Trim(); // phần tử thứ hai là giá trị
                    this[key] = value; // lưu cặp khóa-giá trị này lại sử dụng phép toán indexing
                    // cũng có thể viết theo kiểu khác, trực tiếp sử dụng biến _pairs
                    // _pairs[key] = value;
                }
            }
        }
    }
}
using System;
namespace Framework
{
    /// <summary>
    /// lớp xử lý truy vấn
    /// </summary>
    public class Request
    {
        /// <summary>
        /// thành phần lệnh của truy vấn
        /// </summary>
        public string Route { get; private set; }
        /// <summary>
        /// thành phần tham số của truy vấn
        /// </summary>
        public Parameter Parameter { get; private set; }
        public Request(string request)
        {
            Analyze(request);
        }
        /// <summary>
        /// phân tích truy vấn để tách ra thành phần lệnh và thành phần tham số
        /// </summary>
        /// <param name="request"></param>
        private void Analyze(string request)
        {
            // tìm xem trong chuỗi truy vấn có tham số hay không
            var firstIndex = request.IndexOf('?');
            // trườn hợp truy vấn không chứa tham số
            if (firstIndex < 0)
            {
                Route = request.ToLower().Trim();
            }
            // trường hợp truy vấn chứa tham số
            else
            {
                // nếu chuỗi lối (chỉ chứa tham số, không chứa route)
                if (firstIndex <= 1)
                {
                    throw new Exception("Invalid request parameter");
                }
                // cắt chuỗi truy vấn lấy mốc là ký tự ?
                // sau phép toán này thu được mảng 2 phần tử: thứ nhất là route, thứ hai là chuỗi parameter
                var tokens = request.Split(new[] { '?' }, 2, StringSplitOptions.RemoveEmptyEntries);
                // route là thành phần lệnh của truy vấn
                Route = tokens[0].Trim().ToLower();
                // parameter là thành phần tham số của truy vấn
                var parameterPart = request.Substring(firstIndex + 1).Trim();
                Parameter = new Parameter(parameterPart);
            }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Text;
namespace Framework
{
    /* đây không phải là lệnh sử dụng không gian tên
     * mà là tạo biệt danh cho một kiểu dữ liệu
     * ở đây đang tạo một biệt danh cho kiểu Dictionary<string, ControllerAction>.
     * trong cả file này có thể sử dụng tên kiểu RoutingTable 
     * thay cho Dictionary<string, ControllerAction> 
     */
    using RoutingTable = Dictionary<string, ControllerAction>;
    /// <summary>
    /// delegate này đại diện cho tất cả các phương thức có:
    /// - kiểu ra là void,
    /// - danh sách tham số vào là (Parameter)
    /// </summary>
    /// <param name="parameter"></param>
    public delegate void ControllerAction(Parameter parameter = null);
    /// <summary>
    /// lớp cho phép ánh xạ truy vấn với phương thức
    /// </summary>
    public class Router
    {
        // nhóm 3 lệnh dưới đây biến Router thành một singleton
        private static Router _instance;
        private Router()
        {
            _routingTable = new RoutingTable();
            _helpTable = new Dictionary<string, string>();
        }
        // để ý: constructor là private
        // người sử dụng class thông qua property này để truy xuất các phương thức của class
        // chỉ khi nào _instance == null mới tạo object. Một khi đã tạo object, _instance sẽ
        // không có giá trị null nữa.
        // vì là biến static, _instance một khi được khởi tạo sẽ tồn tại suốt chương trình
        public static Router Instance => _instance ?? (_instance = new Router());
        // lưu ý: ở đây đang sử dụng alias của Dictionary<string, ControllerAction> cho ngắn gọn
        private readonly RoutingTable _routingTable;
        private readonly Dictionary<string, string> _helpTable;
        public string GetRoutes()
        {
            StringBuilder sb = new StringBuilder();
            foreach (var k in _routingTable.Keys)
                sb.AppendFormat("{0}, ", k);
            
            return sb.ToString().TrimEnd(',' ,' ');
        }
        public string GetHelp(string key)
        {
            if (_helpTable.ContainsKey(key))
                return _helpTable[key];
            else
                return "Documentation not ready yet!";
        }
        /// <summary>
        /// đăng ký một route mới, mỗi route ánh xạ một chuỗi truy vấn với một phương thức
        /// </summary>
        /// <param name="route"></param>
        /// <param name="action"></param>
        public void Register(string route, ControllerAction action, string help = "")
        {
            // nếu _routingTable đã chứa route này thì bỏ qua
            if (!_routingTable.ContainsKey(route))
            {
                _routingTable[route] = action;
                _helpTable[route] = help;
            }
        }
        /// <summary>
        /// phân tích truy vấn và gọi phương thức tương ứng với chuỗi truy vấn
        /// <para>chuỗi truy vấn bao gồm hai phần: route và parameter, phân tách bởi ký tự ?</para>
        /// </summary>
        /// <param name="request">chuỗi truy vấn, bao gồm hai phần: 
        /// route, paramete; phân tách bởi ký tự ?</param>
        public void Forward(string request)
        {
            var req = new Request(request);
            if (!_routingTable.ContainsKey(req.Route))
                throw new Exception("Command not found!");
            if (req.Parameter == null)
                _routingTable[req.Route]?.Invoke();
            else
                _routingTable[req.Route]?.Invoke(req.Parameter);
        }
    }
}
using System;
namespace Framework
{
    public static class ViewHelper
    {
        /// <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>
        public static void Write(this object message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
        {
            Console.ForegroundColor = color;            
            Console.Write(message);
            if (resetColor)
                Console.ResetColor();
        }
        /// <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>
        public static void WriteLine(this object message, ConsoleColor color = ConsoleColor.White, bool resetColor = true)
        {
            Write(message, color, resetColor);
            Console.WriteLine();
        }
        /// <summary>
        /// Biến đổi value thành kiểu T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <returns></returns>
        public static T To<T>(this string value)
        {
            return (T)Convert.ChangeType(value, typeof(T));
        }
        /// <summary>
        /// Biến đổi value thành kiểu T qua tham số out
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public static bool To<T>(this string value, out T result)
        {
            var ok = false;
            result = default(T);
            try
            {
                result = value.To<T>();
                ok = true;
            }
            catch (Exception)
            {
            }
            return ok;
        }
        /// <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>
        public static string Read(this 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;
        }
        /// <summary>
        /// Đọc một chuỗi và chuyển về kiểu cơ sở T
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="label"></param>
        /// <param name="labelColor"></param>
        /// <param name="valueColor"></param>
        /// <returns></returns>
        public static T Read<T>(this string label, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            var ok = false;
            T result = default(T);
            while (true)
            {
                var str = Read(label, labelColor, valueColor);
                try
                {
                    result = str.To<T>();
                    ok = true;
                }
                catch (Exception)
                {
                }
                if (ok) break;
            }
            return result;
        }        
        /// <summary>
        /// cập nhật giá trị kiểu string. Nếu ấn enter mà không nhập dữ liệu sẽ trả lại giá trị cũ.
        /// </summary>
        /// <param name="label">dòng thông báo</param>
        /// <param name="oldValue">giá trị gốc</param>
        /// <param name="labelColor">màu chữ thông báo</param>
        /// <param name="valueColor">màu chữ dữ liệu</param>
        /// <returns></returns>
        public static string Update(this string label, string oldValue, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            Write($"{label} ", labelColor);
            WriteLine($"[{oldValue}]", ConsoleColor.Yellow);
            Write(" >> ", ConsoleColor.Green);
            Console.ForegroundColor = valueColor;
            string newValue = Console.ReadLine();
            return string.IsNullOrEmpty(newValue.Trim()) ? oldValue : newValue;
        }
        /// <summary>
        /// Cập nhật giá trị có kiểu cơ bản T bất kỳ
        /// </summary>
        /// <param name="label"></param>
        /// <param name="oldValue"></param>
        /// <param name="labelColor"></param>
        /// <param name="valueColor"></param>
        /// <returns></returns>
        public static T Update<T>(this string label, T oldValue, ConsoleColor labelColor = ConsoleColor.Magenta, ConsoleColor valueColor = ConsoleColor.White)
        {
            Write($"{label} ", labelColor);
            WriteLine($"[{oldValue}]", ConsoleColor.Yellow);
            Write(" >> ", ConsoleColor.Green);
            Console.ForegroundColor = valueColor;
            string str = Console.ReadLine();
            if (string.IsNullOrEmpty(str)) return oldValue;
            if (str.To<T>(out T i)) return i;
            return oldValue;
        }
    }
}
namespace Framework
{
    public abstract class ViewBase
    {
        protected Router Router = Router.Instance;
        public ViewBase() { }
        public abstract void Render();
    }
    public abstract class ViewBase<T> : ViewBase
    {
        protected T Model;
        public ViewBase(T model) => Model = model;        
    }
}
using System;
namespace Framework
{
    public enum MessageType { Success, Information, Error, Confirmation }
    public class Message
    {
        public MessageType Type { get; set; } = MessageType.Success;
        public string Label { get; set; }
        public string Text { get; set; } = "Your action has completed successfully";
        public string BackRoute { get; set; }
    }
    public class MessageView : ViewBase<Message>
    {
        public MessageView(Message model) : base(model)
        {
        }
        public override void Render()
        {
            switch (Model.Type)
            {
                case MessageType.Success:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "SUCCESS", ConsoleColor.Green);
                    break;
                case MessageType.Error:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "ERROR!", ConsoleColor.Red);
                    break;
                case MessageType.Information:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "INFORMATION!", ConsoleColor.Yellow);
                    break;
                case MessageType.Confirmation:
                    ViewHelper.WriteLine(Model.Label != null ? Model.Label.ToUpper() : "CONFIRMATION", ConsoleColor.Cyan);
                    break;
            }
            if (Model.Type != MessageType.Confirmation)
                ViewHelper.WriteLine(Model.Text, ConsoleColor.White);
            else
            {
                ViewHelper.Write(Model.Text, ConsoleColor.Magenta);
                var answer = Console.ReadLine().ToLower();
                if (answer == "y" || answer == "yes")
                    Router.Forward(Model.BackRoute);
            }
        }
    }
}
namespace Framework
{
    public class ControllerBase
    {
        public virtual void Render(ViewBase view) => view.Render();
        public virtual void Render<T>(ViewBase<T> view) => view.Render();
        public virtual void Render(Message message)
        {
            Render(new MessageView(message));
        }
        public virtual void Success(string text, string label = "SUCCESS")
        {
            Render(new Message { Type = MessageType.Success, Text = text, Label = label });
        }
        public virtual void Inform(string text, string label = "INFORMATION")
        {
            Render(new Message { Type = MessageType.Information, Text = text, Label = label });
        }
        public virtual void Error(string text, string label = "ERROR!")
        {
            Render(new Message { Type = MessageType.Error, Text = text, Label = label });
        }
        public virtual void Confirm(string text, string route, string label = "CONFIRMATION")
        {
            Render(new Message { Type = MessageType.Confirmation, Text = text, Label = label, BackRoute = route });
        }
    }
}
using System;
namespace Framework
{
    public class Application
    {
        public Action Config { get; set; }
        public ControllerAction Help { get; set; } = DefaultHelp;
        public string Prompt { get; set; } = "# Request > ";
        public ConsoleColor Color { get; set; } = ConsoleColor.Green;
        public void Run()
        {
            Config();
            Router.Instance.Register("help", Help);
            while (true)
            {
                ViewHelper.Write(Prompt, Color);
                var request = Console.ReadLine();
                try
                {
                    Router.Instance.Forward(request);
                }
                catch (Exception e)
                {
                    ViewHelper.WriteLine($"ERROR: {e.Message}", ConsoleColor.Red);
                }
                finally
                {
                    Console.WriteLine();
                }
            }
        }
        private static void DefaultHelp(Parameter p)
        {
            if (p == null)
            {
                ViewHelper.WriteLine("SUPPORTED COMMANDS:", ConsoleColor.Magenta);
                ViewHelper.WriteLine(Router.Instance.GetRoutes(), ConsoleColor.Yellow);
                Console.WriteLine("type: help ? cmd= <command> to get command details");
                return;
            }
            Console.BackgroundColor = ConsoleColor.DarkBlue;
            var command = p["cmd"].ToLower();
            Console.WriteLine(Router.Instance.GetHelp(command));
            Console.ResetColor();
        }
    }
}

Đến đây tất cả các file mã nguồn của Framework đã hoàn tất. Chúng ta có thể biên dịch để thu lấy file thư viện (dll) và sử dụng trong các project console khác.

Trong bài sau, chúng ta sẽ thử dùng thư viện này để xây dựng một ứng dụng quản lý đơn giản.

Mã nguồn cung cấp ở bài cuối cùng

Loạt bài Xây dựng thư viện hỗ trợ ứng dụng Console:
Phần 1 – Giới thiệu chung
Phần 2 – Truy vấn, xây dựng router
Phần 3 – Hỗ trợ I/O với console, view, controller
Phần 4 – Ví dụ minh họa

Theo dõi
Thông báo của
guest

0 Thảo luận
Phản hồi nội tuyến
Xem tất cả bình luận