WebApiClient百度地图服务接口实践

1. 文章目的

随着WebApiClient的不断完善,越来越多开发者选择WebApiClient替换原生的HttpClient,然而在应用到实际项目中多多少少会遇到一些项目结合上的疑问和困难,本文将以WebApiClient使用者的身份,在Asp.net core mvc项目中使用WebApiClient来请求百度地图服务接口,以展示WebApiClient的使用技巧。

2. 百度地图服务接口

  1. 静态图接口
    http://api.map.baidu.com/staticimage/v2?ak=你的密钥&mcode=666666&center=116.403874,39.914888&width=300&height=200&zoom=11

  2. 坐标转换接口
    http://api.map.baidu.com/geoconv/v1/?coords=114.21892734521,29.575429778924&from=1&to=5&ak=你的密钥

3. 接口分析

通过分析百度地图的接口,我们发现:

  • 所有接口都在api.map.baidu.com这个域名上;
  • ak参数是一个客户端身份标识的参数,所有请求接口都需要附加这个ak值;
  • 接口中需要的116.403874,39.914888这种参数值,实际是(经度,纬度),为两个值组成;
  • from和to是枚举数值类型;

在我们进行Coding的时候,应该重点考虑这些共性,以减少重复的工作内容。

4. 接口声明

4.1 公共域名

[HttpHost("http://api.map.baidu.com/")]
public interface IBdMapApi : IHttpApi
{
}

4.2 公共的AK参数

我们要实现一个接口级或方法级的ApiAction特性,用于给请求路径增加公共的ak参数:

/// <summary>
/// 表示百度AK信息
/// </summary>
public class AkAttribute : ApiActionAttribute
{
    private readonly string ak;

    /// <summary>
    /// 百度AK信息
    /// 执行时追加到请求query
    /// </summary>
    /// <param name="ak"></param>
    public AkAttribute(string ak)
    {
        this.ak = ak;
    }

    /// <summary>
    /// 请求前
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public override async Task BeforeRequestAsync(ApiActionContext context)
    {
        context.RequestMessage.AddUrlQuery("ak", this.ak);
        await Task.CompletedTask;
    }

然后把Ak特性追加到接口上:

[HttpHost("http://api.map.baidu.com/")]
[Ak("qde9uxuEhwMlngvTbWGo3BIQOKfqvjdc")]
public interface IBdMapApi : IHttpApi
{
}

4.3 静态图staticimage接口

参照接口文档,声明最初的StaticImage接口:

[HttpGet("staticimage/v2")]
ITask<Stream> StaticImageAsync(
    string center, 
    int width = 300, 
    int height = 200, 
    int zoom = 11, 
    int mcode = 666666);

目前center参数是string类型,约束性非常弱,与接口要求的(经度,纬度)这种格式数据差异比较大。所以我们应该定一个继承自IApiParameterable的BdLocation类型,将传入的经度和纬度转换为这种文本格式:

/// <summary>
/// 表示位置信息
/// </summary>
public class BdLocation : IApiParameterable
{
    /// <summary>
    /// 获取位置值
    /// </summary>
    public string Value { get; private set; }

    /// <summary>
    /// 位置信息
    /// 执行时追加到请求query
    /// </summary>
    /// <param name="lng">经</param>
    /// <param name="lat">纬</param>
    public BdLocation(decimal lng, decimal lat)
    {
        this.Value = $"{lng},{lat}";
    }

    public override string ToString()
    {
        return this.Value;
    }

    /// <summary>
    /// 自解释模式
    /// </summary>
    /// <param name="context"></param>
    /// <param name="parameter"></param>
    /// <returns></returns>
    Task IApiParameterable.BeforeRequestAsync(ApiActionContext context, ApiParameterDescriptor parameter)
    {
        context.RequestMessage.AddUrlQuery(parameter.Name, this.Value);
        return Task.CompletedTask;
    }
}

修改后StaticImage接口修改为:

[HttpGet("staticimage/v2")]
ITask<Stream> StaticImageAsync(
    BdLocation center,
    int width = 300,
    int height = 200,
    int zoom = 11,
    int mcode = 666666);

4.4 坐标转换Geoconv接口

依照文档,编写出最初的接口

[HttpGet("geoconv/v1/")]
ITask<string> GeoconvAsync(
    string coords, 
    int from = 1, 
    int to = 5);

和StaticImage接口一样,我们还需要合理修改这个接口的参数约束,coords实际为BdLocation类型, from和to可以修改为枚举类型,返回值string修改为强类型的模型,修改后的接口为:

[HttpGet("geoconv/v1/")]
ITask<BdResult<BdPoint[]>> GeoconvAsync(
    BdLocation coords,
    BdFrom from = BdFrom.wgs84,
    BdTo to = BdTo.bd09ll);

4.5 完整的接口声明

/// <summary>
/// 定义百度地图接口
/// </summary>
[Ak("qde9uxuEhwMlngvTbWGo3BIQOKfqvjdc")]
[HttpHost("http://api.map.baidu.com/")]
public interface IBdMapApi : IHttpApi
{
    /// <summary>
    /// 静态图
    /// </summary>
    /// <param name="center"></param>
    /// <param name="width"></param>
    /// <param name="height"></param>
    /// <param name="zoom"></param>
    /// <param name="mcode"></param>
    /// <returns></returns>
    // http://api.map.baidu.com/staticimage/v2?ak=你的密钥&mcode=666666&center=116.403874,39.914888&width=300&height=200&zoom=11  
    [HttpGet("staticimage/v2")]
    ITask<Stream> StaticImageAsync(
        BdLocation center,
        int width = 300,
        int height = 200,
        int zoom = 11,
        int mcode = 666666);

    /// <summary>
    /// 坐标转换
    /// </summary>
    /// <param name="coords"></param>
    /// <param name="from"></param>
    /// <param name="to"></param>
    /// <returns></returns>
    // http://api.map.baidu.com/geoconv/v1/?coords=114.21892734521,29.575429778924&from=1&to=5&ak=你的密钥 //GET请求
    [HttpGet("geoconv/v1/")]
    ITask<BdResult<BdPoint[]>> GeoconvAsync(
        BdLocation coords,
        BdFrom from = BdFrom.wgs84,
        BdTo to = BdTo.bd09ll);
}

5. 接口的依赖注入

WebApiClient的HttpApiClient创建的代理实例,适合使用单例模式,在支持依赖注入的项目开发中,应尽量使用依赖注入来完成HttpApiClient的创建和生命周期管理。

5.1 Asp.net core的依赖注入

在ConfigureServices方法里添加IBdMapApi的注入配置

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(HttpApiClient.Create<IBdMapApi>());
    ......
}

如果项目里声明了很多接口,比如IBaiduApi、ITengxunApi等等,可以循环批量注入:

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    var apis = this.GetType().Assembly.GetTypes().Where(item => typeof(IHttpApi).IsAssignableFrom(item));
    foreach (var api in apis)
    {
        services.AddSingleton(api, HttpApiClient.Create(api, new HttpApiConfig()));
    }
}

5.2 接收和使用IBdMapApi代理实例

public class HomeController : Controller
{
    private readonly IBdMapApi bdMapApi;

    public HomeController(IBdMapApi bdMapApi)
    {
        this.bdMapApi = bdMapApi;
    }

    public async Task<IActionResult> Index()
    {
        var image = await this.bdMapApi.StaticImageAsync(new BdLocation(116.403874m, 39.914888m));
        var geoResult = await this.bdMapApi.GeoconvAsync(new BdLocation(116.403874m, 39.914888m));

        return View();
    }
}

6. 监视请求提交的内容

WebApiClient对Http请求进行的高度抽象,只有声明,没有实现,在没有熟悉WebApiClient的情况下,我们开发中可能需要在请求发送的内容进行监视,从而知道是否符合服务器的接口数据要求。在不使用第三方工具比如Fiddler等的情况下,我们可以为接口修饰一个自定义过滤器,在过滤器里实现访打印求消息内容的能力。

6.1 定义TraceFilter过滤器

/// <summary>
/// 请求内容追踪过滤器
/// </summary>
public class TraceFilter : ApiActionFilterAttribute
{
    /// <summary>
    /// 打印请求内容
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public override async Task OnBeginRequestAsync(ApiActionContext context)
    {
        var request = await context.RequestMessage.ToStringAsync();
        System.Diagnostics.Debug.Print(request);
    }
}

6.2 接口关联TraceFilter

/// <summary>
/// 定义百度地图接口
/// </summary>
[TraceFilter]
[Ak("qde9uxuEhwMlngvTbWGo3BIQOKfqvjdc")]
[HttpHost("http://api.map.baidu.com/")]
public interface IBdMapApi : IHttpApi
{
}

6.3 查看请求内容

开启程序调试,输出窗口里打印

GET /staticimage/v2?ak=qde9uxuEhwMlngvTbWGo3BIQOKfqvjdc&center=116.403874%2c39.914888&width=300&height=200&zoom=11&mcode=666666 HTTP/2.0
Host: api.map.baidu.com

GET /geoconv/v1/?ak=qde9uxuEhwMlngvTbWGo3BIQOKfqvjdc&coords=116.403874%2c39.914888&from=1&to=5 HTTP/2.0
Host: api.map.baidu.com

7. 结束语

博主为WebApiClient库的作者,致力于站在使用者的角度去设计WebApiClient,欢迎大家给WebApiClient提建议。

posted @ 2018-08-01 09:16  jiulang  阅读(3766)  评论(4编辑  收藏  举报