[翻译]创建ASP.NET WebApi RESTful 服务(10)
通过URI实现版本管理
另一种实现版本管理的方式就是通过URI来进行处理,类似于http://localhost:{your_port}/api/v1/students/。这种方式的好处是使用者可以清楚的知道当前自己使用的版本。实现也很简单:
1: config.Routes.MapHttpRoute(
2: name: "Students",
3: routeTemplate: "api/v1/students/{userName}",
4: defaults: new { controller = "students", userName = RouteParameter.Optional }
5: );
6:
7: config.Routes.MapHttpRoute(
8: name: "Students2",
9: routeTemplate: "api/v2/students/{userName}",
10: defaults: new { controller = "studentsV2", userName = RouteParameter.Optional }
11: );
这种方式虽然解决了问题,其实违背了REST的特性,因为路由规则总是在不停的变动。
Controller Selector技术
在引入第三种技术之前,先回顾下Web API的路由原理。在DefaultHttpControllerSelectro类中有个方法SelectController,它接受一个HttpRequestMessage类型的对象,以键值对方式存储了各种路由数据(包括各种Controller的名称,这些都定义在WebApiConfig文件中)。通过这些信息,可以借助反射技术检索所有的从ApiController类派生的对象然后进行匹配。如果出现重复或者没有找到,则抛出异常。否则,返回对应的Controller对象。
要重写这种默认实现,就需要从Http.Dispatcher.DefaultHttpControllerSelector派生一个类,假设叫做LearningControllerSelector,然后可以重写SelectController,代码如下:
1: public class LearningControllerSelector : DefaultHttpControllerSelector
2: {
3: private HttpConfiguration _config;
4: public LearningControllerSelector(HttpConfiguration config)
5: : base(config)
6: {
7: _config = config;
8: }
9:
10: public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
11: {
12: var controllers = GetControllerMapping(); //Will ignore any controls in same name even if they are in different namepsace
13:
14: var routeData = request.GetRouteData();
15:
16: var controllerName = routeData.Values["controller"].ToString();
17:
18: HttpControllerDescriptor controllerDescriptor;
19:
20: if (controllers.TryGetValue(controllerName, out controllerDescriptor))
21: {
22:
23: var version = "2";
24:
25: var versionedControllerName = string.Concat(controllerName, "V", version);
26:
27: HttpControllerDescriptor versionedControllerDescriptor;
28: if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor))
29: {
30: return versionedControllerDescriptor;
31: }
32:
33: return controllerDescriptor;
34: }
35:
36: return null;
37:
38: }
39: }
代码的主要内容包括:
- 通过GetControllerMapping()方法获取所有从ApiController对象派生的Dictionary集合;
- 根据request.GetRouteData()方法检索路由数据,然后查找对应request的Controller;
- 通过Controller的名称,获取HttpControllerDescriptor对象,它包含了对应Controller的信息;
- 如果以上都成功,那就证明我们可以使用类似的技术实现ControllerNameV2的路由。
现在先做一件事,就是将自定义的Controller Selector替换默认,实现方法是在WebApiConfig文件的Register方法中进行处理。
1: config.Services.Replace(typeof(IHttpControllerSelector), new LearningControllerSelector((config)));
通过QueryString实现版本管理
接下来的工作就比较直接,可以直接在URI的后面附加“?V=2”这样的请求,例如http://localhost:{your_port}/api/students/?v=2。如果没有附加类似的信息,那么就默认为最旧的版本。
添加一个函数,用来实现这一过程。
1: private string GetVersionFromQueryString(HttpRequestMessage request)
2: {
3: var query = HttpUtility.ParseQueryString(request.RequestUri.Query);
4:
5: var version = query["v"];
6:
7: if (version != null)
8: {
9: return version;
10: }
11:
12: return "1";
13:
14: }
将这些逻辑加入到SelectorController的逻辑中即可实现按需调用。当然,这种实现方法还是会违背REST的原则,因为URI仍然是不固定的。
通过自定义Header实现版本管理
简单说,就是在Header信息中添加一条记录,使用者在请求的信息中添加一条版本信息,然后再ControllerSelector的地方进行处理。简单的实现代码如下:
1: private string GetVersionFromHeader(HttpRequestMessage request)
2: {
3: const string HEADER_NAME = "X-Learning-Version";
4:
5: if (request.Headers.Contains(HEADER_NAME))
6: {
7: var versionHeader = request.Headers.GetValues(HEADER_NAME).FirstOrDefault();
8: if (versionHeader != null)
9: {
10: return versionHeader;
11: }
12: }
13:
14: return "1";
15: }
接下来可以用Fiddler进行测试,并在Request Header中添加一条对应消息就可。
这种方法的不足之处就是必须在Headers中添加了自定义的Header信息,因此继续介绍另一种方法。
通过AcceptHeader进行版本管理
接下来我们就使用Accept Header实现版本管理,最终的GET请求Header应该是这样的“Accept:application/json; version=2”。因此我们可以修改为如下的逻辑:
1: private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request)
2: {
3: var acceptHeader = request.Headers.Accept;
4:
5: foreach (var mime in acceptHeader)
6: {
7: if (mime.MediaType == "application/json")
8: {
9: var version = mime.Parameters
10: .Where(v => v.Name.Equals("version", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
11:
12: if (version != null)
13: {
14: return version.Value;
15: }
16: return "1";
17: }
18: }
19: return "1";
20: }
这段代码手从MediaType为application/json的Header中检索version信息,下图是Fiddler的测试情况。
这是一种更为规范的方法,因为无须添加新的自定义Header,也不用使用不同的URI。
如果你订阅了Plural sight并且有继续研究的兴趣的话,那么你可以查看Shawn Wildermuth的课程,他介绍了其他的版本管理方式。
来源:http://bitoftech.net/2013/12/16/asp-net-web-api-versioning-accept-header-query-string/