@

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. Python基础数据类型-list(列表)

    a = [1, 2, 3] a.append([1, 4]) a.append(8) a.append([8, 9, '10']) # 添加的是整体,即:将输入对象添加到列表末尾 print(a) # ...

  2. Mac截网页长屏的方法-谷歌浏览器

    打开chrome,左上角帮助,输入[开发者工具],回车,右边显示出一些html代码, 然后shift+command+p: 输入full,选择第一个capture full size screensh ...

  3. 新手IC617安装NCSU pdk

    新手IC617安装NCSU cdk 以CMOSedu网站为参考:https://cmosedu.com/videos/cadence/tutorial1/cadence_tutorial_1.htm ...

  4. 3月2号Android开发学习

    (2)视图基础 1.设置视图的高度 视图宽度通过属性Android:layout_width表达,视图高度通过属性android:layout_heigth表达,宽高的取值主要有以下三种 1.matc ...

  5. Git本地仓库的文件夹不显示红色感叹号、绿色对号等图标

    参考 https://blog.csdn.net/Elon15/article/details/125898375 主要是  在文件名前加8个空格(最少8个)!!!!

  6. mysql 循环 例子

    mysql 循环生成数据demo: DROP PROCEDURE IF EXISTS test_insert; DELIMITER;; CREATE PROCEDURE test_insert() B ...

  7. 【转载】python:获取当前目录、上层目录路径

    import os print("===获取当前文件目录===")# 当前脚本工作的目录路径print(os.getcwd())# os.path.abspath()获得绝对路径p ...

  8. 如何确保获取的输入为整数-C语言基础

    这一篇探讨的是如何确保你输入的数据是一个整数.虽然标题用的是这个,但我其实真正想要探讨的内容是 "在程序调试的过程中,需要注意把输入缓存区中的上一次输入的残留信息清理干净,以免影响下一次的输 ...

  9. Microsoft.CppCommon.targets(138,5): error MSB3073

    我生成 Zlib 库的某个项目的时候,出现了这些error,原来是项目属性---->生成后事件--->命令行 错误的内容就是命令行内容.这些命令行的具体作用我还不知道,但是把他们删除后就成 ...

  10. TCP连接实践解析

    1.初始化. 2.FD_ISSET,是select机制的一个成员,用来检测sockfd是否有动作,对应读写异常等. 3.FD_ZERO 宏完成的工作就是一个初始化套接字集合 4.FD_SET把sock ...