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

在过去几年间,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");elsereturn 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(intzipCode, string lastName = "")
{Task<List<Customer>> resultTask = null;QueryCustomersAsync(zipCode, lastName, ref resultTask);if (resultTask == null)throw new NotImplementedException("RAML defined method wasn’t implemented");elsereturn 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");elseawait 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");elsereturn 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();elsereturn 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世纪的西方武术史并撰写相关文章。

原文地址:http://www.infoq.com/cn/articles/webapr-for-raml


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

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

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

相关文章

wxpython使用folium_wxPython实现文本框基础组件

本文实例为大家分享了wxPython实现文本框的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下#-*- coding:utf-8 -*-"""#############################################StaticText 参数说明 --即 labelparent&#xff1a; -- 父窗口部件。id&#xff1a;…

教你实践ASP.NET Core Authorization(免看文档教程)

准备 创建一个名为AuthorizationForoNetCore的&#xff08;web&#xff09;解决方案,选择Empty模板添加相关nuget包引用Microsoft.AspNetCore.Mvc&#xff08;选择最新版本&#xff09;编辑Startup.cs文件&#xff0c;添加mvcservice并进行默认路由配置 添加Controllers文件夹&…

Class类中的getEnclosingXX、getDeclaredXX

转载自 Class类中的getEnclosingXX、getDeclaredXX一、getEnclosingXX getEnclosingClass():该类是在那个类中定义的&#xff0c; 比如直接定义的内部类或匿名内部类 getEnclosingConstructor()&#xff1a;该类是在哪个构造函数中定义的&#xff0c;比如构造方法中定义的匿名…

二级MYSQL的语法整理_MySQL语法整理

一、基本语句(大写的代表固定语句&#xff0c;小写的代表自己命名部分)1. 数据库部分增&#xff1a;CREATE DATABASE database_name;删&#xff1a;DROP DATEBASE database_name;用&#xff1a;USE database_name;2. 数据表部分增&#xff1a;CREATE TABLE table_name ( field1…

java反射的field.get(null)

转载自 java反射的field.get(null) 在java的反射中,通过字段获取对象,是通过 public Object get(Object obj) 字段不是静态字段的话,要传入反射类的对象.如果传null是会报 java.lang.NullPointerException 但是如果字段是静态字段的话,传入任何对象都是可以的,包括null 下面是…

JFlow CCFlow工作流引擎北京培训邀请函

各位jFlow, CCFlow 爱好者: 驰骋工作流程引擎是国内开源很成功的一款工作流程引擎&#xff0c;广泛应用于大型集团企业、机关事业单位、部队军区、保密军工行业。设计精巧、功能强大、极高的可配置性、概念名词通俗易懂、成长于中国生产制造、管理审批特有的环境下&#xff0c;…

java按列读取数据再存储_Java指定行读写数据

/*** 根据指定行写数据** param lineNumber 要存的行数* param data 要存储的数据*/public static void setAppointedLineNumber(int lineNumber, String data) throws IOException {Path path Paths.get(configuration);List lines Files.readAllLines(path, StandardCharse…

Java通过Class的对象来获取泛型的class示例

转载自 Java通过Class的对象来获取泛型的class示例 在使用spring的JdbcTemplate实现DAO的时候&#xff0c;经常会用到一个类ParameterizedBeanPropertyRowMapper。它的静态方法newInstance()接受一个Class类型的参数&#xff0c;用于将ResultSet中的属性映射到传入的这个Class…

微软觊觎LinkedIn算法

分析师说&#xff0c;LinkedIn算法的价值远超260亿美元买到的数据。 微软在昨天宣布了即将以262亿美元的价格收购企业社交网络LinkedIn。一名分析师称&#xff0c;这起并购由微软对算法的渴望而起。 “微软对LinkedIn的兴趣有两部分”&#xff0c;Gartner研究总监Jenny Sussin在…

阿卡姆疯人院需要java吗_蝙蝠侠阿甘疯人院 这个报错 怎么解决 哪位大神知道...

有关调用实时(JIT)调试而不是此对话框的详细信息&#xff0c;请参见此消息的结尾。************** 异常文本 **************System.Runtime.InteropServices.SEHException: 外部组件发生异常。在 BmLauncherLib.PhysXSDK.{ctor}(PhysXSDK* )在 BmLauncherUtils.PhysXSDK..ctor(…

Java5泛型的用法,T.class的获取和为擦拭法站台

转载自 Java5泛型的用法&#xff0c;T.class的获取和为擦拭法站台Java 5的泛型语法已经有太多书讲了&#xff0c;这里不再打字贴书。GP一定有用&#xff0c;不然Java和C#不会约好了似的同时开始支持GP。但大家也清楚&#xff0c;GP和Ruby式的动态OO语言属于不同的意识形态&…

asp.net core 使用 Redis 和 Protobuf 进行 Session 缓存

目录 Redis 介绍asp.net core Session 介绍Redis & Session 实例讲解Session的使用使用 Protobuf 给 Session添加扩展方法 Redis 介绍 下面是Redis官网的介绍&#xff1a; Redis is an open source (BSD licensed), in-memory data structure store, used as database, cac…

java后台 flex前台例子_flex+blazeds+java后台消息推送(简单示例)

现在有个问题需要大家思考一下&#xff0c;有个已经上线了的项目&#xff0c;有好好几千甚至上万的客户在使用了。现在项目开发商想发布一个通知。在今天下午6点需要重新启动服务器&#xff0c;想让在线的人在在预定的时间内都收到消息&#xff0c;让大家做好相应的准备&#x…

Java和Android中的注解

转载自 Java和Android中的注解1.引言 从JDK1.5开始&#xff0c;引入了注解类Annotation&#xff0c;Annotation其实是一种接口&#xff0c;可以作用于类、方法、属性等等 &#xff0c;它可以通过反射机制来访问annotation信息&#xff0c;获取所加上注解信息&#xff0c;做相应…

第二章 指南(4.3)添加 View

原文&#xff1a;Adding a view作者&#xff1a;Rick Anderson翻译&#xff1a;魏美娟(初见)校对&#xff1a;赵亮(悲梦)、高嵩(Jack)、娄宇(Lyrics)、许登洋(Seay)、姚阿勇&#xff08;Dr.Yao&#xff09; 本节将修改 HelloWorldController 类&#xff0c;把使用 Razor 视图模…

java多表查询返回数据_spring data jpa如何在多张数据库表中查询返回某些字段值?...

对于多表联查需要使用springdata jpa的Query标注实现&#xff0c;例如最代码的我的私信列表的查询&#xff1a;public static final String POSTREPOSITORY_FINDALLBYTYPEANDGROUPBYUSERID "select id from (select id,target_id,case when user_id?1 and type?2 then …

JAVA反射修改常量,以及其局限

转载自 JAVA反射修改常量&#xff0c;以及其局限问题&#xff0c;以及一个解决方案 今天公司的JAVA项目碰到一个问题&#xff1a;在生成xls文件的时候&#xff0c;如果数据较多&#xff0c;会出现ArrayIndexOutOfBoundsException。Google发现是项中所用的jxl包&#xff08;开源…

java出代码1601_LeetCode 1601. 最多可达成的换楼请求数目

题目描述我们有 n 栋楼&#xff0c;编号从 0 到 n - 1 。每栋楼有若干员工。由于现在是换楼的季节&#xff0c;部分员工想要换一栋楼居住。给你一个数组 requests &#xff0c;其中 requests[i] [fromi, toi] &#xff0c;表示一个员工请求从编号为 fromi 的楼搬到编号为 toi …

asp.net core 中间件详解及项目实战

前言 在上篇文章主要介绍了DotNetCore项目状况&#xff0c;本篇文章是我们在开发自己的项目中实际使用的&#xff0c;比较贴合实际应用&#xff0c;算是对中间件的一个深入使用了&#xff0c;不是简单的Hello World&#xff0c;如果你觉得本篇文章对你有用的话&#xff0c;不妨…

一分钟实现分布式锁

转载自 一分钟实现分布式锁一、缘起 分布式环境下&#xff0c;多台机器上多个进程对一个数据进行操作&#xff0c;如果不做互斥&#xff0c;就有可能出现“余额扣成负数”&#xff0c;或者“商品超卖”的情况&#xff0c;如何实现简易分布式锁&#xff0c;对分布式环境下的临界…