最近開始重構一個稍嫌古老的C/S項目,原先採用的技術棧是『WinForm』+『WCF』+『EF』。相對於現在鋪天蓋地的B/S架構來說,看上去似乎和Win95一樣古老,很多新入行的,可能就沒有見過經典的C/S架構的系統。事實上,作為企業信息管理系統,包括ERP/CRM/SCM等,桌面客戶端還是很OK的 ...
最近開始重構一個稍嫌古老的C/S項目,原先採用的技術棧是『WinForm』+『WCF』+『EF』。相對於現在鋪天蓋地的B/S架構來說,看上去似乎和Win95一樣古老,很多新入行的,可能就沒有見過經典的C/S架構的系統。事實上,作為企業信息管理系統,包括ERP/CRM/SCM等,桌面客戶端還是很OK的。
這次重構原定的目標有兩個:
1、客戶端還是WinForm不變,但使用MVC模式重寫;
2、WCF改成WebAPI。
經過2周時間的嘗試和探索,重構計劃變更為:
1、使用VMVC模式來重構WinForm客戶端;
2、用WCF實現偽WebAPI,其本質還是個WCF服務,但實現了RESTful風格的WebAPI。
這次和大家分享我對客戶端架構的一些探索,就不展開服務端相關的話題了。那麼,什麼是VMVC呢?呵呵,這個是我發明的新名稱,和MVC的區別在於用ViewModel替換了Model。ViewModel和View之間實現雙向數據綁定,View上面的交互產生的操作指令,還是由Controller接收,然後通過對ViewModel的操作,更新View的數據。
簡單地說,就是ViewModel負責數據流,View負責顯示和接受用戶指令,而Controller則居中調度。示意圖如下:
由於實現了數據雙向綁定,所以在一定程度上簡化了數據的存儲。只需要執行ViewModel上的Save()方法,就可以將新的數據通過WebAPI存儲到資料庫了。
ViewModel的職責非常明確,就是一個數據流引擎!所以基本上都是Load()、Save()、Show()、Refresh()、Close()這些無腦方法,一丁點的業務邏輯都木有。非常適合有一定編程經驗,但不瞭解業務邏輯的程式員編寫。
而View就更簡單了,完全由VS的窗體設計器生成。UI設計師從此不需要PS了,根據產品原型直接拖控制項就OK。
最後,所有的業務邏輯都寫在Controller裡面,這樣就為自動化測試提供了可能。測試工程師只需要編寫一段測試代碼替代Controller,同時對View的數據進行註入就可以跑單元測試。
下麵是我用於嘗試這種模式的示例,希望能夠起到拋磚引玉的作用。
代碼結構:
Controller(部分代碼),通過訂閱View上面的確定按鈕點擊事件實現用戶操作的委托:
1 /// <summary> 2 /// 修改伺服器配置 3 /// </summary> 4 private void ConfigServer() 5 { 6 _SetModel = new SetModel(); 7 8 // 訂閱確定按鈕點擊事件 9 _SetModel.View.ConfirmButton.Click += SetConfirm_Click; 10 _SetModel.ShowDialog(); 11 } 12 13 /// <summary> 14 /// 點擊確定按鈕 15 /// </summary> 16 /// <param name="sender"></param> 17 /// <param name="e"></param> 18 private void SetConfirm_Click(object sender, EventArgs e) 19 { 20 if (!_SetModel.Test()) return; 21 22 _SetModel.Save(); 23 _SetModel.Close(); 24 }
ViewModel:
1 using System; 2 using System.Windows.Forms; 3 using Insight.Utils.Client; 4 using Insight.Utils.Common; 5 using Insight.WS.Client.Common.Utils; 6 using Insight.WS.Client.MainApp.Views; 7 8 namespace Insight.WS.Client.MainApp.Models 9 { 10 public class SetModel 11 { 12 public LoginSet View = new LoginSet(); 13 14 private string _Address = Config.BaseAddress(); 15 private string _Port = Config.Port(); 16 private bool _SaveUser = Config.IsSaveUserInfo(); 17 18 /// <summary> 19 /// 構造方法,初始化控制項初始值 20 /// 通過訂閱事件實現雙向數據綁定 21 /// </summary> 22 public SetModel() 23 { 24 View.AddressInput.EditValueChanged += AddressChanged; 25 View.AddressInput.Text = _Address; 26 27 View.PortInput.EditValueChanged += PortChanged; 28 View.PortInput.Text = _Port; 29 30 View.SaveUserCheckBox.CheckStateChanged += SaveUserChanged; 31 View.SaveUserCheckBox.Checked = _SaveUser; 32 } 33 34 /// <summary> 35 /// 顯示對話框 36 /// </summary> 37 public void ShowDialog() 38 { 39 View.ShowDialog(); 40 } 41 42 /// <summary> 43 /// 關閉對話框 44 /// </summary> 45 public void Close() 46 { 47 View.DialogResult = DialogResult.OK; 48 View.Close(); 49 } 50 51 /// <summary> 52 /// 測試伺服器連通性 53 /// </summary> 54 /// <returns>bool 是否通過連通性測試</returns> 55 public bool Test() 56 { 57 var url = $"http://{_Address}:{_Port}/commonapi/v1.0/test"; 58 var result = new HttpClient(url).Request(Params.Token); 59 if (result.Code != "400") return true; 60 61 Messages.ShowError("請配置正確的伺服器地址和埠號!"); 62 return false; 63 } 64 65 /// <summary> 66 /// 保存設置 67 /// </summary> 68 public void Save() 69 { 70 if (!_SaveUser) Config.SaveUserName(string.Empty); 71 72 Config.SaveIsSaveUserInfo(_SaveUser); 73 Config.SaveAddress(_Address, _Port); 74 75 Params.InsightServer = $"http://{_Address}:{_Port}"; 76 } 77 78 /// <summary> 79 /// 伺服器地址發生變化 80 /// </summary> 81 /// <param name="sender"></param> 82 /// <param name="e"></param> 83 private void AddressChanged(object sender, EventArgs e) 84 { 85 _Address = View.AddressInput.Text; 86 } 87 88 /// <summary> 89 /// 服務埠發生變化 90 /// </summary> 91 /// <param name="sender"></param> 92 /// <param name="e"></param> 93 private void PortChanged(object sender, EventArgs e) 94 { 95 _Port = View.PortInput.Text; 96 } 97 98 /// <summary> 99 /// 保存用戶賬號選項發生變化 100 /// </summary> 101 /// <param name="sender"></param> 102 /// <param name="e"></param> 103 private void SaveUserChanged(object sender, EventArgs e) 104 { 105 _SaveUser = View.SaveUserCheckBox.Checked; 106 } 107 } 108 }View Code