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

在前面几篇随笔介绍了我对ABP框架的改造,包括对ABP总体的介绍,以及对各个业务分层的简化,Web API 客户端封装层的设计,使得我们基于ABP框架的整体方案越来越清晰化, 也越来越接近实际的项目开发需求,一旦整个模式比较成熟,并以一种比较固化的模式来指导开发,那么就可以很方便的应用在实际项目开发当中了。本篇随笔是基于前面几篇的基础上,在Winform项目上进一步改造为实际项目的场景,把我原来基于微软企业库底层的数据库访问方式的Winform框架或者混合框架的字典模块界面改造为基于ABP框架基础上的字典应用模块。

1)APICaller层接口的回顾

在上一篇随笔《ABP开发框架前后端开发系列---(4)Web API调用类的封装和使用》中,我介绍了Web API调用类的封装和使用,并介绍了在.net 控制台程序中,测试对ApiCaller层的调用,并能够顺利返回我们所需要的数据。测试代码如下所示。

    #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

这些ApiCaller对象的接口测试代码,包括了授权登录,获取所有记录,获取条件查询记录,创建、更新、删除这些接口都成功执行,验证了我们对整体架构的设计改良,并通过对ApiCaller层基类的设计,减少我们对常规增删改查接口的编码,我们只需要编写我们的自定义业务接口代码封装类即可。

image

其中基类的代码如下所示。

image

针对Web API接口的封装,为了适应客户端快速调用的目的,这个封装作为一个独立的封装层,以方便各个模块之间进行共同调用。

image

也就是说,上面我们全部是基于基类接口的调用,还不需要为我们自定义接口编写任何一行代码,已经具备了常规的各种查询和数据处理功能了。

我们完整的字典类型ApiCaller类的代码如下所示。

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

这里面的函数定义才是我们需要根据实际的自定义接口封装的调用类函数代码。

前面我们介绍了,我们把ApiCaller层的项目设计为.net Standard的类库项目,因此可以在.net core或者在.net framework中进行使用,并且也在基于.net core的控制台程序中测试成功了。

image

下面就重点介绍一下,基于.net framework的Winfrom程序中对ABP框架的Web API接口的调用,如果以后Winform支持.net core了(据说9月份出的.net core3就包含了),那么也一样的模式进行调用。

2)Winform对ApiCaller层的调用

我们先来看看字典模块,通过封装对ABP框架的Web API调用后,实际的功能界面效果吧。

先设计一个授权登录的界面获取访问令牌信息。

image

字典管理界面,列出字典类型,并对字典类型下的字典数据进行分页展示,分页展示利用分页控件展示。

image

新增或者编辑窗体界面如下

image

这个界面是来自于我的框架里面的字典模块界面,不过里面对数据的处理代码确实已经更改为适应ABP框架的Web API接口的调用的了(基于ApiCaller 层的调用)。

我们下面来一一进行分析即可。

登陆界面,我们看看主要的逻辑就是调用获取授权令牌的接口,并存储起来供后续界面中的业务类进行调用即可。

image

由于我们自己封装的ApiCaller类,都是基于异步的方式封装的,因此我们可以看到很多地方调用都使用await的关键字,这个是异步调用的关键字,如果方法需要定义为异步,就需要增加async关键字,一般这两个关键字是配套使用的。

如果我们在事件处理代码里面使用了异步,那么事件的函数也需要标记为async,如下是字典管理模块窗体的加载函数,也是用了async声明 和await调用异步方法标记。

        private async void FrmDictionary_Load(object sender, EventArgs e)
        {
            await InitTreeView();

            this.lblDictType.Text = "";
            await BindData();
            
            //分页控件事件处理代码
            this.winGridViewPager1.OnPageChanged += new EventHandler(winGridViewPager1_OnPageChanged);
            this.winGridViewPager1.OnStartExport += new EventHandler(winGridViewPager1_OnStartExport);
            this.winGridViewPager1.OnEditSelected += new EventHandler(winGridViewPager1_OnEditSelected);
            this.winGridViewPager1.OnAddNew += new EventHandler(winGridViewPager1_OnAddNew);
            this.winGridViewPager1.OnDeleteSelected += new EventHandler(winGridViewPager1_OnDeleteSelected);
            this.winGridViewPager1.OnRefresh += new EventHandler(winGridViewPager1_OnRefresh);
            this.winGridViewPager1.AppendedMenu = this.contextMenuStrip2;

            this.winGridViewPager1.BestFitColumnWith = false;
            this.winGridViewPager1.gridView1.DataSourceChanged += new EventHandler(gridView1_DataSourceChanged);
        }

我们的数据,主要是在BindData里面实现,这个函数是我们自己加的,由于使用了异步方法,因此也用async进行声明。

整个对于分页的数据获取和控件的数据绑定过程,代码如下所示。

        /// <summary>
        /// 获取数据
        /// </summary>
        /// <returns></returns>
        private async Task<IPagedResult<DictDataDto>> GetData()
        {
            //构建分页的条件和查询条件
            var pagerDto = new DictDataPagedDto(this.winGridViewPager1.PagerInfo)
            {
                DictType_ID = string.Concat(this.lblDictType.Tag)
            };
            var result = await DictDataApiCaller.Instance.GetAll(pagerDto);
            return result;
        }

        /// <summary>
        /// 绑定数据
        /// </summary>
        private async Task BindData()
        {
            #region 添加别名解析
            this.winGridViewPager1.DisplayColumns = "Name,Value,Seq,Remark,EditTime";
            this.winGridViewPager1.AddColumnAlias(Id_FieldName, "编号");
            this.winGridViewPager1.AddColumnAlias("DictType_ID", "字典大类");
            this.winGridViewPager1.AddColumnAlias("Name", "项目名称");
            this.winGridViewPager1.AddColumnAlias("Value", "项目值");
            this.winGridViewPager1.AddColumnAlias("Seq", "字典排序");
            this.winGridViewPager1.AddColumnAlias("Remark", "备注");
            this.winGridViewPager1.AddColumnAlias("Editor", "修改用户");
            this.winGridViewPager1.AddColumnAlias("EditTime", "更新日期");
            #endregion

            if (this.lblDictType.Tag != null)
            {
                var result = await GetData();

                //设置所有记录数和列表数据源
                this.winGridViewPager1.DataSource = result.Items;
                this.winGridViewPager1.PagerInfo.RecordCount = result.TotalCount;
            }
        }

其中注意的是GetAll方式是传入一个条件查询的对象,这个就是DictDataPagedDto是我们定义的,放入我们DictDataDto里面的常见属性,方便我们根据属性匹配精确或者模糊查询。

    /// <summary>
    /// 用于根据条件查询
    /// </summary>
    public class DictDataPagedDto : PagedResultRequestDto
    {
        /// <summary>
        /// 字典类型ID
        /// </summary>
        public virtual string DictType_ID { get; set; }

        /// <summary>
        /// 类型名称
        /// </summary>
        public virtual string Name { get; set; }

        /// <summary>
        /// 指定值
        /// </summary>
        public virtual string Value { get; set; }

        /// <summary>
        /// 备注
        /// </summary>
        public virtual string Remark { get; set; }
    }

我们在调用的时候,让它限定为一个类型的ID进行精确查询,如下代码

//构建分页的条件和查询条件
var pagerDto = new DictDataPagedDto(this.winGridViewPager1.PagerInfo)
{
    DictType_ID = string.Concat(this.lblDictType.Tag)
};

这个精确或者模糊查询,则是在应用服务层里面定义规则的,这个之前没有详细介绍了,这里稍微补充说明一下。

在应用服务层接口类里面,重写CreateFilteredQuery可以设置GetAll的查询规则,重写ApplySorting则可以指定列表的排序顺序。

image

再次回到Winform界面的调用上来,删除类型下面字典数据的事件的处理函数如下所示。


        private async void menu_ClearData_Click(object sender, EventArgs e)
        {
            TreeNode selectedNode = this.treeView1.SelectedNode;
            if (selectedNode != null && selectedNode.Tag != null)
            {
                string typeId = selectedNode.Tag.ToString();
                var dict = await DictDataApiCaller.Instance.GetDictByTypeID(typeId);
                int count = dict.Count;

                var format = "您确定要删除节点:{0},该节点下面有【{1}】项数据";
                format = JsonLanguage.Default.GetString(format);
                string message = string.Format(format, selectedNode.Text, count);

                if (MessageDxUtil.ShowYesNoAndWarning(message) == DialogResult.Yes)
                {
                    try
                    {
                        await DictDataApiCaller.Instance.DeleteByTypeID(typeId);
                        await InitTreeView();
                        await BindData();
                    }
                    catch (Exception ex)
                    {
                        LogTextHelper.Error(ex);
                        MessageDxUtil.ShowError(ex.Message);
                    }
                }
            }
        }

我们看看编辑窗体界面的后台处理,编辑和更新数据的逻辑代码如下所示。

#region 编辑大类
var info = await DictTypeApiCaller.Instance.Get(new EntityDto<string>(ID));
if (info != null)
{
    SetInfo(info);

    try
    {
        var updatedDto = await DictTypeApiCaller.Instance.Update(info);
        if (updatedDto != null)
        {
            MessageDxUtil.ShowTips("保存成功");
            this.DialogResult = DialogResult.OK;
        }
    }
    catch (Exception ex)
    {
        LogTextHelper.Error(ex);
        MessageDxUtil.ShowError(ex.Message);
    }
} 
#endregion

最后来一段gif动图,展示程序的操作功能吧。

image

好了,这些事件的使用规则一旦确定了,我们好利用代码生成工具对窗体界面的代码进行统一规则的生成,就好像我前面对于我Winform框架和混合框架里面的Winform窗体界面的生成一样,我们只需要稍微修改一下代码生成工具的NVelocity模板,利用上数据库表的元数据就可以快速生成整个框架所需要的代码了。

这样基于整个ABP框架,而快速应用起来的项目,其实开发项目的工作量看起来也不会很多,而且我们可以把字典、权限控制、整体框架等基础设施建设好,就会形成一整套的开发方法和思路了,这样对于我们利用ABP框架来开发业务系统,是不是有事半功倍的感觉。

一旦某个东西你很喜欢,你就会用的越来越好。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,710评论 4 376
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,839评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,295评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,776评论 0 223
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,198评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,074评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,200评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,986评论 0 214
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,733评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,877评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,348评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,675评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,393评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,209评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,212评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,003评论 2 280

推荐阅读更多精彩内容