1. 首先经过反射将《服务类型》经过ApiControllerBuilder 构建成成 DynamicApiControllerInfo
2. 在DynamicApiControllerInfo中同时构建DynamicApiActionInfo
3. Ioc注入DynamicApiController<TService> Tservice就是最开始的《服务类型》
3. 最后将DynamicApiControllerInfo添加到DynamicApiControllerManager,经过ServiceName缓存web
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处理流程api
实际在Abp中 DyanamicHttpActionDescriptor 的 ExecuteAsync 中实际是经过AOP拦截实现的.这里我作了修改数组
首先将DynamicController改成组合的方式注入IService来做为代理对象以下图缓存
而后执行的时候采用获取IDynamicApiController 的ProxyObject 来使用反射执行架构
其中因为MVC并无放出ReflectedHttpActionDescriptor.ActionExecutor 这个类型,因此用了点技巧。并发
1. 首先在 DynamicApiControllerInfo 中增长属性 FullNameActions 类型和Actions 一致
2. 而后再初始化的时候同时初始化FullNameActions ,Action的key是Name,FullNameActions 是Method.ToString()[这种包含的信息更多,可做为惟一标识]
3. 最后在客户端调用的时候放到Header便可区分,实现函数重载app
在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 代码以下async
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,分别对应两个参数便可ide
我这里使用Abp并非为了基于界面作,而是为了作服务化,客户端经过接口,而后走http请求,最后由服务端的服务实现去执行结果最后返回,有点像之前webservice不过更加轻量级。
先来看看实现,至于做用会在后面的文章中陆续分享。
动态代理/真实代理,大部分状况下用来做为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); }
方法调用者这里抽象成接口是由于我有两个实现,一个是基于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, Put, Delete, }
重点在于要结合服务端的实现考虑怎么获得请求,参数组织,认证信息组织等等, 代码逻辑应该还算清晰,能够从Invoke 方法开始看起来
注入代码比较简单,就是扫描全部的接口,而后利用动态代理注入Ioc容器便可
//代理工厂代码以下 public class ProxyFactory { public static TService GetService<TService>(ProxyType proxyType,string host,string preFixedString) { switch (proxyType) { case ProxyType.Rest: return (TService)new Core.ClientProxy(typeof(TService), new RestActionInvoker(host, preFixedString)).GetTransparentProxy(); case ProxyType.Hessian: return (TService)new Core.ClientProxy(typeof(TService), new HessianActionInvoker(host)).GetTransparentProxy(); default: return (TService)new Core.ClientProxy(typeof(TService), new RestActionInvoker(host, preFixedString)).GetTransparentProxy(); } } public static object GetService(Type serviceType, ProxyType proxyType, string host, string preFixedString) { switch (proxyType) { case ProxyType.Rest: return new Core.ClientProxy(serviceType, new RestActionInvoker(host, preFixedString)).GetTransparentProxy(); case ProxyType.Hessian: return new Core.ClientProxy(serviceType, new HessianActionInvoker(host)).GetTransparentProxy(); default: return new Core.ClientProxy(serviceType, new RestActionInvoker(host, preFixedString)).GetTransparentProxy(); } } } public enum ProxyType { Rest = 0, Hessian, } //利用Ioc注入代码以下 MetaDataManager.Type.GetAll().ForEach(type => { if (!type.IsInterface) return; if (type.GetInterface(typeof(IService).Name) == null) return; if (type.GetGenericArguments().Length != 0) return; //todo 这里最好放在特性里面判断 if (type.GetCustomAttribute(typeof(InjectAttribute)) != null) return; //Debug.WriteLine("客户端注册类型{0}", type); var obj = ProxyFactory.GetService(type, ProxyType.Rest, "http://localhost:28135", "apiservice"); UnityService.RegisterInstance(type, obj); //var obj = ProxyFactory.GetService(type, ProxyType.Hessian, "http://localhost:28135", ""); //UnityService.RegisterInstance(type, obj); //var interfaces = type.GetInterfaces(); //LinqExtensions.ForEach(interfaces, item => //{ // Debug.WriteLine(type.FullName + "-" + item.FullName); // UnityService.RegisterType(item, type); //}); });
工程结构以下
ClientProxy是客户端测试工程 ClientModule中作扫描注入
ServiceContractTest是契约:包含服务接口和DTO
ServiceHost :包含Abp的注册DynamicController和契约的实现
调用代码以下:
ITestService service = UnityService.Resolve<ITestService>();
var items = service.GetNames("123");
实现和契约比较简单就不贴代码了,这一篇贴了不少代码了。冏!_!
有上面测试工程的结构之后,顺势就能够想象到一个Api容器的概念:
经过将Client和ServiceHost逻辑放到框架层面,客户端只需引用这两个dll,客户端只引用契约。
服务端将契约放到bin下面的Module(或者其余目录) 经过反射加载程序集便可。
总体来讲Host能够为几种形态,IIs/WindowsServer/Console/....不管哪一种只要放入dll便可实现Host
DDD典型六边形架构中,保证应用层和领域层的内聚性。简单来讲如下几个dll:
1. Contract.XXX
2. App.XXX
3. Domain.XXX
4. Repository.XXX
以下几个dll组织成一个内聚的BC也能够理解为模块,这样你可让任意一个模块切换部署方式IIS/Service/Console等。业务更加脱离于技术
最开始的单体架构中若是咱们以DDD的方式来组织业务,如上所述那几个dll是一组业务/模块/bc,那么咱们多组业务在最开始业务复杂度不高的状况下以单体应用的方式来组成解决方案。
到后来随着业务复杂度增大,或者并发性能的因素影响须要对应用进行切割,只须要将解决方案进行从新组织,引入代理和Api容器便可实现几乎零修改的方式将一个单体的应用分割到不一样的进程中进行调用。
1. 假设咱们把一个Contract当作一个业务模块也就是一个服务
2. 将Contract上传到nuget中
3. 扩展nuget功能,提取Contract的注释做为元数据持久化
4. 提供搜索功能,搜索关键字便可获取相关的服务。而后下载dll实现
这里经过扩展nuget把全部的服务进行集中管理,能够进行分组,至关于构建了一个业务的仓库,全部的业务为都进行集中的存放,这样若是要引用某项服务只须要获取相关dll结合客户端代理便可开始开发对应的功能。
1. 由于开发人员接触到纯粹的业务,并且屏蔽掉了技术细节,那么架构上就留出了迭代的空间
2. 若是某个服务作集群,那么能够经过某个Contract和服务注册发现结合修改客户端代理实现。等等这些架构上的工做均可以循序渐进的进行,而且业务上能够实现无影响的迁移
基于这个Api容器和客户端代理的方式结合DDD可让业务和技术并行几乎无影响分别进行迭代,避免由于技术架构的修改致使大量的业务代码的修改。
基于这个Api容器,本身后面作一些组件的时候也相对容易。下一篇文章将介绍一个做业调度模块的设计,就会用到这