[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 MethodRoute
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,并指定一个应用服务类对象。就完成了,不需要自己写一大堆的控制器方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/27264.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32单片机OLED智能饮水机童锁自动出水补水加热水位检测

实践制作DIY- GC0159--OLED智能饮水机 基于STM32单片机设计---OLED智能饮水机 二、功能介绍&#xff1a; 电路组成&#xff1a;STM32F103CXT6最小系统 OLED显示器DS18B20检测水温1个继电器模拟加热1个继电器模拟补水1个继电器模拟出水水位传感器超声波测距多个按键&#xff08…

81. 正则表达式

一、概述二、匹配单个字符三、匹配一组字符四、使用元字符五、重复匹配六、位置匹配七、使用子表达式八、回溯引用九、前后查找十、嵌入条件参考资料 一、概述 正则表达式用于文本内容的查找和替换。 正则表达式内置于其它语言或者软件产品中&#xff0c;它本身不是一种语言或…

【docker】部署svn服务器,docker安装部署svn服务器

话不多说直接上步骤&#xff01; 1.下载镜像&#xff0c;创建容器 # 下载镜像 docker pull elleflorio/svn-server # 创建svn仓库目录&#xff0c;进入svn仓库目录 mkdir -p /var/svn # 创建svn服务容器&#xff0c;把容器中的svn仓库映射到本机&#xff0c;并映射3690端口 d…

设计模式-单例模式

面向对象语言讲究的是万物皆对象。通常流程是先定义可实例化类&#xff0c;然后再通过各种不同的方式创建对象&#xff0c;因此类一般可以实例化出多个对象。但是实际项目开发时&#xff0c;我们还是希望保证项目运行时有且仅包含一个实例对象。这个需求场景的出发点包括但不限…

Unity游戏源码分享-射击游戏Low Poly FPS Pack 3.2

Unity游戏源码分享-射击游戏Low Poly FPS Pack 3.2 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88057717

循环退出语句break、continue,有什么区别?

目录 一、break语句二、continue语句三、break、continue语句有什么区别&#xff1f; 一、break语句 在Java中&#xff0c;break语句用于终止当前循环或switch语句的执行&#xff0c;并跳出该结构。当break语句被执行时&#xff0c;程序将会跳出包含该break语句的最内层的循环…

前端学习记录~2023.7.15~CSS杂记 Day7

前言一、介绍 CSS 布局1、正常布局流2、display 属性3、弹性盒子&#xff08;1&#xff09;设置 display&#xff1a;flex&#xff08;2&#xff09;设置 flex 属性 4、Grid 布局&#xff08;1&#xff09;设置 display&#xff1a;grid&#xff08;2&#xff09;在网格内放置元…

杨辉三角 II

给定一个非负索引 rowIndex&#xff0c;返回「杨辉三角」的第 rowIndex 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: rowIndex 3 输出: [1,3,3,1] 示例 2: 输入: rowIndex 0 输出: [1] 示例 3: 输入: rowIndex 1 输出: [1,1]…

C++中的“三重”

博文内容&#xff1a;重载、重定义&#xff08;隐藏&#xff09;&#xff0c;重写&#xff08;覆盖&#xff09; 三重区别及联系 概念联系及区别1、作用域2、函数要求 概念 重载 函数名相同,函数的参数列表不同(包括参数个数和参数类型)&#xff0c;至于返回类型可同可不同。 …

【ABAP】数据类型(八)「表类型」

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读,同时任汉硕云(广东)科技有限公司ABAP开发顾问。在学习工作中,我通常使用偏后端的开发语言ABAP,SQL进行任务的完成,对SAP企业管理系统,SAP ABAP开发和数据库具有较…

h5最新mtgsig1.1成品

h5最新mtgsig1.1成品 千锤百炼&#xff0c;方得始终

印刷企业如何利用MES管理系统实现智能计划排产

在数字化时代&#xff0c;印刷企业面临着日益激烈的市场竞争和不断攀升的成本压力。为了提高生产效率和质量&#xff0c;印刷企业需要采用先进的生产管理系统。其中&#xff0c;MES生产管理系统已成为实现智能计划排产的重要工具。本文将探讨如何利用印刷MES管理系统实现印刷企…