爲了 順利迭代升級,web api 在維護過程是不斷升級的,但用戶是不能強迫他們每次都跟隨你去升級,這樣會讓用戶不勝其煩。爲了保證不一樣版本的客戶端能同時兼容,在web api接口上加入版本控制就頗有必要了。html
固然,對於咱們開發的代碼進行版本控制也有利,不至於陷入混亂。版本參數能夠放置在請求的url 做爲路由參數的一部分,也能夠放在header裏。實現的辦法是 實現 IHttpControllerSelector 並在WebApiConfig的註冊方法裏進行替換。web
public class VersionHttpControllerSelector : IHttpControllerSelector { private const string VersionKey = "version"; private const string ControllerKey = "controller"; private readonly HttpConfiguration _configuration; private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers; private readonly HashSet<string> _duplicates; public VersionHttpControllerSelector(HttpConfiguration config) { _configuration = config; _duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase); _controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary); } private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary() { var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase); 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); var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length); string version = segments[segments.Length - 1]; var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version, controllerName); if (version == "Controllers") { key = String.Format(CultureInfo.InvariantCulture, "{0}", controllerName); } // Check for duplicate keys. if (dictionary.Keys.Contains(key)) { _duplicates.Add(key); } else { dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t); } } foreach (string s in _duplicates) { dictionary.Remove(s); } return dictionary; } // 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); } public HttpControllerDescriptor SelectController(HttpRequestMessage request) { IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Get the version and controller variables from the route data. string version = GetRouteVariable<string>(routeData, VersionKey); if (string.IsNullOrEmpty(version)) { version = GetVersionFromHTTPHeaderAndAcceptHeader(request); } string controllerName = GetRouteVariable<string>(routeData, ControllerKey); if (controllerName == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Find a matching controller. string key = String.Format(CultureInfo.InvariantCulture, "{0}", controllerName); if (!string.IsNullOrEmpty(version)) { key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", version, 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); } } public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() { return _controllers.Value; } private string GetVersionFromHTTPHeaderAndAcceptHeader(HttpRequestMessage request) { if (request.Headers.Contains(VersionKey)) { var versionHeader = request.Headers.GetValues(VersionKey).FirstOrDefault(); if (versionHeader != null) { return versionHeader; } } 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(VersionKey, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (version != null) { return version.Value; } return string.Empty; } } return string.Empty; } }
重點是SelectController方法,從http請求裏找出合適版本的controller。我這裏兼容了從路由和header裏獲取版本,先從路由裏獲取,沒有再從header裏獲取。json
IHttpRouteData routeData = request.GetRouteData(); if (routeData == null) { throw new HttpResponseException(HttpStatusCode.NotFound); } // Get the version and controller variables from the route data. string version = GetRouteVariable<string>(routeData, VersionKey); if (string.IsNullOrEmpty(version)) { version = GetVersionFromHTTPHeaderAndAcceptHeader(request); }
private string GetVersionFromHTTPHeaderAndAcceptHeader(HttpRequestMessage request) { if (request.Headers.Contains(VersionKey)) { var versionHeader = request.Headers.GetValues(VersionKey).FirstOrDefault(); if (versionHeader != null) { return versionHeader; } } 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(VersionKey, StringComparison.OrdinalIgnoreCase)) .FirstOrDefault(); if (version != null) { return version.Value; } return string.Empty; } } return string.Empty; }
WebApiConfig文件調用代碼以下:api
public static void Register(HttpConfiguration config) { 。。。 config.Services.Replace(typeof(IHttpControllerSelector), new VersionHttpControllerSelector((config))); }
web api的定義呢,則從命名空間上區分就能夠了。 好比版本號爲V1的 LoginApiController 的命名空間 爲定義爲 xxx.WebAPI.Controllers.V1,版本號爲V2的 LoginApiController 的命名空間 爲定義爲 xxx.WebAPI.Controllers.V2,如此類推,app
客戶端在header里加上參數 versoin=v1/v2... 就能夠指定使用不一樣版本的api了。ide