[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序
@
Volo.Abp 配置应用层自动生成Controller,增删查改服务(CrudAppService)将会以RESTful Api的方式生成对应的接口
(官方文档),这与旧版本的Abp区别很大。RESTful固然好,虽然项目里新的Api会逐步使用RESTful Api代替旧的,但在前后端分离的项目中已经定好的接口,往往需要兼容之前的方式。
原理分析
旧版行为
应用层继承于AsyncCrudAppService的类,在Web层调用CreateControllersForAppServices后,Abp框架将以默认的规则实现Controller,具体的规则如下:
- Get: 如果方法名称以GetList,GetAll或Get开头.
- Put: 如果方法名称以Put或Update开头.
- Delete: 如果方法名称以Delete或Remove开头.
- Post: 如果方法名称以Create,Add,Insert或Post开头.
- Patch: 如果方法名称以Patch开头.
- 其他情况, Post 为 默认方式.
- 自动删除'Async'后缀.
例子:

新版行为:
将会以RESTful Api的方式生成对应的接口,具体规则如下
| 服务方法名称 | HTTP Method | Route |
|---|---|---|
| GetAsync(Guid id) | GET | /api/app/book/ |
| GetListAsync() | GET | /api/app/book |
| CreateAsync(CreateBookDto input) | POST | /api/app/book |
| UpdateAsync(Guid id, UpdateBookDto input) | PUT | /api/app/book/ |
| DeleteAsync(Guid id) | DELETE | /api/app/book/ |
| GetEditorsAsync(Guid id) | GET | /api/app/book/{id}/editors |
| CreateEditorAsync(Guid id, BookEditorCreateDto input) | POST | /api/app/book/{id}/editor |
例子

开始改造
更换基类型
为了兼容旧版Abp,先来还原增删查改服务(CrudAppService)的方法签名。
注意到
- Volo.Abp 中 UpdateAsync方法签名已与旧版不同
- 旧版中的GetAllAsync方法,被GetListAsync所取代。
新建一个CrudAppServiceBase类继承 CrudAppService。并重写UpdateAsync和GetListAsync方法。
为了还原旧版的接口,将用private new关键字覆盖掉 UpdateAsync,GetListAsync方法,并重新实现更改和查询列表的功能
public abstract class CrudAppServiceBase<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CrudAppService<TEntity, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected CrudAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
private new Task<TGetOutputDto> UpdateAsync(TKey id, TUpdateInput input)
{
return base.UpdateAsync(id, input);
}
private new Task<PagedResultDto<TGetListOutputDto>> GetListAsync(TGetListInput input)
{
return base.GetListAsync(input);
}
public virtual async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
{
await CheckUpdatePolicyAsync();
var entity = await GetEntityByIdAsync((input as IEntityDto<TKey>).Id);
MapToEntity(input, entity);
await Repository.UpdateAsync(entity, autoSave: true);
return await MapToGetOutputDtoAsync(entity);
}
public virtual Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
{
return this.GetListAsync(input);
}
}
基于扩展性考虑,我们可以像官方实现一样做好类型复用
public abstract class CrudAppServiceBase<TEntity, TEntityDto, TKey>
: CrudAppServiceBase<TEntity, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CrudAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
public abstract class CrudAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput>
: CrudAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TEntityDto>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CrudAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
public abstract class CrudAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput>
: CrudAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CrudAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
}
public abstract class CrudAppServiceBase<TEntity, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: CrudAppServiceBase<TEntity, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntity : class, IEntity<TKey>
where TEntityDto : IEntityDto<TKey>
{
protected CrudAppServiceBase(IRepository<TEntity, TKey> repository)
: base(repository)
{
}
protected override Task<TEntityDto> MapToGetListOutputDtoAsync(TEntity entity)
{
return MapToGetOutputDtoAsync(entity);
}
protected override TEntityDto MapToGetListOutputDto(TEntity entity)
{
return MapToGetOutputDto(entity);
}
}
重写接口
重写增删查改服务接口
public interface IBaseCrudAppService<TGetOutputDto, TGetListOutputDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
{
Task<TGetOutputDto> GetAsync(TKey id);
Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input);
Task<TGetOutputDto> CreateAsync(TCreateInput input);
Task<TGetOutputDto> UpdateAsync(TUpdateInput input);
Task DeleteAsync(TKey id);
}
基于扩展性考虑,我们可以像官方实现一样做好类型复用
public interface IBaseCrudAppService<TEntityDto, in TKey>
: IBaseCrudAppService<TEntityDto, TKey, PagedAndSortedResultRequestDto>
{
}
public interface IBaseCrudAppService<TEntityDto, in TKey, in TGetListInput>
: IBaseCrudAppService<TEntityDto, TKey, TGetListInput, TEntityDto>
{
}
public interface IBaseCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput>
: IBaseCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
{
}
public interface IBaseCrudAppService<TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput>
: IBaseCrudAppService<TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
{
}
将应用服务接口IReservationAppService继承于IBaseCrudAppService和IApplicationService
public interface IReservationAppService: IBaseCrudAppService<ReservationDto, long>, IApplicationService
{
//除增删查改业务的其他业务
}
创建应用服务类ReservationAppService,此时应用服务派生自CrudAppServiceBase,应用服务应该会完全实现接口
public class ReservationAppService : CrudAppServiceBase<Workflow.Reservation.Reservation, ReservationDto, long>, IReservationAppService
{
...
}
替换默认规则
Abp封装了Controller自动生成规则,利用了Asp.Net MVC的约定接口IApplicationModelConvention,这一特性,所谓规则即Convention,AbpServiceConvention是此接口的实现类,在此类中约定了如何将应用层程序集增删查改服务(CrudAppService)中的成员方法,按上述规则生成Controller。
规则的具体代码封装在ConventionalRouteBuilder里
既然是默认规则方式,我们就重写一个自定义的Convention来代替它默认的那个。
假设有领域Workflow,在Web层中新建WorkflowServiceConvention,把原AbpServiceConvention类中的所有内容复制到这个类中
public class WorkflowServiceConvention : IAbpServiceConvention, ITransientDependency
{
}
将不需要用到的对象删掉
// 删除 protected IConventionalRouteBuilder ConventionalRouteBuilder { get; }
重写CreateAbpServiceAttributeRouteModel
protected virtual AttributeRouteModel CreateAbpServiceAttributeRouteModel(string rootPath, string controllerName, ActionModel action, string httpMethod, [CanBeNull] ConventionalControllerSetting configuration)
{
return new AttributeRouteModel(
new RouteAttribute(
$"api/services/{rootPath}/{controllerName}/{action.ActionName}"
)
);
}
在Web层的Module文件WorkflowHostModule中,添加WorkflowApplicationModule
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options
.ConventionalControllers
.Create(typeof(WorkflowApplicationModule).Assembly);
});
用WorkflowServiceConvention替换原始的AbpServiceConvention实现。
Configure<MvcOptions>(options =>
{
options.Conventions.RemoveAt(0);
options.Conventions.Add(convention.Value);
});
在微服务架构中的问题
Asp.Net MVC在微服务的网关层中无法通过仅引用应用层方法的接口,生成Controller,即便改写 ControllerFeatureProvider, 还是需要引用实现类,这些实现类在应用层中。
但网关仅仅依赖定义层,若要拿到实现类,将改变微服务架构的依赖关系。
在官方的微服务实例中,也没有用Controller的自动生成,在这个issue中作者也给出了解答
https://github.com/abpframework/abp/issues/1731

因此如果想达到目的,只能用重写controller基类的方式了,这个方式好处在于简单好用,可读性和可维护性高,缺陷就是每写一个应用层类,需要写一个对应的Controller类,但在项目不多用CV大法还是可以接受的。
新建WorkflowController并继承于AbpControllerBase,并创建增删查改(Curd)的终结点路由,通过调用ITAppService的方法,实现各业务功能
public abstract class WorkflowController<ITAppService, TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: AbpControllerBase
where ITAppService : IBaseCrudAppService<TGetOutputDto, TGetListOutputDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TGetOutputDto : IEntityDto<TKey>
where TGetListOutputDto : IEntityDto<TKey>
{
protected WorkflowController()
{
LocalizationResource = typeof(WorkflowResource);
}
private readonly ITAppService _recipeAppService;
public WorkflowController(ITAppService recipeAppService)
{
_recipeAppService = recipeAppService;
}
[HttpPost]
[Route("Create")]
public async Task<TGetOutputDto> CreateAsync(TCreateInput input)
{
return await _recipeAppService.CreateAsync(input);
}
[HttpDelete]
[Route("Delete")]
public async Task DeleteAsync(TKey id)
{
await _recipeAppService.DeleteAsync(id);
}
[HttpGet]
[Route("GetAll")]
public async Task<PagedResultDto<TGetListOutputDto>> GetAllAsync(TGetListInput input)
{
return await _recipeAppService.GetAllAsync(input);
}
[HttpGet]
[Route("Get")]
public async Task<TGetOutputDto> GetAsync(TKey id)
{
return await _recipeAppService.GetAsync(id);
}
[HttpPut]
[Route("Update")]
public async Task<TGetOutputDto> UpdateAsync(TUpdateInput input)
{
return await _recipeAppService.UpdateAsync(input);
}
}
基于扩展性考虑,我们可以做好类型复用
public abstract class WorkflowController<ITAppService, TEntityDto, TKey>
: WorkflowController<ITAppService, TEntityDto, TKey, PagedAndSortedResultRequestDto>
where ITAppService : IBaseCrudAppService<TEntityDto, TKey>
where TEntityDto : IEntityDto<TKey>
{
protected WorkflowController(ITAppService appService)
: base(appService)
{
}
}
public abstract class WorkflowController<ITAppService, TEntityDto, TKey, TGetListInput>
: WorkflowController<ITAppService, TEntityDto, TKey, TGetListInput, TEntityDto>
where ITAppService : IBaseCrudAppService<TEntityDto, TKey, TGetListInput>
where TEntityDto : IEntityDto<TKey>
{
protected WorkflowController(ITAppService appService)
: base(appService)
{
}
}
public abstract class WorkflowController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput>
: WorkflowController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TCreateInput>
where ITAppService : IBaseCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput>
where TEntityDto : IEntityDto<TKey>
{
protected WorkflowController(ITAppService appService)
: base(appService)
{
}
}
public abstract class WorkflowController<ITAppService, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
: WorkflowController<ITAppService, TEntityDto, TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where ITAppService : IBaseCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
where TEntityDto : IEntityDto<TKey>
{
protected WorkflowController(ITAppService appService)
: base(appService)
{
}
}
创建实际的Controller,定义Area名称和Controller路由“api/Workflow/reservation”
此时Controller派生自WorkflowController,应用服务应该会完全实现接口
[Area(WorkflowRemoteServiceConsts.ModuleName)]
[RemoteService(Name = WorkflowRemoteServiceConsts.RemoteServiceName)]
[Route("api/Workflow/reservation")]
public class ReservationController : WorkflowController<IReservationAppService, ReservationDto,long>, IReservationAppService
{
private readonly IReservationAppService _reservationAppService;
public ReservationController(IReservationAppService reservationAppService):base(reservationAppService)
{
_reservationAppService = reservationAppService;
}
}
运行程序,我们将得到一个旧版的接口

每次为新的应用服务类创建Controller,只需要新建一个派生自WorkflowController类的Controller,并指定一个应用服务类对象。就完成了,不需要自己写一大堆的控制器方法。
[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序的更多相关文章
- 【API设计】RESTful API 设计指南
RESTful API URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作. 例如 . REST描述的是在网络中client和server的一种交互形式:REST本身不 ...
- 我是如何根据豆瓣api来理解Restful API设计的
1.什么是REST REST全称是Representational State Transfer,表述状态转移的意思.它是在Roy Fielding博士论文首次提出.REST本身没有创造新的技术.组件 ...
- Web API 入门系列 - RESTful API 设计指南
参考:https://developer.github.com/v3/ https://github.com/bolasblack/http-api-guide HTTP 协议 目前使用HTTP1. ...
- Volo.Abp.EntityFrameworkCore.MySQL 使用
创建新项目 打开 https://cn.abp.io/Templates ,任意选择一个项目类型,然后创建项目,我这里创建了一个Web Api 解压项目,还原Nuget,项目目录如下: 首先我们来查看 ...
- 好RESTful API的设计原则
说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间,如有人愿意转载请注明出处,谢谢^_^ P ...
- RESTful API的设计原则
好RESTful API的设计原则 说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间, ...
- 你真的了解restful api吗?
前言 在以前,一个网站的完成总是“all in one”,页面,数据,渲染全部在服务端完成,这样做的最大的弊端是后期维护,扩展极其痛苦,开发人员必须同时具备前后端知识.于是慢慢的后来兴起了前后端分离的 ...
- 利用koa打造restful API
概述 最近学习利用koa搭建API接口,小有所得,现在记录下来,供以后开发时参考,相信对其他人也有用. 就目前我所知道的而言,API有2种,一种是jsonp这种API,前端通过ajax来进行跨域请求获 ...
- 避免自己写的 url 被diss!建议看看这篇RestFul API简明教程!
大家好我是 Guide 哥!这是我的第 210 篇优质原创!这篇文章主要分享了后端程序员必备的 RestFul API 相关的知识. RestFul API 是每个程序员都应该了解并掌握的基本知识,我 ...
- 使用 .NET Core 3.x 构建 RESTFUL Api
准备工作:在此之前你需要了解关于.NET .Core的基础,前面几篇文章已经介绍:https://www.cnblogs.com/hcyesdo/p/12834345.html 首先需要明确一点的就是 ...
随机推荐
- 创建Django项目的两种方式
有两种方式可创建django项目: 方式一:命令行 1. cmd 命令行,进入到指定的目录,执行:django-admin startproject mydiary [mydiary 为项目名],创建 ...
- Mybatis框架中 collection 标签 和 association标签中关于 columnPrefix 属性的底层逻辑
columnPrefix的作用是给column自动拼接上前缀, 已知多重嵌套的collection 和 association的columnPrefix属性的值是会叠加的 <associatio ...
- pycharm的安装与使用
官网下载最新版本,然后用激活码,激活,注意是专业版. 方法找到后更新在这里. 进入软件之后创建新的文件夹,可以自定义,建议自定义在系统盘以外, 1.新建文件略过 2.ctrl + 鼠标中键, 调节字体 ...
- 内部类(Java)
内部类 基本介绍 概念:在一个类的内部再定义一个完整类 特点:编译之后可生成独立的字节码文件:内部类可以直接访问外部类的私有属性,不破坏封装性 分类:成员内部类:静态内部类:局部内部类:匿名内部类 p ...
- 循环结构(Java)
基本介绍 while循环语法 while(布尔表达式){循环内容} 只要布尔表达式为true,循环则会一直循环下去 我们大多数会让循环停止下来,我们需要一个让表达式失效的方式来结束循环 少部分需要循环 ...
- ant design upload组件的beforeUpload阻止默认上传行为
const onImportExcel = (file) => { return new Promise(async (resolve, reject) => { ... //要执行的语句 ...
- SQL五十题记录 1-2
前言: 创建以下四张表:①:课程表 ②:成绩表 ③:学生表 ④:教师表 1.查询课程编号为""01""的课程比"02"的课程成绩高的所有学生 ...
- doy 19 进程管理
1.进程管理 1.什么是进程,什么是线程 1.什么是程序 一般情况下,代码,安装包等全部都是应用程序. 2.什么是进程 应用程序运行起来的能够提供某种服务的实例. 3.什么是线程 进程中处理具体事务 ...
- 6.Vue路由
一.路由的基本概念与原理 路由是一个广义与抽象的概念,路由的本质就是对应关系 在开发中,路由分为: (1) 后端路由 (2) 前端路由 1.1 路由 1. 后端路由(根据不同的URL地址分发不同的资源 ...
- PHP判断0和空的方法
可以兼容,传参数,或者不参数与0的判断 if ( isset($data['other_id']) && (!empty($data['other_id']) || is_nume ...