Evil 域

当Evil遇上先知

导航

实战Bing MAP REST服务的封装、调用

Posted on 2011-08-30 05:58  Saar  阅读(2681)  评论(2编辑  收藏  举报

什么是REST服务?

如果您不知道答案,那么,请您花5分钟时间来浏览一下这个教程:

Learn REST: A Tutorial

(就5分钟,您就读一下吧。)

image

(图片来自Bing搜索)

 

您已经知道了,由于REST是基于HTTP协议的服务,它有以下优点

  • 平台无关——例如用Unix架的服务器提供的服务,可以在PC、MAC等其它终端,甚至手机上调用;
  • 语言无关——C#架的服务,可以被Java甚至JavaScript调用;
  • 便于跨防火墙存取数据;

接下来,用一个例子,来说明怎么设计一组类(三个),用来封装调用REST服务

(如果你想知道用.NET怎么创建REST服务?请花30分钟左右看看:An Introduction To RESTful Services With WCF

 

任务:封装Bing地图的REST服务,来计算一个两地的驾驶距离。(你知道从徐家汇开车到人民广场要多少路吗? Smile

要调用一个REST服务,要知道两个要素:

  • URL模板——发送请求的字符串的格式;
  • XML响应——用XML表达的返回的结果;

Bing Map的URL模板如下(来源于MSDN):

http://dev.virtualearth.net/REST/v1/Routes/travelMode?wayPoint.1=wayPoint1&heading=heading&waypoint.2=wayPoint2&wayPoint.n=wayPointn
&optimize=optimize&avoid=avoidOptions&distanceBeforeFirstTurn=distanceBeforeFirstTurn&timeType=timeType&dateTime=dateTime&maxSolutions=maxSolutions
&routePathOutput=routePathOutput&tolerances=tolerance1,tolerance2,tolerancen&distanceUnit=distanceUnit&key=BingMapsKey

-.-||好长的字符串啊。

对应的XML响应就不“如下”了,大家根据需要看MSDN吧。为了便于直观理解,MSDN同时提供了一些例子

另外,对于Bing地图的开发,还需要有一个对应的应用程序Key,这个Key用于识别调用Bing地图服务的应用程序。大家可以在这儿免费申请和查看开发者KEY

我的Key是:我不告诉你。您就自己申请一个吧

 

这样,准备就做得差不多了。

既然是实战,达成目标优先,因此,设计一个类来达成目标。

这个类得有一个方法可以计算距离:

  • 它的返回值是一个double类型;
  • 它需要根据参数来计算距离,最直观的参数就是URL;
  • 我们可以用WebClient来触发一个进行一个Http请求;
  • 返回的结果是HTML,因此,我们LinqToXml来进行处理;

所以,就有了如下的代码:

   1:  using System;
   2:  using System.Linq;
   3:  using System.Net;
   4:  using System.Xml.Linq;
   5:   
   6:  namespace BingMapRouteDistanceDemo
   7:  {
   8:      public class RouteDistanceCalculator
   9:      {
  10:          public double GetDistance(string requestUrl)
  11:          {
  12:              WebClient client = new WebClient();
  13:              string resultXML = client.DownloadString(requestUrl);
  14:              XElement xml = XElement.Parse(resultXML);
  15:              string resultString = xml.Descendants("TravelDistance").First().Value;
  16:              return Convert.ToDouble(resultString);
  17:          }
  18:      }
  19:  }

嗯,您是不是已经准备拍砖了?Smile (千万冷静,除非您正想换显示器愁没借口……)

至少,这个类基本上能够正常用(其实是用不起来的)。如果把这样一个类应用到项目中,会有哪些问题?

您一定已经看出来了,至少有两个明显的问题(欢迎补充)

  • 输入参数是一个字符串——而且是一个长长的字符串
    • 构建这个字符串的任务交给用户,效率低,函数难用且极易出错;
    • 这么一串长长的字符串中出了错,你也不知道到底哪儿错了,得一个一个排查。

让我们看看,如果要调用这个类,大概的代码是什么样子的:

   1:              RouteDistanceCalculator route = new RouteDistanceCalculator();
   2:              Console.WriteLine(
   3:                  route.GetDistance(
   4:                  @"http://dev.virtualearth.net/REST/V1/Routes?wp.0=37.779160067439079,-122.42004945874214&wp.1=32.715685218572617,-117.16172486543655&key=BingMapsKey")
   5:                  );

如果您熟悉数据库编程,想想手动编写数据库连接字符串的感觉就有体会了。

  • 当前类既处理输入(URL),还处理输出(XML),不符合单一责任原则——不解释Smile

在封装REST服务时,这两个问题也较普遍。我们分别来解决它们。

 

3d_wallpaper_gallery20290

(图片来自Bing搜索)

 

首先,我们可以构建一个专门用于创建URL请求的类,这个类有以下特征:

  • 把提供给用户输入的每一个参数对应于一个属性;参数的值对应于属性的值;
  • 暴露一个方法,用于返回最终的URL;

示意代码如下,文后附完整项目。

   1:      public class RouteDistanceCalculator
   2:      {
   3:          // Pay attention to change of the parameter type.
   4:          public double GetDistance(RouteRequestBuilder requestBuilder)
   5:          {
   6:              WebClient client = new WebClient();
   7:              // Builder have the responsibility of forming query string.
   8:              string resultXML = client.DownloadString(requestBuilder.GetQueryString());
   9:  ...
  10:          }
  11:      }
  12:   
  13:      public class RouteRequestBuilder
  14:      {
  15:          #region Public Method
  16:          /// <summary>
  17:          /// Go over properties one by another to construct the query uri.
  18:          /// </summary>
  19:          public virtual string GetQueryString()
  20:          {
  21:              StringBuilder resultBuilder = new StringBuilder();
  22:              resultBuilder.Append(BasicQueryUri);
  23:              // Construct for travel mode
  24:              resultBuilder.AppendFormat("{0}?", TravelMode.ToString());
  25:              // ...
  26:              return resultBuilder.ToString();
  27:          }
  28:          #endregion
  29:   
  30:          #region Query conditions
  31:          public IList<Location> WayPoints { get; set; }
  32:          public TravelMode TravelMode { get; set; }
  33:          public TransitTimeType TravelTimeType { get; set; }
  34:          public DateTime TravelDateTime { get; set; }
  35:          #endregion
  36:      }
RouteRequestBuilder就是这么一个前文描述的类。其分为两大部分,15到28行Public Method暴露了一返回URL的方法GetQueryString;而30到35行的Query Conditions把需要用户输入的查询条件做成的属性。
这样,用户就只要创建对象并给它的属性赋值,再不用担心构建字符串的问题了。
 
接下来,看输出结果。XML是REST服务输出的普遍形式。如果像文中一样,直接处理XML,一旦返回的XML格式调整或者扩展,我们需要一个方法一个方法的修改。
假如我们要多个结果,并且结果还被多处调用,那么,对于项目来说,结果是灾难性的。
所以,您已经想到了,可以把处理结果的逻辑封装起来形成一个类,这个类有如下特征:
  • 有一个方法或者属性,可以获取到原始的结果——例子中webClient返回的XML;
  • 把用户关心的内容,以属性的形式暴露出来;
  • 用户无须知道XML与属性值的对应关系;

由于用户不需要知道XML的细节,因此,对于这个类来说,如果把结果保存到ResultString属性中,我们不需要暴露它的读方法,意味着这是一个“只写”属性,那么,我们就可以把它做成一个方法

在这里,我们只关心行驶距离,因此,它应该有一个叫Distance的属性,返回一个double类型的值。

形成的类主要代码如下:

   1:      public class RouteResponseResolver
   2:      {
   3:          #region Properties
   4:          /// <summary>
   5:          /// Get distance of the route.
   6:          /// </summary>
   7:          public double Distance
   8:          {
   9:              get
  10:              {
  11:                  return GetDistance();
  12:              }
  13:          }
  14:          #endregion
  15:   
  16:          #region Private Methods
  17:          private double GetDistance()
  18:          {
  19:              // Covert xml into property
  20:          }
  21:          #endregion
  22:   
  23:          #region Public Methods
  24:          /// <summary>
  25:          /// Update the result string to resolve with
  26:          /// </summary>
  27:          /// <param name="resultString"></param>
  28:          public void UpdateResultString(string resultString)
  29:          {
  30:              // Update result string
  31:          }
  32:          #endregion
  33:      }

对应的,我们就需要修改一下RouteDistanceCalculator类,把RouteResponseResolver也作为一个参数传入:

   1:      public class RouteDistanceCalculator
   2:      {
   3:          public double GetDistance(RouteRequestBuilder requestBuilder, RouteResponseResolver resultResolver)
   4:          {
   5:              WebClient client = new WebClient();
   6:              string resultXML = client.DownloadString(requestBuilder.GetQueryString());
   7:              resultResolver.UpdateResultString(resultXML);
   8:              return resultResolver.Distance;
   9:          }
  10:      }

费了这么多神,我们得到了什么?让我们再来看一下,现在的用户如何来调用RouteDistanceCalculator.

   1:          static void Main(string[] args)
   2:          {
   3:              RouteDistanceCalculator route = new RouteDistanceCalculator();
   4:              RouteRequestBuilder requestBuilder = new RouteRequestBuilder()
   5:              {
   6:                  TravelMode = TravelMode.Driving,
   7:                  WayPoints = new List<Location>()
   8:                  {
   9:                      // Location for Xujiahui, Shanghai
  10:                      new Location(){ Latitude=31.1970231682062, Longitude=121.441092118621},
  11:                      // Location for people's square
  12:                      new Location(){ Latitude=31.233944, Longitude=121.467813}
  13:                  }
  14:              };
  15:              RouteResponseResolver responseResolver = new RouteResponseResolver();
  16:              var distance = route.GetDistance(requestBuilder, responseResolver);
  17:              Console.WriteLine("Drive distance between Xujiahui & People's square are: {0} km.", distance);
  18:          }

可以看到,通过RouteRequestBuilder类,我们指定了路径类型为驾程,一共涉及到两个点,分别是徐家汇和人民广场;我们用responseResolver来解析查询到的结果……

  • 由于请求不再要求用户输入字符串,出错机率减小,即使出错,调试也相对容易许多;
  • 对于RouteDistanceCalculator来说,请求和响应解析的类是可以被替换的;

最后,揭晓徐家汇到人民广场的车程:

Garfield-drives-a-car

(图片来自Bing搜索)

DistanceFromXujiahuiToPeoplesSquare

最后的后面:

下一步……在这里的请求生成和响应解析类都是具体类,如果把它们抽象成接口,会进一步降低它们之间的耦合性

 

小结一下:

一个REST服务总可以封装成三个类:

  • 调用类——调用以下两个类;
  • 请求生成类——以属性形式接受查询条件的输入,提供生成请求URL的方法;
  • 响应解析类——用于解析返回的结果,以属性返回用户感兴趣的数据;

如果将后两个类抽象成对应接口,即可实现请求、响应与调用类之间的解耦合。

 

资源:

Learn REST: A Tutorial

An Introduction To RESTful Services With WCF

Bing Map开发中心

Bing Map SDK

示例代码:为了方便大家看上下类的关系,我把所有的类全放在一个文件里了,一般不推荐这样做;

另外,使用示例前,请把文件底部的BingMapKey换成您自己的Key。