在前面随笔介绍 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 = 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 界面模块的调用, 我在后面随笔在另起一篇随笔进行介绍吧, 毕竟那是毕竟漂亮的字典模块呈现了.
来源: https://www.cnblogs.com/wuhuacong/p/10932139.html