@

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)的方法签名。

注意到

  1. Volo.Abp 中 UpdateAsync方法签名已与旧版不同
  2. 旧版中的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以兼容老程序的更多相关文章

  1. 【API设计】RESTful API 设计指南

    RESTful API URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作. 例如 . REST描述的是在网络中client和server的一种交互形式:REST本身不 ...

  2. 我是如何根据豆瓣api来理解Restful API设计的

    1.什么是REST REST全称是Representational State Transfer,表述状态转移的意思.它是在Roy Fielding博士论文首次提出.REST本身没有创造新的技术.组件 ...

  3. Web API 入门系列 - RESTful API 设计指南

    参考:https://developer.github.com/v3/  https://github.com/bolasblack/http-api-guide HTTP 协议 目前使用HTTP1. ...

  4. Volo.Abp.EntityFrameworkCore.MySQL 使用

    创建新项目 打开 https://cn.abp.io/Templates ,任意选择一个项目类型,然后创建项目,我这里创建了一个Web Api 解压项目,还原Nuget,项目目录如下: 首先我们来查看 ...

  5. 好RESTful API的设计原则

    说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间,如有人愿意转载请注明出处,谢谢^_^ P ...

  6. RESTful API的设计原则

    好RESTful API的设计原则   说在前面,这篇文章是无意中发现的,因为感觉写的很好,所以翻译了一下.由于英文水平有限,难免有出错的地方,请看官理解一下.翻译和校正文章花了我大约2周的业余时间, ...

  7. 你真的了解restful api吗?

    前言 在以前,一个网站的完成总是“all in one”,页面,数据,渲染全部在服务端完成,这样做的最大的弊端是后期维护,扩展极其痛苦,开发人员必须同时具备前后端知识.于是慢慢的后来兴起了前后端分离的 ...

  8. 利用koa打造restful API

    概述 最近学习利用koa搭建API接口,小有所得,现在记录下来,供以后开发时参考,相信对其他人也有用. 就目前我所知道的而言,API有2种,一种是jsonp这种API,前端通过ajax来进行跨域请求获 ...

  9. 避免自己写的 url 被diss!建议看看这篇RestFul API简明教程!

    大家好我是 Guide 哥!这是我的第 210 篇优质原创!这篇文章主要分享了后端程序员必备的 RestFul API 相关的知识. RestFul API 是每个程序员都应该了解并掌握的基本知识,我 ...

  10. 使用 .NET Core 3.x 构建 RESTFUL Api

    准备工作:在此之前你需要了解关于.NET .Core的基础,前面几篇文章已经介绍:https://www.cnblogs.com/hcyesdo/p/12834345.html 首先需要明确一点的就是 ...

随机推荐

  1. 创建Django项目的两种方式

    有两种方式可创建django项目: 方式一:命令行 1. cmd 命令行,进入到指定的目录,执行:django-admin startproject mydiary [mydiary 为项目名],创建 ...

  2. Mybatis框架中 collection 标签 和 association标签中关于 columnPrefix 属性的底层逻辑

    columnPrefix的作用是给column自动拼接上前缀, 已知多重嵌套的collection 和 association的columnPrefix属性的值是会叠加的 <associatio ...

  3. pycharm的安装与使用

    官网下载最新版本,然后用激活码,激活,注意是专业版. 方法找到后更新在这里. 进入软件之后创建新的文件夹,可以自定义,建议自定义在系统盘以外, 1.新建文件略过 2.ctrl + 鼠标中键, 调节字体 ...

  4. 内部类(Java)

    内部类 基本介绍 概念:在一个类的内部再定义一个完整类 特点:编译之后可生成独立的字节码文件:内部类可以直接访问外部类的私有属性,不破坏封装性 分类:成员内部类:静态内部类:局部内部类:匿名内部类 p ...

  5. 循环结构(Java)

    基本介绍 while循环语法 while(布尔表达式){循环内容} 只要布尔表达式为true,循环则会一直循环下去 我们大多数会让循环停止下来,我们需要一个让表达式失效的方式来结束循环 少部分需要循环 ...

  6. ant design upload组件的beforeUpload阻止默认上传行为

    const onImportExcel = (file) => { return new Promise(async (resolve, reject) => { ... //要执行的语句 ...

  7. SQL五十题记录 1-2

    前言: 创建以下四张表:①:课程表 ②:成绩表 ③:学生表 ④:教师表 1.查询课程编号为""01""的课程比"02"的课程成绩高的所有学生 ...

  8. doy 19 进程管理

    1.进程管理 1.什么是进程,什么是线程​ 1.什么是程序 一般情况下,代码,安装包等全部都是应用程序. 2.什么是进程 应用程序运行起来的能够提供某种服务的实例. 3.什么是线程 进程中处理具体事务 ...

  9. 6.Vue路由

    一.路由的基本概念与原理 路由是一个广义与抽象的概念,路由的本质就是对应关系 在开发中,路由分为: (1) 后端路由 (2) 前端路由 1.1 路由 1. 后端路由(根据不同的URL地址分发不同的资源 ...

  10. PHP判断0和空的方法

    可以兼容,传参数,或者不参数与0的判断   if ( isset($data['other_id']) && (!empty($data['other_id']) || is_nume ...