第二篇 基於.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。
 
         
/// <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對象,支持模塊的加載與卸載

 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)




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM