第二篇 基于.net搭建热插拔式web框架(沙箱的构建)
上周五写了一个实现原理篇,在评论中看到有朋友也遇到了我的问题,真的是有种他乡遇知己的感觉,整个系列我一定会坚持写完,并在最后把代码开源到git中。上一篇文章很多人看了以后,都表示不解,觉得不知道我到底要干什么,可能就像隔行如隔山吧,就像做移动端开发的人很少去考虑分布式中的通信一样。大家都知道模块化,但模块化的思路有很多,我的只是其中一种,也许你看到最后会觉得这种思路在经过不断地演化后会成为一种很好的解决方案,当然这离不开以后大家对代码及思想的贡献。
好了不扯了,还是回到主题上来吧....
沙箱是什么?怎么用呢?
沙箱说白了就是插件、模块运行的环境,有点像docker又不像(哈哈)。沙箱主要由两部分组成:沙箱管道和沙箱启动器(为了显得高大上一些,就起了两个难以理解的名字),还有一个以后为功能做铺垫的实体类——controller/action 封包类
首先贴一下这个实体类的代码:
/// <summary>controller/action 封包类 /// </summary> public class CAModel { public string ControllerName{get;set;} public string ActionName{get;set;} public string UrlPath { get; set; } /// <summary>构造 /// </summary> /// <param name="controllerName">controller 全名(带命名空间)</param> /// <param name="actionName">action 全名(不带参数)</param> public CAModel(string controllerName,string actionName) { ControllerName=controllerName; ActionName=actionName; UrlPath = controllerName.Replace(".Areas.", "/").Replace(".Controllers.", "/");//controller转Url if (UrlPath.EndsWith("Controller")) { UrlPath = string.Format("/{0}/{1}", UrlPath.Substring(0, UrlPath.Length - 10),actionName); } } public CAModel() { } }
这个封装类主要是为了以后系统核心功能:权限管理,方便获取模块内所有action对应的Url路径,现在就不过多投入精力了。
下边我们说一下沙箱管道(SandBoxChannel ):沙箱管道服务于沙箱启动器,这个类需要继承MarshalByRefObject,也就是必须要支持跨域访问。这个类中最关键的就是_assembly和InvokeMothod。
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | /// <summary>沙箱管道 /// </summary> public class SandBoxChannel : MarshalByRefObject { /// <summary>沙箱内加载的程序集 /// </summary> private Assembly _assembly; /// <summary>加载程序集 /// </summary> /// <param name="assemblyFile">程序集文件路径(主程序类库路径,依赖类库自动加载)</param> public void LoadAssembly( string assemblyFile) { try { _assembly = Assembly.LoadFrom(assemblyFile); } catch (Exception ex) { throw ex; } } /// <summary>沙箱内方法调用 /// </summary> /// <param name="typeName">类名称(全名称)</param> /// <param name="methodName">方法名称</param> /// <param name="args">参数</param> /// <returns></returns> public object InvokeMothod( string typeName, string methodName, params object [] args) { var _assembly_temp = _assembly; if (_assembly_temp == null ) return null ; if (typeName == "Huber.Kernel.Entity.CurVariable" && methodName == "SetCurWebDir" ) { //设置当前沙箱内的系统变量,非外部方法 foreach ( var a in AppDomain.CurrentDomain.GetAssemblies()) { if (a.GetName().Name == "Huber.Kernel" ) { _assembly_temp = a; break ; } } } Type tp = _assembly_temp.GetType(typeName, true , false ); if (tp == null ) return null ; MethodInfo method = tp.GetMethod(methodName); if (method == null ) return null ; Object obj = Activator.CreateInstance(tp); //ContentResult //FileContentResult //FileStreamResult //FilePathResult //HttpNotFoundResult //JavaScriptResult //JsonResult //PartialViewResult //RedirectToRouteResult //RedirectResult //ViewResult object result = method.Invoke(obj, args); return result; } /// <summary>获取所有的action /// </summary> /// <returns></returns> public Dictionary< string , CAModel> GetAllAction() { Dictionary< string , CAModel> result = null ; Type[] types = _assembly.GetTypes(); MethodInfo[] meths = null ; string controller = string .Empty; if (types != null ) { result = new Dictionary< string , CAModel>(); string url = string .Empty; CAModel temp; foreach ( var t in types) { if (t.BaseType != null && t.BaseType.ToString() == "Huber.Kernel.MVC.HuberController" ) { meths = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly); foreach ( var m in meths) { temp = new CAModel(t.ToString(), m.Name); result.Add(temp.UrlPath.ToLower(), temp); } } } } return result; } public override object InitializeLifetimeService() { //Remoting对象 无限生存期 return null ; } |
再看一下沙箱启动器(SandBoxDynamicLoader):所谓沙箱启动器就是创建一个启动器对象,把模块的类库、配置文件等加载进去,当然这个启动内部是一个沙箱(即appdomain)。在SandBoxDynamicLoader的构造方法中创建的了一个appdomain和一个SandBoxChannel对象,支持模块的加载与卸载
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 | public class SandBoxDynamicLoader { /// <summary>沙箱对应的应用程序域 /// </summary> private AppDomain appDomain; /// <summary>沙箱管道 /// </summary> private SandBoxChannel channelChannel; /// <summary>程序域的ID /// </summary> public int AppDomainID { get ; set ; } /// <summary>插件名称 /// </summary> public string PluginName { get ; set ; } /// <summary>构造 /// </summary> /// <param name="ApplicationBase">插件的所在的目录(bin目录)</param> /// <param name="_PluginName">插件名称</param> /// <param name="configPath">config文件位置</param> /// <param name="_AppDomainID">域标识(唯一)</param> public SandBoxDynamicLoader( string ApplicationBase, string _PluginName, string configPath, int _AppDomainID) { PluginName = _PluginName; AppDomainID = _AppDomainID; AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = ApplicationBase; DirectoryInfo di= new DirectoryInfo(ApplicationBase); if (configPath != string .Empty) { setup.ConfigurationFile = configPath; } setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "private" ); setup.CachePath = setup.ApplicationBase; setup.ShadowCopyFiles = "true" ; setup.ShadowCopyDirectories = setup.ApplicationBase+ "\\SandBoxRunShadow" ; AppDomain.CurrentDomain.SetShadowCopyFiles(); this .appDomain = AppDomain.CreateDomain(PluginName, null , setup); this .appDomain.SetData( "APP_CONFIG_FILE" , configPath); String name = Assembly.GetExecutingAssembly().GetName().FullName; try { this .channelChannel = (SandBoxChannel) this .appDomain.CreateInstanceAndUnwrap(name, typeof (SandBoxChannel).FullName); } catch (Exception ex) { } } /// <summary>加载程序集 /// </summary> /// <param name="assemblyFile"></param> public void LoadAssembly( string assemblyFile) { channelChannel.LoadAssembly(assemblyFile); } /// <summary>获取当前模块内所有action /// </summary> /// <returns></returns> public Dictionary< string , CAModel> GetAllAction() { if (channelChannel == null ) return null ; return channelChannel.GetAllAction(); } /// <summary>方法调用 /// </summary> /// <param name="typeName">类名称(全名称)</param> /// <param name="methodName">方法名称</param> /// <param name="args">参数</param> /// <returns></returns> public object InvokeMothod( string typeName, string methodName, params object [] args) { return channelChannel.InvokeMothod(typeName, methodName, args); } /// <summary>卸载 /// </summary> public void Unload() { try { if (appDomain == null ) return ; AppDomain.Unload( this .appDomain); this .appDomain = null ; } catch (CannotUnloadAppDomainException ex) { throw ex; } } |
到此沙箱模型就完了,其实整个过程可以归纳为:创建一个appdomain,利用反射调用方法处理请求。这个模型不仅在web平台上可以使用,其实他早就在系统服务型框架、窗体框架中大范围使用了。
转载请注明出处:http://www.cnblogs.com/eric-z/p/5028243.html
第三篇 基于.net搭建热插拔式web框架(重造Controller)
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步