Waittingforyou 2017-03-06
关web api的版本控制网上有很多,如Web API 版本控制的几种方式 Web API 版本化的介绍 但是具体的code并不多,或者说可以run的demo 不多。
版本控制如果项目一开始还好做关键是很多项目都在中后期才引入版本控制。如:
如这里的ValuesController先前是没有版本控制的,可能项目运行一段时间后才需要增加版本控制,不得影响已有的使用哦。
版本控制的原理主要就是如何确定controler。新建VersionHttpControllerSelector如下:
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); // 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); 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); } } // 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; } // 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; } }
同时需要修改WebApiConfig.cs如下:
public static class WebApiConfig { public static void Register(HttpConfiguration config) { config.Routes.MapHttpRoute( name: "DefaultVersion", routeTemplate: "api/{version}/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Services.Replace(typeof(IHttpControllerSelector), new VersionHttpControllerSelector((config))); } } namespace WebApi.Controllers { public class ValuesController : ApiController { public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("This is a value response.") }; } } } namespace WebApi.Controllers.V1 { public class ValuesController : ApiController { public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("This is a V1 response.") }; } } } namespace WebApi.Controllers.V2 { public class ValuesController : ApiController { public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("This is a V2 response.") }; } } }
我的demo中的version可以从url获取也可以从request header中获取。至于实际中把version放在url还是request中还的根据实际情况。 如标准中推荐放到request header中,但是实际放在url中比较一目了然。并且在跨域的时候可能 减少预请求。
再强调一下 这里的版本控制主要是控制controler的版本,也就是SelectController方法。