前言
一旦我们将API发布之后,消费者就会开始使用并和其他的一些数据混在一起。然而,当新的需求出现时变化是不可避免的,你也许会庆幸API变了对现有客户端没受到影响,但是这种情况不会一直发生。
因此,在具体实现之前仔细考虑一下ASP.NET Web Api的版本策略就变得很有必要了。在我们的案例中,需求发生了变化而且我们通过创建不同版本的API来解决变化,同时不影响已经在使用API的客户端。我们把新的API版本和旧的API版本一起返回给客户端,让它有足够的时间迁移到最新版本的API,有时候多版本共存也是有可能的。
实现版本控制的方式有好多,本文主要介绍URI,query string,自定义Header和接收Header
API变了
简单起见,我们让“StudentsController”中的Get方法发生变化——在响应报文的body中,我们用“CoursesDuration”和“FullName”属性替换原来的“FirstName”和“LastName”属性。
最简单的做法就是创建一个与“StudentsController”一样的Controller并命名为“StudentsV2Controller”,我们将根据不同的API版本选择合适的Controller。在新的Controller中我们实现上述变化并使用相同的Http方法,同时不做任何介绍
现在我们请求“StudentsController”的Get方法是,会返回如下数据:
我们期待访问“StudentsV2Controller”的Get方法后应该的到:
ok,下面来实现,复制粘贴”StudnetsController”并重命名为“StudnetsV2Controller”,更改Get方法的实现:
可以看到,这里我们改的很少,返回的类型变成了“StudentV2BaseModel”,而这个类型是由ModelFactory的CreateV2Summary方法创建的。因此我们需要添加StudentV2BaseModel类和CreateV2Summary方法:
到目前为止,我们的准备工作就算做完了,下面介绍四种方式实现版本变化
使用URI控制Web Api的版本
这种做法的好处就是客户端知道自己用的是哪一版本的api,实现方法就是在“WebApiConfig”中添加2条路由:
在上面代码中,我们添加了2条路由规则,它们彼此对应了相应的Controller。如果以后我们打算添加V3,那么就得再加一条。这里就会变得越来越混乱。
这种技术的主要缺点就是不符合REST规范因为URI一直会变,换句话说一旦我们发布一个新版本,就得添加一条新路由。
在我们讲解另外3种实现模式之前,我们先来看一下在web api框架是怎么根据我们的请求来选择相应的Controller的:在web api中有一个“DefaultHttpControllerSelector”类,其中有一个方法“SelectController()”,这个方法接收一个“HttpRequestMessage”类型的参数。这个对象包含一个含key/value键值对的route data,其中就包括在“WebApiConfig”中配置的controller的名字。根据这一条信息,通过反射获取所有实现“ApiController”的类,web api就会匹配到这个Controller,如果匹配结果不等于1(等于0或大于等于2),那么就会抛出一个异常。
我们自定义一个类“LearningControllerSelector”继承自“Http.Dispatcher.DefaultHttpControllerSelector”,重写“SelectController()”方法,具体代码如下:
上述代码主要意思如下:
1.调用父类方法GetControllerMapping()获取所有实现了ApiController的类。
2.通过request对象获取routeData ,然后进一步获得Controller的name
3.根据我们刚刚得到的Controller,名字创建“HttpControllerDescriptor”对象,这个对象包含了描述Controller的信息
.4.接着,在我们找到的Controller的名字后面加上“V”和版本号,重复上面步骤即可。关于如何获得版本号,我们一会儿讨论,这里暂时写死成“2”。
为了使我们自定义的“Controller Selector”生效,因此需要在“WebApiConfig”中做如下配置:
接下来我们就来实现请求如何发送版本号
使用Query String设置版本
我们可以认为客户端没有提供query string的版本号,那么版本号默认为“1”。
实现起来也不复杂,在我们的“LearningControllerSelector”类中添加一个“GetVersionFromQueryString()”方法,该方法接收一个HttpRequestMessage参数,并从这个请求对象中获取客户端所需要的版本:
我们只需要在SelectController方法中调用这个方法即可,唯一的缺点依然是URI会变,不符合REST规范。
通过自定义请求头设置版本
现在我们使用另一种方式来发生版本号——自定义请求头,它不是URI的一部分,添加一个头“X-Learning-Version”并把版本号设置在里面,当客户端没有这条头信息是我们可以认为它需要V1版本。
实现这个技术,我们在“LearningControllerSelector”中添加一个“GetVersionFromHeader”方法,代码如下:
这里做法很简单,我们先确定好请求头的名字,然后去request的Header中找,如果有数据,就获得。
客户端发送的请求如下:
这么做也有缺点,就是添加了一个请求头(注:这个缺点不是很理解),下面介绍第四种方式
使用Accept Header设置版本
这种方法是直接使用Accept Header, 请求的时候将它设置为“Accept:application/json; version=2”,我们依旧这么认为:如果客户端不提供版本号,我们就给他V1的数据。
在“LearningControllerSelector”类中添加“GetVersionFromAcceptHeaderVersion”方法,具体实现如下:
这个实现看上去比上面更标准,更专业了。