随笔 - 35  文章 - 0  评论 - 121  阅读 - 56623

电商系统架构总结4(webapi 版本控制)

    为了 顺利迭代升级,web api 在维护过程是不断升级的,但用户是不能强迫他们每次都跟随你去升级,这样会让用户不胜其烦。为了保证不同版本的客户端能同时兼容,在web api接口上加入版本控制就很有必要了。

当然,对于我们开发的代码进行版本控制也有利,不至于陷入混乱。版本参数可以放置在请求的url 作为路由参数的一部分,也可以放在header里。实现的办法是 实现 IHttpControllerSelector 并在WebApiConfig的注册方法里进行替换。

复制代码
    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;
        }
    }
View Code
复制代码

重点是SelectController方法,从http请求里找出合适版本的controller。我这里兼容了从路由和header里获取版本,先从路由里获取,没有再从header里获取。

复制代码
           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文件调用代码如下:

   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,如此类推,

客户端在header里加上参数 versoin=v1/v2... 就可以指定使用不同版本的api了。

posted on   lindping  阅读(422)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示