Skip to content

WebApiClient高级

老九 edited this page Mar 7, 2018 · 33 revisions

1. HttpRequestMessage简说

System.Net.Http.HttpRequestMessage表示一个请求消息,一般而言它包含一个完整的请求数据,主要由请求头和请求体组成,对于Post、Delete、Put等请求,其Content属性包含提交的数据体内容。

WebApiClient的ApiActionContex对象有个RequestMessage对象,是派生于HttpRequestMessage,同时实现了多个Addxxx方法用于给其属性Content对象添加数据内容。

2. HttpClientHandler简说

System.Net.Http.HttpClientHandler是一个与tcp层相关的对象,负责与远程服务器进行tcp连接,将HttpRequestMessage转换为http协议数据发送给服务端,并等待服务端的响应,一般的网络相关配置的证书、代理和认证等在都在这里可以配置。

  • 在.Netframework下,HttpClientHandler每进行一次SendAsync(HttpRequestMessage)操作,都会创建一个与HttpRequestMessage对应的HttpWebRequest实例,然后发送请求,将请求结果转换为HttpResponseMessage;
  • 在Netcore20 windows平台下,HttpClientHandler使用系统的WinHttp组件进行发送请求,在unix系下,使用curl发送请求。

3. HttpClient简说

System.Net.Http.HttpClient必须与HttpClientHandler(或其delegating包装)关联才能使用,一个HttpRequestMessage经过HttpClient之后,HttpClient的一些默认配置会影响到它,比如默认请求头等。HttpClient是使用关联的HttpClientHandler将HttpRequestMessage发送出去,也就是说,完全可以跳过HttpClient而使用HttpClientHandler来发送请求,方法是写一个类,继承于HttpClientHandler并公开一个方法,调用基类的SendAsync方法就可以发送请求。

4. WebApiClient库的HttpClient配置

WebApiClient库是对HttpClient的封装,所有的配置项在HttpApiConfig对象, 实例化HttpApiConfig对象时有个构造器可以传入IHttpClient的实例,而IHttpClient是对System.Net.Http.HttpClient的一个包装接口定义,WebApiClient.Defaults.HttpClient是对IHttpClient接口的一个实现,才下代码是WebApiCient与System.Net.Http.HttpClient的一个衔接:

IHttpClient client = new WebApiClient.Defaults.HttpClient();
var config = new WebApiClient.HttpApiConfig(client);

5. IHttpClient接口

5.1 IHttpClient的接口定义

/// <summary>
/// 定义HttpClient的接口
/// </summary>
public interface IHttpClient : IDisposable
{
    /// <summary>
    /// 获取关联的Http处理对象
    /// </summary>
    HttpClientHandler Handler { get; }

    /// <summary>
    /// 获取默认的请求头管理对象
    /// </summary>
    HttpRequestHeaders DefaultRequestHeaders { get; }

    /// <summary>
    /// 异步发送请求
    /// </summary>
    /// <param name="request">请求消息</param>
    /// <returns></returns>
    Task<HttpResponseMessage> SendAsync(HttpApiRequestMessage request);
    
    ...
}

5.2 IHttpClient的接口意图

IHttpClient接口意图将System.Net.Http.HttpClient实例和System.Net.Http.HttpClientHandler实例进行组合封装,隐藏底层的一些细节,同时描述了HttpClient和HttpClientHandler不可分割的关系,其默认实现对象WebApiClient.Defaults.HttpClient将System.Net.Http.HttpClient难用的几个功能也封装了一次:比如设置Cookie和设置代理等。

5.3 更换WebApiClient.Defaults.HttpClient关联的HttpClientHandler

一般而言,HttpClient没有多少扩展的价值,但HttpClientHandler就有很多扩展空间,其中System.Net.Http.WebRequestHandler也派生于HttpClientHandler,多了很一些配置的属性,很多时候,需要替换WebApiClient.Defaults.HttpClient的HttpClientHandler就可以,而不用从头实现IHttpClient接口,以下方式可以替换HttpClientHandler:

class MyHttpClient : WebApiClient.Defaults.HttpClient
{
    protected override HttpClientHandler CreateHttpClientHandler()
    {
        // or return your handler
        return new WebRequestHandler();
    }
} 

var config = new HttpApiConfig(new MyHttpClient());
var client = HttpApiClient.Create<IMyWebApi>(config);

如果是外部的HttpMessageHandler实例,可以使用如下方式关联:

var httpClient = new WebApiClient.Defaults.HttpClient(handler);
var config = new HttpApiConfig(httpClient);
var client = HttpApiClient.Create<IMyWebApi>(config);

6. 扩展JsonFormatter

WebApiClient.Defaults.JsonFormatter使用了json.net,每次序列化或反序列化时都会创建JsonSerializerSettings,可以派生WebApiClient.Defaults.JsonFormatter返回自定义的JsonSerializerSettings:

class MyJsonFormatter : WebApiClient.Defaults.JsonFormatter
{
    protected override JsonSerializerSettings CreateSerializerSettings()
    {
        return new JsonSerializerSettings
        {
            // your setting
        };
    }
}


var config = new HttpApiConfig
{
    JsonFormatter = new MyJsonFormatter()
};
var client = HttpApiClient.Create<IMyWebApi>(config);

如果你需要使用其它第三方的json序列化库,可以实现IJsonFormatter接口,然后实例化设置到HttpApiConfig的JsonFormatter属性。

7. 扩展WebApiClient.Defaults.KeyValueFormatter

WebApiClient.Defaults.KeyValueFormatter支持序列化以下类型:

  • 常用简单类型及其空类型(byte、int、short、long、doublue、flout、string、decimal、DateTime、Guid、enum、Version和Uri)
  • 支持IEnumerable递归拆解,默认最多16层
  • KeyValuePair<,>的任意泛型
  • 多属性模型的第一层属性拆解

如果你需要支持更多的类型,需要派生KeyValueFormatter增加功能:

class MyKeValueFormatter : WebApiClient.Defaults.KeyValueFormatter
{
    protected override IEnumerable<ConverterBase> GetConverters()
    {
        // 在原有转换器之前插入DynamicObjectConverter
        var addin = new[] { new DynamicObjectConverter() };
        return addin.Concat(base.GetConverters());
    }
}

/// <summary>
/// 表示动态类型转换器
/// </summary>
class DynamicObjectConverter : ConverterBase
{
    /// <summary>
    /// 执行转换
    /// </summary>
    /// <param name="context">转换上下文</param>
    /// <returns></returns>
    public override IEnumerable<KeyValuePair<string, string>> Invoke(ConvertContext context)
    {
        var dynamicObject = context.Data as DynamicObject;
        if (dynamicObject != null)
        {
            return from name in dynamicObject.GetDynamicMemberNames()
                   let value = this.GetValue(dynamicObject, name)
                   let ctx = new ConvertContext(name, value, context.Depths, context.Options)
                   select ctx.ToKeyValuePair();
        }

        return this.Next.Invoke(context);
    }

    /// <summary>
    /// 获取动态类型的值
    /// </summary>
    /// <param name="dynamicObject">实例</param>
    /// <param name="name">名称</param>
    /// <returns></returns>
    private object GetValue(DynamicObject dynamicObject, string name)
    {
        object value;
        var binder = new MemberBinder(name);
        dynamicObject.TryGetMember(binder, out value);
        return value;
    }

    /// <summary>
    /// 表示成员值的获取绑定
    /// </summary>
    private class MemberBinder : GetMemberBinder
    {
        /// <summary>
        /// 键的信息获取绑定
        /// </summary>
        /// <param name="key">键名</param>
        public MemberBinder(string key)
            : base(key, false)
        {
        }

        /// <summary>
        /// 在派生类中重写时,如果无法绑定目标动态对象,则执行动态获取成员操作的绑定
        /// </summary>
        /// <param name="target"></param>
        /// <param name="errorSuggestion"></param>
        /// <returns></returns>
        public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
        {
            throw new NotImplementedException();
        }
    }
}

8. 扩展WebApiClient.Defaults.ApiInterceptor

ApiInterceptor是默认实现的IApiInterceptor,用于拦截http接口的调用,通过实现IApiInterceptor接口或派生WebApiClient.Defaults.ApiInterceptor,可以加入自己的拦截逻辑:

class MyApiInterceptor : WebApiClient.Defaults.ApiInterceptor
{
    public MyApiInterceptor(HttpApiConfig config)
        : base(config)
    {
    }

    public override object Intercept(object target, MethodInfo method, object[] parameters)
    {
        Console.WriteLine($"正在请求方法{method.Name}");
        return base.Intercept(target, method, parameters);
    }

    protected override ApiActionDescriptor GetApiActionDescriptor(MethodInfo method, object[] parameters)
    {
        return base.GetApiActionDescriptor(method, parameters);
    }
}

var config = new HttpApiConfig();
var apiInerceptor = new MyApiInterceptor(config);
var client = HttpApiClient.Create(typeof(IMyWebApi), apiInerceptor) as IMyWebApi;

9. 自定义业务异常、异常抛出和异常捕获处理

WebApiClient是不带业务性质的http客户端库,对于实际项目应用中,你可能会将服务器的一些响应情况,归类为对应的业务异常,常见的有种方式:

  • 使用Http状态码来标记业务异常
  • 统一返回200状态,在回复内容使用自定义业务状态码

不管是哪一种,客户端都需要根据不同的状态码执行一些业务规则逻辑,在WebApiClient使用中,可以将那些业务上任务异常的状态码转换为对应的自定义Exception,并抛出这些自定义Exception,将状态码转换为自定义的Exception,可以使用两种方式实现:

  • 继承ApiActionFilterAttribute创建一个Filter,重写OnEndRequestAsync方法,根据Http状态码或业务状态码创建自定义业务Exception并抛出,然后将Filter应用到全局过滤器或修饰在接口或修饰在接口的方法上。
  • 继承AutoReturnAttribute或ApiReturnAttribute,重写GetTaskResult方法,根据Http状态码或业务状态码创建自定义业务Exception并抛出,然后修饰在接口或修饰在接口的方法上。

使用ITask<>的Retry扩展方法捕获自定义业务Exception并实现重试机制,使用ITask<>的Handle()扩展方法捕获自定义业务Exception实现返回指定值。

Clone this wiki locally