本文中,我们来讨论一下Web API版本化的问题,以及同类需求。
当你准备提供公共服务接口时,Web API的版本化问题会显得非常必要。它是我们为API提供向前、向后兼容支持的唯一途径。通过版本化,你能够不影响现有用户的使用,而且不会破坏那些依赖于你提供的API的应用。
默认的Web API的路由逻辑是:通过类名来查找controller。Controller的选取则是在Web API内部通过选择器 DefaultHttpControllerSelector(它实现了 IhttpControllerSelector.SelectController)来处理。我们无法通过默认的controller选择器实现Web API的版本化,但却可以在运行时通过自定义插件实现正确的版本选择。实现 IhttpControllerSelector接口就可以轻松创建自定义的HTTP controller选择器。
来看看两种不同的Web API版本化方式,你可以自己决定哪一个是符合需求的最佳方式。一个是利用命名空间,一个是基于自定义HTTP头部信息的机制。
本人在Web API方面知识渊博、有很好的理解和开发经验。
下面的代码片段是为了得到HTTP自定义头部的版本号。在这我们设定Web API的默认版本号总是空的。
private string GetVersionFromHTTPHeader(HttpRequestMessage request) { if (request.Headers.Contains("version")) { var versionHeader = request.Headers.GetValues("version").FirstOrDefault(); if (versionHeader != null) { return versionHeader; } } return string.Empty; }
下面的代码片段用于获取基于MIME类型的版本号。你能任选其一来获取版本号。
private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request) { var acceptHeader = request.Headers.Accept; foreach (var mime in acceptHeader) { if (mime.MediaType == "application/json" || mime.MediaType == "text/html") { var version = mime.Parameters .Where(v => v.Name.Equals("version", StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (version != null) { return version.Value; } return string.Empty; } } return string.Empty; }
下面的代码片段是自定义Web API 控制选择器,它实现了IHttpControllerSelector.SelectController函数。从下面的代码中你能注意到我们会基于匹配到的控制映射或者通过版本来返回控制器描述信息。
public override HttpControllerDescriptor SelectController(HttpRequestMessage request) { var controllers = GetControllerMapping(); var routeData = request.GetRouteData(); var controllerName = routeData.Values["controller"].ToString(); HttpControllerDescriptor controllerDescriptor; if (controllers.TryGetValue(controllerName, out controllerDescriptor)) { var version = GetVersionFromHTTPHeader(request); if (!string.IsNullOrEmpty(version)) { var versionedControllerName = string.Concat(controllerName, "V", version); HttpControllerDescriptor versionedControllerDescriptor; if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor)) { return versionedControllerDescriptor; } } return controllerDescriptor; } return null; }
那么接下来呢?
上面我们已经实现了自定义的控制器选择器,现在必须让它替换默认的以使生效。只要在 WebApiConfig 中添加一行就行。
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API configuration and services // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); // 在这替换控制器选择器 config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector((config))); } }
以下是HTTP请求和响应的快照。你可以注意到在HTTP请求头中被传递的版本信息“version”。
我们应该看到过有关基于命名空间的 Web API 版本控制的简介。这是一种最常见的被用到的技术,你会看到最流行的服务提供商也在使用这种技术。
下面的代码片段通过返回基于命名空间的控制器描述符来实现查找功能。
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary() { var dictionary = new Dictionary<string,>(StringComparer.OrdinalIgnoreCase); // Create a lookup table where key is "namespace.controller". The value of "namespace" is the last // segment of the full namespace. For example: // MyApplication.Controllers.V1.ProductsController => "V1.Products" IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver(); IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver(); ICollection<type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver); foreach (Type t in controllerTypes) { var segments = t.Namespace.Split(Type.Delimiter); // For the dictionary key, strip "Controller" from the end of the type name. // This matches the behavior of DefaultHttpControllerSelector. var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length); var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], controllerName); // Check for duplicate keys. if (dictionary.Keys.Contains(key)) { _duplicates.Add(key); } else { dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t); } } // Remove any duplicates from the dictionary, because these create ambiguous matches. // For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products". foreach (string s in _duplicates) { dictionary.Remove(s); } return dictionary; }
下面的代码片段对应于自定义控制选择器的命名空间。我们将从TTP请求消息中获取命名空间和控制器,然后在字典中查找匹配的控制器。为了匹配对应关系我们构建了匹配映射:<namespace name>:<controller name>
public HttpControllerDescriptor SelectController(HttpRequestMessage request) { IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Get the namespace and controller variables from the route data. string namespaceName = GetRouteVariable<string>(routeData, “namespace”); if (namespaceName == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } string controllerName = GetRouteVariable<string>(routeData, “controller”); if (controllerName == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Find a matching controller. string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, controllerName); HttpControllerDescriptor controllerDescriptor; if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) { return controllerDescriptor; } else if (_duplicates.Contains(key)) { throw new HttpResponseException( request.CreateErrorResponse(HttpStatusCode.InternalServerError, "Multiple controllers were found that match this request.")); } else { throw new HttpResponseException(HttpStatusCode.NotFound); } } // Get a value from the route data, if present. private static T GetRouteVariable<t>(IHttpRouteData routeData, string name) { object result = null; if (routeData.Values.TryGetValue(name, out result)) { return (T)result; } return default(T); }
下面是利用基于名称空间的 WebAPI 版本控制的代码片段。你可以注意到HTTP请求带有指定的版本和控制器名称。
static void RunClient() { HttpClient client = new HttpClient(); client.BaseAddress = _baseAddress; using (HttpResponseMessage response = client.GetAsync("api/v1/values").Result) { response.EnsureSuccessStatusCode(); string content = response.Content.ReadAsStringAsync().Result; Console.WriteLine("Version 1 response: '{0}' ", content); } using (HttpResponseMessage response = client.GetAsync("api/v2/values").Result) { response.EnsureSuccessStatusCode(); string content = response.Content.ReadAsStringAsync().Result; Console.WriteLine("Version 2 response: '{0}' ", content); } }
http://blogs.msdn.com/b/webdev/archive/2013/03/08/using-namespaces-to-version-web-apis.aspx
https://mathieu.fenniak.net/aint-nobody-got-time-for-that-api-versioning/
http://bitoftech.net/2013/12/16/asp-net-web-api-versioning-strategy/
Web API的版本控制对我来说是我曾想到应提供给公众的服务中一个所必需的。实现 Web API 版本控制很有趣。我真的很爱即插即用或HTTP控制器选择器的更换。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务