天天看點

第二篇 基于.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)