[2018-12-18]ABP中的AsyncCrudAppService介绍
前言
自从写完上次略长的《用ABP入门DDD》后,针对ABP框架的项目模板初始化,我写了个命令行工具Abp-CLI,其中子命令abplus init
可以从github拉取项目模板以初始化项目。自然而然的,又去处理了aspnetboilerplate/module-zero-core-template这个项目模板库当中的vue项目模板,解决以前发现的,又貌似一直没人修复的几个问题PR362,PR366,PR367。
在更新vue项目模板的示例代码时,感觉有必要讲解下ABP中的AsyncCrudAppService<>
怎么用。
跟我做
先自卖自夸一下,只要你本地有装dotnet环境,就可以跟着我一步一步来做。
安装dotnet core全局工具:AbpTools
可以在任意目录下,打开powershell命令窗口(按住Shift键的同时鼠标右键点击目录空白处,可以看到右键菜单在此处打开Powershell窗口
),执行以下命令安装AbpTools:
dotnet tool install -g AbpTools
如果安装成功,会提示你已经可以使用abplus命令了。
可以通过以下命令查看已安装了哪些dotnet core全局工具:
PS$>dotnet tool list -g
包 ID 版本 命令
-------------------------------
abptools 1.0.4 abplus
目前是1.0.4版。
初始化项目Personball.CrudDemo
在powershell命令窗口中选择一个放代码的目录(用cd命令),执行以下命令初始化项目:
abplus init Personball.CrudDemo -T personball/module-zero-core-template@v4.2.2
由于撰写本文时默认的项目模板库aspnetboilerplate/module-zero-core-template虽然合并了上述的PR362、PR366、PR367,但还没Release下一版本,所以暂时通过-T
指定使用我自己修复过的vue项目模板。
运行起来看看
先运行 Api Host:
- 用VS2017打开
Personball.CrudDemo/aspnet-core
目录下的Personball.CrudDemo.sln
- 右键点击
Personball.CrudDemo.Web.Mvc
,移除 - 右键点击
Personball.CrudDemo.Web.Host
,设为启动项 - 在VS2017的视图菜单中选择SQL Server 对象资源管理器,展开
SQL Server>(localdb)\...(localdb特定版本的实例名)
,右键数据库,选择添加新数据库,数据库名称输入CrudDemoDb
,确认。 - 右键
CrudDemoDb
数据,点击属性,找到连接字符串,复制下来,粘贴替换Personball.CrudDemo.Web.Host
项目的appsettings.json
配置文件的ConnectionStrings
配置下的Default
值。 - 打开程序包管理器控制台,默认项目选
Personball.CrudDemo.EntityFrameworkCore
,输入Update-Database
- 按F5运行
继续运行前端vue项目(需要nodejs和npm):
- 用VSCode打开
Personball.CrudDemo/vue
目录 - 在VsCode的终端窗口中运行
yarn install
- Install完成后,运行
yarn serve
先进后台,体验下功能
- 打开浏览器,输入刚才
yarn serve
提示的访问地址,默认是http://localhost:8080 - 输入默认账号admin,密码123qwe,租户空着不选
- 登陆成功后,展开左侧菜单,选择用户
这样我们就到了后台的用户列表,可以先试试输入查询条件,试一下列表查询功能。
加断点,再试一下
切换到VS2017,我们加个断点
- 展开Personball.CrudDemo.Application项目,展开Users目录,找到
UserAppService
- 找到
CreateFilteredQuery
方法,在return语句的地方加个断点,再到后台里试一下用户列表查询。
接下来看代码
以后端代码UserAppService
和前端vue模板中的src/views/setting/user/user.vue
为例:
后台接收列表查询参数使用的是PagedUserResultRequestDto
,继承自PagedResultRequestDto
,加上了UI界面所需的一些自定义查询条件属性:
public class PagedUserResultRequestDto : PagedResultRequestDto
{
public string UserName { get; set; }
public string Name { get; set; }
public bool? IsActive { get; set; }
public DateTimeOffset? From { get; set; }//javascript date within timezone
public DateTimeOffset? To { get; set; }//javascript date within timezone
}
而前端在user.vue
文件中,使用PageUserRequest
和后端的DTO对应:
class PageUserRequest extends PageRequest{
userName:string;
name:string;
isActive:boolean=null;//nullable
from:Date;
to:Date;
}
这里可以直接用PageUserRequest
类型的前端变量做UI控件绑定:
pagerequest:PageUserRequest=new PageUserRequest();
creationTime:Date[]=[];//时间范围控件的值绑定另外处理
<FormItem :label="L('UserName')+':'" style="width:100%">
<Input v-model="pagerequest.userName"></Input>
</FormItem>
对于复杂的,比如时间范围控件(上面的creationTime),再另外处理:
async getpage(){
//set page parameters
this.pagerequest.maxResultCount=this.pageSize;
this.pagerequest.skipCount=(this.currentPage-1)*this.pageSize;
//filters
if (this.creationTime.length>0) {
this.pagerequest.from=this.creationTime[0];
}
if (this.creationTime.length>1) {
this.pagerequest.to=this.creationTime[1];
}
await this.$store.dispatch({
type:'user/getAll',
data:this.pagerequest
})
}
前端集成了typescript的vue代码的用法基本介绍到这,主要是前一版的vue项目模板中出现了在前端代码里组装where条件的情况。所以说明下,以免后端真的去处理where字符串可能引起SQL注入问题。
我们继续回到后端代码。
AsyncCrudAppService说明
ABP作为开发框架,非常优秀的一个地方,就是作者对DRY的追求。
对于CRUD这种通用功能,必须要有一个解决方案,这就有了泛型版的应用服务基类CrudAppService<>
。
我们先看下这个基类上有哪些成员:
namespace Abp.Application.Services
{
public abstract class CrudAppServiceBase<TEntity, TEntityDto, TPrimaryKey, TGetAllInput, TCreateInput, TUpdateInput> :
ApplicationService
where TEntity : class, IEntity<TPrimaryKey>
where TEntityDto : IEntityDto<TPrimaryKey>
where TUpdateInput : IEntityDto<TPrimaryKey>
{
protected readonly IRepository<TEntity, TPrimaryKey> Repository;
protected CrudAppServiceBase(IRepository<TEntity, TPrimaryKey> repository);
protected virtual string CreatePermissionName { get; set; }
protected virtual string GetAllPermissionName { get; set; }
protected virtual string GetPermissionName { get; set; }
protected virtual string UpdatePermissionName { get; set; }
protected virtual string DeletePermissionName { get; set; }
protected virtual IQueryable<TEntity> ApplyPaging(IQueryable<TEntity> query, TGetAllInput input);
protected virtual IQueryable<TEntity> ApplySorting(IQueryable<TEntity> query, TGetAllInput input);
protected virtual void CheckCreatePermission();
protected virtual void CheckDeletePermission();
protected virtual void CheckGetAllPermission();
protected virtual void CheckGetPermission();
protected virtual void CheckPermission(string permissionName);
protected virtual void CheckUpdatePermission();
protected virtual IQueryable<TEntity> CreateFilteredQuery(TGetAllInput input);
protected virtual void MapToEntity(TUpdateInput updateInput, TEntity entity);
protected virtual TEntity MapToEntity(TCreateInput createInput);
protected virtual TEntityDto MapToEntityDto(TEntity entity);
}
}
其中的泛型参数,依次说明如下:
- TEntity:CRUD操作对应的实体类
- TEntityDto:GetAll方法返回的实体DTO
- TPrimaryKey:实体的主键
- TGetAllInput:GetAll方法接收的输入参数
- TCreateInput:Create方法接收的输入参数
- TUpdateInput:Update方法接收的输入参数
从上面我们还可以看到有关于权限(xxxPermissionName
属性和CheckxxxPermission
方法),关于分页(ApplyPaging
),关于排序(ApplySorting
),关于查询条件(CreateFilteredQuery
),关于对象映射(MapToxxx
),所有CRUD涉及的环节都提供了扩展点(方法是virtual,可以override)。
所以对于单页后台来说,基于CrudAppServiceBase实现CRUD功能非常简便,而且很容易扩展定制。
以前面说的UserAppService
为例,它继承AsyncCrudAppService<>
(AsyncCrudAppService继承了上面的CrudAppServiceBase,提供了异步版本的CRUD接口实现)。除了IUserAppService
中额外定义的两个方法:
Task<ListResultDto<RoleDto>> GetRoles();
Task ChangeLanguage(ChangeUserLanguageDto input);
其他方法都是基于AsyncCrudAppService<>
的可扩展点进行自定义以满足需求。
如果只是一个非常简单的纯数据实体(User还是有不少逻辑的),这个AppService还可以更简单:
[AbpAuthorize]
public class ArticleAppService : AsyncCrudAppService<Article, ArticleDto, int, PagedArticleResultRequestDto, CreateArticleDto, ArticleDto>, IArticleAppService
{
public ArticleAppService(IRepository<Article, int> repository) : base(repository)
{
LocalizationSourceName = JsxConsts.LocalizationSourceName;
}
protected override IQueryable<Article> CreateFilteredQuery(PagedArticleResultRequestDto input)
{
return Repository.GetAll()
.WhereIf(input.Category.HasValue, a => a.Category == input.Category)
.WhereIf(!input.Keyword.IsNullOrWhiteSpace(), a => a.Title.Contains(input.Keyword) || a.Content.Contains(input.Keyword))
.WhereIf(input.From.HasValue, b => b.CreationTime >= input.From.Value.LocalDateTime)
.WhereIf(input.To.HasValue, b => b.CreationTime <= input.To.Value.LocalDateTime);
}
}
类似这个ArticleAppService
,只要定制下CreateFilteredQuery
中的查询过滤条件,其他功能代码都免了,而CRUD的接口都是完整可以用的。
不说代码生成器,只要自定义一个代码片段来快速产出这个ArticleAppService,就可以节省很多的敲键盘时间,效率是非常高的,关键是省事——DRY,Don't Repeat Yourself。
再回到UserAppService
中的PagedUserResultRequestDto
。
这个DTO就是泛型参数中的TGetAllInput
。通过继承PagedResultRequestDto
,在AsyncCrudAppService基类中的各个涉及方法的签名里以OOP多态方式传递该参数完全没有问题。
而TEntityDto
和TUpdateInput
有时候可以共用一个DTO,只要定制好映射关系,问题一般不大。例如Users/Dto/UserMapProfile
中:
CreateMap<UserDto, User>()//use UserDto as TUpdateInput
.ForMember(x => x.Roles, opt => opt.Ignore())
.ForMember(x => x.CreationTime, opt => opt.Ignore())
.ForMember(x => x.LastLoginTime, opt => opt.Ignore());
最后,关于vue项目模板,提两个注意点
1.时区问题
不管什么前端框架,可能都会遇到前端提交的JavaScript中的Date类型对象是带时区的,或者默认是UTC时间。
这个问题,建议在接口接收参数的时候用DateTimeOffset
类型接收,再通过其属性LocalDateTime
转为服务器本地时间使用,当然如果你数据库直接存了带时区的时间,那连转换都免了。
2.iview框架版本问题
原先打算在PR366中降iview的主版本号到^2.13.1
来修复yarn serve
编译错误的问题,后来发现改成~3.0.0
也行得通。
但是本地demo跑起来时发现页签的选中样式还是有点问题,懒得改css的话,可以直接降到^2.13.1
。
[2018-12-18]ABP中的AsyncCrudAppService介绍的更多相关文章
- 2018.12.25 Spring中JDBCTemplate模版API学习
1 Spring整合JDBC模版 1.1 spring中土拱了一个可以操作数据库的对象.对象封装了jdbc技术 JDBCTemplateJDBC模板对象 1.2 与DBUtils中的QueryRunn ...
- 2018.12.24 Spring中的aop演示(也就是运用aop技术实现代理模式)
Aop的最大意义是:在不改变原来代码的前提下,也不对源代码做任何协议接口要求.而实现了类似插件的方式,来修改源代码,给源代码插入新的执行代码. 1.spring中的aop演示 aop:面向方面编程.不 ...
- 2018.12.18 bzoj5296: [Cqoi2018]破解D-H协议(bsgs)
传送门 bsgsbsgsbsgs基础题. 考虑到给的是原根,因此没无解的情况. 于是只需要每次把a,ba,ba,b解出来. 然后可以通过预处理节省一部分时间. 代码: #include<bits ...
- 2018.12.18 bzoj2242: [SDOI2011]计算器(数论)
传送门 数论基础题. 对于第一种情况用快速幂,第二种用exgcdexgcdexgcd,第三种用bsgsbsgsbsgs 于是自己瞎yyyyyy了一个bsgsbsgsbsgs的板子(不知道是不是数据水了 ...
- flask 小知识总结 2018.12.18
Visual Studio Code 1.自定义python组建,方便某功能和代码的便捷操作 文件--首选项--用户代码片段--python.json--自定义需要的代码和功能 2.自定义快捷键方法 ...
- AutoMapper之ABP项目中的使用介绍
最近在研究ABP项目,昨天写了Castle Windsor常用介绍以及其在ABP项目的应用介绍 欢迎各位拍砖,有关ABP的介绍请看阳光铭睿 博客 AutoMapper只要用来数据转换,在园里已经有很多 ...
- Sprint3(12.18)总结
Sprint3第三阶段 1.类名:软件工程-第三阶段 2.时间:至12.18 3.选题内容:web版-餐厅到店点餐系统 4.团队博客地址: http://www.cnblogs.com/queenju ...
- Linux课程---13、linux中任务计划介绍(任务计划分类)
Linux课程---13.linux中任务计划介绍(任务计划分类) 一.总结 一句话总结: 1.一次性任务计划:at 2.周期性任务计划:crontab 1.linux中如何添加一次性任务计划? at ...
- abp中多种登陆用户的设计
项目地址:https://gitee.com/bxjg1987/abp 场景 在<学校管理系统>中,学生.家长.教师.教务都可能登陆,做一些属于他们自己的操作.这些用户需要的属性各不相同, ...
随机推荐
- Android属性动画:动画流控制
今天的文章里,我将会和大家讨论对动画流的控制.我们可以通过Animator系列的API来控制动画的开始.停止和取消.在 KitKat也就是API level 19中,我们还可以控制动画的暂停和恢复.在 ...
- wxWidgets 安装方法(Windows 8.1 + Visual Studio 2013)
在windows 8.1上面,搭建基于visual studio 2013的wxWidgets的开发环境,方法如下: 下载 目前最新版本为3.0.0,下载地址: http://sourceforge ...
- Qt编程简介与基本知识
1. 什么是Qt? Qt是一个基于C++的跨平台应用程序和UI开发框架.它包含一个类库,和用于跨平台开发及国际化的工具. 由挪威Trolltech公司开发,后被Nokia收购,目前被Digia公司收购 ...
- SVN merge 三种方式
1.Merge a range of revisions 2.Reintegrate a branch 3.Merge two different trees ———————————————————— ...
- java中的占位符\t\n\r\f
\t 相当于tab,缩进\n NewLine 换行 System.out.println("aaa\tbbb"); //aaa bbbSystem.out.println(&quo ...
- script脚本中写不写$(document).ready(function() {});的差别
$(document).ready() 里的代码是在页面内容都载入完才运行的,假设把代码直接写到script标签里.当页面载入完这个script标签就会运行里边的代码了,此时假设你标签里运行的代码调用 ...
- BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第9章节--client对象模型和REST APIs概览 client对象模型(CSOM)基础
BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第9章节--client对象模型和REST APIs概览 client对象模型(CSOM)基础 在SP2 ...
- (三)初识jQuery
进入jQuery官网:http://jquery.com/ 点击Download jQuery v3.1.1--->下载最新版本的jQuery版本--->放到你需要引入jQuery的文件中 ...
- C# MVC VS WebAPI
获取路径: MVC:Server.MapPath("/Templates/vshop/default.json") WebAPI:System.Web.Hosting.Hostin ...
- 利用JS最真实的模拟鼠标点击
为了破解永乐票务登录验证码问题 http://www.228.com.cn/auth/login?logout 当然,打码的过程自然依赖第三方平台,但问题是,哪怕平台给了你需要点击的(相对)坐标.你又 ...