@

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. LeetCode系列之 (JavaScript) => 88. 合并两个有序数组

    题目描述: [Leetcode 题目链接]:88. 合并两个有序数组 - 力扣(LeetCode) (leetcode-cn.com) 解题思路分析: 在nums1中找到nums2 插入的位置,然后在 ...

  2. Absolute Path Traversal 错误解决

    Absolute Path Traversal (APT) 是一种常见的安全漏洞,攻击者可以通过该漏洞访问应用程序的文件系统中的文件, 包括敏感信息,从而可能导致应用程序遭受攻击. 一.使用专门的文件 ...

  3. 浙大版《C语言程序设计(第3版)》题目集 习题3-1 比较大小(10 分)

    本题要求将输入的任意3个整数从小到大输出. 输入格式: 输入在一行中给出3个整数,其间以空格分隔. 输出格式: 在一行中将3个整数从小到大输出,其间以"->"相连. 输入样例 ...

  4. MySQL8.0使用GROUP BY的问题

    当使用group by的语句中,select后面跟的列,在group by后面没有时,会报以下错误: Expression #2 of SELECT list is not in GROUP BY c ...

  5. Vue 加载显示源代码 , Vue 屏幕闪动

    1.当网络较慢,网页还在加载 Vue.js ,而导致 Vue 来不及渲染,这时页面就会显示出 Vue 源代码.我们可以使用 v-cloak 指令来解决这一问题. <style> [v-cl ...

  6. 服务器端口对外开放(包括,mysql,django)

    1.查看对外开放端口号,并开放端口 查看开放的端口 ,有两个命令 1.1.iptables -L -n (比较清晰明了) 1. 2.firewall-cmd --list-ports 1.3 .打开端 ...

  7. jetson TX2 + opencv3.4 + python3 + 双目 +人脸检测

    淘宝看到一款很便宜的双目,150元,就买了.想着用它学习一下opencv,好换个工作.当然,也想着能否用它做一些好玩的,比如三维重建之类高大上的东西.先用便宜的入个门,等以后眼界高了再看是不是买那些更 ...

  8. 基于Dijkstra算法的郑州地铁路径规划

    需要引入geopy库 pip install geopy 安装即可 import requests from bs4 import BeautifulSoup import pandas as pd ...

  9. Crypto入门 (十一)easychallenge

    前言: 这题跟python有关,可见看懂python代码还是很有必要得,需要有一些python基础才好 easychallenge: 题目: 下载后来发现是一个.pyc为后缀得文件,查找资料可知,该文 ...

  10. [Leetcode 104]二叉树最大深度Maximum Depth of Binary Tree

    题目 求二叉树的深度,即根节点出发的最长路径上点的个数,即最长路径+1(本身这个点 https://leetcode.com/problems/maximum-depth-of-binary-tree ...