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;
        
    
dispachproxy

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;
        
    
proxy

3、创建ApiClient 类调用interface实例方法

 public partial class ApiClient
    
        public T GetApiService<T>(Func<string> token) where T : IApiService
        
            return HttpClientProxy<T>.Create(this, token);
        
    
APIClient

这里指定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;
        
    
APIClient

4、创建接口,添加对应attribute

    public interface IApiService
    
        [Api(HttpMethod = "GET", Url = "endpointWeatherForecast")]
        Task<IEnumerable<WeatherForecast>> GetAsync(
            [ApiParameter(Endpoint = "https://localhost:7101", IsRequestUri = true)] string endpoint = default
            );
    
interface

 ApiAttribute

   [AttributeUsage(AttributeTargets.Method)]
    public class ApiAttribute : Attribute
    
        public string HttpMethod  get; set; 

        public string Url  get; set; 
    
api attribute

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;
        
    
api parameter attribute

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)
         
        
    
exception

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;
        
    
controller

 7、验证,正确取到数据

 

 

Notes:这里加了authentication验证,之前blog中有提过,可以参考。

OK 搞定!

以上是关于NetCore 之 DispatchProxy的主要内容,如果未能解决你的问题,请参考以下文章

netcore6揭秘怎么样

NetCore2.0学习和工作系列文章汇总贴

NetCore微服务实现事务一致性masstransit之saga使用

Nginx知多少系列之工作原理

NETCORE 之 openSUSE docker 安装

.netcore持续集成测试篇之测试方法改造