• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

使用EasyUI的树控件构建Web界面ASP.NET是如何在IIS下工作的

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

最近花了不少时间在重构和进一步提炼我的Web开发框架上,力求在用户体验和界面设计方面,和Winform开发框架保持一致,而在Web上,我主要采用EasyUI的前端界面处理技术,走MVC的技术路线,在重构完善过程中,很多细节花费不少时间进行研究和提炼,一步步走过来,也积累了不少经验,本系列将主要介绍我在进一步完善我的Web框架基础上积累的经验进行分享,本随笔主要介绍使用EasyUI的树控件构建Web界面的相关经验。

在很多界面设计上,我们可能都需要引入树列表控件,这个控件可以用zTree来实现,也可以用EasyUI的内置树控件进行展示,由于历史原因,我原来倾向于使用zTree,最新把它全部修改为EasyUI的树控件,并进行了完善优化,发现代码更加简洁明快,非常不错。

1、在界面上使用EasyUI的树控件

一般情况下,使用EasyUI的树控件,代码很简单,脚本代码如下所示,主要就是通过调用url来获得Json数据,然后就可以显示了,通过onClick就可以响应用户单击节点的操作,每个节点有id, text, iconCls, checked,state,children等属性。

1)树控件的Json数据绑定

            $('#treeDept').tree({
                url: '/User/GetMyDeptTreeJson?userId=@Session["UserId"]',
                onClick: function (node) {
                    loadData(node.id);
                }
            });

2)树控件的折叠和展开

树控件的展开和折叠,可以通过定义两个通用的脚本进行处理,如下所示。

        function expandAll(treeName) {
            var node = $('#' + treeName).tree('getSelected');
            if (node) {
                $('#' + treeName).tree('expandAll', node.target);
            }
            else {
                $('#' + treeName).tree('expandAll');
            }
        }
        function collapseAll(treeName) {
            var node = $('#' + treeName).tree('getSelected');
            if (node) {
                $('#' + treeName).tree('collapseAll', node.target);
            }
            else {
                $('#' + treeName).tree('collapseAll');
            }
        }

然后,在页面加载完毕后,绑定指定的按钮控件就可以了吗,如下代码所示。

        //初始化对象
        $(document).ready(function () {
            //初始化机构分类
            initOUCategorys();

            //机构基础信息
            initDeptTreeview();
            $("#deptExpand").bind("click", function () {
                expandAll("treeDept");
            });
            $("#deptCollapse").bind("click", function () {
                collapseAll("treeDept");
            });                       

            $("#loading").center(); //loading的图片显示居中
        });

3)树控件的复选框显示

树控件默认是没有复选框的,它可以通过属性checkbox设置让它进行展示的,如下代码是我项目里面的代码。

其中cascadeCheck是否让树控件级联的,默认是级联,也就是只要父控件被选中,所有其子控件都会被选中,我们可以设置它为false,让它不级联,这样在很多情况下是需要的。

            $('#treeFunctionView').tree({
                checkbox: true,
                cascadeCheck: false,
                url: '/Function/GetRoleFunctionByUser?userId=@Session["UserId"]',
                onClick: function (node) {
                    //
                }
            });

4)树控件的全选和全不选择

这个全部不选的特性,我找了很多文章,都没有找到,其实后来才发现,我们对树的节点理解有偏差,认识到后,实现起来也很容易。

如取消全部节点的选中状态,代码如下所示。它的方法getChecked是返回所有的节点,而不是一个节点。它们把全部选中的节点放到一个结合里面,不像Winform里面,树节点需要递归查询,这里只需要一个for循环就可以展开了,我这里把所有勾选的节点,设置为非勾选状态就可以实现取消全部树节点勾选状态了。

        function unCheckTree(tree) {
            var nodes = $('#' + tree).tree('getChecked');
            if (nodes) {
                for (var i = 0; i < nodes.length; i++) {
                    $('#' + tree).tree('uncheck', nodes[i].target);
                }
            }
        }

我们知道,很多树控件,为了方便操作,都提供了一个全选或者全部不选的操作,这个在EasyUI的树控件里面,也是很容易实现的。这里的getChildren和上面的意思类似,也是返回所有的子节点,不需要在进行递归,用一个for循环就可以遍历全部节点和其下面的多级子节点了。也就是说,它是一个二维的数据,不用递归查询。

        function checkAllTree(tree, checked) {
            var children = $('#' + tree).tree('getChildren');
            for (var i = 0; i < children.length; i++) {
                if (checked) {
                    $('#' + tree).tree('check', children[i].target);
                } else {
                    $('#' + tree).tree('uncheck', children[i].target);
                }
            }
        }

 

5)下拉列表的树控件初始化

除了普通的树列表,还有一种比较特殊的树控件,就是在ComboTree,也就是在下拉列表中集成树控件,它的操作和普通的树控件差不多,很多事件属性都一样,它的使用代码如下所示。

        //初始化公司
        function initCompany() {
            $('#txtCompany_ID').combotree({
                url: '/User/GetMyCompanyTreeJson?userId=@Session["UserId"]',
                valueField: 'id',
                textField: 'text',
                required: true,
                onClick: function (node) {
                    //
                }
            });
        }

 

2、树控件的优化

1)普通的Json数据生成

前面说了,我们为了方便,一般使用Json数据和javascript打交道,而EasyUI的树控件支持很好地的Json链接绑定,因此我们只需要在对应的控制器里面实现json数据的生成即可,如果是一开始想要确定的Json数据,一般也是通过手工生成的居多,如下代码所示。

        public ActionResult GetTreeJson()
        {
            string folder = "/Content/JqueryEasyUI/themes/icons/customed/" + "organ.png";
            string leaf = "/Content/JqueryEasyUI/themes/icons/customed/" + "organ.png";
            string json = GetTreeJson(-1, folder, leaf);
            json = json.Trim(',');
            return Content(string.Format("[{0}]", json));
        }

        /// <summary>
        /// 递归获取树形信息
        /// </summary>
        /// <param name="PID"></param>
        /// <returns></returns>
        private string GetTreeJson(int PID, string folderIcon, string leafIcon)
        {
            string condition = string.Format("PID={0}", PID);
            List<OUInfo> nodeList = BLLFactory<OU>.Instance.Find(condition);
            StringBuilder content = new StringBuilder();
            foreach (OUInfo model in nodeList)
            {
                int ParentID = (model.PID == -1 ? 0 : model.PID);
                //string tempMenu = string.Format("{{ id:{0}, pId:{1}, name:\"{2}\",icon:\"{3}\" }},", model.ID, ParentID, model.Name, imgsrc);
                string subMenu = this.GetTreeJson(model.ID, folderIcon, leafIcon);
                string parentMenu = string.Format("{{ \"id\":{0}, \"pId\":{1}, \"name\":\"{2}\" ", model.ID, ParentID, model.Name);
                if (string.IsNullOrEmpty(subMenu))
                {
                    if (!string.IsNullOrEmpty(leafIcon))
                    {
                        parentMenu += string.Format(",\"icon\":\"{0}\" }},", leafIcon);
                    }
                    else
                    {
                        parentMenu += "},";
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(folderIcon))
                    {
                        parentMenu += string.Format(",\"icon\":\"{0}\" }},", folderIcon);
                    }
                    else
                    {
                        parentMenu += "},";
                    }
                }

                content.AppendLine(parentMenu.Trim());
                content.AppendLine(subMenu.Trim());
            }

            return content.ToString().Trim();
        } 

上面的代码很好实现了根据数据库结构的关系,生成Json数据,但是感觉部分硬编码,凑出来的数据,始终感觉不太理想,如果我们要简化,该如何操作呢?

 

2)简洁美观的Json数据生成

本小节继续上面的议题,看如何简化json的生成,因为我们需要很多这样的json操作,如果采用上面的方法,我感觉很容易出错,而且也不太美观。为了解决这个问题,我们可以通过定义一个json数据的实体类,用来承载相关的信息,如下定义所示。

    /// <summary>
    /// 定义EasyUI树的相关数据,方便控制器生成Json数据进行传递
    /// </summary>
    [DataContract]
    [Serializable]
    public class EasyTreeData
    {
        /// <summary>
        /// ID
        /// </summary>
        [DataMember]
        public string id { get; set; }

        /// <summary>
        /// 节点名称
        /// </summary>
        [DataMember]
        public string text { get; set; }
        
        /// <summary>
        /// 是否展开
        /// </summary>
        [DataMember]
        public string state  { get; set; }

        /// <summary>
        /// 图标样式
        /// </summary>
        [DataMember]
        public string iconCls { get; set; }


        /// <summary>
        /// 子节点集合
        /// </summary>
        [DataMember]
        public List<EasyTreeData> children { get; set; }
        
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public EasyTreeData() 
        {
            this.children = new List<EasyTreeData>();
            this.state = "open";
        }

        /// <summary>
        /// 常用构造函数
        /// </summary>
        public EasyTreeData(string id, string text, string iconCls = "", string state = "open")
            : this()
        {
            this.id = id;
            this.text = text;
            this.state = state;
            this.iconCls = iconCls;
        }

        /// <summary>
        /// 常用构造函数
        /// </summary>
        public EasyTreeData(int id, string text, string iconCls = "", string state = "open")
            : this()
        {
            this.id = id.ToString();
            this.text = text;
            this.state = state;
            this.iconCls = iconCls;
        }
    }

然后,我们在需要生成Json数据的地方,使用这个实体类进行承载,然后把它列表生成Json就可以了,很简单了,呵呵。

        /// <summary>
        /// 根据用户获取对应人员层次的树Json
        /// </summary>
        /// <param name="deptId">用户所在部门</param>
        /// <returns></returns>
        public ActionResult GetUserTreeJson(int deptId)
        {
            List<EasyTreeData> treeList = new List<EasyTreeData>();
            treeList.Insert(0, new EasyTreeData(-1, "无"));

            List<UserInfo> list = BLLFactory<User>.Instance.FindByDept(deptId);
            foreach (UserInfo info in list)
            {
                treeList.Add(new EasyTreeData(info.ID, info.FullName, "icon-user"));
            }

            string json = ToJson(treeList);
            return Content(json);
        }

如果需要递归的操作,一样的方式处理就可以了。

        /// <summary>
        /// 获取用户的部门树结构(分级需要)
        /// </summary>
        /// <param name="userId">用户ID</param>
        /// <returns></returns>
        public ActionResult GetMyDeptTreeJson(int userId)
        {
            StringBuilder content = new StringBuilder();
            UserInfo userInfo = BLLFactory<User>.Instance.FindByID(userId);
            if (userInfo != null)
            {
                OUInfo groupInfo = GetMyTopGroup(userInfo);
                if (groupInfo != null)
                {
                    List<OUNodeInfo> list = BLLFactory<OU>.Instance.GetTreeByID(groupInfo.ID);

                    EasyTreeData treeData = new EasyTreeData(groupInfo.ID, groupInfo.Name, GetIconcls(groupInfo.Category));
                    GetTreeDataWithOUNode(list, treeData);

                    content.Append(base.ToJson(treeData));
                }
            }
            string json = string.Format("[{0}]", content.ToString().Trim(','));
            return Content(json);
        }

上面使用EasyTreeData来承载数据,然后构建列表,其本身就是一个多层级的树对象,然后一个ToJson的方法就可以把列表对象完美转换为Jason数据了。

这里的ToJson,主要就是调用JavaScriptSerializer 对象进行的操作,如下所示。

        /// <summary>
        /// 把对象为json字符串
        /// </summary>
        /// <param name="obj">待序列号对象</param>
        /// <returns></returns>
        protected string ToJson(object obj)
        {
            string jsonData = (new JavaScriptSerializer()).Serialize(obj);
            return jsonData;
        }

 

3、树控件效果展示

在介绍如何使用它之后,我们来看看我几个场景中使用树控件进行的展示效果,方便我们加深上面EasyUI树控件使用的了解。

1)组织机构列表如下所示:

2)角色树列表展示

 

3)功能树列表展示

4)菜单树列表展示

5)登陆日志树列表展示

6)下拉列表树展示

  

    

 

 

 

主要研究技术:代码生成工具、Visio二次开发、客户关系管理软件、送水管理软件等共享软件开发
专注于Winform开发框架Web开发框架、WCF开发框架的研究及应用。
  转载请注明出处:
撰写人:伍华聪  http://www.iqidi.com 
    

ASP.NET是如何在IIS下工作的

ASP.NET与IIS是紧密联系的,由于IIS6.0与IIS7.0的工作方式的不同,导致ASP.NET的工作原理也发生了相应的变化。

 

IIS6(IIS7的经典模式)与IIS7的集成模式的不同

IIS6的运行过程:

分析上图可知:

    在 User Mode 下,http.sys 接收到 http request,然后它会根据 IIS 中的 Metabase 查看基于该 Request 的 Application 属于哪个 Application Pool, 如果该 Application Pool 不存在,则创建之。否则直接将 request 发到对应 Application Pool 的 Queue中。每个 Application Pool 对应着一个 Worker Process — w3wp.exe,(运行在 User Mode 下)。

    在 IIS Metabase 中维护着 Application Pool 和 Worker Process 的Mapping。WAS(Web Administrative Service)根据这样一个 mapping,将存在于某个 Application Pool Queue 的 request 传递到对应的 Worker Process (如果没有,就创建这样一个进程)。在 Worker Process 初始化的时候,加载 ASP.NET ISAPI,ASP.NET ISAPI 进而加载 CLR。最后通过 AppManagerAppDomainFactory 的 Create 方法为 Application 创建一个 Application Domain;通过 ISAPIRuntime 的  ProcessRequest 处理 Request,进而将流程进入到 ASP.NET Http Runtime Pipeline。

   几个知识点:

  • HTTP.SYS:(Kernel)的一个组件,它负责侦听(Listen)来自于外部的HTTP请求,根据请求的URL将其转发给相应的应用程序池 (Application Pool)。当此HTTP请求处理完成时,它又负责将处理结果发送出去.为了提供更好的性能,HTTP.SYS内部建立了一个缓冲区,将最近的HTTP请求处理结果保存起来。
  • Application Pool:  IIS总会保持一个单独的工作进程:应用程序池。所有的处理都发生在这个进程里,包括ISAPI dll的执行。对于IIS6而言,应用程序池是一个重大的改进,因为它们允许以更小的粒度控制一个指定进程的执行。你可以为每一个虚拟目录或者整个Web 站点配置应用程序池,这可以使你很容易的把每一个应用程序隔离到各自的进程里,这样就可以把它与运行在同一台机器上其他程序完全隔离。从Web处理的角度看,如果一个进程死掉,至少它不会影响到其它的进程。 
    当应用程序池接收到HTTP请求后,交由在此应用程序池中运行的工作者进程Worker Process: w3wp.exe来处理此HTTP请求。
  • Worker Process: 当工作者进程接收到请求后,首先根据后缀找到并加载对应的ISAPI扩展 (如:aspx 对应的映射是aspnet_isapi.dll),工作者进程加载完aspnet_isapi.dll后,由aspnet_isapi.dll负责加载 ASP.NET应用程序的运行环境即CLR (.NET Runtime)。 
    Worker Process运行在非托管环境,而.NET中的对象则运行在托管环境之上(CLR),它们之间的桥梁就是ISAPI扩展。
  • WAS(Web Admin Service):这是一个监控程序,它一方面可以存取放在InetInfo元数据库(Metabase)中的各种信息,另一方面也负责监控应用程序池(Application Pool)中的工作者进程的工作状态况,必要时它会关闭一个老的工作者进程并创建一个新的取而代之。

IIS7的运行过程:

     

分析上图可知:

    1、当客户端浏览器开始 HTTP 请求一个WEB 服务器的资源时,HTTP.sys 拦截到这个请求。

    2、HTTP.sys 联系 WAS 获取配置信息。

    3、WAS 向配置存储中心(applicationHost.config)请求配置信息。

    4、WWW 服务接收到配置信息,配置信息指类似应用程序池配置信息,站点配置信息等等。

    5、WWW 服务使用配置信息去配置 HTTP.sys 处理策略。

    6、WAS为请求创建一个进程(如果不存在的话)。

    7、工作者进程处理请求并对HTTP.sys做出响应。

    8、客户端接受到处理结果信息。

 

除了IIS的整体运行方式不同之外,IIS7相比IIS6最大的不同之处在于它提供了两种应用程序池管道模式:

经典模式:是与IIS 6或者之前版本保持兼容的一种模式,一个典型问题就是,在处理ASP.NET这种动态网站的时候,它是通过一个所谓的ISAPI程序,作为插件的方式来工作的。针对不同的动态应用程序(例如ASP,PHP等),会需要不同的ISAPI(Internet Server Application Programe Interface,互联网服务器应用程序接口)。如图,在IIS中,打开“处理程序映射”,可以看到aspx类型页面的处理程序为aspnet_isapi.dll。

下图展示了IIS7经典模式与IIS6的应用程序池管道模式运行原理,针对不同的请求,会指定不同的ISAPI(dll)进行处理:

 

集成模式:asp.net不再像IIS6一样只限定于aspnet_isapi.dll中,而是被解放出来,从IIS接收到HTTP请求开始,即进入asp.net的控制范围,asp.net可以存在于一个请求在IIS中各个处理阶段。允许我们将ASP.NET更好地与IIS集成,甚至允许我们在ASP.NET中编写一些功能(例如Module)来改变IIS的行为(扩 展)。集成的好处是,不再通过ISAPI的方式,提高了速度和稳定性。至于扩展,则可以使得我们对于IIS,以及其他类型的请求有更多的控制。(例如,我 们希望静态网页也具备一些特殊的行为)。如图

 

 

如下图在IIS7集成模式中,打开处理程序映射,可以看到aspx类型页面所对应的不再是一个dll,而是一个类型。

 

总结与扩展:

对于处理ASP.NET应用程序而言,IIS6及IIS7的经典模式需要aspnet_isapi.dll来处理,而IIS7集成模式不需要aspnet_isapi.dll来处理,而可以直接根据文件扩展名找到相应的处理程序接口。例如aspx的处理程序是System.Web.UI.PageHandlerFactory类型。

 

介绍完IIS的工作原理,来看一下ASP.NET内部的运行机制。

首先看一下IIS处理模型:

 

上面介绍IIS工作原理时,已经介绍了从发起HTTP请求,到响应请求的过程,这里主要介绍当请求到达.NET Runtime之后,.NET运行时所发生的一系列工作。

先看如下的.NET运行时工作序列图:

 


1.HTTP请求进入Web服务器后,首先由HTTP.SYS来判断请求的页面是否存在,如果存在的话将把请求信息转交给.NET Runtime。在这部分实际是完成两个步骤,在将请求转交给.NET Runtime的同时将请求信息封存在HTTPWorkRequest类中供其它步骤调用。HttpWorkRequest类在以后的操作中至关重要,它第一次将Http请求信息转换为类信息。 
2.当请求到达.NET Runtime后,接下来的操作将会在托管环境中完成,这时请求就真正进入了.NET中,对请求信息的操作是由.NET的底层类库来实现。首先.NET Runtime将会针对请求信息做两个动作,一是准备HostingEnvironment;二是调用ApplicationManager类为HTTP请求动态的分配AppDomain,并把处理权交给AppDomain。 
3.HTTP请求进入AppDomain后,将由对象ISAPIRuntime来接管,一方面经方法ProcessRequest()得到HttpWorkerRequest对象,另一方面由方法StartProcessing()生成HttpRuntime对象,接下来把处理权交给了HttpRuntime(HttpWorkerRequest对象将作为HttpRuntime方法中的参数被使用)。 
4.HTTPRuntime接收到Http请求后,方法ProcessRequest处理请求。将对第1步中的HTTPWorkRequest类中的信息进行操作,具体的实现由ProcessRequest方法实现。内部代码如下:

 
  1. [AspNetHostingPermission(SecurityAction.Demand, Level=AspNetHostingPermissionLevel.Medium)]  
  2. public static void ProcessRequest(HttpWorkerRequest wr)  
  3. {  
  4.     if (wr == null)  
  5.     {  
  6.         throw new ArgumentNullException("wr");  
  7.     }  
  8.     if (UseIntegratedPipeline)  
  9.     {  
  10.         throw new PlatformNotSupportedException(System.Web.SR.GetString("Method_Not_Supported_By_Iis_Integrated_Mode"new object[] { "HttpRuntime.ProcessRequest" }));  
  11.     }  
  12.     ProcessRequestNoDemand(wr);  
  13. }  
  14. internal static void ProcessRequestNoDemand(HttpWorkerRequest wr)  
  15. {  
  16.     RequestQueue queue = _theRuntime._requestQueue;  
  17.     if (queue != null)  
  18.     {  
  19.         wr = queue.GetRequestToExecute(wr);  
  20.     }  
  21.     if (wr != null)  
  22.     {  
  23.         CalculateWaitTimeAndUpdatePerfCounter(wr);  
  24.         wr.ResetStartTime();  
  25.         ProcessRequestNow(wr);  
  26.     }  
  27. }  
  28. internal static void ProcessRequestNow(HttpWorkerRequest wr)  
  29. {  
  30.     _theRuntime.ProcessRequestInternal(wr);  
  31. }  

5.在HttpRunTime中经过一系列的驱动后,将会在ProcessRequestInternal方法中为Http请求分配应用程序。在这一步中还将创建HttpContext对象。

 
  1. context = new HttpContext(wr, false); // 基于HttpWorkerRequest生成HttpContext  
  2. IHttpHandler applicationInstance = HttpApplicationFactory.GetApplicationInstance(context); // 得到HttpApplication  
  3. handler2.BeginProcessRequest(context, this._handlerCompletionCallback, context); // 由HttpApplication处理请求  

6.经过步骤5后HTTP请求信息才由基本信息转交给了Asp.net中的各个对象。接下来的操作会触发一些列的管道事件,这时的请求才真正转到HttpModule和HttpHandler中。 
接下来我们看看常说的管道事件的创建过程:

 
  1. internal override void BuildSteps(WaitCallback stepCallback)  
  2. {  
  3.     ArrayList steps = new ArrayList();  
  4.     HttpApplication app = base._application;  
  5.     bool flag = false;  
  6.     UrlMappingsSection urlMappings = RuntimeConfig.GetConfig().UrlMappings;  
  7.     flag = urlMappings.IsEnabled && (urlMappings.UrlMappings.Count > 0);  
  8.     steps.Add(new HttpApplication.ValidatePathExecutionStep(app));  
  9.     if (flag)  
  10.     {  
  11.         steps.Add(new HttpApplication.UrlMappingsExecutionStep(app));  
  12.     }  
  13.     app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);  
  14.     app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);  
  15.     app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);  
  16.     app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);  
  17.     app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);  
  18.     app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);  
  19.     app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);  
  20.     app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);  
  21.     steps.Add(new HttpApplication.MapHandlerExecutionStep(app));  
  22.     app.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);  
  23.     app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);  
  24.     app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);  
  25.     app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);  
  26.     steps.Add(new HttpApplication.CallHandlerExecutionStep(app));  
  27.     app.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);  
  28.     app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);  
  29.     app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);  
  30.     steps.Add(new HttpApplication.CallFilterExecutionStep(app));  
  31.     app.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);  
  32.     app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps);  
  33.     this._endRequestStepIndex = steps.Count;  
  34.     app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);  
  35.     steps.Add(new HttpApplication.NoopExecutionStep());  
  36.     this._execSteps = new HttpApplication.IExecutionStep[steps.Count];  
  37.     steps.CopyTo(this._execSteps);  
  38.     this._resumeStepsWaitCallback = stepCallback;  
  39. }  

管道事件请求序列图如下:

人生没有回头路,珍惜当下。
 
asp.net

最近花了不少时间在重构和进一步提炼我的Web开发框架上,力求在用户体验和界面设计方面,和Winform开发框架保持一致,而在Web上,我主要采用EasyUI的前端界面处理技术,走MVC的技术路线,在重构完善过程中,很多细节花费不少时间进行研究和提炼,一步步走过来,也积累了不少经验,本系列将主要介绍我在进一步完善我的Web框架基础上积累的经验进行分享,本随笔主要介绍使用EasyUI的树控件构建Web界面的相关经验。

在很多界面设计上,我们可能都需要引入树列表控件,这个控件可以用zTree来实现,也可以用EasyUI的内置树控件进行展示,由于历史原因,我原来倾向于使用zTree,最新把它全部修改为EasyUI的树控件,并进行了完善优化,发现代码更加简洁明快,非常不错。

1、在界面上使用EasyUI的树控件

一般情况下,使用EasyUI的树控件,代码很简单,脚本代码如下所示,主要就是通过调用url来获得Json数据,然后就可以显示了,通过onClick就可以响应用户单击节点的操作,每个节点有id, text, iconCls, checked,state,children等属性。

1)树控件的Json数据绑定

            $('#treeDept').tree({
                url: '/User/GetMyDeptTreeJson?userId=@Session["UserId"]',
                onClick: function (node) {
                    loadData(node.id);
                }
            });

2)树控件的折叠和展开

树控件的展开和折叠,可以通过定义两个通用的脚本进行处理,如下所示。

        function expandAll(treeName) {
            var node = $('#' + treeName).tree('getSelected');
            if (node) {
                $('#' + treeName).tree('expandAll', node.target);
            }
            else {
                $('#' + treeName).tree('expandAll');
            }
        }
        function collapseAll(treeName) {
            var node = $('#' + treeName).tree('getSelected');
            if (node) {
                $('#' + treeName).tree('collapseAll', node.target);
            }
            else {
                $('#' + treeName).tree('collapseAll');
            }
        }

然后,在页面加载完毕后,绑定指定的按钮控件就可以了吗,如下代码所示。

        //初始化对象
        $(document).ready(function () {
            //初始化机构分类
            initOUCategorys();

            //机构基础信息
            initDeptTreeview();
            $("#deptExpand").bind("click", function () {
                expandAll("treeDept");
            });
            $("#deptCollapse").bind("click", function () {
                collapseAll("treeDept");
            });                       

            $("#loading").center(); //loading的图片显示居中
        });

3)树控件的复选框显示

树控件默认是没有复选框的,它可以通过属性checkbox设置让它进行展示的,如下代码是我项目里面的代码。

其中cascadeCheck是否让树控件级联的,默认是级联,也就是只要父控件被选中,所有其子控件都会被选中,我们可以设置它为false,让它不级联,这样在很多情况下是需要的。

            $('#treeFunctionView').tree({
                checkbox: true,
                cascadeCheck: false,
                url: '/Function/GetRoleFunctionByUser?userId=@Session["UserId"]',
                onClick: function (node) {
                    //
                }
            });

4)树控件的全选和全不选择

这个全部不选的特性,我找了很多文章,都没有找到,其实后来才发现,我们对树的节点理解有偏差,认识到后,实现起来也很容易。

如取消全部节点的选中状态,代码如下所示。它的方法getChecked是返回所有的节点,而不是一个节点。它们把全部选中的节点放到一个结合里面,不像Winform里面,树节点需要递归查询,这里只需要一个for循环就可以展开了,我这里把所有勾选的节点,设置为非勾选状态就可以实现取消全部树节点勾选状态了。

        function unCheckTree(tree) {
            var nodes = $('#' + tree).tree('getChecked');
            if (nodes) {
                for (var i = 0; i < nodes.length; i++) {
                    $('#' + tree).tree('uncheck', nodes[i].target);
                }
            }
        }

我们知道,很多树控件,为了方便操作,都提供了一个全选或者全部不选的操作,这个在EasyUI的树控件里面,也是很容易实现的。这里的getChildren和上面的意思类似,也是返回所有的子节点,不需要在进行递归,用一个for循环就可以遍历全部节点和其下面的多级子节点了。也就是说,它是一个二维的数据,不用递归查询。

        function checkAllTree(tree, checked) {
            var children = $('#' + tree).tree('getChildren');
            for (var i = 0; i < children.length; i++) {
                if (checked) {
                    $('#' + tree).tree('check', children[i].target);
                } else {
                    $('#' + tree).tree('uncheck', children[i].target);
                }
            }
        }

 

5)下拉列表的树控件初始化

除了普通的树列表,还有一种比较特殊的树控件,就是在ComboTree,也就是在下拉列表中集成树控件,它的操作和普通的树控件差不多,很多事件属性都一样,它的使用代码如下所示。

        //初始化公司
        function initCompany() {
            $('#txtCompany_ID').combotree({
                url: '/User/GetMyCompanyTreeJson?userId=@Session["UserId"]',
                valueField: 'id',
                textField: 'text',
                required: true,
                onClick: function (node) {
                    //
                }
            });
        }

 

2、树控件的优化

1)普通的Json数据生成

前面说了,我们为了方便,一般使用Json数据和javascript打交道,而EasyUI的树控件支持很好地的Json链接绑定,因此我们只需要在对应的控制器里面实现json数据的生成即可,如果是一开始想要确定的Json数据,一般也是通过手工生成的居多,如下代码所示。

        public ActionResult GetTreeJson()
        {
            string folder = "/Content/JqueryEasyUI/themes/icons/customed/" + "organ.png";
            string leaf = "/Content/JqueryEasyUI/themes/icons/customed/" + "organ.png";
            string json = GetTreeJson(-1, folder, leaf);
            json = json.Trim(',');
            return Content(string.Format("[{0}]", json));
        }

        /// <summary>
        /// 递归获取树形信息
        /// </summary>
        /// <param name="PID"></param>
        /// <returns></returns>
        private string GetTreeJson(int PID, string folderIcon, string leafIcon)
        {
            string condition = string.Format("PID={0}", PID);
            List<OUInfo> nodeList = BLLFactory<OU>.Instance.Find(condition);
            StringBuilder content = new StringBuilder();
            foreach (OUInfo model in nodeList)
            {
                int ParentID = (model.PID == -1 ? 0 : model.PID);
                //string tempMenu = string.Format("{{ id:{0}, pId:{1}, name:\"{2}\",icon:\"{3}\" }},", model.ID, ParentID, model.Name, imgsrc);
                string subMenu = this.GetTreeJson(model.ID, folderIcon, leafIcon);
                string parentMenu = string.Format("{{ \"id\":{0}, \"pId\":{1}, \"name\":\"{2}\" ", model.ID, ParentID, model.Name);
                if (string.IsNullOrEmpty(subMenu))
                {
                    if (!string.IsNullOrEmpty(leafIcon))
                    {
                        parentMenu += string.Format(",\"icon\":\"{0}\" }},", leafIcon);
                    }
                    else
                    {
                        parentMenu += "},";
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(folderIcon))
                    {
                        parentMenu += string.Format(",\"icon\":\"{0}\" }},", folderIcon);
                    }
                    else
                    {
                        parentMenu += "},";
                    }
                }

                content.AppendLine(parentMenu.Trim());
                content.AppendLine(subMenu.Trim());
            }

            return content.ToString().Trim();
        } 

上面的代码很好实现了根据数据库结构的关系,生成Json数据,但是感觉部分硬编码,凑出来的数据,始终感觉不太理想,如果我们要简化,该如何操作呢?

 

2)简洁美观的Json数据生成

本小节继续上面的议题,看如何简化json的生成,因为我们需要很多这样的json操作,如果采用上面的方法,我感觉很容易出错,而且也不太美观。为了解决这个问题,我们可以通过定义一个json数据的实体类,用来承载相关的信息,如下定义所示。

    /// <summary>
    /// 定义EasyUI树的相关数据,方便控制器生成Json数据进行传递
    /// </summary>
    [DataContract]
    [Serializable]
    public class EasyTreeData
    {
        /// <summary>
        /// ID
        /// </summary>
        [DataMember]
        public string id { get; set; }

        /// <summary>
        /// 节点名称
        /// </summary>
        [DataMember]
        public string text { get; set; }
        
        /// <summary>
        /// 是否展开
        /// </summary>
        [DataMember]
        public string state  { get; set; }

        /// <summary>
        /// 图标样式
        /// </summary>
        [DataMember]
        public string iconCls { get; set; }


        /// <summary>
        /// 子节点集合
        /// </summary>
        [DataMember]
        public List<EasyTreeData> children { get; set; }
        
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public EasyTreeData() 
        {
            this.children = new List<EasyTreeData>();
            this.state = "open";
        }

        /// <summary>
        /// 常用构造函数
        /// </summary>
        public EasyTreeData(string id, string text, string iconCls = "", string state = "open")
            : this()
        {
            this.id = id;
            this.text = text;
            this.state = state;
            this.iconCls = iconCls;
        }

        /// <summary>
        /// 常用构造函数
        /// </summary>
        public EasyTreeData(int id, string text, string iconCls = "", string state = "open")
            : this()
        {
            this.id = id.ToString();
            this.text = text;
            this.state = state;
            this.iconCls = iconCls;
        }
    }

然后,我们在需要生成Json数据的地方,使用这个实体类进行承载,然后把它列表生成Json就可以了,很简单了,呵呵。

        /// <summary>
        /// 根据用户获取对应人员层次的树Json
        /// </summary>
        /// <param name="deptId">用户所在部门</param>
        /// <returns></returns>
        public ActionResult GetUserTreeJson(int deptId)
        {
            List<EasyTreeData> treeList = new List<EasyTreeData>();
            treeList.Insert(0, new EasyTreeData(-1, "无"));

            List<UserInfo> list = BLLFactory<User>.Instance.FindByDept(deptId);
            foreach (UserInfo info in list)
            {
                treeList.Add(new EasyTreeData(info.ID, info.FullName, "icon-user"));
            }

            string json = ToJson(treeList);
            return Content(json);
        }

如果需要递归的操作,一样的方式处理就可以了。

        /// <summary>
        /// 获取用户的部门树结构(分级需要)
        /// </summary>
        /// <param name="userId">用户ID</param>
        /// <returns></returns>
        public ActionResult GetMyDeptTreeJson(int userId)
        {
            StringBuilder content = new StringBuilder();
            UserInfo userInfo = BLLFactory<User>.Instance.FindByID(userId);
            if (userInfo != null)
            {
                OUInfo groupInfo = GetMyTopGroup(userInfo);
                if (groupInfo != null)
                {
                    List<OUNodeInfo> list = BLLFactory<OU>.Instance.GetTreeByID(groupInfo.ID);

                    EasyTreeData treeData = new EasyTreeData(groupInfo.ID, groupInfo.Name, GetIconcls(groupInfo.Category));
                    GetTreeDataWithOUNode(list, treeData);

                    content.Append(base.ToJson(treeData));
                }
            }
            string j 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap