在前面随笔介绍ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以及简化了ABP框架的各个层的内容,使得我们项目结构更加清晰。上篇随笔已经介绍了字典模块中应用服务层接口的实现情况,并且通过运行Web API的宿主程序,可以在界面上进行接口测试了,本篇随笔基于前面介绍的基础上,介绍Web API调用类的封装和使用,使用包括控制台和Winform中对调用封装类的使用。

在上篇随笔《ABP开发框架前后端开发系列---(3)框架的分层和文件组织》中我绘制了改进后的ABP框架的架构图示,如下图所示。

这个项目分层里面的 03-Application.Common 应用服务通用层,我们主要放置在各个模块里面公用的DTO和应用服务接口类。有了这些DTO文件和接口类,我们就不用在客户端(如Winform客户、控制台、WPF/UWP等)重复编写这部分的内容,直接使用即可。

这些DTO文件和接口类文件,我们的主要用途是用来封装客户端调用Web API的调用类,使得我们在界面使用的时候,调用更加方便。

1)Web API调用类封装

为了更方便在控制台客户端、Winform客户端等场景下调用Web API的功能,我们需要对应用服务层抛出的Web API接口进行封装,然后结合DTO类实现一个标准的接口实现。

由于这些调用类可能在多个客户端中进行共享,因此根据我们在混合框架中积累的经验,我们把它们独立为一个项目进行管理,如下项目视图所示。

其中DictDataApiCaller 就是对应领域对象 <领域对象>ApiCaller的命名规则。

如对于字典模块的API封装类,它们继承一个相同的基类,然后实现特殊的自定义接口即可,这样可以减少常规的Create、Get、GetAll、Update、Delete等操作的代码,这些全部由调用基类进行处理,而只需要实现自定义的接口调用即可。如下是字典模块DictType和DictData两个业务对象的API封装关系。

如对于字典类型的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.Instance

对于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;
}
}
}

2)API封装类的调用

前面小节介绍了针对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 = , MaxResultCount = };
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 = , MaxResultCount = };
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界面模块的调用,我在后面随笔在另起一篇随笔进行介绍吧,毕竟那是毕竟漂亮的字典模块呈现了。

ABP开发框架前后端开发系列---(4)Web API调用类的封装和使用的更多相关文章

  1. ABP开发框架前后端开发系列---(10)Web API调用类的简化处理

    在较早期的随笔<ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目中的使用>已经介绍了Web API调用类的封装处理,虽然这些调用类我们可以使用代码生成工具快 ...

  2. ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目中的使用

    在前面几篇随笔介绍了我对ABP框架的改造,包括对ABP总体的介绍,以及对各个业务分层的简化,Web API 客户端封装层的设计,使得我们基于ABP框架的整体方案越来越清晰化, 也越来越接近实际的项目开 ...

  3. ABP开发框架前后端开发系列---(3)框架的分层和文件组织

    在前面随笔<ABP开发框架前后端开发系列---(2)框架的初步介绍>中,我介绍了ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以便基于数据库应用的简化处理.本篇随笔进一步对 ...

  4. ABP开发框架前后端开发系列---(2)框架的初步介绍

    在前面随笔<ABP开发框架前后端开发系列---(1)框架的总体介绍>大概介绍了这个ABP框架的主要特点,以及介绍了我对这框架的Web API应用优先的一些看法,本篇继续探讨ABP框架的初步 ...

  5. ABP开发框架前后端开发系列---(11)菜单的动态管理

    在前面随笔<ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理>中介绍了基于ABP框架服务构建的Winform客户端,客户端通过Web API调用的方式进行获取数据,从而实现 ...

  6. ABP开发框架前后端开发系列---(12)配置模块的管理

    一般来说,一个系统或多或少都会涉及到一些系统参数或者用户信息的配置,而ABP框架也提供了一套配置信息的管理模块,ABP框架的配置信息,必须提前定义好配置的各项内容,然后才能在系统中初始化或者通过接口查 ...

  7. ABP开发框架前后端开发系列---(14)基于Winform的ABP快速开发框架

    前面介绍了很多ABP系列的文章,一步一步的把我们日常开发中涉及到的Web API服务构建.登录日志和操作审计日志.字典管理模块.省份城市的信息维护.权限管理模块中的组织机构.用户.角色.权限.菜单等内 ...

  8. ABP开发框架前后端开发系列---(6)ABP基础接口处理和省份城市行政区管理模块的开发

    最近没有更新ABP框架的相关文章,一直在研究和封装相关的接口,总算告一段落,开始继续整理下开发心得.上次我在随笔<ABP开发框架前后端开发系列---(5)Web API调用类在Winform项目 ...

  9. ABP开发框架前后端开发系列---(16)ABP框架升级最新版本的经验总结

    有一小段时间没有持续升级ABP框架了,最近就因应客户的需要,把ABP框架进行全面的更新,由于我们应用的ABP框架,基础部分还是会使用官方的内容,因此升级的时候需要把官方基础ABP的DLL进行全面的更新 ...

随机推荐

  1. Windows PowerShell 学习之——Cmdlet处理生命周期

    这一次介绍一下Cmdlet处理过程的生命周期 总共分为六个部分 1.概述 2. 命令行输入绑定参数(parameters) 3. 开始指令处理 4. 接受管道输入绑定参数 5. 处理记录 6. 处理记 ...

  2. Attribute-based identification schemes for objects in internet of things

    Methods and arrangements for object identification. An identification request is received from diffe ...

  3. android Notification分析—— 您可能会遇到各种问题

    使用的各种总结上线通知,csdn还有一个非常到位的总结,不这样做,反复总结,学生需要能够搜索自己或参考下面给出的链接. 研究开始时仔细阅读一些,今天,功能开发,一些问题和经验自己最近的遭遇给大家分享. ...

  4. 数据中台解析Hive SQL过程

    一.数据中台解析SQL的目的: 数据中台需要对外提供数据特征查询的能力,因此中台查找并解析各个平台的sql,找出哪些表中的字段经常被使用,以便沉淀为特征,而我们要做的是找出sql中的数据表及其字段.以 ...

  5. Git 内部原理--初探 .git

    说到Git大家应该都非常熟悉,几乎每天都会用到它.在日常使用过程中,我们貌似并不需要关注其内部的原理,只需要记住那几个常用的命令,就可以说自己是会Git的人了.可是,事实真的是这样子的吗?今天我们就来 ...

  6. typescript Json Convert

    关键代码 this.data={}; // json string this.dataStr=JSON.stringify(this.data); // json object this.conver ...

  7. Telnet协议详解(远程登陆协议)

    1. 概述 Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议.Telnet协议的目的是提供一个相对通用的,双向的,面向八位字节的通信方法,允许界面终端设备和面向终 ...

  8. 将自定义控件加载到RichTextbox并进行交互

    先前遇到一个问题,将自定义控件加载到RichTextbox后,无法触发自定义控件的各种事件,最终找到解决办法:设置RichTextbox的IsDocumentEnabled="True&qu ...

  9. 利用WIX制作安装包(2)

    原文 利用WIX制作安装包(2) 这一篇文章将为大家介绍如何使用WIX自定义UI.上一篇文章我们讲过WIX为我们提供了五种安装界面.每种安装界面都是由不同的Dialog组成.在这里我们挑选一种比较常用 ...

  10. JS实时检测文本框内容长度

    通过js代码实时监测,文本框内容的变化以及长度,下图是一个实际使用场景. HTML部分: <input id="Text1" type="text" on ...