/// <summary>
        /// 添加新的节点
        /// </summary>
        /// <returns></returns>
        public ActionResult AddNewNode(string CallBackIndexView = "Index")
        {
            //获取合法的新节点ID
            int targetDefaultNodeId = 0;

            lock (((ICollection)ConfigNodeList).SyncRoot)
            {
                do
                {
                    targetDefaultNodeId++;
                } while (ConfigNodeList.ContainsKey(targetDefaultNodeId.ToString()));

                //添加默认的数据
                ResponseNodeConfig          newOne  = new ResponseNodeConfig(targetDefaultNodeId.ToString());
                DefaultDealingHandlerConfig dealing = new DefaultDealingHandlerConfig()
                {
                    DataType = DataTypes.Text,
                    RawData  = "呈現的文本"
                };
                newOne.DealingHandlerConfig = dealing;
                newOne.DoneHandlerConfig    = new DefaultDoneHandlerConfig();
                ConfigNodeList.Add(newOne.NodeID, newOne);

                //写入到配置文件
                ResponseConfiguration.SaveConfig_ResponseChain(ConfigNodeList.Values.ToArray());

                ViewData["CurrentEditedNode"] = newOne;                                                                  //进入编辑状态
                Session["AdvantanceConfig_CustomHandler_Result"] = new Dictionary <Type, IConfigClassOfCustomHandler>(); //每次进去编辑状态,清空自定义处理器的参数配置结果
            }

            LoadDatas();
            ViewBag.CallBackIndexView = CallBackIndexView;
            return(View("Index"));
        }
        /// <summary>
        /// 保存单个节点的配置
        /// </summary>
        private void SaveOneConfig(ResponseNodeConfig configNode, string oldNodeId)
        {
            lock (((ICollection)ConfigNodeList).SyncRoot)
            {
                //删除旧的
                if (ConfigNodeList.ContainsKey(oldNodeId))
                {
                    ConfigNodeList.Remove(oldNodeId);
                }

                //新增
                ConfigNodeList[configNode.NodeID] = configNode;
            }

            //写入到配置文件
            ResponseConfiguration.SaveConfig_ResponseChain(ConfigNodeList.Values.ToArray());
        }
        /// <summary>
        /// 将节点信息转化为Json格式
        /// </summary>
        /// <param name="nodeConfig">节点配置</param>
        /// <param name="pID">节点ID(用于ZTree)</param>
        /// <param name="parentID">父节点ID(用于ZTree)</param>
        /// <returns>Json描述</returns>
        private string GetTreeStruct_Helper_Convert2Json(ResponseNodeConfig nodeConfig, string pID, string parentID, bool MarkFlag_Repeat = false)
        {
            //格式:{ id: 1, pId: 0, name: "父节点 1", open: true ,Editable:true, Deleteable:true },

            string currentNodeId = nodeConfig.NodeID;
            string summary       = nodeConfig.GetSummary();

            string json = BuildTreeNodeJson(pID,
                                            parentID,
                                            MarkFlag_Repeat ? (summary + " (鏈路迴圈的節點,停止展開)") : (summary),
                                            currentNodeId,
                                            true,
                                            true,
                                            currentNodeId == ConstString.ROOT_NODE_ID ? false : true,   //根节点不允许删除
                                            MarkFlag_Repeat ? "#ECFFE8" : "#0"

                                            );

            return(json);
        }
        /// <summary>
        /// 生成树的枝干
        /// </summary>
        /// <param name="nodeConfig">节点配置</param>
        /// <param name="pID">节点ID(用于ZTree)</param>
        /// <param name="parentID">父节点ID(用于ZTree)</param>
        /// <param name="parentID">记录单条链路出现过的ID<para/>(当FindSubNodes标记为False时,此集合不关注,此情况下可以传NULL)</param>
        /// <param name="FindSubNodes">行为:是否展开处理子节点</param>
        /// <param name="ShowNextNodeThenStopFindSubNodes">最后呈现多一次子节点,后续不再呈现(目前Obsolete此属性,请勿使用)</param>
        /// <returns></returns>
        private string GetTreeStruct_Helper_GenerateBranch(ResponseNodeConfig nodeConfig,
                                                           string pID, string parentID,
                                                           HashSet <string> usedIDs,
                                                           bool FindSubNodes = true, bool ShowNextNodeThenStopFindSubNodes = false)
        {
            //创建当前节点的信息
            string json = GetTreeStruct_Helper_Convert2Json(nodeConfig, pID, parentID);

            //当前节点ID
            string currentNodeId = nodeConfig.NodeID;

            usedIDs.Add(currentNodeId);

            //记录:该节点属于"有父亲关联",非孤儿状态(断链状态)
            UsefulNodes.Add(currentNodeId);

            //处理子节点们
            if (FindSubNodes == true &&
                nodeConfig.DealingHandlerConfig is TextMenuHandlerConfig)
            {
                StringBuilder jsonSub   = new StringBuilder();
                int           pID_Index = 0;

                TextMenuHandlerConfig menus = (TextMenuHandlerConfig)nodeConfig.DealingHandlerConfig;
                foreach (var menu in menus.Menus)
                {
                    //获得目标子节点的Config
                    string             subID           = menu.Value.ToString();
                    ResponseNodeConfig targetSubConfig = GetTreeStruct_Helper_GetConfigByNodeId(subID);

                    if (targetSubConfig == null)
                    {
                        continue;
                    }

                    #region 树形结构的过滤

                    //## subID指定"根节点",只显示节点信息,不再呈现子节点们
                    if (subID == ConstString.ROOT_NODE_ID)
                    {
                        //jsonSub.AppendLine(GetTreeStruct_Helper_GenerateBranch(targetSubConfig, currentNodeId, null, false));       //3:false,不再展开呈现子节点
                        jsonSub.AppendLine(GetTreeStruct_Helper_Convert2Json(targetSubConfig, pID + "." + pID_Index++, pID, true));
                        continue;
                    }

                    #region 准备废弃
                    /* 考虑之后,还是不再呈现子节点的效果比较好,下面方案先注释。 */

                    /*
                     *
                     * //## 最后呈现多一次子节点,后续不再呈现
                     * if (ShowNextNodeThenStopFindSubNodes)
                     * {
                     *  jsonSub.AppendLine(GetTreeStruct_Helper_GenerateBranch(targetSubConfig, currentNodeId, false));       //3:false,不再展开呈现子节点
                     *  continue;
                     * }
                     * //## 子节点如果是TextMenuHandler,当开始重复时,标记为“最后呈现多一次子节点,后续不再呈现”,然后递归处理      //3:true,呈现子节点;  4:true,如果有孙节点不再展开
                     * {
                     *  jsonSub.AppendLine(GetTreeStruct_Helper_GenerateBranch(targetSubConfig, currentNodeId,true,true));
                     *  continue;
                     * }
                     *
                     */
                    #endregion

                    //## 子节点如果是TextMenuHandler,当开始重复时,只呈现信息但是不再展开孙节点
                    if (usedIDs.Contains(subID))
                    {
                        //jsonSub.AppendLine(GetTreeStruct_Helper_GenerateBranch(targetSubConfig, currentNodeId, null, false));       //3:false,不再展开呈现子节点
                        jsonSub.AppendLine(GetTreeStruct_Helper_Convert2Json(targetSubConfig, pID + "." + pID_Index++, pID, true));
                        UsefulNodes.Add(subID);
                        continue;
                    }

                    //## 其他情景,正常呈现节点信息 + 递归子节点
                    HashSet <string> currentLink_usedIDs = new HashSet <string>(usedIDs);
                    currentLink_usedIDs.Add(currentNodeId);
                    jsonSub.AppendLine(GetTreeStruct_Helper_GenerateBranch(targetSubConfig, pID + "." + pID_Index++, pID, currentLink_usedIDs));

                    #endregion
                }

                json += jsonSub.ToString();
            }

            return(json);
        }
        public ActionResult SaveEdit(Dictionary <string, string> parmDic)
        {
            /* Doubt: 只能序列化成 <Stirng,String>?  当使用<Stirng,dynamic>,无法为Value赋值"集合"/"JObject";会转换成无法使用的 System.Object?
             *
             * 因此这里的“DefaultNews”和 “TextMenu”,
             * 从View层序列化成 JsonString,然后再在Controller还原。
             * 求解决方案。
             */

            //抽取基本数据
            string targetNodeId   = parmDic["NodeID"];
            string NewNodeId      = parmDic["NewNodeId"];
            string DealingHandler = parmDic["DealingHandler"];
            string DoneHandler    = parmDic["DoneHandler"];

            //检查ID是否重复、格式是否正确
            if (targetNodeId != ConstString.ROOT_NODE_ID &&
                targetNodeId != NewNodeId)        //修改了NodeID时才检查
            {
                if (NodeIdValidator.IsValid(NewNodeId) == false)
                {
                    return(Json(new { IsSuccess = false, errorMessage = "節點ID格式不正確。必須為 x.y.z 序號格式。" }));
                }

                bool isExisted = false;
                lock (((ICollection)ConfigNodeList).SyncRoot)
                    isExisted = ConfigNodeList.ContainsKey(NewNodeId);

                if (isExisted)
                {
                    return(Json(new { IsSuccess = false, errorMessage = "修改的節點ID已經存在,不可以重複。" }));
                }
            }

            ResponseNodeConfig configNode   = new ResponseNodeConfig(NewNodeId);
            StringBuilder      errorMessage = new StringBuilder();

            //开始根据不同类型,解析具体的数据
            bool isNeedDoneHandler = true;      //标记是否需要Done阶段;因为有部分Dealing处理器,处理完则跳转节点,此时不需要再配置Done。

            #region DealingHandler

            switch (DealingHandler)
            {
            default:
                errorMessage.AppendLine("找不到對應的Dealing類型。");
                break;

            case "DefaultText":
            {
                DefaultDealingHandlerConfig HandlerResult = new DefaultDealingHandlerConfig();
                HandlerResult.DataType          = DataTypes.Text;
                HandlerResult.RawData           = parmDic["DealingHandler_DefaultText"];
                configNode.DealingHandlerConfig = HandlerResult;
            }
            break;

            case "DefaultNews":
            {
                DefaultDealingHandlerConfig HandlerResult = new DefaultDealingHandlerConfig();
                HandlerResult.DataType = DataTypes.News;
                dynamic news = Newtonsoft.Json.Linq.JObject.Parse(parmDic["DealingHandler_DefaultNews"]);
                HandlerResult.RawData = new ArticleCan(news.title.ToString(),
                                                       news.description.ToString(),
                                                       news.picUrl.ToString(),
                                                       news.pageUrl.ToString());
                configNode.DealingHandlerConfig = HandlerResult;
            }
            break;

            case "TextMenu":
            {
                TextMenuHandlerConfig HandlerResult = new TextMenuHandlerConfig();

                //##提示文字
                ResponseTextMessageConfig ready = new ResponseTextMessageConfig();
                ready.Context = parmDic["DealingHandler_TextMenu_ReadyMessage"];
                HandlerResult.ReadyMessageConfig = ready;

                //##菜单项
                var menus = Newtonsoft.Json.Linq.JObject.Parse(parmDic["DealingHandler_TextMenu_Menus"]);
                foreach (var menu in menus)
                {
                    string theNodeid = menu.Value["Id"].ToString().Replace("#", String.Empty);               //??Json序列化时,如果Key为纯数字(即使类型是字符串),会被排序。因此这个用#前缀处理。

                    //有效性检查
                    if (NodeIdValidator.IsValid(theNodeid) == false)
                    {
                        errorMessage.Append(theNodeid);
                        errorMessage.AppendLine("不是有效的節點ID格式。必須為 x.y.z 序號格式。");
                        break;
                    }

                    //重复检查(紧跟其下的目标跳转的节点ID,不能与当前节点ID一样,否则会直接循环跳转 ;  跨节点的,不限制。)
                    if (String.Equals(NewNodeId, theNodeid, StringComparison.OrdinalIgnoreCase))
                    {
                        errorMessage.Append("子節點");
                        errorMessage.Append(theNodeid);
                        errorMessage.AppendLine(",不能與當前節點的ID重複。");
                        break;
                    }

                    HandlerResult.Menus.Add(new DictionaryEntry(menu.Value["data"].ToString(), theNodeid));
                }

                configNode.DealingHandlerConfig = HandlerResult;
                isNeedDoneHandler = false;                //标记不处理Done
            }
            break;

            case "CustomHandler":
            {
                CustomHandlerConfig HandlerResult = new CustomHandlerConfig();
                HandlerResult.HandlerTypeName   = parmDic["DealingHandler_CustomHandler"];
                configNode.DealingHandlerConfig = HandlerResult;

                //自定义处理的参数配置
                Type targetType   = CustomHandlerConfig.GetICustomHandlerTypeFromCurrentDomain(HandlerResult.HandlerTypeName);
                bool isConfigable = typeof(ICustomHandlerConfigable).IsAssignableFrom(targetType);
                if (isConfigable && Session["AdvantanceConfig_CustomHandler_Result"] != null)
                {
                    var dictionary = Session["AdvantanceConfig_CustomHandler_Result"] as IDictionary <Type, IConfigClassOfCustomHandler>;
                    if (dictionary != null && dictionary.ContainsKey(targetType))
                    {
                        IConfigClassOfCustomHandler configData = dictionary[targetType] as IConfigClassOfCustomHandler;
                        if (configData != null)
                        {
                            //获取配置类
                            Type configClassType = ConfigClassOfCustomHandlerHelper.GetConfigClassType(targetType);
                            if (configClassType != null)
                            {
                                //加载
                                XDocument xdoc = ConfigClassOfCustomHandlerHelper.LoadConfigByType(configClassType);
                                ConfigClassOfCustomHandlerHelper.RemoveConfig(xdoc, targetNodeId);
                                ConfigClassOfCustomHandlerHelper.UpdateConfig(xdoc, NewNodeId, configData);

                                //保存
                                ConfigClassOfCustomHandlerHelper.SaveConfigByType(configClassType, xdoc);
                            }
                        }
                    }
                }
            }
            break;
            }

            #endregion

            #region DoneHandler

            if (isNeedDoneHandler)
            {
                switch (DoneHandler)
                {
                default:
                    errorMessage.AppendLine("找不到對應的Done類型。");
                    break;

                case "DefaultRootNode":
                {
                    //DefaultDoneHandlerConfig HandlerResult = new DefaultDoneHandlerConfig();
                    configNode.DoneHandlerConfig = null;
                }
                break;

                case "DefaultTargetNode":
                {
                    if (NodeIdValidator.IsValid(parmDic["DoneHandler_JumpNode"]) == false)
                    {
                        errorMessage.Append(parmDic["DoneHandler_JumpNode"]);
                        errorMessage.AppendLine("不是有效的節點ID格式。必須為 x.y.z 序號格式。");
                        break;
                    }

                    DefaultDoneHandlerConfig HandlerResult = new DefaultDoneHandlerConfig();
                    HandlerResult.NodeId         = parmDic["DoneHandler_JumpNode"];
                    HandlerResult.TipText        = parmDic["DoneHandler_TipText"];
                    configNode.DoneHandlerConfig = HandlerResult;
                }
                break;
                }
            }

            #endregion

            //反馈结果
            if (errorMessage.Length <= 0)
            {
                SaveOneConfig(configNode, targetNodeId);
                return(Json(new { IsSuccess = true }));
            }
            else
            {
                return(Json(new { IsSuccess = false, errorMessage = errorMessage.ToString() }));
            }
        }