WebApiClient 接口注册与选项 1 配置文件中配置HttpApiOptions选项
配置示例
1 2 3 4 5 6 7 8 9 "IUserApi" : { "HttpHost" : "http://www.webappiclient.com/" , "UseParameterPropertyValidate" : false , "UseReturnValuePropertyValidate" : false , "JsonSerializeOptions" : { "IgnoreNullValues" : true , "WriteIndented" : false } }
2 Service注册
示例
1 2 3 4 5 6 7 services .ConfigureHttpApi<IUserApi>(Configuration.GetSection(nameof (IUserApi))) .ConfigureHttpApi<IUserApi>(o => { o.JsonSerializeOptions.Converters.Add(new JsonDateTimeConverter("yyyy-MM-dd HH:mm:ss" )); });
HttpApiOptions详细展示 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 public class HttpApiOptions { public Uri? HttpHost { get ; set ; } public bool UseLogging { get ; set ; } = true ; public bool UseDefaultUserAgent { get ; set ; } = true ; public bool . { get ; set ; } = true ; public bool UseReturnValuePropertyValidate { get ; set ; } = true ; public JsonSerializerOptions JsonSerializeOptions { get ; } = CreateJsonSerializeOptions(); public JsonSerializerOptions JsonDeserializeOptions { get ; } = CreateJsonDeserializeOptions(); public XmlWriterSettings XmlSerializeOptions { get ; } = new XmlWriterSettings(); public XmlReaderSettings XmlDeserializeOptions { get ; } = new XmlReaderSettings(); public KeyValueSerializerOptions KeyValueSerializeOptions { get ; } = new KeyValueSerializerOptions(); public Dictionary<object , object > Properties { get ; } = new Dictionary<object , object >(); public IList<IApiFilter> GlobalFilters { get ; } = new List<IApiFilter>(); private static JsonSerializerOptions CreateJsonSerializeOptions () { return new JsonSerializerOptions { PropertyNameCaseInsensitive = true , PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; } private static JsonSerializerOptions CreateJsonDeserializeOptions () { var options = CreateJsonSerializeOptions(); options.Converters.Add(JsonCompatibleConverter.EnumReader); options.Converters.Add(JsonCompatibleConverter.DateTimeReader); return options; } }
Uri(url)拼接规则 所有的Uri拼接都是通过Uri(Uri baseUri, Uri relativeUri)这个构造器生成。
带/结尾的baseUri
http://a.com/ + b/c/d = http://a.com/b/c/d
http://a.com/path1/ + b/c/d = http://a.com/path1/b/c/d
http://a.com/path1/path2/ + b/c/d = http://a.com/path1/path2/b/c/d
不带/结尾的baseUri
http://a.com + b/c/d = http://a.com/b/c/d
http://a.com/path1 + b/c/d = http://a.com/b/c/d
http://a.com/path1/path2 + b/c/d = http://a.com/path1/b/c/d
事实上http://a.com与http://a.com/是完全一样的,他们的path都是/,所以才会表现一样。为了避免低级错误的出现,请使用的标准baseUri书写方式,即使用/作为baseUri的结尾的第一种方式。
OAuths&Token 推荐使用自定义TokenProvider 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class TestTokenProvider : TokenProvider { private readonly IConfiguration _configuration; public TestTokenProvider (IServiceProvider services,IConfiguration configuration ) : base (services ) { _configuration = configuration; } protected override Task<TokenResult> RefreshTokenAsync (IServiceProvider serviceProvider, string refresh_token ) { return this .RefreshTokenAsync(serviceProvider, refresh_token); } protected override async Task<TokenResult> RequestTokenAsync (IServiceProvider serviceProvider ) { LoginInput login = new LoginInput(); login.UserNameOrEmailAddress = "admin" ; login.Password = "bb123456" ; var result = await serviceProvider.GetRequiredService<ITestApi>().RequestToken(login).Retry(maxCount: 3 ); return result; } }
TokenProvider的注册
1 services.AddTokenProvider<ITestApi,TestTokenProvider>();
OAuthTokenHandler 可以自定义OAuthTokenHandler官方定义是属于http消息处理器,功能与OAuthTokenAttribute一样,除此之外,如果因为意外的原因导致服务器仍然返回未授权(401状态码),其还会丢弃旧token,申请新token来重试一次请求 。
OAuthToken在webapiclient中一般是保存在http请求的Header的Authrization
当token在url中时我们需要自定义OAuthTokenHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class UriQueryOAuthTokenHandler : OAuthTokenHandler { public UriQueryOAuthTokenHandler (ITokenProvider tokenProvider ) : base (tokenProvider ) { } protected override void UseTokenResult (HttpRequestMessage request, TokenResult tokenResult ) { var uriValue = new UriValue(request.RequestUri).AddQuery("myToken" , tokenResult.Access_token); request.RequestUri = uriValue.ToUri(); } }
AddQuery是请求的的url中携带token的key
自定义OAuthTokenHandler的使用
1 2 3 4 5 6 7 8 9 services .AddHttpApi<IUserApi>() .AddOAuthTokenHandler((s, tp) => new UriQueryOAuthTokenHandler(tp)); apiBulider.AddOAuthTokenHandler<UrlTokenHandler>((sp,token)=> { token=sp.GetRequiredService<TestTokenProvider>(); return new UrlTokenHandler(token); },WebApiClientCore.Extensions.OAuths.TypeMatchMode.TypeOrBaseTypes);
OAuthToken 特性 OAuthToken可以定义在继承IHttpApi的接口上也可以定义在接口的方法上
在使用自定义TokenProvier时要注意OAuthToken特性不要定义在具有请求token的Http请求定义上
Patch请求 json patch是为客户端能够局部更新服务端已存在的资源而设计的一种标准交互,在RFC6902里有详细的介绍json patch,通俗来讲有以下几个要点:
使用HTTP PATCH请求方法;
请求body为描述多个opration的数据json内容;
请求的Content-Type为application/json-patch+json;
声明Patch方法 1 2 3 4 5 public interface IUserApi { [HttpPatch("api/users/{id}")] Task<UserInfo> PatchAsync(string id, JsonPatchDocument<User> doc); }
实例化JsonPatchDocument 1 2 3 var doc = new JsonPatchDocument<User>(); doc.Replace(item => item.Account, "laojiu"); doc.Replace(item => item.Email, "laojiu@qq.com");
请求内容 1 2 3 4 5 6 7 PATCH /api/users/id001 HTTP/1.1 Host: localhost:6000 User-Agent: WebApiClientCore/1.0.0.0 Accept: application/json; q=0.01, application/xml; q=0.01 Content-Type: application/json-patch+json [{"op":"replace","path":"/account","value":"laojiu"},{"op":"replace","path":"/email","value":"laojiu@qq.com"}]
异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 try { var model = await api.GetAsync(); } catch (HttpRequestException ex) when (ex.InnerException is ApiInvalidConfigException configException){ } catch (HttpRequestException ex) when (ex.InnerException is ApiResponseStatusException statusException){ } catch (HttpRequestException ex) when (ex.InnerException is ApiException apiException){ } catch (HttpRequestException ex) when (ex.InnerException is SocketException socketException){ } catch (HttpRequestException ex){ } catch (Exception ex){ }
请求重试 使用ITask<>异步声明,就有Retry的扩展,Retry的条件可以为捕获到某种Exception或响应模型符合某种条件。
1 2 3 4 5 6 7 8 9 10 GetNumberTemplateForEditOutput put = new GetNumberTemplateForEditOutput(); var res = await _testApi.GetForEdit(id).Retry(maxCount: 1 ).WhenCatchAsync<ApiResponseStatusException>(async p => { if (p.StatusCode == HttpStatusCode.Unauthorized) { await Token(); } }); put = res.Result; return put;
API接口处理 使用ITask<>异步声明
1 2 3 4 5 6 7 8 9 10 11 [HttpHost("请求地址" ) ] public interface ITestApi : IHttpApi { [OAuthToken ] [JsonReturn ] [HttpGet("/api/services/app/NumberingTemplate/GetForEdit" ) ] ITask<AjaxResponse<GetNumberTemplateForEditOutput>> GetForEdit([Required] string id); [HttpPost("api/TokenAuth/Authenticate" ) ] ITask<string > RequestToken ([JsonContent] AuthenticateModel login ) ; }
基于WebApiClient的扩展类 扩展类声明 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public static class WebApiClientExentions { public static IServiceCollection AddWebApiClietHttp <THttp >(this IServiceCollection services, Action<HttpApiOptions>? options = null ) where THttp : class , IHttpApi { HttpApiOptions option = new HttpApiOptions(); option.JsonSerializeOptions.Converters.Add(new JsonDateTimeConverter("yyyy-MM-dd HH:mm:ss" )); option.UseParameterPropertyValidate = true ; if (options != null ) { options.Invoke(option); } services.AddHttpApi<THttp>().ConfigureHttpApi(p => p = option); return services; } public static IServiceCollection AddWebApiClietHttp <THttp >(this IServiceCollection services,IConfiguration configuration ) where THttp : class , IHttpApi { services.AddHttpApi<THttp>().ConfigureHttpApi((Microsoft.Extensions.Configuration.IConfiguration)configuration); return services; } public static IServiceCollection AddWebApiClientHttpWithTokeProvider <THttp , TTokenProvider >(this IServiceCollection services, Action<HttpApiOptions>? options = null ) where THttp : class , IHttpApi where TTokenProvider : class , ITokenProvider { services.AddWebApiClietHttp<THttp>(options); services.AddTokenProvider<THttp,TTokenProvider>(); return services; } public static IServiceCollection AddWebApiClientHttpWithTokeProvider <THttp , TTokenProvider >(this IServiceCollection services, IConfiguration configuration ) where THttp : class , IHttpApi where TTokenProvider : class , ITokenProvider { services.AddWebApiClietHttp<THttp>(configuration); services.AddTokenProvider<THttp, TTokenProvider>(); return services; } }
扩展类使用 1 services.AddWebApiClientHttpWithTokeProvider<ITestApi, TestTokenProvider>();