Web API 接口版本控制 SDammann.WebApi.Versioning
前言
在设计对外 Web API 时,实务上可能会有新旧版本 API 并存的情况,例如开放 Web API 给厂商串接,但同一个服务更新版本时,不一定所有厂商可以在同一时间都跟着更新他们的系统,但如果直接把服务修改成新的,这些厂商可能就无法跟你的服务串 接了,直到他们修成新版的程序代码,他们方能正常运作。
当这样的情况不被允许时,通常就会希望可以透过不同的 version 来呼叫「同一个 API 」,这里的同一个 API 包含了新旧版本的服务。
目前的环境是 .NET framework 4.0, Web API 1.0, ASP.NET MVC 4,希望可以透过比较简单的方式来提供呼叫端可以简单易懂地明白:这是不同版本的同一个服务。
开发上则希望能让 developer 很直觉地知道,怎么开发不同版本的服务,不需要管 routing 这件事,只要直接设计 controller 的内容即可。
说明
在 Github 上找到一个能满足我这些基本需求的 open source: SDammann.WebApi.Versioning,相关的参考文章可以参考:
ASP.NET Web API: Using Namespaces to Version Web APIs Versioning ASP.NET Web API Services Using HTTP Headers
这个 solution 提供了两种定义 version 的方式:
透过 routing, 也就是 url 上定义版本,以便分辨并找到对应的 controller 透过 request header, 好处是 url 不变,只要在 request 的 header 中指定 version, 就能找到对应的 controller
这篇文章则带着大家快速、简单的 adopt 这个 solution。
实作
首先建立一个 ASP.NET MVC 4.0 的 project, 选择 Web API。
接着透过 Nuget 安装 SDammann.WebApi.Versioning,NuGet 基本上只帮我们加入了相关参考的 dll 。
接下来的动作超级简单,在 Controller 的文件夹中, 加入 Version1 的 folder ,因为 ControllerSelector 会依据 namespace 来找到对应的 Controller。建立好 Version1 的 folder 后,加入一个 HelloController ,如图所示:
简单定义一个 Get 的方法,以及自定义回传的讯息格式 Message entity,程序代码如下所示:
namespace WebApiWithVersioning.Controllers.Version1
{
public class HelloController : ApiController
{
public Message Get()
{
return new Message { Token = "Joey-v1", Signature = "91" };
}
}
public class Message
{
public string Token { get; set; }
public string Signature { get; set; }
}
}
接着在 Controller 文件夹下新增一个 Version2 的 folder ,一样加入一个 HelloController,如图所示:
一样定义一个 Get 的方法,但方法签章可以跟 Version1 的不一样,例如回传格式为 AnotherMessage, 程序代码如下所示:
namespace VersioningWebApiSample.Controllers.Version2
{
public class HelloController : ApiController
{
public AnotherMessage Get()
{
return new AnotherMessage { NewToken = "Joey-v2", NewSignature = "91" };
}
}
public class AnotherMessage
{
public string NewToken { get; set; }
public string NewSignature { get; set; }
}
}
透过 url routing 来决定呼叫的版本
两个版本的 HelloController.Get() 都已经实作完毕了,接着只要让呼叫端可以决定要呼叫哪一个版本的Hello.Get() 即可。
这里其实只要将 IApiExplorer 换成 VersionedApiExplorer ,接着视需求决定将 IHttpControllerSelector 换成 RouteVersionedControllerSelector 或是 AcceptHeaderVersionedControllerSelector ,前者是透过 url routing 来决定呼叫哪一个版本的服务,后者则是透过 request 的 accept header 来决定版本。
上一篇文章 [ASP.NET Web API]3 分钟搞定 DI framework–Unity Application Block 已经介绍过,怎么快速地透过 Unity 来进行 DI 的动作,这边一样透过 NuGet 来安装 Unity.WebAPI ,接着在 Bootstrapper 中注册上面两个 interface 即可,程序代码如下所示:
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
// register all your components with the container here
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IApiExplorer, VersionedApiExplorer>(new InjectionConstructor(GlobalConfiguration.Configuration));
container.RegisterType<IHttpControllerSelector, RouteVersionedControllerSelector>(new InjectionConstructor(GlobalConfiguration.Configuration));
//container.RegisterType<IHttpControllerSelector, AcceptHeaderVersionedControllerSelector>(new InjectionConstructor(GlobalConfiguration.Configuration)); return container;
}
上述的程序代码是先透过 routing 来区分版本。
一样别忘了在 Global.asax 中,呼叫 Bootstrapper.Initialise() ,如下所示:
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
Bootstrapper.Initialise();
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
}
最后一个动作,则是修改 routing 方式,在 WebApiConfig 中,加入 version 的 routing rule ,程序代码如下所示:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApiWithVersion",
routeTemplate: "api/v{version}/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
// Uncomment the following line of code to enable query support for actions with an IQueryable or IQueryable<T> return type.
// To avoid processing unexpected or malicious queries, use the validation settings on QueryableAttribute to validate incoming queries.
// For more information, visit http://go.microsoft.com/fwlink/?LinkId=279712.
//config.EnableQuerySupport();
// To disable tracing in your application, please comment out or remove the following line of code
// For more information, refer to: http://www.asp.net/web-api
config.EnableSystemDiagnosticsTracing();
}
}
routeTemplate 的部分中间加入一个 /v{version}/ ,来分辨要呼叫哪一个版本即可。
大功告成,来看一下怎么呼叫 Version1 的服务,如下图所示:
url 为 /api/v1/Hello/Get ,回传的是内容是 Message 。
那 Version2 的服务呢?如下图所示:
url 为 /api/v2/Hello/Get ,回传的是内容是 AnotherMessage 。
总结一句话:透过 url routing 来决定要呼叫哪一个 namespace 的 Controller ,就这么简单!
透过 Request Header 来决定呼叫的版本
如果希望 url 是固定的,只要变更 request header 就能呼叫不同版本的服务,在这个 solution 中也相当简单。
首先,将 container 注册 IHttpControllerSelector 的实体,换成 AcceptHeaderVersionedControllerSelector ,如下所示:
public static class Bootstrapper
{
public static void Initialise()
{
var container = BuildUnityContainer();
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
}
private static IUnityContainer BuildUnityContainer()
{
var container = new UnityContainer();
// register all your components with the container here
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IApiExplorer, VersionedApiExplorer>(new InjectionConstructor(GlobalConfiguration.Configuration));
//container.RegisterType<IHttpControllerSelector, RouteVersionedControllerSelector>(new InjectionConstructor(GlobalConfiguration.Configuration));
container.RegisterType<IHttpControllerSelector, AcceptHeaderVersionedControllerSelector>(new InjectionConstructor(GlobalConfiguration.Configuration));
return container;
}
}
没了,就这样,接着透过 Help Page 的 Test Client 来测试看看。(Test Client 只需要透过 NuGet 安装 A Simple Test Client for ASP.NET Web API 即可,如下图所示)
接着打开 api/v1/Hello/Get 的说明页面,如图所示:
将 url 修改成:api/Hello/Get ,并加入 accept header: application/json; version=1 ,如下图所示:
点下 Send 后,就可以看到呼叫 Version1 的结果,如下图所示:
我们将刚刚的 header 改成 version=2 ,则出来的结果就会是 Version2 的结果,如下图所示:
延伸议题
这个 solution 有一个缺点,就是 Help Page 会多出很多奇怪的 API ,如下图所示:
这几个事实上都不应该被呼叫,也无法正常被呼叫。
这里简单的修改一下 Help Page 的内容,让读者可以看到我们期望的 Help Page 样子,设计方式要如何弹性,可以请读者自行思考。
首先打开 Areas.HelpPage 的 ApiDescriptionExtensions ,加入一个扩充方法 CheckIsVersionControllerValid 来判断:
Controller 的 namespace 是不是有含 Version routeTemplate 是不是有含 Version
程序代码如下所示:
//todo by joey, 新增为了version controller的处理, 目前是hard-code硬干,未来可修改成regex
public static bool CheckIsVersionControllerValid(this ApiDescription description)
{
var controllerName = (description.ActionDescriptor).ControllerDescriptor.ControllerType.FullName;
var routeTemplate = (description.Route).RouteTemplate;
var isControllerWithVersion = controllerName.Contains(".Version");
var isRouteTemplateWithVersion = routeTemplate.Contains("{version}");
return isControllerWithVersion == isRouteTemplateWithVersion;
}
接着修改 ApiGroup.cshtml ,加入判断如果 Controller 的 type 与 Version 的 routeTemplate 是吻合的,才显示在 Help Page 上,程序代码如下图所示:
出来的 Help Page 就是我们要的,如下图所示:
下一步我们希望根据版本号显示对应的帮助文档
修改 HelpController.cs 中的 Index
public ActionResult Index(string version)
{
var model = Configuration.Services.GetApiExplorer().ApiDescriptions;
if (!string.IsNullOrEmpty(version))
{
var oldapis = Configuration.Services.GetApiExplorer().ApiDescriptions;
foreach (var oldapi in oldapis)
{
if (!oldapi.RelativePath.Contains("/" + version + "/"))
{
model.Remove(oldapi);
}
}
}
ViewBag.DocumentationProvider = Configuration.Services.GetDocumentationProvider();
return View(model);
}
接着修改路由 HelpPageAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"HelpPage_Version",
"Help/{version}",
new { controller = "Help", action = "Index", version = UrlParameter.Optional }); context.MapRoute(
"HelpPage_Default",
"Help/{action}/{apiId}",
new { controller = "Help", action = "Index", apiId = UrlParameter.Optional }); HelpPageConfig.Register(GlobalConfiguration.Configuration);
}
结果
---------------------------------------
结论
希望这个简单、方便的方式,可以满足多版本同时服务的需求,也可以让呼叫端能够简便、好懂地呼叫不同版本的服务,而 server 端的切换与设计也可以相当单纯, developer 几乎只要新增不同 Version 的文件夹,继续往下写 controller 的内容即可。
Web API 接口版本控制 SDammann.WebApi.Versioning的更多相关文章
- Http下的各种操作类.WebApi系列~通过HttpClient来调用Web Api接口
1.WebApi系列~通过HttpClient来调用Web Api接口 http://www.cnblogs.com/lori/p/4045413.html HttpClient使用详解(java版本 ...
- WebApi系列~通过HttpClient来调用Web Api接口~续~实体参数的传递
回到目录 上一讲中介绍了使用HttpClient如何去调用一个标准的Web Api接口,并且我们知道了Post,Put方法只能有一个FromBody参数,再有多个参数时,上讲提到,需要将它封装成一个对 ...
- ASP.NET Web API 接口执行时间监控
软件产品常常会出现这样的情况:产品性能因某些无法预料的瓶颈而受到干扰,导致程序的处理效率降低,性能得不到充分的发挥.如何快速有效地找到软件产品的性能瓶颈,则是我们感兴趣的内容之一. 在本文中,我将解释 ...
- 不使用jQuery对Web API接口POST,PUT,DELETE数据
前些天,Insus.NET有演示Web API接口的操作: <怎样操作WebAPI接口(显示数据)>http://www.cnblogs.com/insus/p/5670401.html ...
- Winform混合式开发框架访问Web API接口的处理
在我的混合式开发框架里面,集成了WebAPI的访问,这种访问方式不仅可以实现简便的数据交换,而且可以在多种平台上进行接入,如Winform程序.Web网站.移动端APP等多种接入方式,Web API的 ...
- Web API接口设计经验总结
在Web API接口的开发过程中,我们可能会碰到各种各样的问题,我在前面两篇随笔<Web API应用架构在Winform混合框架中的应用(1)>.<Web API应用架构在Winfo ...
- 如何让你的 Asp.Net Web Api 接口,拥抱支持跨域访问。
由于 web api 项目通常是被做成了一个独立站点,来提供数据,在做web api 项目的时候,不免前端会遇到跨域访问接口的问题. 刚开始没做任何处理,用jsonp的方式调用 web api 接口, ...
- Asp.Net Web Api 接口,拥抱支持跨域访问。
如何让你的 Asp.Net Web Api 接口,拥抱支持跨域访问. 由于 web api 项目通常是被做成了一个独立站点,来提供数据,在做web api 项目的时候,不免前端会遇到跨域访问接口的问题 ...
- Asp.Net Web Api 接口
如何让你的 Asp.Net Web Api 接口,拥抱支持跨域访问. 由于 web api 项目通常是被做成了一个独立站点,来提供数据,在做web api 项目的时候,不免前端会遇到跨域访问接口的 ...
随机推荐
- 问题MySQL Error (2013): Lost connection to MySQL server at waiting for initial communication packet
错误说明: SQL Error (2013): Lost connection to MySQL server at 'waiting for initial communication packet ...
- kotlin 之单表达式函数
fun d(x:Int):Int=x* 如果函数体只有一条语句,而且是return语句,那么可以省略大括号以及return关键字,
- linux上安装phpstudy
摘要:安装:wget-chttp://lamp.phpstudy.net/phpstudy.bin chmod+xphpstudy.bin #权限设置./phpstudy.bin#运行安装用时十到几 ...
- 阶段5 3.微服务项目【学成在线】_day05 消息中间件RabbitMQ_15.RabbitMQ研究-与springboot整合-声明交换机和队列
复制topic的代码 把常量都设置成public方便其他的类引用 ExchangeBuilder里面有4个方法分别对应四个交换机. 声明Email和短信的队列 队列绑定交换机 所以需要把Bean注入到 ...
- 阶段5 3.微服务项目【学成在线】_day05 消息中间件RabbitMQ_6.RabbitMQ研究-入门程序-消费者
我们在consumer这个功能下进行代码的编写 首先是新建这个层级的包 创建入门程序的消费者 消费者也需要和mq建立通道.建立连接创建通道 在顶部都声明这个队列 下面写核心代码监听队列.basicCo ...
- [Scikit-learn] *2.3 Clustering - DBSCAN: Density-Based Spatial Clustering of Applications with Noise
http://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html#sklearn.cluster.DBSCAN ...
- 硬核讲解 Jetpack 之 LifeCycle 源码篇
前一篇 硬核讲解 Jetpack 之 LifeCycle 使用篇 主要介绍了 LifeCycle 存在的意义,基本和进阶的使用方法.今天话不多说,直接开始撸源码. 本文基于我手里的 android_9 ...
- Tomcat 80端口被占用
1.“运行”中输入cmd2.在命令行中输入netstat -ano,得到端口号对应的PID 3.打开任务管理器,点击“查看“菜单,选择“选择列”,给进程列表中添加”PID“列,然后找到PID对应的进程 ...
- PL-VIO Docker测试
Docker里面测试PL-VIO的代码 PL-VIO贺一家大佬最近开源出来的一个点线特征融合的VIO代码,基于Vins-Mono,想要快速测试一下代码,所以就写了个简单的Dockerfile来测试一下 ...
- 关于macOS上常用操作命令(持续更新)
1.mac上显示/隐藏Finder中的隐藏文件: 显示隐藏文件:在终端中输代码“defaults write com.apple.finder AppleShowAllFiles -boolean t ...