[聚合文章] 基于Abp的WebApi容器

.Net 2017-12-17 25 阅读

 简述 对 Abp的动态web api的改造过程

注册

1. 首先通过反射将《服务类型》通过ApiControllerBuilder 构建成成 DynamicApiControllerInfo
2. 在DynamicApiControllerInfo中同时构建DynamicApiActionInfo
3. Ioc注入DynamicApiController<TService> Tservice就是最开始的《服务类型》
3. 最后将DynamicApiControllerInfo添加到DynamicApiControllerManager,通过ServiceName缓存

执行

1. AbpHttpControllerSelector 通过路由获取出“service” 这个参数即ServiceName
2. 通过ServiceName从DynamicApiControllerManager中获取DynamicApiControllerInfo 的信息
3. 将DynamicApiControllerInfo 放入HttpControllerDescriptor.Properties中,返回DynamicHttpControllerDescriptor给MVC处理流程
4. AbpControllerActivator 通过DynamicApiControllerInfor中的ControllerType激活Controller
5. AbpApiControllerActionSelector 获取HttpControllerDescriptor.Properties中的将DynamicApiControllerInfo 信息
6. AbpApiControllerActionSelector 通过 路由的{action}参数获取 方法名
7. AbpApiControllerActionSelector 在 DynamicApiControllerInfor通过方法名获取DynamicApiActionInfo 的信息
8. 最后返回DyanamicHttpActionDescriptor 给MVC处理流程

改造

Action执行的改造

实际在Abp中 DyanamicHttpActionDescriptor 的 ExecuteAsync 中实际是通过AOP拦截实现的.这里我做了修改

首先将DynamicController改为组合的方式注入IService来作为代理对象如下图

DynamicController

然后执行的时候采用获取IDynamicApiController 的ProxyObject 来使用反射执行

执行

 

其中由于MVC并没有放出ReflectedHttpActionDescriptor.ActionExecutor 这个类型,所以用了点技巧。

支持重载

1. 首先在 DynamicApiControllerInfo 中增加属性 FullNameActions 类型和Actions 一致
2. 然后再初始化的时候同时初始化FullNameActions ,Action的key是Name,FullNameActions 是Method.ToString()[这种包含的信息更多,可作为唯一标识]
3. 最后在客户端调用的时候放到Header即可区分,实现函数重载

支持多个复杂参数

在ParameterBindingRules 中添加规则

 //增加服务中多个参数的情况            ApiGlobalConfiguration.Configuration.ParameterBindingRules.Insert(0, descriptor =>            {                if (descriptor.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Get) ||                    descriptor.ActionDescriptor.SupportedHttpMethods.Contains(HttpMethod.Delete))                    return null;                if (descriptor.ActionDescriptor.GetParameters().Count(item => !item.ParameterType.IsSimpleUnderlyingType()) < 2)                    return null;                if (descriptor.ParameterType.IsSimpleUnderlyingType())                    return null;                if (descriptor.ParameterType.GetCustomAttribute(typeof(ParameterBindingAttribute)) != null)                    return null;                var config = descriptor.Configuration;                IEnumerable<MediaTypeFormatter> formatters = config.Formatters;                var validators = config.Services.GetBodyModelValidator();                return new MultiPostParameterBinding(descriptor, formatters, validators);            });

  

2. 在MultiPostParameterBinding 代码如下

 

public class MultiPostParameterBinding : FormatterParameterBinding    {        // Magic key to pass cancellation token through the request property bag to maintain backward compat.        private const string CancellationTokenKey = "MS_FormatterParameterBinding_CancellationToken";        public MultiPostParameterBinding(HttpParameterDescriptor descriptor, IEnumerable<MediaTypeFormatter> formatters,            IBodyModelValidator bodyModelValidator) :            base(descriptor, formatters, bodyModelValidator)        {        }        public override bool WillReadBody => false;        public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider,            HttpActionContext actionContext,            CancellationToken cancellationToken)        {            var paramFromBody = Descriptor;            var type = paramFromBody.ParameterType;            var request = actionContext.ControllerContext.Request;            IFormatterLogger formatterLogger =                new ModelStateFormatterLogger(actionContext.ModelState, paramFromBody.ParameterName);            var task = ExecuteBindingAsyncCore(metadataProvider, actionContext, paramFromBody, type, request,                formatterLogger, cancellationToken);            return task;        }        // Perf-sensitive - keeping the async method as small as possible        private async Task ExecuteBindingAsyncCore(ModelMetadataProvider metadataProvider,            HttpActionContext actionContext,            HttpParameterDescriptor paramFromBody, Type type, HttpRequestMessage request,            IFormatterLogger formatterLogger,            CancellationToken cancellationToken)        {            // pass the cancellation token through the request as we cannot call the ReadContentAsync overload that takes            // CancellationToken for backword compatibility reasons.            request.Properties[CancellationTokenKey] = cancellationToken;            //todo 这里如果只是服务端使用需要要构造一个匿名对象去接受数据            Dictionary<string, object> allModels;            if (actionContext.ActionArguments.ContainsKey("MultiDictionary"))            {                allModels = actionContext.ActionArguments["MultiDictionary"] as Dictionary<string, object>;            }            else            {                allModels = await ReadContentAsync(request, typeof(Dictionary<string, object>), Formatters,                        formatterLogger, cancellationToken)                    as Dictionary<string, object>;                actionContext.ActionArguments["MultiDictionary"] = allModels;            }            if (allModels != null)            {                var model = JsonConvert.DeserializeObject(allModels[paramFromBody.ParameterName].ToString(), type);                actionContext.ActionArguments[paramFromBody.ParameterName] = model;                // validate the object graph.                // null indicates we want no body parameter validation                if (BodyModelValidator != null)                    BodyModelValidator.Validate(model, type, metadataProvider, actionContext, paramFromBody.ParameterName);            }        }    }

  

原理实际是如果有两个复杂类型User user和Company company,那么客户端需要传入的是一个字典有两个key,user和company,分别对应两个参数即可

客户端代理

我这里使用Abp并不是为了基于界面做,而是为了做服务化,客户端通过接口,然后走http请求,最后由服务端的服务实现去执行结果最后返回,有点像以前webservice不过更加轻量级。
先来看看实现,至于作用会在后面的文章中陆续分享。

RealProxy

动态代理/真实代理,大部分情况下用来作为aop的实现,例如,日志,事务等,这里我们直接用来代理发送Http请求。实现如下 

public class ClientProxy : RealProxy, IRemotingTypeInfo    {        private readonly Type _proxyType;        private readonly IActionInvoker _actionInvoker;        private List<string> _unProxyMethods = new List<string>        {            "InitContext",            "Dispose",        };        public ClientProxy(Type proxyType, IActionInvoker actionInvoker) :            base(proxyType)        {            _proxyType = proxyType;            _actionInvoker = actionInvoker;        }        public bool CanCastTo(Type fromType, object o)        {            return fromType == _proxyType || fromType.IsAssignableFrom(_proxyType);        }        public string TypeName { get { return _proxyType.Name; } set { } }        private static ConcurrentDictionary<Type, List<MethodInfo>> _typeMethodCache = new ConcurrentDictionary<Type, List<MethodInfo>>();        private List<MethodInfo> GetMethods(Type type)        {            return _typeMethodCache.GetOrAdd(type, item =>            {                List<MethodInfo> methods = new List<MethodInfo>(type.GetMethods());                foreach (Type interf in type.GetInterfaces())                {                    foreach (MethodInfo method in interf.GetMethods())                        if (!methods.Contains(method))                            methods.Add(method);                }                return methods;            });        }        public override IMessage Invoke(IMessage msg)        {            // Convert to a MethodCallMessage            IMethodCallMessage methodMessage = new MethodCallMessageWrapper((IMethodCallMessage)msg);            var methodInfo = GetMethods(_proxyType).FirstOrDefault(item => item.ToString() == methodMessage.MethodBase.ToString());            //var argumentTypes = TypeUtil.GetArgTypes(methodMessage.Args);            //var methodInfo = _proxyType.GetMethod(methodMessage.MethodName, argumentTypes) ?? methodMessage.MethodBase as MethodInfo;            object objReturnValue = null;            if (methodMessage.MethodName.Equals("GetType") && (methodMessage.ArgCount == 0))            {                objReturnValue = _proxyType;            }            else if (methodInfo != null)            {                if (methodInfo.Name.Equals("Equals")                    || methodInfo.Name.Equals("GetHashCode")                    || methodInfo.Name.Equals("ToString")                    || methodInfo.Name.Equals("GetType"))                {                    throw CoralException.ThrowException<ClientProxyErrorCode>(item => item.UnValideMethod);                }                if (_unProxyMethods.All(item => item != methodInfo.Name))                {                    objReturnValue = _actionInvoker.Invoke(_proxyType, methodInfo, methodMessage.Args);                }            }            // Create the return message (ReturnMessage)            return new ReturnMessage(objReturnValue, methodMessage.Args, methodMessage.ArgCount,                methodMessage.LogicalCallContext, methodMessage);        }

 IActionInvoker 

方法调用者这里抽象成接口是因为我有两个实现,一个是基于WebApi一个是基于Hession,以后还可能有其他的,这样代理的逻辑可以复用,只是实现不同的请求转发就可以了。以RestActionInvoker为例(就是webApi)

public class RestActionInvoker : IActionInvoker    {        private readonly string _host;        private readonly string _preFixString;        public RestActionInvoker(string host, string preFixString = "")        {            _host = host;            _preFixString = string.IsNullOrEmpty(preFixString) ? "api" : preFixString;        }        /// <summary>        /// 执行方法        /// </summary>        /// <param name="proxyType"></param>        /// <param name="methodInfo"></param>        /// <param name="parameters"></param>        /// <returns></returns>        public object Invoke(Type proxyType, MethodInfo methodInfo, params object[] parameters)        {            var url = TypeUtil.GetUrl(proxyType, methodInfo, _preFixString);            var requestType = GetRequestType(methodInfo);            ResultMessage result;            switch (requestType)            {                case RequestType.Get:                    {                        var getParam = PrepareGetParams(methodInfo, parameters);                        result = AppComminutService.Get<ResultMessage>(_host, url, getParam, PrepareHeader(methodInfo));                        break;                    }                case RequestType.Delete:                    {                        var delParams = PrepareGetParams(methodInfo, parameters);                        result = AppComminutService.Delete<ResultMessage>(_host, url, delParams, PrepareHeader(methodInfo));                        break;                    }                case RequestType.Post:                    {                        var bodyParam = PrepareBodyParams(methodInfo, parameters);                        var simpaleParam = PrepareSampleParams(methodInfo, parameters);                        url = AppendGetParamToUrl(url, simpaleParam);                        result = AppComminutService.Post<object, ResultMessage>(_host, url, bodyParam.Count == 1 ? bodyParam.First().Value : bodyParam, PrepareHeader(methodInfo));                        break;                    }                case RequestType.Put:                    {                        var simpaleParam = PrepareSampleParams(methodInfo, parameters);                        url = AppendGetParamToUrl(url, simpaleParam);                        var putParam = PrepareBodyParams(methodInfo, parameters);                        result = AppComminutService.Put<object, ResultMessage>(_host, url, putParam.Count == 1 ? putParam.First().Value : putParam, PrepareHeader(methodInfo));                        break;                    }                default:                    throw new ArgumentOutOfRangeException();            }            if (result.State == ResultState.Success)            {                if (result.Data != null)                {                    try                    {                        return JsonConvert.DeserializeObject(result.Data.ToString(), methodInfo.ReturnType);                    }                    catch                    {                        return result.Data;                    }                }                return null;            }            throw CoralException.ThrowException<ClientProxyErrorCode>(item => item.UnknowError, result.Message);        }        private RequestType GetRequestType(MethodInfo methodInfo)        {            if (methodInfo.GetCustomAttribute(typeof(HttpGetAttribute)) != null)                return RequestType.Get;            else if (methodInfo.GetCustomAttribute(typeof(HttpDeleteAttribute)) != null)                return RequestType.Delete;            else if (methodInfo.GetCustomAttribute(typeof(HttpPostAttribute)) != null)                return RequestType.Post;            else if (methodInfo.GetCustomAttribute(typeof(HttpPutAttribute)) != null)                return RequestType.Put;            else if (methodInfo.Name.StartsWith("Get") && methodInfo.GetParameters().All(item => item.ParameterType.IsSimpleUnderlyingType()))                return RequestType.Get;            else if (methodInfo.Name.StartsWith("Delete") && methodInfo.GetParameters().All(item => item.ParameterType.IsSimpleUnderlyingType()))                return RequestType.Delete;            else if (methodInfo.Name.StartsWith("Remove") && methodInfo.GetParameters().All(item => item.ParameterType.IsSimpleUnderlyingType()))                return RequestType.Delete;            else if (methodInfo.Name.StartsWith("Update") && methodInfo.GetParameters().Any(item => !item.ParameterType.IsSimpleUnderlyingType()))                return RequestType.Put;            else if (methodInfo.Name.StartsWith("Modify") && methodInfo.GetParameters().Any(item => !item.ParameterType.IsSimpleUnderlyingType()))                return RequestType.Put;            return RequestType.Post;        }        /// <summary>        /// 准备Header的数据        /// </summary>        /// <returns></returns>        private Dictionary<string, string> PrepareHeader(MethodInfo methodInfo)        {            var header = new Dictionary<string, string>();            if (UserContext.Current != null && UserContext.Current.User != null)            {                header.Add("UserId", UserContext.Current.User.Id.ToString());                header.Add("UserName", HttpUtility.UrlEncode(UserContext.Current.User.Name));                header.Add("UserToken", UserContext.Current.User.Token);                header.Add("UserAccount", HttpUtility.UrlEncode(UserContext.Current.User.Account));                LoggerFactory.Instance.Info($"{methodInfo}存在认证信息,线程{Thread.CurrentThread.ManagedThreadId}");                LoggerFactory.Instance.Info($"用户信息为:{JsonConvert.SerializeObject(header)}");            }            else            {                header.Add("IsAnonymous", "true");                LoggerFactory.Instance.Info($"{methodInfo}不存在认证信息,线程{Thread.CurrentThread.ManagedThreadId}");            }            if (SessionContext.Current != null)                header.Add("SessionId", HttpUtility.UrlEncode(SessionContext.Current.SessionKey));            if (PageContext.Current != null)                header.Add("ConnectionId", HttpUtility.UrlEncode(PageContext.Current.PageKey));            if (methodInfo.DeclaringType != null && methodInfo.DeclaringType.GetMethods().Count(item => item.Name == methodInfo.Name) > 1)                header.Add("ActionInfo", CoralSecurity.DesEncrypt(methodInfo.ToString()));            return header;        }        /// <summary>        /// 准备Url的请求数据        /// </summary>        /// <param name="methodInfo"></param>        /// <param name="parameters"></param>        /// <returns></returns>        private Dictionary<string, string> PrepareGetParams(MethodInfo methodInfo, params object[] parameters)        {            var paramInfos = methodInfo.GetParameters();            var dict = new Dictionary<string, string>();            for (int i = 0; i < paramInfos.Length; i++)            {                //todo 这里要支持嵌套                dict.Add(paramInfos[i].Name, parameters[i]?.ToString() ?? string.Empty);            }            return dict;        }        /// <summary>        /// 准备Body的参数        /// </summary>        /// <param name="methodInfo"></param>        /// <param name="parameters"></param>        /// <returns></returns>        private Dictionary<string, object> PrepareBodyParams(MethodInfo methodInfo, params object[] parameters)        {            var paramInfos = methodInfo.GetParameters();            var dict = new Dictionary<string, object>();            for (var i = 0; i < paramInfos.Length; i++)            {                var item = paramInfos[i];                if (item.ParameterType.IsSimpleType())                    continue;                dict.Add(item.Name, parameters[i]);            }            return dict;        }        /// <summary>        /// 准备Url的参数        /// </summary>        /// <param name="methodInfo"></param>        /// <param name="parameters"></param>        /// <returns></returns>        private Dictionary<string, string> PrepareSampleParams(MethodInfo methodInfo, params object[] parameters)        {            var paramInfos = methodInfo.GetParameters();            var dict = new Dictionary<string, string>();            for (var i = 0; i < paramInfos.Length; i++)            {                var item = paramInfos[i];                if (!item.ParameterType.IsSimpleType())                    continue;                dict.Add(item.Name, parameters[i]?.ToString() ?? string.Empty);            }            return dict;        }        /// <summary>        /// 拼接url参数        /// </summary>        /// <param name="url"></param>        /// <param name="dict"></param>        /// <returns></returns>        private string AppendGetParamToUrl(string url, Dictionary<string, string> dict)        {            if (dict == null || dict.Count == 0)                return url;            if (url.Contains("?"))                url += "&";            else                url += "?";            url += string.Join("&", dict.Select(item => $"{item.Key}={item.Value ?? string.Empty}"));            return url;        }    }    internal enum RequestType    {        Get = 0,        Post,
                

注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。