NetCore 之 DispatchProxy
Posted 云霄宇霁
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NetCore 之 DispatchProxy相关的知识,希望对你有一定的参考价值。
如何使用Dispatchproxy封装REST API,让API调用更简单。
1、创建HttpClientDispathProxy类继承自DispatchProxy
public class HttpClientDispathProxy<TInterface> : DispatchProxy public Func<string> token; public ApiClient client; protected override object? Invoke(MethodInfo? targetMethod, object?[]? args) if (targetMethod?.ReturnType == typeof(Task)) return this.InvokeAsync(targetMethod, args); if (IsGenericTask(targetMethod?.ReturnType)) var method = this.GetType().GetMethod("InvokeAsyncT", BindingFlags.NonPublic | BindingFlags.Instance); var methodInfo = method!.MakeGenericMethod(targetMethod?.ReturnType?.GenericTypeArguments ?? new Type[] ); return methodInfo.Invoke(this, new object[] targetMethod, args ); ; if (targetMethod?.ReturnType != typeof(void)) var response = this.SendAsync(targetMethod, args); var result = this.client.ReadResponse(targetMethod.ReturnType, response.Result); return result; return default; protected async Task InvokeAsync(MethodInfo? method, object?[]? args) await this.SendAsync(method, args); protected async Task<T> InvokeAsyncT<T>(MethodInfo? method, object?[]? args) var response = await this.SendAsync(method, args); var result = this.client.ReadJson<T>(response); return result; protected virtual async Task<string> SendAsync(MethodInfo methodInfo, Object[] args) var attr = methodInfo.GetCustomAttribute<ApiAttribute>(); if (attr == null) throw new ArgumentNullException(nameof(attr)); using (HttpRequestMessage request = new()) request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token()); BuildHttpRequestMessage(methodInfo, args, attr, request); return await client.SendAsync(request); protected virtual void BuildHttpRequestMessage(MethodInfo method, object[] args, ApiAttribute attr, HttpRequestMessage request) request.Method = _ConvertHttpMethod(attr.HttpMethod); var parameters = this.GetParameters(method, args); if (attr.HttpMethod == "POST" || attr.HttpMethod == "PUT" || attr.HttpMethod == "PTCH") request.RequestUri = GetRequestUrl(true, attr.Url, parameters.uri); if (parameters.forms != null) JsonSerializerOptions op = new() DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull ; var contentString = ""; if (parameters.forms.Count() == 1) contentString = JsonSerializer.Serialize(parameters.forms.First().Value, op); else if (parameters.forms.Count() > 1) contentString = JsonSerializer.Serialize(parameters.forms, op); if (!String.IsNullOrEmpty(contentString)) request.Content = new StringContent(contentString, Encoding.UTF8, "application/json"); else request.RequestUri = GetRequestUrl(false, attr.Url, parameters.uri); protected virtual Uri GetRequestUrl(Boolean isPost, String path, Dictionary<String, Object> parameters) var url = $"path.TrimEnd(\'/\')".ToLower(); var qMark = url.IndexOf("?") == -1 ? "?" : String.Empty; var and = String.IsNullOrEmpty(qMark) ? "&" : String.Empty; if (parameters != null) List<String> parsedParam = new(); foreach (var kv in parameters) var holder = "" + kv.Key.ToLower() + ""; if (url.Contains(holder)) if (kv.Value == null) throw new ArgumentNullException(kv.Key, "Parameter in uri can not be null."); String tempValue; if (kv.Value is Enum enumValue) tempValue = enumValue.ToString();//enumValue.GetEnumDescription(); else tempValue = kv.Value.ToString(); url = url.Replace(holder, tempValue); parsedParam.Add(kv.Key); var leftParams = parameters.Where(i => !parsedParam.Contains(i.Key)); if (leftParams.Any()) List<String> tempParamsList = new(); foreach (var item in leftParams) var value = item.Value?.ToString(); if (string.IsNullOrEmpty(value)) if (item.Value is GraphQuery query) if (query.Top != 0) tempParamsList.Add("$top=" + query.Top); if (string.IsNullOrEmpty(query.Expand)) tempParamsList.Add("$expand=" + query.Expand); if (string.IsNullOrEmpty(query.Select)) tempParamsList.Add("$select=" + query.Select); if (string.IsNullOrEmpty(query.Orderby)) tempParamsList.Add("$orderby=" + query.Orderby); if (string.IsNullOrEmpty(query.Filter)) tempParamsList.Add("$filter=" + query.Filter); if (query.Skip != 0) tempParamsList.Add("$skip=" + query.Skip); if (query.Count) tempParamsList.Add("$count=true"); else tempParamsList.Add($"item.Key=HttpUtility.UrlEncode(item.Value.ToString()).Replace("+", "%20")"); url = $"urlqMarkandString.Join("&", tempParamsList.ToArray())"; return new Uri(url); protected virtual (Dictionary<string, object> uri, Dictionary<string, object> forms) GetParameters(MethodInfo method, object[] args) var uri = new Dictionary<string, object>(); var forms = new Dictionary<string, object>(); if (args != null && args.Any()) var parameters = method.GetParameters(); foreach (var param in parameters) var customAttributes = param.GetCustomAttributes(); foreach (var customAttribute in customAttributes) if (customAttribute is ApiParameterAttribute attribute) if (attribute.IsRequestUri) if (!string.IsNullOrEmpty(attribute.Endpoint)) uri.Add(attribute.Name ?? "endpoint", new Uri(attribute.Endpoint)); else uri.Add(attribute.Name, args[param.Position]); else forms.Add(attribute.Name, args[param.Position]); else forms.Add(param.Name, args[param.Position]); return (uri, forms); private static HttpMethod _ConvertHttpMethod(string method) => method.ToLowerInvariant() switch "get" => HttpMethod.Get, "post" => HttpMethod.Post, "put" => HttpMethod.Put, "delete" => HttpMethod.Delete, "patch" => HttpMethod.Patch, _ => throw new ArgumentOutOfRangeException($"The method type is not supported. method: method.") ; private static Boolean IsGenericTask(Type? type) if ((type?.IsGenericType ?? false) && type?.GetGenericTypeDefinition() == typeof(Task<>)) return true; return false;
2、创建HttpClientProxy类继承自HttpClientDispathProxy,用于创建泛型Interface实例
public class HttpClientProxy<TInterface>: HttpClientDispathProxy<TInterface> public static TInterface Create(ApiClient client, Func<string> func) Object proxy = Create<TInterface, HttpClientDispathProxy<TInterface>>()!; ((HttpClientDispathProxy<TInterface>)proxy).token = func; ((HttpClientDispathProxy<TInterface>)proxy).client = client; return (TInterface)proxy;
3、创建ApiClient 类调用interface实例方法
public partial class ApiClient public T GetApiService<T>(Func<string> token) where T : IApiService return HttpClientProxy<T>.Create(this, token);
这里指定partial class,原因是方便扩展
public partial class ApiClient private readonly IHttpClientFactory _httpClient; public ApiClient(IHttpClientFactory httpClient) this._httpClient = httpClient; internal async Task<string> SendAsync(HttpRequestMessage request) using var response = await this._httpClient.CreateClient().SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) return content; throw new ApiClientException(response.StatusCode, content); internal Object ReadResponse(Type type, String content) if (!string.IsNullOrEmpty(content) && type != typeof(void)) try return System.Text.Json.JsonSerializer.Deserialize(content, type); catch (Exception ex) throw new ApiClientException(ex.Message); return default; internal T ReadJson<T>(String content) if(!string.IsNullOrEmpty(content)) try return (T)this.ReadResponse(typeof(T), content); catch (Exception ex) throw new ApiClientException(ex.Message); return default;
4、创建接口,添加对应attribute
public interface IApiService [Api(HttpMethod = "GET", Url = "endpointWeatherForecast")] Task<IEnumerable<WeatherForecast>> GetAsync( [ApiParameter(Endpoint = "https://localhost:7101", IsRequestUri = true)] string endpoint = default );
ApiAttribute
[AttributeUsage(AttributeTargets.Method)] public class ApiAttribute : Attribute public string HttpMethod get; set; public string Url get; set;
ApiParameterAttribute
public class ApiParameterAttribute: Attribute public string Name get; set; public bool IsRequestUri get; set; public string Endpoint get; set; public ApiParameterAttribute() public ApiParameterAttribute(string name) Name = name;
5、exception
public class ApiClientException : Exception public HttpStatusCode StatusCode get; set; public ApiClientException(HttpStatusCode httpStatusCode, string message) : base(message) StatusCode = httpStatusCode; public ApiClientException(string message) : base(message)
6、demo controller
[ApiExplorerSettings(GroupName = "demo1")] [ApiController] [Route("[controller]")] public class SwagggerDemoController : ControllerBase private readonly ApiClient _apiClient; public SwagggerDemoController(ApiClient apiClient) this._apiClient = apiClient; [HttpGet("getdispatchproxydemo"), Authorize] public IEnumerable<WeatherForecast> DispatchProxyDemo(string token) var client = _apiClient.GetApiService<IApiService>(() => token); var response = client.GetAsync().GetAwaiter().GetResult(); return response;
7、验证,正确取到数据
Notes:这里加了authentication验证,之前blog中有提过,可以参考。
OK 搞定!
以上是关于NetCore 之 DispatchProxy的主要内容,如果未能解决你的问题,请参考以下文章