为了 顺利迭代升级,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. 求Sn=a+aa+aaa+aaaa+aaaaa的前5项之和,其中a是一个数字

    思路:所求和为一个数字的前n项和,例如前4项和就是从4+44+444+4444,一直加到第4位,为4个4.所以可以用一个循环来表示每一项的数字,加到前几项就循环几次.然后将每项进行相加就可以求出总和. ...

  2. Revit api 创建族并加载到当前项目

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  3. js 判断滚动条是否停止滚动

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  4. create-react-app 知识点

    用 create-react-app 创建项目时,最好FQ:否则有些安装包会安装不上,报超时错误. create-react-app 创建的项目,执行yarn eject后,按需引入antd的2种方式 ...

  5. bzoj 2870 最长道路tree——边分治

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2870 关于边分治:https://www.cnblogs.com/Khada-Jhin/p/ ...

  6. ORACLE SQL Developer日期显示格式不全

    如下图,只有月-日-年,不能完整显示小时,分,秒 解决方法: 工具->首选项->数据库->NLS->日期格式: DD-MON-RR 修改为: YYYY-MM-DD HH24:M ...

  7. 如何使用Hanlp加载大字典

        问题 因为需要加载一个 近 1G 的字典到Hanlp中,一开始使用了CustomDictionay.add() 方法来一条条的加载,果然到了中间,维护DoubleArraTre 的成本太高,添 ...

  8. java实现文件的上传和下载

    1. servlet 如何实现文件的上传和下载? 1.1上传文件 参考自:http://blog.csdn.net/hzc543806053/article/details/7524491 通过前台选 ...

  9. Git-打标签

    打标签同大多数 VCS 一样,Git 也可以对某一时间点上的版本打上标签.人们在发布某个软件版本(比如 v1.0 等等)的时候,经常这么做.本节我们一起来学习如何列出所有可用的标签,如何新建标签,以及 ...

  10. Linux系统下启动tomcat报错【java.util.prefs.BackingStoreException: Couldn't get file lock】的解决方法

    Linux环境下,启动tomcat报出如题的警告信息,虽然对系统正常使用没有多大影响,但是会导致tomcat的日志垃圾信息很多,而且看起来很不爽... 具体的警告信息如下: Jan , :: PM j ...