为了 顺利迭代升级,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 - ];
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里获取。

           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了。

电商系统架构总结4(webapi 版本控制)的更多相关文章

  1. 电商系统架构总结1(EF)

    最近主导了一个电商系统的设计开发过程,包括前期分析设计,框架搭建,功能模块的具体开发(主要负责在线支付部分),成功上线后的部署维护,运维策略等等全过程. 虽然这个系统不是什么超大型的电商系统 数亿计的 ...

  2. 电商系统架构总结3(webapi授权机制)

    三 Web API 授权方式 web api的客户端,包括 android,ios,h5,自然对访问权限要加上授权机制.对于h5,要求把h5站点和web api部署在同一个域名下,然后对web api ...

  3. 电商系统架构总结2(Redis)

    二  Redis缓存 考虑到将来服务器的升级扩展,使用redis代替.net内置缓存是比较理想的选择.redis是非常成熟好用的缓存系统,安装配置非常简单,直接上官网下载安装包 安装启动就行了. 1 ...

  4. 电商系统的演变可以看出架构演变 Dubbo入门 远程过程调用 需要解决的问题

    Dubbo入门---搭建一个最简单的Demo框架 - CSDN博客 https://blog.csdn.net/noaman_wgs/article/details/70214612 Dubbo背景和 ...

  5. 属性 每秒10万吞吐 并发 架构 设计 58最核心的帖子中心服务IMC 类目服务 入口层是Java研发的,聚合层与检索层都是C语言研发的 电商系统里的SKU扩展服务

    小结: 1. 海量异构数据的存储问题 如何将不同品类,异构的数据统一存储起来呢? (1)全品类通用属性统一存储: (2)单品类特有属性,品类类型与通用属性json来进行存储: 2. 入口层是Java研 ...

  6. 集DDD,TDD,SOLID,MVVM,DI,EF,Angularjs等于一身的.NET(C#)开源可扩展电商系统–Virto Commerce

    今天一大早来看到园友分享的福利<分享一个前后端分离方案源码-前端angularjs+requirejs+dhtmlx 后端asp.net webapi>,我也来分享一个吧.以下内容由笔者写 ...

  7. 基于SpringBoot+MyBatis实现一套电商系统

    项目介绍 mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现. 前台商城系统包含首页门户.商品推荐.商品搜索.商品展示.购物车.订单流程.会员中心 ...

  8. (1)dotnet开源电商系统-brnshop&brnMall 和老外开发的nopCommerce(dotnet两套电商来PK--第一篇)

    一直想做电商软件,但是实在不想学PHP了,所以前后关注了这两个开源电商系统.一个是国人出品的,一个据说是俄罗斯人写得(不知道对不对).目前两个开源软件都在学习了解中,以下的博文可能会涉及到这两套系统, ...

  9. SpringBoot+Security+MyBatis+ES+MQ+Redis+Docker+Vue的电商系统

    今天鹏哥给大家推荐的项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现. 前台商城系统包含首页门户.商品推荐.商品搜索.商品展示.购物车.订单流程.会员中 ...

随机推荐

  1. 为什么 PCB 生产时推荐出 Gerber 给工厂?

    为什么 PCB 生产时推荐出 Gerber 给工厂? 事情是这样的,有一天电工王工,画了一块 PCB,发给 PCB 板厂. 过了几天 PCB 回来了,一看不对呀,这里的丝印怎么少了,那里怎么多了几条线 ...

  2. 我发起了一个 .Net 平台上的 直播平台 开源项目 BalaBala

    直播平台, 需要解决的 技术点 是 2 个: 1  直播数据 的 传输 和 在 客户端 的 播放 2  大并发 关于 网络通信, 数据传输, 可以参考 <利用 MessageRPC 和 Shar ...

  3. 一个 CPU 核 开多少个 线程 比较合适 ?

    一个 CPU 核 开多少个 线程 比较合适 ? 这是一个 线程池 的 问题 . 我之前也 反对 过 线程池, 因为我认为 线程池 影响了 对 用户 的 实时响应性 . 我也认为, 分时 (对 CPU ...

  4. js正则表达式只能是数字、字母或下划线

    //只能是数字.字母或下划线 function isValid(str) { var reg = /^\w+$/g; return reg.test(str); }

  5. CenterOS下安装NodeJS

    1. 首先下载NodeJS的版本 wget https://nodejs.org/dist/v8.1.3/node-v8.1.3-linux-x64.tar.gz 我这里下载的是v8.1.3 版本 n ...

  6. cat命令详解

    命令cat cat 命令用于连接文件并打印到标准输出设备上 语法格式: cat [-AbeEnstTuv] [--help] [--version] fileName 参数说明: -n 或 --num ...

  7. RHEL 6.5系统安装配置图解教程(rhel-server-6.5)

    转自:http://www.jb51.NET/os/128752.html 说明: 截止目前RHEL 6.x最新版本为RHEL 6.5,下面介绍RHEL 6.5的具体安装配置过程 服务器相关设置如下: ...

  8. minicom的安装及配置

    1. sudo apt-get install minicom 2. 配置 (1). sudo minicom –s (2). (3). 按“A”配置设备,再按回车保存.按”F”,把留空改为NO.回车 ...

  9. 顶级域名和二级域名共享cookie及相互删除cookie

    在CSDN看到一个cookie设置domain时,如何删除的问题, 自己也只知道domain设置为顶级域名时可以被其他二级域名共享,但是如何删除还是有一点搞不清楚,所以特意测试了下cookie和dom ...

  10. [NEWS]Microsoft expands partnerships with AOL and AppNexus, Bing to power search for AOL properties

    http://advertising.microsoft.com/en/blog/33906/microsoft-expands-partnerships-with-aol-and-appnexus- ...