ABP开发框架先后端开发系列---(4)Web API调用类的封装和使用

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

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

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

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

1)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;
        }
    }
}

 

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

相关文章
相关标签/搜索