在前面几篇随笔介绍了我对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 = , 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

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

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

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

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

我们完整的字典类型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的控制台程序中测试成功了。

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

2)Winform对ApiCaller层的调用

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

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

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

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

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

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

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

由于我们自己封装的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则可以指定列表的排序顺序。

再次回到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动图,展示程序的操作功能吧。

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

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

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

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

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

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

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

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

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

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

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

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

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

    在前面随笔介绍ABP应用框架的项目组织情况,以及项目中领域层各个类代码组织,以及简化了ABP框架的各个层的内容,使得我们项目结构更加清晰.上篇随笔已经介绍了字典模块中应用服务层接口的实现情况,并且通过 ...

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

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

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

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

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

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

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

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

随机推荐

  1. Unable to find a single main class from the following candidates

    关于start-class,spring boot官方手册是这么说明的: The plugin rewrites your manifest, and in particular it manages ...

  2. ADO.net之2-成功连接到数据库---ShinePans

    配置数据库: 连接字符串: server=潘尚\\SQLEXPRESS;database=db_test;Trusted_Connection=true 连接代码: using System; usi ...

  3. 探索jquery方法中empty,remove与detach的区别

    最近一直疑惑此三种方法的具体区别在于何处,随即想弄明白其具体的区别,看了一些说明,也依照官方文档,终于把这三个方法弄明白了,果然功夫不负有心人,继续努力. 上正文,先简单介绍下这三种方法 .empty ...

  4. Template简介

    分类   ControlTemplate ItemsPanelTemplate DataTemplate 样式Style和模板Template对比 Style:样式,风格Template:模版,某种控 ...

  5. asp .net core 读取读取Views文件夹下的js和css

    //读取Views文件夹下的js和css app.UseStaticFiles(new StaticFileOptions() { FileProvider = new PhysicalFilePro ...

  6. 在vs2015上使用asp.net core+ef core

    官方的文档https://docs.asp.net/en/latest/tutorials/first-mvc-app/start-mvc.html 先来看一下实现的效果

  7. 【WPF】右下角弹出自定义通知样式(Notification)——简单教程

    原文:[WPF]右下角弹出自定义通知样式(Notification)--简单教程 1.先看效果 2.实现 1.主界面是MainWindow 上面就只摆放一个Button即可.在Button的点击事件中 ...

  8. PHP 实现自动加载器(Autoloader)

    我们知道PHP可以实现自动加载,避免了繁重的体力活,代码更规范,整洁.那如果我们把这个自动加载再升华一下,变成自动加载类,每次只需要引入这个类,那么其他类就自动加载了,已经开源,仓库地址在这里.同时如 ...

  9. Qt SizePolicy 属性(每个控件都有一个合理的缺省sizePolicy。QWidget.size()默认返回值是(640, 480),QWidget.sizeHint()默认返回值是(-1, -1))

    控件的sizePolicy说明控件在布局管理中的缩放方式.Qt提供的控件都有一个合理的缺省sizePolicy,但是这个缺省值有时不能适合 所有的布局,开发人员经常需要改变窗体上的某些控件的sizeP ...

  10. AFN小结(简单的封装)

    AFN小结 1,AFN概念.原理 2,AFN的封装使用 3,AFN与其它框架对比 ————————————————————————————————— 1 , AFN的概念原理: AFN的基础是NSUR ...