ABP開發(fā)框架前后端開發(fā)系列---(4)Web API調(diào)用類的封裝和使用

在前面隨筆介紹ABP應(yīng)用框架的項(xiàng)目組織情況穷劈,以及項(xiàng)目中領(lǐng)域?qū)痈鱾€(gè)類代碼組織,以及簡化了ABP框架的各個(gè)層的內(nèi)容很澄,使得我們項(xiàng)目結(jié)構(gòu)更加清晰锄弱。上篇隨筆已經(jīng)介紹了字典模塊中應(yīng)用服務(wù)層接口的實(shí)現(xiàn)情況,并且通過運(yùn)行Web API的宿主程序礼仗,可以在界面上進(jìn)行接口測試了吐咳,本篇隨筆基于前面介紹的基礎(chǔ)上逻悠,介紹Web API調(diào)用類的封裝和使用,使用包括控制臺和Winform中對調(diào)用封裝類的使用韭脊。

在上篇隨筆《ABP開發(fā)框架前后端開發(fā)系列---(3)框架的分層和文件組織》中我繪制了改進(jìn)后的ABP框架的架構(gòu)圖示童谒,如下圖所示。

image

這個(gè)項(xiàng)目分層里面的 03-Application.Common 應(yīng)用服務(wù)通用層沪羔,我們主要放置在各個(gè)模塊里面公用的DTO和應(yīng)用服務(wù)接口類饥伊。有了這些DTO文件和接口類,我們就不用在客戶端(如Winform客戶蔫饰、控制臺琅豆、WPF/UWP等)重復(fù)編寫這部分的內(nèi)容,直接使用即可篓吁。

這些DTO文件和接口類文件茫因,我們的主要用途是用來封裝客戶端調(diào)用Web API的調(diào)用類,使得我們在界面使用的時(shí)候越除,調(diào)用更加方便节腐。

1)Web API調(diào)用類封裝

為了更方便在控制臺客戶端、Winform客戶端等場景下調(diào)用Web API的功能摘盆,我們需要對應(yīng)用服務(wù)層拋出的Web API接口進(jìn)行封裝翼雀,然后結(jié)合DTO類實(shí)現(xiàn)一個(gè)標(biāo)準(zhǔn)的接口實(shí)現(xiàn)。

由于這些調(diào)用類可能在多個(gè)客戶端中進(jìn)行共享孩擂,因此根據(jù)我們在混合框架中積累的經(jīng)驗(yàn)狼渊,我們把它們獨(dú)立為一個(gè)項(xiàng)目進(jìn)行管理,如下項(xiàng)目視圖所示类垦。

image

其中DictDataApiCaller 就是對應(yīng)領(lǐng)域?qū)ο?<領(lǐng)域?qū)ο?gt;ApiCaller的命名規(guī)則狈邑。

如對于字典模塊的API封裝類,它們繼承一個(gè)相同的基類蚤认,然后實(shí)現(xiàn)特殊的自定義接口即可米苹,這樣可以減少常規(guī)的Create、Get砰琢、GetAll蘸嘶、Update、Delete等操作的代碼陪汽,這些全部由調(diào)用基類進(jìn)行處理训唱,而只需要實(shí)現(xiàn)自定義的接口調(diào)用即可。如下是字典模塊DictType和DictData兩個(gè)業(yè)務(wù)對象的API封裝關(guān)系挚冤。

image

如對于字典類型的API封裝類定義代碼如下所示况增。

    /// <summary>
    /// 字典類型對象的Web API調(diào)用處理
    /// </summary>
    public class DictTypeApiCaller : AsyncCrudApiCaller<DictTypeDto, string, DictTypePagedDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService
    {
        /// <summary>
        /// 提供單件對象使用
        /// </summary>
        public static DictTypeApiCaller Instance
        {
            get
            {
                return Singleton<DictTypeApiCaller>.Instance;
            }
        }

......

這里我們可以通過單件的方式來使用字典類型API的封裝類實(shí)例 DictTypeApiCaller.Instance

對于Web API的調(diào)用,我們知道训挡,一般需要使用WebClient或者HttpRequest的底層類進(jìn)行Url的訪問處理澳骤,通過提供相應(yīng)的數(shù)據(jù)歧强,獲取對應(yīng)的返回結(jié)果。

而對于操作方法的類型宴凉,是使用POST誊锭、GET、INPUT弥锄、DELETE的不同丧靡,需要看具體的接口,我們可以通過Swagger UI 呈現(xiàn)出來的進(jìn)行處理即可籽暇,如下所示的動作類型温治。

image

如果處理動作不匹配,如本來是Post的用Get方法戒悠,或者是Delete的用Post方法熬荆,都會出錯(cuò)。

在Abp.Web.Api項(xiàng)目里面有一個(gè)AbpWebApiClient的封裝方法绸狐,里面實(shí)現(xiàn)了POST方法卤恳,可以參考來做對應(yīng)的WebClient的封裝調(diào)用。

image

我在它的基礎(chǔ)上擴(kuò)展了實(shí)現(xiàn)方法寒矿,包括了Get突琳、Put、Delete方法的調(diào)用符相。

我們使用的時(shí)候拆融,初始化它就可以了。

apiClient = new AbpWebApiClient();

例如啊终,我們對于常規(guī)的用戶登錄處理镜豹,它的API調(diào)用封裝的操作代碼如下所示,這個(gè)是一個(gè)POST方法蓝牲。

        /// <summary>
        /// 對用戶身份進(jìn)行認(rèn)證
        /// </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;
        }

對于業(yè)務(wù)接口來說趟脂,我們都是基于約定的規(guī)則來命名接口名稱和地址的,如對于GetAll這個(gè)方法來說例衍,字典類型的地址如下所示昔期。

/api/services/app/DictData/GetAll

另外還包括服務(wù)器的基礎(chǔ)地址,從而構(gòu)建一個(gè)完整的調(diào)用地址如下所示肄渗。

http://localhost:21021/api/services/app/DictData/GetAll

由于這些規(guī)則確定,因此我們可以通過動態(tài)構(gòu)建這個(gè)API地址即可咬最。

            string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數(shù))
            url += string.Format("?SkipCount={0}&MaxResultCount={1}", dto.SkipCount, dto.MaxResultCount);

而對于GetAll函數(shù)來說翎嫡,這個(gè)定義如下所示。

Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input)

它是需要根據(jù)一定的條件進(jìn)行查詢的永乌,不僅僅是 SkipCount 和 MaxResultCount兩個(gè)屬性惑申,因此我們需要?jiǎng)討B(tài)組合它的url參數(shù)具伍,因此建立一個(gè)輔助類來動態(tài)構(gòu)建這些輸入?yún)?shù)地址。

        /// <summary>
        /// 獲取所有對象列表
        /// </summary>
        /// <param name="input">獲取所有條件</param>
        /// <returns></returns>
        public async virtual Task<PagedResultDto<TEntityDto>> GetAll(TGetAllInput input)
        {
            AddRequestHeaders();//加入認(rèn)證的token頭信息
            string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數(shù))
            url = GetUrlParam(input, url);

            var result = await apiClient.GetAsync<PagedResultDto<TEntityDto>>(url);
            return result;
        }

這樣我們這個(gè)API的調(diào)用封裝類的基類就實(shí)現(xiàn)了常規(guī)的功能了圈驼。效果如下所示人芽。

image

而字典類型的API封裝類,我們只需要實(shí)現(xiàn)特定的自定義接口即可绩脆,省卻我們很多的工作量萤厅。

namespace MyProject.Caller
{
    /// <summary>
    /// 字典類型對象的Web API調(diào)用處理
    /// </summary>
    public class DictTypeApiCaller : AsyncCrudApiCaller<DictTypeDto, string, DictTypePagedDto, CreateDictTypeDto, DictTypeDto>, IDictTypeAppService
    {
        /// <summary>
        /// 提供單件對象使用
        /// </summary>
        public static DictTypeApiCaller Instance
        {
            get
            {
                return Singleton<DictTypeApiCaller>.Instance;
            }
        }

        /// <summary>
        /// 默認(rèn)構(gòu)造函數(shù)
        /// </summary>
        public DictTypeApiCaller()
        {
            this.DomainName = "DictType";//指定域?qū)ο竺Q,用于組裝接口地址
        }

        public async Task<Dictionary<string, string>> GetAllType(string dictTypeId)
        {
            AddRequestHeaders();//加入認(rèn)證的token頭信息
            string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數(shù))
            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();//加入認(rèn)證的token頭信息
            string url = GetActionUrl(MethodBase.GetCurrentMethod());//獲取訪問API的地址(未包含參數(shù))
            url += string.Format("?pid={0}", pid);

            var result = await apiClient.GetAsync<IList<DictTypeNodeDto>>(url);
            return result;
        }
    }
}

2)API封裝類的調(diào)用

前面小節(jié)介紹了針對Web API接口的封裝靴迫,以適應(yīng)客戶端快速調(diào)用的目的惕味,這個(gè)封裝作為一個(gè)獨(dú)立的封裝層,以方便各個(gè)模塊之間進(jìn)行共同調(diào)用玉锌。

image

到這里為止名挥,我們還沒有測試過具體的調(diào)用,還沒有了解實(shí)際調(diào)用過程中是否有問題主守,當(dāng)然我們在開發(fā)的時(shí)候禀倔,一般都是一步步來的,但也是確保整個(gè)路線沒有問題的参淫。

實(shí)際情況如何救湖,是騾是馬拉出來溜溜就知道了。

首先我們創(chuàng)建一個(gè)基于.net Core的控制臺程序黄刚,項(xiàng)目情況如下所示捎谨。

image

在其中我們定義這個(gè)項(xiàng)目的模塊信息,它是依賴于APICaller層的模塊憔维。

namespace RemoteApiConsoleApp
{
    [DependsOn(typeof(CallerModule))]
    public class MyModule : AbpModule
    {
        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        }
    }
}

在ABP里面涛救,模塊是通過一定順序啟動的,如果我們通過AbpBootstrapper類來啟動相關(guān)的模塊业扒,啟動模塊的代碼如下所示检吆。

//使用AbpBootstrapper創(chuàng)建類來處理
using (var bootstrapper = AbpBootstrapper.Create<MyModule>())
{
    bootstrapper.Initialize();

        ..........

模塊啟動后,系統(tǒng)的IOC容器會為我們注冊好相關(guān)的接口對象程储,那么調(diào)用API封裝類的代碼如下所示蹭沛。

    //使用AbpBootstrapper創(chuàng)建類來處理
    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

上面是對角色的相關(guān)接口操作,如果對于我們之前創(chuàng)建的字典模塊章鲤,那么它的操作代碼類似摊灭,如下所示。

    #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

測試字典模塊的處理败徊,執(zhí)行效果如下所示帚呼。

image

刪除內(nèi)容,我們是配置為軟刪除的,因此可以通過數(shù)據(jù)庫記錄查看是否標(biāo)記為刪除了煤杀。

image

同時(shí)眷蜈,我們可以看到審計(jì)日志里面,有對相關(guān)應(yīng)用層接口的調(diào)用記錄沈自。

image

以上就是.net core控制臺程序中對于API封裝接口的調(diào)用酌儒,上面代碼如果需要在.net framework里面跑,也是一樣的枯途,我同樣也做了一個(gè)基于.net framework控制臺程序忌怎,代碼調(diào)用都差不多的,它的ApiCaller我們做成了 .net standard程序類庫的柔袁,因此都是通用的呆躲。

image

前面我們提到,我們的APICaller的類捶索,設(shè)計(jì)了單件的實(shí)例調(diào)用插掂,因此我們調(diào)用起來更加方便,除了上面使用ABP的啟動模塊的方式調(diào)用外腥例,我們可以用傳統(tǒng)的方式進(jìn)行調(diào)用辅甥,也就是創(chuàng)建一個(gè)ApiCaller的實(shí)例對象的方式進(jìn)行調(diào)用,如下代碼所示燎竖。

    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錯(cuò)誤信息:" + ex.Message);
        return;
    }

由于篇幅的原因,基于winform界面模塊的調(diào)用构回,我在后面隨筆在另起一篇隨筆進(jìn)行介紹吧夏块,畢竟那是畢竟漂亮的字典模塊呈現(xiàn)了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纤掸,一起剝皮案震驚了整個(gè)濱河市脐供,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌借跪,老刑警劉巖政己,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掏愁,居然都是意外死亡歇由,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門果港,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沦泌,“玉大人,你說我怎么就攤上這事辛掠⌒磺” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長他宛。 經(jīng)常有香客問我,道長欠气,這世上最難降的妖魔是什么厅各? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮预柒,結(jié)果婚禮上队塘,老公的妹妹穿的比我還像新娘。我一直安慰自己宜鸯,他們只是感情好憔古,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著淋袖,像睡著了一般鸿市。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上即碗,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天焰情,我揣著相機(jī)與錄音,去河邊找鬼剥懒。 笑死内舟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的初橘。 我是一名探鬼主播验游,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼保檐!你這毒婦竟也來了耕蝉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤展东,失蹤者是張志新(化名)和其女友劉穎赔硫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盐肃,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爪膊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了砸王。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片推盛。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谦铃,靈堂內(nèi)的尸體忽然破棺而出耘成,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布瘪菌,位于F島的核電站撒会,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏师妙。R本人自食惡果不足惜诵肛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望默穴。 院中可真熱鬧怔檩,春花似錦、人聲如沸蓄诽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仑氛。三九已至乙埃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锯岖,已是汗流浹背膊爪。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嚎莉,地道東北人米酬。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像趋箩,于是被迫代替她去往敵國和親赃额。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容