在前面隨筆介紹ABP應用框架的項目組織狀況,以及項目中領域層各個類代碼組織,以及簡化了ABP框架的各個層的內容,使得咱們項目結構更加清晰。上篇隨筆已經介紹了字典模塊中應用服務層接口的實現狀況,而且經過運行Web API的宿主程序,能夠在界面上進行接口測試了,本篇隨筆基於前面介紹的基礎上,介紹Web API調用類的封裝和使用,使用包括控制檯和Winform中對調用封裝類的使用。html
在上篇隨筆《ABP開發框架先後端開發系列---(3)框架的分層和文件組織》中我繪製了改進後的ABP框架的架構圖示,以下圖所示。數據庫
這個項目分層裏面的 03-Application.Common 應用服務通用層,咱們主要放置在各個模塊裏面公用的DTO和應用服務接口類。有了這些DTO文件和接口類,咱們就不用在客戶端(如Winform客戶、控制檯、WPF/UWP等)重複編寫這部分的內容,直接使用便可。bootstrap
這些DTO文件和接口類文件,咱們的主要用途是用來封裝客戶端調用Web API的調用類,使得咱們在界面使用的時候,調用更加方便。後端
爲了更方便在控制檯客戶端、Winform客戶端等場景下調用Web API的功能,咱們須要對應用服務層拋出的Web API接口進行封裝,而後結合DTO類實現一個標準的接口實現。api
因爲這些調用類可能在多個客戶端中進行共享,所以根據咱們在混合框架中積累的經驗,咱們把它們獨立爲一個項目進行管理,以下項目視圖所示。服務器
其中DictDataApiCaller 就是對應領域對象 <領域對象>ApiCaller的命名規則。架構
如對於字典模塊的API封裝類,它們繼承一個相同的基類,而後實現特殊的自定義接口便可,這樣能夠減小常規的Create、Get、GetAll、Update、Delete等操做的代碼,這些所有由調用基類進行處理,而只須要實現自定義的接口調用便可。以下是字典模塊DictType和DictData兩個業務對象的API封裝關係。app
如對於字典類型的API封裝類定義代碼以下所示。框架
/// <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; } } ......
這裏咱們能夠經過單件的方式來使用字典類型API的封裝類實例 DictTypeApiCaller.Instanceasync
對於Web API的調用,咱們知道,通常須要使用WebClient或者HttpRequest的底層類進行Url的訪問處理,經過提供相應的數據,獲取對應的返回結果。
而對於操做方法的類型,是使用POST、GET、INPUT、DELETE的不一樣,須要看具體的接口,咱們能夠經過Swagger UI 呈現出來的進行處理便可,以下所示的動做類型。
若是處理動做不匹配,如原本是Post的用Get方法,或者是Delete的用Post方法,都會出錯。
在Abp.Web.Api項目裏面有一個AbpWebApiClient的封裝方法,裏面實現了POST方法,能夠參考來作對應的WebClient的封裝調用。
我在它的基礎上擴展了實現方法,包括了Get、Put、Delete方法的調用。
咱們使用的時候,初始化它就能夠了。
apiClient = new AbpWebApiClient();
例如,咱們對於常規的用戶登陸處理,它的API調用封裝的操做代碼以下所示,這個是一個POST方法。
/// <summary> /// 對用戶身份進行認證 /// </summary> /// <param name="username">用戶名</param> /// <param name="password">用戶密碼</param> /// <returns></returns> public async virtual Task<AuthenticateResult> Authenticate(string username, string password) { var url = string.Format("{0}/api/TokenAuth/Authenticate", ServerRootAddress); var input = new { UsernameOrEmailAddress = username, Password = password }; var result = await apiClient.PostAsync<AuthenticateResult>(url, input); return result; }
對於業務接口來講,咱們都是基於約定的規則來命名接口名稱和地址的,如對於GetAll這個方法來講,字典類型的地址以下所示。
/api/services/app/DictData/GetAll
另外還包括服務器的基礎地址,從而構建一個完整的調用地址以下所示。
http://localhost:21021/api/services/app/DictData/GetAll
因爲這些規則肯定,所以咱們能夠經過動態構建這個API地址便可。
string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數) url += string.Format("?SkipCount={0}&MaxResultCount={1}", dto.SkipCount, dto.MaxResultCount);
而對於GetAll函數來講,這個定義以下所示。
Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input)
它是須要根據必定的條件進行查詢的,不只僅是 SkipCount 和 MaxResultCount兩個屬性,所以咱們須要動態組合它的url參數,所以創建一個輔助類來動態構建這些輸入參數地址。
/// <summary> /// 獲取全部對象列表 /// </summary> /// <param name="input">獲取全部條件</param> /// <returns></returns> public async virtual Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input) { AddRequestHeaders();//加入認證的token頭信息 string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數) url = GetUrlParam(input, url); var result = await apiClient.GetAsync<PagedResultDto<TEntityDto>>(url); return result; }
這樣咱們這個API的調用封裝類的基類就實現了常規的功能了。效果以下所示。
而字典類型的API封裝類,咱們只須要實現特定的自定義接口便可,省卻咱們不少的工做量。
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; } } }
前面小節介紹了針對Web API接口的封裝,以適應客戶端快速調用的目的,這個封裝做爲一個獨立的封裝層,以方便各個模塊之間進行共同調用。
到這裏爲止,咱們尚未測試過具體的調用,尚未了解實際調用過程當中是否有問題,固然咱們在開發的時候,通常都是一步步來的,但也是確保整個路線沒有問題的。
實際狀況如何,是騾是馬拉出來溜溜就知道了。
首先咱們建立一個基於.net Core的控制檯程序,項目狀況以下所示。
在其中咱們定義這個項目的模塊信息,它是依賴於APICaller層的模塊。
namespace RemoteApiConsoleApp { [DependsOn(typeof(CallerModule))] public class MyModule : AbpModule { public override void Initialize() { IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly()); } } }
在ABP裏面,模塊是經過必定順序啓動的,若是咱們經過AbpBootstrapper類來啓動相關的模塊,啓動模塊的代碼以下所示。
//使用AbpBootstrapper建立類來處理 using (var bootstrapper = AbpBootstrapper.Create<MyModule>()) { bootstrapper.Initialize(); ..........
模塊啓動後,系統的IOC容器會爲咱們註冊好相關的接口對象,那麼調用API封裝類的代碼以下所示。
//使用AbpBootstrapper建立類來處理 using (var bootstrapper = AbpBootstrapper.Create<MyModule>()) { bootstrapper.Initialize(); #region Role using (var client = bootstrapper.IocManager.ResolveAsDisposable<RoleApiCaller>()) { 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("Getting roles..."); var pagerDto = new PagedResultRequestDto() { SkipCount = 0, MaxResultCount = 10 }; var result = caller.GetAll(pagerDto); Console.WriteLine(result.ToJson()); Console.WriteLine("Create role..."); List<string> permission = new List<string>() { "Pages.Roles" }; var createRoleDto = new CreateRoleDto { DisplayName = "test", Name = "Test", Description = "test", Permissions = permission }; var roleDto = caller.Create(createRoleDto).Result; Console.WriteLine(roleDto.ToJson()); var singleDto = new EntityDto<int>() { Id = roleDto.Id }; Console.WriteLine("Getting role by id..."); roleDto = caller.Get(singleDto).Result; Console.WriteLine(roleDto); Console.WriteLine("Delete role..."); var delResult = caller.Delete(singleDto); Console.WriteLine(delResult.ToJson()); Console.ReadLine(); } #endregion
上面是對角色的相關接口操做,若是對於咱們以前建立的字典模塊,那麼它的操做代碼相似,以下所示。
#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
測試字典模塊的處理,執行效果以下所示。
刪除內容,咱們是配置爲軟刪除的,所以能夠經過數據庫記錄查看是否標記爲刪除了。
同時,咱們能夠看到審計日誌裏面,有對相關應用層接口的調用記錄。
以上就是.net core控制檯程序中對於API封裝接口的調用,上面代碼若是須要在.net framework裏面跑,也是同樣的,我一樣也作了一個基於.net framework控制檯程序,代碼調用都差很少的,它的ApiCaller咱們作成了 .net standard程序類庫的,所以都是通用的。
前面咱們提到,咱們的APICaller的類,設計了單件的實例調用,所以咱們調用起來更加方便,除了上面使用ABP的啓動模塊的方式調用外,咱們能夠用傳統的方式進行調用,也就是建立一個ApiCaller的實例對象的方式進行調用,以下代碼所示。
string loginName = this.txtUserName.Text.Trim(); string password = this.txtPassword.Text; AuthenticateResult result = null; try { result = await DictTypeApiCaller.Instance.Authenticate(loginName, password); } catch(AbpException ex) { MessageDxUtil.ShowTips("用戶賬號密碼不正確。\r\n錯誤信息:" + ex.Message); return; }
因爲篇幅的緣由,基於winform界面模塊的調用,我在後面隨筆在另起一篇隨筆進行介紹吧,畢竟那是畢竟漂亮的字典模塊呈現了。