在前面幾篇隨筆介紹了我對ABP框架的改造,包括對ABP整體的介紹,以及對各個業務分層的簡化,Web API 客戶端封裝層的設計,使得咱們基於ABP框架的總體方案愈來愈清晰化, 也愈來愈接近實際的項目開發需求,一旦整個模式比較成熟,並以一種比較固化的模式來指導開發,那麼就能夠很方便的應用在實際項目開發當中了。本篇隨筆是基於前面幾篇的基礎上,在Winform項目上進一步改造爲實際項目的場景,把我原來基於微軟企業庫底層的數據庫訪問方式的Winform框架或者混合框架的字典模塊界面改造爲基於ABP框架基礎上的字典應用模塊。html
1)APICaller層接口的回顧數據庫
在上一篇隨筆《ABP開發框架先後端開發系列---(4)Web API調用類的封裝和使用》中,我介紹了Web API調用類的封裝和使用,並介紹了在.net 控制檯程序中,測試對ApiCaller層的調用,並可以順利返回咱們所須要的數據。測試代碼以下所示。bootstrap
#region DictType using (var client = bootstrapper.IocManager.ResolveAsDisposable<DictTypeApiCaller>()) { var caller = client.Object; Console.WriteLine("Logging in with TOKEN based auth..."); var token = caller.Authenticate("admin", "123qwe").Result; Console.WriteLine(token.ToJson()); caller.RequestHeaders.Add(new NameValue("Authorization", "Bearer " + token.AccessToken)); Console.WriteLine("Get All ..."); var pagerDto = new DictTypePagedDto() { SkipCount = 0, MaxResultCount = 10 }; var result = caller.GetAll(pagerDto).Result; Console.WriteLine(result.ToJson()); Console.WriteLine("Get All by condition ..."); var pagerdictDto = new DictTypePagedDto() { Name = "民族" }; result = caller.GetAll(pagerdictDto).Result; Console.WriteLine(result.ToJson()); Console.WriteLine("Get count by condition ..."); pagerdictDto = new DictTypePagedDto() {}; var count = caller.Count(pagerdictDto).Result; Console.WriteLine(count); Console.WriteLine(); Console.WriteLine("Create DictType..."); var createDto = new CreateDictTypeDto { Id = Guid.NewGuid().ToString(), Name = "Test", Code = "Test" }; var dictDto = caller.Create(createDto).Result; Console.WriteLine(dictDto.ToJson()); Console.WriteLine("Update DictType..."); dictDto.Code = "testcode"; var updateDto = caller.Update(dictDto).Result; Console.WriteLine(updateDto.ToJson()); if (updateDto != null) { Console.WriteLine("Delete DictType..."); caller.Delete(new EntityDto<string>() { Id = dictDto.Id }); } } #endregion
這些ApiCaller對象的接口測試代碼,包括了受權登陸,獲取全部記錄,獲取條件查詢記錄,建立、更新、刪除這些接口都成功執行,驗證了咱們對總體架構的設計改良,並經過對ApiCaller層基類的設計,減小咱們對常規增刪改查接口的編碼,咱們只須要編寫咱們的自定義業務接口代碼封裝類便可。後端
其中基類的代碼以下所示。api
針對Web API接口的封裝,爲了適應客戶端快速調用的目的,這個封裝做爲一個獨立的封裝層,以方便各個模塊之間進行共同調用。架構
也就是說,上面咱們所有是基於基類接口的調用,還不須要爲咱們自定義接口編寫任何一行代碼,已經具有了常規的各類查詢和數據處理功能了。app
咱們完整的字典類型ApiCaller類的代碼以下所示。框架
namespace MyProject.Caller { /// <summary> /// 字典類型對象的Web API調用處理 /// </summary> public class DictTypeApiCaller : AsyncCrudApiCaller<DictTypeDto, string, DictTypePagedDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService { /// <summary> /// 提供單件對象使用 /// </summary> public static DictTypeApiCaller Instance { get { return Singleton<DictTypeApiCaller>.Instance; } } /// <summary> /// 默認構造函數 /// </summary> public DictTypeApiCaller() { this.DomainName = "DictType";//指定域對象名稱,用於組裝接口地址 } public async Task<Dictionary<string, string>> GetAllType(string dictTypeId) { AddRequestHeaders();//加入認證的token頭信息 string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數) url += string.Format("?dictTypeId={0}", dictTypeId); var result = await apiClient.GetAsync<Dictionary<string, string>>(url); return result; } public async Task<IList<DictTypeNodeDto>> GetTree(string pid) { AddRequestHeaders();//加入認證的token頭信息 string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數) url += string.Format("?pid={0}", pid); var result = await apiClient.GetAsync<IList<DictTypeNodeDto>>(url); return result; } }
這裏面的函數定義纔是咱們須要根據實際的自定義接口封裝的調用類函數代碼。異步
前面咱們介紹了,咱們把ApiCaller層的項目設計爲.net Standard的類庫項目,所以能夠在.net core或者在.net framework中進行使用,而且也在基於.net core的控制檯程序中測試成功了。async
下面就重點介紹一下,基於.net framework的Winfrom程序中對ABP框架的Web API接口的調用,若是之後Winform支持.net core了(聽說9月份出的.net core3就包含了),那麼也同樣的模式進行調用。
咱們先來看看字典模塊,經過封裝對ABP框架的Web API調用後,實際的功能界面效果吧。
先設計一個受權登陸的界面獲取訪問令牌信息。
字典管理界面,列出字典類型,並對字典類型下的字典數據進行分頁展現,分頁展現利用分頁控件展現。
新增或者編輯窗體界面以下
這個界面是來自於個人框架裏面的字典模塊界面,不過裏面對數據的處理代碼確實已經更改成適應ABP框架的Web API接口的調用的了(基於ApiCaller 層的調用)。
咱們下面來一一進行分析便可。
登錄界面,咱們看看主要的邏輯就是調用獲取受權令牌的接口,並存儲起來供後續界面中的業務類進行調用便可。
因爲咱們本身封裝的ApiCaller類,都是基於異步的方式封裝的,所以咱們能夠看到不少地方調用都使用await的關鍵字,這個是異步調用的關鍵字,若是方法須要定義爲異步,就須要增長async關鍵字,通常這兩個關鍵字是配套使用的。
若是咱們在事件處理代碼裏面使用了異步,那麼事件的函數也須要標記爲async,以下是字典管理模塊窗體的加載函數,也是用了async聲明 和await調用異步方法標記。
private async void FrmDictionary_Load(object sender, EventArgs e) { await InitTreeView(); this.lblDictType.Text = ""; await BindData(); //分頁控件事件處理代碼 this.winGridViewPager1.OnPageChanged += new EventHandler(winGridViewPager1_OnPageChanged); this.winGridViewPager1.OnStartExport += new EventHandler(winGridViewPager1_OnStartExport); this.winGridViewPager1.OnEditSelected += new EventHandler(winGridViewPager1_OnEditSelected); this.winGridViewPager1.OnAddNew += new EventHandler(winGridViewPager1_OnAddNew); this.winGridViewPager1.OnDeleteSelected += new EventHandler(winGridViewPager1_OnDeleteSelected); this.winGridViewPager1.OnRefresh += new EventHandler(winGridViewPager1_OnRefresh); this.winGridViewPager1.AppendedMenu = this.contextMenuStrip2; this.winGridViewPager1.BestFitColumnWith = false; this.winGridViewPager1.gridView1.DataSourceChanged += new EventHandler(gridView1_DataSourceChanged); }
咱們的數據,主要是在BindData裏面實現,這個函數是咱們本身加的,因爲使用了異步方法,所以也用async進行聲明。
整個對於分頁的數據獲取和控件的數據綁定過程,代碼以下所示。
/// <summary> /// 獲取數據 /// </summary> /// <returns></returns> private async Task<IPagedResult<DictDataDto>> GetData() { //構建分頁的條件和查詢條件 var pagerDto = new DictDataPagedDto(this.winGridViewPager1.PagerInfo) { DictType_ID = string.Concat(this.lblDictType.Tag) }; var result = await DictDataApiCaller.Instance.GetAll(pagerDto); return result; } /// <summary> /// 綁定數據 /// </summary> private async Task BindData() { #region 添加別名解析 this.winGridViewPager1.DisplayColumns = "Name,Value,Seq,Remark,EditTime"; this.winGridViewPager1.AddColumnAlias(Id_FieldName, "編號"); this.winGridViewPager1.AddColumnAlias("DictType_ID", "字典大類"); this.winGridViewPager1.AddColumnAlias("Name", "項目名稱"); this.winGridViewPager1.AddColumnAlias("Value", "項目值"); this.winGridViewPager1.AddColumnAlias("Seq", "字典排序"); this.winGridViewPager1.AddColumnAlias("Remark", "備註"); this.winGridViewPager1.AddColumnAlias("Editor", "修改用戶"); this.winGridViewPager1.AddColumnAlias("EditTime", "更新日期"); #endregion if (this.lblDictType.Tag != null) { var result = await GetData(); //設置全部記錄數和列表數據源 this.winGridViewPager1.DataSource = result.Items; this.winGridViewPager1.PagerInfo.RecordCount = result.TotalCount; } }
其中注意的是GetAll方式是傳入一個條件查詢的對象,這個就是DictDataPagedDto是咱們定義的,放入咱們DictDataDto裏面的常見屬性,方便咱們根據屬性匹配精確或者模糊查詢。
/// <summary> /// 用於根據條件查詢 /// </summary> public class DictDataPagedDto : PagedResultRequestDto { /// <summary> /// 字典類型ID /// </summary> public virtual string DictType_ID { get; set; } /// <summary> /// 類型名稱 /// </summary> public virtual string Name { get; set; } /// <summary> /// 指定值 /// </summary> public virtual string Value { get; set; } /// <summary> /// 備註 /// </summary> public virtual string Remark { get; set; } }
咱們在調用的時候,讓它限定爲一個類型的ID進行精確查詢,以下代碼
//構建分頁的條件和查詢條件 var pagerDto = new DictDataPagedDto(this.winGridViewPager1.PagerInfo) { DictType_ID = string.Concat(this.lblDictType.Tag) };
這個精確或者模糊查詢,則是在應用服務層裏面定義規則的,這個以前沒有詳細介紹了,這裏稍微補充說明一下。
在應用服務層接口類裏面,重寫CreateFilteredQuery能夠設置GetAll的查詢規則,重寫ApplySorting則能夠指定列表的排序順序。
再次回到Winform界面的調用上來,刪除類型下面字典數據的事件的處理函數以下所示。
private async void menu_ClearData_Click(object sender, EventArgs e) { TreeNode selectedNode = this.treeView1.SelectedNode; if (selectedNode != null && selectedNode.Tag != null) { string typeId = selectedNode.Tag.ToString(); var dict = await DictDataApiCaller.Instance.GetDictByTypeID(typeId); int count = dict.Count; var format = "您肯定要刪除節點:{0},該節點下面有【{1}】項數據"; format = JsonLanguage.Default.GetString(format); string message = string.Format(format, selectedNode.Text, count); if (MessageDxUtil.ShowYesNoAndWarning(message) == DialogResult.Yes) { try { await DictDataApiCaller.Instance.DeleteByTypeID(typeId); await InitTreeView(); await BindData(); } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } } }
咱們看看編輯窗體界面的後臺處理,編輯和更新數據的邏輯代碼以下所示。
#region 編輯大類 var info = await DictTypeApiCaller.Instance.Get(new EntityDto<string>(ID)); if (info != null) { SetInfo(info); try { var updatedDto = await DictTypeApiCaller.Instance.Update(info); if (updatedDto != null) { MessageDxUtil.ShowTips("保存成功"); this.DialogResult = DialogResult.OK; } } catch (Exception ex) { LogTextHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); } } #endregion
最後來一段gif動圖,展現程序的操做功能吧。
好了,這些事件的使用規則一旦肯定了,咱們好利用代碼生成工具對窗體界面的代碼進行統一規則的生成,就好像我前面對於我Winform框架和混合框架裏面的Winform窗體界面的生成同樣,咱們只須要稍微修改一下代碼生成工具的NVelocity模板,利用上數據庫表的元數據就能夠快速生成整個框架所須要的代碼了。
這樣基於整個ABP框架,而快速應用起來的項目,其實開發項目的工做量看起來也不會不少,並且咱們能夠把字典、權限控制、總體框架等基礎設施建設好,就會造成一整套的開發方法和思路了,這樣對於咱們利用ABP框架來開發業務系統,是否是有事半功倍的感受。
一旦某個東西你很喜歡,你就會用的愈來愈好。