最新发布《数智时代的AI人才粮仓模型解读白皮书(2024版)》,立即领取! 了解详情
写点什么

RAML 用户应遵循的 C#与 Web API 代码生成模式

  • 2016-06-15
  • 本文字数:4754 字

    阅读完需:约 16 分钟

在过去几年间,REST 规范的各种语言正在逐渐流行起来,例如 RAML Swagger 以及 API Blueprint 。但这些语言的主要范畴在于客户端工具,主要用于生成 JavaScript 或 TypeScript 文件、模拟对象(mock),以及对应的客户端单元测试。

与此同时,传统的.NET 后端开发者往往拥有 C#与 SQL 方面的经验,而对于如何暴露 REST 服务的各种细节缺乏兴趣。他们更乐于通过在 controller 的方法中添加一些路由特性(attribute)的方式完成任务,而在数据存储与服务端之间的通信方面发挥他们的核心竞争力。

这种方式通常会造成出现不计其数的信息传达错误,虽然这种错误并不太严重。一旦 UI 开发者与服务端开发者对于如何暴露某个 REST 终结点产生了分歧,就必须有人去更新他的代码。通常来说,这种更新只是一个较小的变更,但如果不断重复这一过程,则会造成开发者生产力的极大下降。

为了克服这一问题,UI 开发者可以寻求规规范语言的帮助,例如 RAML,以生成他们所需的 Web API 代码。而服务的开发者可专注于如何连接这些代码,而不是为路由特性和 HTTP 谓词生成各种模拟对象。

本文并不打算讨论如何使用 RAML,而是强调 RAML,或你所选择的规范语言需要为你生成怎样的代码。

C#代码生成的概念

C# 2.0 在设计时就考虑到了代码生成的问题。如今代码生成器的使用已经变得非常普遍,甚至包括 Visual Studio 本身。代码生成器可创建部分类(partial class)。一个部分类中包括组成整个类所需的部分代码,但未必是全部的代码。这就允许你将类的定义分散在多个文件中,其中部分代码是自动生成的,而另一部分则是手写的。这种分离性能够防止代码生成器删除开发者手写的代码。

不幸的是,这种方式还不完善。部分类允许你添加新的方法,但不能够修改现有方法的行为。因为这一点,我们不得不等待 2008 年所发布的 C# 3,其中引入了部分方法的概念。

从表面上看,部分方法与抽象方法非常相似,但这种比喻是错误的。抽象方法必须在某处实现,否则会使代码无法编译。而部分方法更类似于 C++ 中的空的宏,如果未实现某个部分方法,则编译器会直接取消对该方法的调用,就像这行调用代码从不存在一样。

部分方法的使用有一些严格的需求。由于编译器可能会取消该方法,因此不可返回任何类型,也不可以使用任何“out”参数。不过,你可以在部分方法中使用“ref”参数以返回某个值。由于在使用 ref 参数时必须在调用该部分方法之前为其赋值,那么即使该部分方法被删除,编译器仍然能够满足明确赋值的规则。

由于以上限制的存在,我们的实现将很大程度上依赖于 ref 参数。

Controller 的模式

所有的 REST 终结点都必须包含在某个 controller 类中,以下代码是一个简单的示例:

复制代码
[GeneratedCode("My Tool", "1.0.0.0")]
[RoutePrefix("api/customer")]
public partial class CustomerController : ApiController
{
//methods go here
}

以这种方式创建的 Web API controller 通常包含两种特性,通过中括号表示。GeneratedCode 表示这个类是由某个工具生成的,因此开发者不应直接修改它。RoutePrefix 这个可选的特性将用于基于特性的路由,我们稍后将展开讨论。

同步方法的模式

以下代码展示了一种可用于你的 Web API 代码生成的基本模式。

复制代码
[Route("search")]
[HttpGet]
public List<Customer> QueryCustomers(int zipCode, string lastName = "")
{
Tuple<List<Customer>> result = null;
QueryCustomers(zipCode, lastName, ref result);
if (result == null)
throw new NotImplementedException("RAML defined method wasn’t implemented");
else
return result.Item1;
}
partial void QueryCustomers(int zipCode, string lastName, ref Tuple<List<Customer>> result);

请注意一点,该部分方法将返回结果封装为一个元组(Tuple)对象,这样就使你能够区分“该方法未实现”以及“该方法返回 null”中 null 的不同意义。

我们之后将始终遵循这一模式以确保代码可编译。虽然对某个由 RAML 定义的 REST 方法进行变更可能会破坏构建,但如果仅仅是添加一个新方法,则不应当产生破坏性的后果。

每个 REST 方法应当至少包含两个特性。route 特性用于确定 URL 的形式,如果该类具有一个 RoutePrefix 特性,则方法中的 route 特性将附加在 RoutePrefix 特性之后。你可以通过 Mike Wasson 所撰写的文章“ Attribute Routing in ASP.NET Web API 2 ”学习这一主题的更多内容。

另一个特性则表现了该方法对应的谓词。从技术上说,谓词是可以从方法的名称中推断出来的,但一个明确的特性可使代码的阅读者更方便地快速理解其内容。而且由于这部分代码是自动生成的,因此不会造成额外的冗长感。

你可能还需要代码生成器为方法添加 Authorize 特性,表示该方法只能够由已登录的用户进行访问。

而无返回类型的 REST 方法看起来稍有一些不同之处:

复制代码
[Route("update")]
[HttpPost]
public void UpdateCustomer(Customer customer)
{
bool wasExecuted = false;
UpdateCustomer(customer, ref wasExecuted);
if (!wasExecuted)
throw new NotImplementedException("RAML defined method wasn’t implemented");
}
partial void UpdateCustomer(Customer customer, ref bool wasExecuted);

在这一模式中,方法的实现者需要将 wasExecuted 改为 true。

异步方法的模式

现如今,只要任何一个方法提供了异步的调用方式,那么同步的调用方式往往是受人鄙视的。虽然在延迟性方面表现得稍慢一些,但异步代码对于负载很大的服务器来说能够提供更好的吞吐性,从而提升整体的用户体验。

复制代码
[Route("search")]
[HttpGet]
public async Task<List<Customer>> QueryCustomersAsync(int
zipCode, string lastName = "")
{
Task<List<Customer>> resultTask = null;
QueryCustomersAsync(zipCode, lastName, ref resultTask);
if (resultTask == null)
throw new NotImplementedException("RAML defined method wasn’t implemented");
else
return await resultTask;
}
partial void QueryCustomersAsync(int zipCode, string lastName, ref
Task<List<Customer>> resultTask);

你需要注意的第一个不同之处在于返回类型被封装在一个 Task 中,这允许框架以异步方式等待该方法的完成。

为了获取 Task 对象其中的实际内容,你需要使用“await”关键字。即使该方法未返回任何值,该关键字也允许你等待该 Task 的完成。在异步上下文中绝对不要直接读取 Task.Result 中的内容,因为这可能会造成死锁。

而对于不返回值的 REST 方法,其模式也稍有不同。

复制代码
[Route("update")]
[HttpPost]
public async Task UpdateCustomerAsync(Customer customer)
{
Task resultTask = null;
UpdateCustomerAsync(customer, ref resultTask);
if (resultTask == null)
throw new NotImplementedException("RAML defined method wasn’t implemented");
else
await resultTask;
}
partial void UpdateCustomerAsync(Customer customer, ref Task resultTask);

支持客户端断开连接的情况

如果用户撤消了某个请求,或是断开了连接,那么取消一个运行时间较长的操作是很有益处的。为了在异步代码中实现这一点,你需要通过 ClientDisconnectedToken 侦听撤消操作。以下代码展示了一个使用该对象的示例:

复制代码
[Route("search")]
[HttpGet]
public async Task<List<Customer>> QueryCustomersCancellableAsync(int zipCode, string lastName = "",
CancellationToken cancellationToken = default(CancellationToken))
{
Task<List<Customer>> resultTask = null;
QueryCustomersCancellableAsync(zipCode, lastName, cancellationToken, ref resultTask);
if (resultTask == null)
throw new NotImplementedException("RAML defined method wasn’t implemented");
else
return await resultTask;
}
partial void QueryCustomersCancellableAsync(int zipCode, string
lastName, CancellationToken cancellationToken, ref
Task<List<Customer>> resultTask);

注意,cancellationtoken 在 REST 方法中被标记为可选的,即使框架会确保为其提供一个值,因此该参数可出现在任意可选参数之后的位置上。

Controller 基类

Web API controller 的标准基类是 ApiController,但服务的开发者可能会选择覆盖这个基类,为了能够进行覆盖,所生成的代码必须忽略这个基类。这就需要服务的开发者必须指定一个基类,否则该 API 就将变得不可见。

作为一个临时方案,代码生成器可以指定一个由服务开发者所命名的自定义基类,该基类需要继承自 ApiController,并包含任何共享的功能。

Model

当需要使用一些复杂对象时,代码生成器将试图生成这些类。虽然你可以使用一些纯粹的对象,但更好的方式是为其添加 DataContract 和 DataMember 这些特性的标注。这将允许服务开发者为其添加一些不需要暴露给客户端的额外属性(property),只要不将某个属性标注为 DataMember 即可。

复制代码
[DataContract]
public partial class Customer
{
[DataMember]
public int CustomerKey { get; set; }
[DataMember]
public string CustomerName { get; set; }
}

Model 的校验

为了对 model 进行校验,可以对需要检查的属性添加适当的特性。常见的校验特性包括 Required、MaxLength、MinLength、Phone 以及 EmailAddress。在 DataAnnotations 命名空间中包含了内置的校验特性的列表。

一旦定义了 model 校验逻辑之后,你还需要强制实施他们,可以在 REST 方法的开头添加这两行代码以实现该操作。

复制代码
if (!ModelState.IsValid)
throw new HttpResponseException(HttpStatusCode.BadRequest);

进阶应用

一旦你实现了基本的代码生成器之后,可以进一步探索可移除样板代码的场合。举例来说,你可以在生成的代码中加入对 username 的解析操作,将结果传递至部分方法中。比方说:

复制代码
[Route("search")]
[HttpGet]
public List<Customer> QueryCustomers(int zipCode, string lastName = "")
{
var user = [application specific logic]
Tuple<List<Customer>> result = null;
QueryCustomers(zipCode, lastName, ref result, user);
if (result == null)
throw new NotImplementedException();
else
return result.Item1;
}
partial void QueryCustomers(int zipCode, string lastName, ref
Tuple<List<Customer>> result, User user);

另一种移除样板代码的方式是使用一个数据上下文或其他资源,并传递给部分方法。你也可以选择通过日志记录的条目捕获请求与响应的信息。基本上,只要是公式化的或是重复性的操作,都可以按照这种方式进行简化。

付诸实践

RAML 与 C#的代码生成功能结合使用可极大地减少前端与后端开发团队之间的摩托。实现这种方式的秘密取决于一个可靠的设计步骤,即 JavaScript 与 C#的开发工作在开始具体编码之前需要对 RAML 达成一致。实现了这一点之后,当开发流程中出现各种不可避免的变更时,双方应当能够心平气和地接受对共享的 RAML 进行更改。

如果你希望发布与 RAML、代码生成、或是其他.NET 方面的文章,欢迎你通过 jonathan@infoq.com 联系 Jonathan Allen。如果你正在寻找某种将 RAML 转换至 C#的代码生成器,可以参考一下 Mulesoft Lab 发布的 RAML Tools for .NET

关于作者

Jonathan Allen的第一份工作是在上世纪 90 年代后期为某个诊所开发的 MIS 项目,该项目从早期的 Access 与 Excel 逐渐发展为一个企业级解决方案。之后,他又为财政部门编写自动交易系统达五年之久,在那之后,他决定转而进行高端用户界面的开发工作。在空余时间,他的兴趣是学习 15 世纪至 17 世纪的西方武术史并撰写相关文章。

查看英文原文 C#/Web API Code Generation Patterns for the RAML User

2016-06-15 19:233042
用户头像

发布了 428 篇内容, 共 172.0 次阅读, 收获喜欢 38 次。

关注

评论

发布
暂无评论
发现更多内容

油猴浏览器管理脚本:Tampermonkey油猴插件安装教程

Rose

油猴插件 Tampermonkey插件 油猴脚本使用

PDF Reader Pro for Mac v3.1.0.0中文激活版下载

影影绰绰一往直前

Things3 for Mac v3.19.4中文免激活版

影影绰绰一往直前

Cardhop for Mac(通讯录管理工具)v2.2.14汉化版

影影绰绰一往直前

同城双活的必修课 - 落地经验与关键挑战解析

柠檬汁Code(binbin0325)

容灾 异地多活 故障恢复 部署架构 同城双活

【教程】苹果推送证书的创建和使用流程详解

雪奈椰子

无人巡检 | AIRIOT变电站无人机运防一体管理解决方案

AIRIOT

无人机 变电站 智能变电站

深挖数据资产价值,释放数字风控效能,用友智慧模型革新虚假贸易监控新手段

用友BIP

虚假贸易零容忍

12 | 排序(下):如何用快排思想在O(n)内查找第K大元素

鲁米

系统测试的实践与思考

老张

软件测试 质量保障 系统测试

MATLAB实战 | 求矩阵指数、预定义变量i和j的含义以及梯形积分法

TiAmo

matlab

Dropzone 4 for mac(文件拖拽增强工具)v4.80.1激活版

影影绰绰一往直前

Databend 开源周报第 122 期

Databend

揭秘华为研发代码大模型是如何实现的

华为云开发者联盟

人工智能 华为 华为云 华为云开发者联盟

数智化驱动建企人才管理创新

用友BIP

人才管理

DirEqual for Mac(文件夹比较工具)v5.7.4激活版

影影绰绰一往直前

【Mac/win】Red Giant Trapcode Suite序列号分享(红巨星粒子插件)

Rose

红巨星粒子插件 Red Giant Trapcode Suite 3D动画和视觉效果 Trapcode Suite序列号

Amazon CodeWhisperer 正式可用, 并面向个人开发者免费开放

亚马逊云科技 (Amazon Web Services)

人工智能 API CodeWhisperer Amazon Lambda 云上探索实验室

第30期 | GPTSecurity周报

云起无垠

风靡万千软件开发者:揭秘华为研发代码大模型是如何实现的?

华为云PaaS服务小智

云计算 软件开发 华为云

数据“表”的增删改查

小齐写代码

带你走进灵动岛 | 京东云技术团队

京东科技开发者

ios 开发 灵动岛 UI适配

3D渲染:hdrlightstudio8插件怎么安装?HDR Light Studio 8 Mac破解版下载安装教程

Rose

3D渲染 HDR Light Studio 8 HDR Light Studio安装

一起学Elasticsearch系列-索引的批量操作

Java随想录

Java 大数据 Elastic Search

Beyond Compare 4 for Mac中文注册激活 附 激活码 支持m1/m2

Rose

mac软件下载 Beyond Compare 4 注册版 Beyond Compare 4 下载 文件比较工具

Live Wallpaper & Themes 4K Pro for Mac(超高清4K动态壁纸)v19.0中文激活版

影影绰绰一往直前

基于阿里云服务网格流量泳道的全链路流量管理(一):严格模式流量泳道

阿里巴巴云原生

阿里云 云原生 Service Mesh 服务网格

3.2.1.0 发布!时间转换函数+BI 集成+视图正式上线!

TDengine

tdengine 时序数据库

如何让“省钱”“赚钱”相结合,资产管理实现效益最大化

用友BIP

资产管理

车企数据治理实践案例,实现数据生产、消费的闭环链路

袋鼠云数栈

大数据 数字化转型 数据治理

火山引擎DataTester升级MAB功能,助力企业营销决策

字节跳动数据平台

A/B 测试 对比实验

RAML用户应遵循的C#与Web API代码生成模式_.NET_Jonathan Allen_InfoQ精选文章