2KB项目,专业的源码交易网站 帮助 收藏 每日签到

Web API 版本化的介绍

  • 时间:2019-01-23 18:37 编辑:2KB 来源:2KB.COM 阅读:324
  • 扫一扫,手机访问
  • 分享
摘要: 英文原文:Int
英文原文:Introduction to Web API Versioning

介绍

本文中,我们来讨论一下Web API版本化的问题,以及同类需求。

当你准备提供公共服务接口时,Web API的版本化问题会显得非常必要。它是我们为API提供向前、向后兼容支持的唯一途径。通过版本化,你能够不影响现有用户的使用,而且不会破坏那些依赖于你提供的API的应用。

默认的Web API的路由逻辑是:通过类名来查找controller。Controller的选取则是在Web API内部通过选择器 DefaultHttpControllerSelector(它实现了 IhttpControllerSelector.SelectController)来处理。我们无法通过默认的controller选择器实现Web API的版本化,但却可以在运行时通过自定义插件实现正确的版本选择。实现 IhttpControllerSelector接口就可以轻松创建自定义的HTTP controller选择器。

来看看两种不同的Web API版本化方式,你可以自己决定哪一个是符合需求的最佳方式。一个是利用命名空间,一个是基于自定义HTTP头部信息的机制。

背景

本人在Web API方面知识渊博、有很好的理解和开发经验。

代码示例

下面的代码片段是为了得到HTTP自定义头部的版本号。在这我们设定Web API的默认版本号总是空的。

private string GetVersionFromHTTPHeader(HttpRequestMessage request)
{
      if (request.Headers.Contains("version"))
      {
          var versionHeader = request.Headers.GetValues("version").FirstOrDefault();
          if (versionHeader != null)
          {
               return versionHeader;
          }
      }

      return string.Empty;
}

下面的代码片段用于获取基于MIME类型的版本号。你能任选其一来获取版本号。

private string GetVersionFromAcceptHeaderVersion(HttpRequestMessage request)
 {
       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("version", StringComparison.OrdinalIgnoreCase))
                                    .FirstOrDefault();

                   if (version != null)
                   {
                       return version.Value;
                   }
                   return string.Empty;
             }
       }
       return string.Empty;
 }

下面的代码片段是自定义Web API 控制选择器,它实现了IHttpControllerSelector.SelectController函数。从下面的代码中你能注意到我们会基于匹配到的控制映射或者通过版本来返回控制器描述信息。

public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
        var controllers = GetControllerMapping(); 

        var routeData = request.GetRouteData();

        var controllerName = routeData.Values["controller"].ToString();

        HttpControllerDescriptor controllerDescriptor;

        if (controllers.TryGetValue(controllerName, out controllerDescriptor))
        {
                var version = GetVersionFromHTTPHeader(request);

                if (!string.IsNullOrEmpty(version))
                {
                    var versionedControllerName = string.Concat(controllerName, "V", version);

                    HttpControllerDescriptor versionedControllerDescriptor;
                    if (controllers.TryGetValue(versionedControllerName, out versionedControllerDescriptor))
                    {
                        return versionedControllerDescriptor;
                    }
                }

                return controllerDescriptor;
        }

        return null;
}

那么接下来呢?

上面我们已经实现了自定义的控制器选择器,现在必须让它替换默认的以使生效。只要在 WebApiConfig 中添加一行就行。

public static class WebApiConfig
{
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            // 在这替换控制器选择器
            config.Services.Replace(typeof(IHttpControllerSelector), new CustomControllerSelector((config)));
        }
}

以下是HTTP请求和响应的快照。你可以注意到在HTTP请求头中被传递的版本信息“version”。

我们应该看到过有关基于命名空间的 Web API 版本控制的简介。这是一种最常见的被用到的技术,你会看到最流行的服务提供商也在使用这种技术。

下面的代码片段通过返回基于命名空间的控制器描述符来实现查找功能。

private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary()
{
            var dictionary = new Dictionary<string,>(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);

                var key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", segments[segments.Length - 1], 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;
}

下面的代码片段对应于自定义控制选择器的命名空间。我们将从TTP请求消息中获取命名空间和控制器,然后在字典中查找匹配的控制器。为了匹配对应关系我们构建了匹配映射:<namespace name>:<controller name>

public HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
            IHttpRouteData routeData = request.GetRouteData();
            if (routeData == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Get the namespace and controller variables from the route data.
            string namespaceName = GetRouteVariable<string>(routeData, “namespace”);
            if (namespaceName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            string controllerName = GetRouteVariable<string>(routeData, “controller”);
            if (controllerName == null)
            {
                throw new HttpResponseException(HttpStatusCode.NotFound);
            }

            // Find a matching controller.
            string key = String.Format(CultureInfo.InvariantCulture, "{0}.{1}", namespaceName, 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);
            }
}

// 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);
}

下面是利用基于名称空间的 WebAPI 版本控制的代码片段。你可以注意到HTTP请求带有指定的版本和控制器名称。

static void RunClient()
{
      HttpClient client = new HttpClient();
      client.BaseAddress = _baseAddress;

      using (HttpResponseMessage response = client.GetAsync("api/v1/values").Result)
      {
          response.EnsureSuccessStatusCode();
          string content = response.Content.ReadAsStringAsync().Result;
          Console.WriteLine("Version 1 response: &apos;{0}&apos;
", content);
      }

      using (HttpResponseMessage response = client.GetAsync("api/v2/values").Result)
      {
          response.EnsureSuccessStatusCode();
          string content = response.Content.ReadAsStringAsync().Result;
          Console.WriteLine("Version 2 response: &apos;{0}&apos;
", content);
      }
}

参考资料:

http://blogs.msdn.com/b/webdev/archive/2013/03/08/using-namespaces-to-version-web-apis.aspx

https://mathieu.fenniak.net/aint-nobody-got-time-for-that-api-versioning/

http://aspnet.codeplex.com/SourceControl/changeset/view/dd207952fa86#Samples/WebApi/NamespaceControllerSelector/

http://bitoftech.net/2013/12/16/asp-net-web-api-versioning-strategy/

兴趣点

Web API的版本控制对我来说是我曾想到应提供给公众的服务中一个所必需的。实现 Web API 版本控制很有趣。我真的很爱即插即用或HTTP控制器选择器的更换。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。


2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务

  • 全部评论(0)
资讯详情页最新发布上方横幅
最新发布的资讯信息
【计算机/互联网|】Nginx出现502错误(2020-01-20 21:02)
【计算机/互联网|】网站运营全智能软手V0.1版发布(2020-01-20 12:16)
【计算机/互联网|】淘宝这是怎么了?(2020-01-19 19:15)
【行业动态|】谷歌关闭小米智能摄像头,因为窃听器显示了陌生人家中的照片(2020-01-15 09:42)
【行业动态|】据报道谷歌新闻终止了数字杂志,退还主动订阅(2020-01-15 09:39)
【行业动态|】康佳将OLED电视带到美国与LG和索尼竞争(2020-01-15 09:38)
【行业动态|】2020年最佳AV接收机(2020-01-15 09:35)
【行业动态|】2020年最佳流媒体设备:Roku,Apple TV,Firebar,Chromecast等(2020-01-15 09:31)
【行业动态|】CES 2020预览:更多的流媒体服务和订阅即将到来(2020-01-08 21:41)
【行业动态|】从埃隆·马斯克到杰夫·贝佐斯,这30位人物定义了2010年代(2020-01-01 15:14)
联系我们

Q Q: 7090832

电话:400-0011-990

邮箱:7090832@qq.com

时间:9:00-23:00

联系客服
商家入住 服务咨询 投拆建议 联系客服
0577-67068160
手机版

扫一扫进手机版
返回顶部