Hello World
Spiga

在视图中使用递归生成树状结构

2009-09-27 13:45 by 老赵, 15365 visits

在开发过程中往往会有一个需求,就是将一个树状的数据结构在视图中表示出来。例如最传统的多级分类,系统中有一系列根分类,每个分类中又带有一些子分类,而我们的目标便是在页面上生成一个由ul和li嵌套组成的HTML结构。这个问题看似简单,但是如何让实现变的轻松、易于使用也是一个值得讨论的问题。这次就来谈谈这部分的情况。

实现目标

首先来明确一下实现目标。例如我们有一个Category对象,表示一个类别:

public class Category
{
    public string Name { get; set; }

    public List<Category> Children { get; set; }
}

然后我们准备一个嵌套的数据结构:

public ActionResult Categories()
{
    var model = new List<Category>
    {
        new Category
        {
            Name = "Category 1",
            Children = new List<Category>
            {
                new Category
                {
                    Name = "Category 1 - 1",
                    Children = new List<Category>()
                },
                new Category
                {
                    Name = "Category 1 - 2",
                    Children = new List<Category>()
                },
            }
        },
        new Category
        {
            Name = "Category 2",
            Children = new List<Category>
            {
                new Category
                {
                    Name = "Category 2 - 1",
                    Children = new List<Category>()
                },
                new Category
                {
                    Name = "Category 2 - 2",
                    Children = new List<Category>()
                },
            }
        },
    };

    return View(model);
}

自然还会有一个Model类型为List<Category>的视图:

<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<List<Category>>" %>

...

而我们的目标,便是要在视图中显示出这样的HTML:

<ul>
    <li>Category 1
        <ul>
            <li>Category 1 - 1 </li>
            <li>Category 1 - 2 </li>
        </ul>
    </li>
    <li>Category 2
        <ul>
            <li>Category 2 - 1 </li>
            <li>Category 2 - 2 </li>
        </ul>
    </li>
</ul>

那么我们又该怎么做呢?

使用局部视图

如果在平时让我们处理这种数据结构,很明显会使用递归。但是,在视图模板中表示递归是非常困难的,因此我们会借助局部视图。例如:

<%@ Control Language="C#" Inherits="ViewUserControl<List<Category>>" %>

<% if (Model.Count > 0) { %>
    <ul>
        <% foreach (var cat in Model) { %>
    
            <li>
                <%= Html.Encode(cat.Name) %>
                <% Html.RenderPartial("CategoryTree", cat.Children); %>
            </li>
    
        <% } %>
    </ul>
<% } %>

这个局部视图的作用便是生成我们想要的HTML片段。在局部视图内部还会调用自身来生成下一级的HTML。在主视图中生成局部视图也很容易:

<% Html.RenderPartial("CategoryTree", Model); %>

这就实现了递归,也是实现这一功能最易于理解的方式。只可惜这种做法比较麻烦,需要定义额外的局部视图。这种局部视图往往只是为一个主视图服务的,它会和主视图的前后环境相关,分离开去在维护上就会有些不便了。

在页面中定义委托

我们知道,在运行时ASP.NET页面会被编译为一个类,而其中的各种标记,或内嵌的代码都会被作为一个方法里定义或执行的局部变量。如果说我们要在一个方法内“定义”另一个方法,自然只能是使用匿名方法的特性来构造一个委托了。这个委托为了可以“递归”调用,就必须这么写:

<% Action<List<Category>> renderCategories = null; // 先设为null %>
<% renderCategories = (categories) => { // 再定义 %>

    <% if (categories.Count > 0) { %>
        <ul>
            <% foreach (var cat in categories) { %>
        
                <li>
                    <%= Html.Encode(cat.Name) %>
                    <% renderCategories(cat.Children); %>
                </li>
        
            <% } %>
        </ul>
    <% } %>

<% }; %>

<% renderCategories(Model); // 最后再调用,即生成HTML %>

这段代码的确可以生成HTML,但是我不喜欢。我不喜欢的原因倒不是因为这是我眼中的“伪递归”,而是因为这在页面将“定义”与“执行”分开了。事实上,在我们看到HTML标记及逻辑控制的地方并没有在“同时”生成内容,这只是在“定义”。生成内容的时机其实是在最后对renderCategories委托的调用,这容易造成一定误导,因为最后的“生成”可能会遗漏,而定义和生成之间可能会插入一些其他内容。

这种做法的优势,就是在于不用额外分离出一个局部视图,它直接写在主视图中,易于维护,也相对易于理解。

使用Lambda表达式构建递归方法

“定义”与“执行”分离的一个重要原因,还是因为Lambda表达式无法定义递归函数。否则,我们就可以直接定义一个递归执行的委托,并在最后跟上Invoke或直接调用即可。

因此,其实这里就正是使用Lambda表达式编写递归函数的用武之地。例如,我们补充一个类似的Fix方法:

public static class HtmlExtensions
{
    public static Action<T> Fix<T>(this HtmlHelper helper, Func<Action<T>, Action<T>> f)
    {
        return x => f(Fix(helper, f))(x);
    }
}

于是在视图中便可以:

<% Html.Fix<List<Category>>(render => categories => { %>

    <% if (categories.Count > 0) { %>
        <ul>
            <% foreach (var cat in categories) { %>
        
                <li>
                    <%= Html.Encode(cat.Name) %>
                    <% render(cat.Children); %>
                </li>
        
            <% } %>
        </ul>
    <% } %>

<% }).Invoke(Model); %>

不过严格说来,它还是“定义”与“执行”分离的,只是我们现在可以把它们写在一块儿。此外,Fix方法对于模板中的HTML生成实在没有什么意义。

提供一个Render方法辅助递归

Fix方法对页面生成没有什么作用,不过如果有一个可以辅助递归的Render方法便有意义多了:

public static class HtmlExtensions
{
    private static Action<T> Fix<T>(Func<Action<T>, Action<T>> f)
    {
        return x => f(Fix(f))(x);
    }

    public static void Render<T>(this HtmlHelper helper, T model, Func<Action<T>, Action<T>> f)
    {
        Fix(f)(model);
    }
}

于是,我们在页面中就可以这么写:

<% Html.Render(Model, render => categories => { %>

    <% if (categories.Count > 0) { %>
        <ul>
            <% foreach (var cat in categories) { %>
        
                <li>
                    <%= Html.Encode(cat.Name) %>
                    <% render(cat.Children); %>
                </li>
        
            <% } %>
        </ul>
    <% } %>

<% }); %>

您是否觉得这么做难以理解?我不这么认为,因为从语法上来说,这种HTML生成方式是很简单的:

<% Html.Render(参数, 用于递归的方法 => 当前参数 => { %>

    ...

    <% 递归调用 %>

    ...

<% }); %>

至于背后的原理?关心那些做什么。

性能

可惜,根据性能比较,使用Fix构造递归的做法,比使用SelfApplicable的做法要慢上许多。虽然我认为这里不会是性能的关键,但如果您实在觉得无法接受的话,也可以利用SelfApplicable来构造递归的HTML呈现方式。其辅助方法为:

public delegate void SelfApplicable<T>(SelfApplicable<T> self, T arg);

public static class HtmlExtensions
{
    public static void Render<T>(this HtmlHelper helper, T model, SelfApplicable<T> f)
    {
        f(f, model);
    }
}

于是在视图中:

<% Html.Render(Model, (render, categories) => { %>

    <% if (categories.Count > 0) { %>
        <ul>
            <% foreach (var cat in categories) { %>
        
                <li>
                    <%= Html.Encode(cat.Name) %>
                    <% render(render, cat.Children); %>
                </li>
        
            <% } %>
        </ul>
    <% } %>

<% }); %>

同样,我们只要记住这么做的“语法”就可以了。

总结

相比之下,我喜欢最后两种做法。因为他们直接构造了“HTML生成”的功能,且“内置”了递归。如果使用一个额外的局部视图,虽然“朴素”但使用较为麻烦。使用“伪递归”的方式,从概念上看这不太像是在生成HTML,程序构造的痕迹(先声明,再定义,最后调用)过于明显了。

您喜欢哪种做法呢?如果您遇到了我这样的需求,您会怎么做呢?

最后我想进行一个小调查:您满意WebForm的页面作为视图模板引擎吗?您平时最喜欢使用什么视图模板引擎,为什么呢?

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

74 条回复

  1. 哦,奇怪
    *.*.*.*
    链接

    哦,奇怪 2009-09-27 13:49:00

    沙发拿掉先!

  2. jrgl[未注册用户]
    *.*.*.*
    链接

    jrgl[未注册用户] 2009-09-27 14:03:00

    “宁波晚报讯宁波吉日软件开发有限公司以招工为名进行非法培训的事件经本报报道后,有关部门迅速赶往现场调查查处。据悉,昨天,市科技园区劳动监察部门已经向其发出“限期整改”的通知书。

    http://news.sina.com.cn/o/2006-03-24/11038520824s.shtml
    吉日嘎啦就是个骗子。。。。

  3. 老赵
    admin
    链接

    老赵 2009-09-27 14:07:00

    @jrgl
    这件事情他自己不都早就写过了吗?

  4. pangxiaoliang[北京]流浪者
    *.*.*.*
    链接

    pangxiaoliang[北京]流浪者 2009-09-27 14:29:00

    实际上就是用了“组合模式”的思想吧,赵大侠。
    嘿嘿~~

  5. 老赵
    admin
    链接

    老赵 2009-09-27 14:52:00

    @pangxiaoliang[北京]流浪者
    没有发现组合模式的意味……

  6. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:14:00

    我对mvc中如何用我的递归枚举产生了兴趣 决定下去玩玩

  7. 老赵
    admin
    链接

    老赵 2009-09-27 15:23:00

    @韦恩卑鄙
    啥叫递归枚举?

  8. Leem
    *.*.*.*
    链接

    Leem 2009-09-27 15:30:00

    视图模板引擎?NVelocity算不算?

  9. 老赵
    admin
    链接

    老赵 2009-09-27 15:32:00

    @Leem
    算,你喜欢NVelocity?为什么呢?

  10. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:33:00

    恩就是我上次写的那个 用枚举器访问树

    正在考虑的时候发现 我要用那个枚举器 很容易就落入构造dom的怪思路里了>_< 看来还是不太适合在服务器端做树呢。



    话说我也和pangxiaoliang一样
    觉得你这是利用扩展方法在不具备组合模式的树中强行植入组合模式。。。。

  11. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:36:00

    我上次给一个asp.net web form 做的程序大概是这样的

    
    class Node
    {
      values;
      id;
      List<Node> children;
      void Render(stringbuilder context)
      {
         children.foreach(x=>x.Render(context))
      }
     
    }
    
    


    形成树 然后render

  12. 老赵
    admin
    链接

    老赵 2009-09-27 15:37:00

    @韦恩卑鄙
    哦哦……好像有点理解你们为什么说它像组合模式了,虽然我觉得没啥模式在里面……
    WebForm真是一个无比nx的视图模板,因为编译成类,因此想咋搞就可以咋搞。

  13. 老赵
    admin
    链接

    老赵 2009-09-27 15:39:00

    @韦恩卑鄙
    可惜视图中不太方便定义一个这样的类型,我觉得。
    不过如果你可以提取公共部分,转化为一个漂亮的API,那么应该也可以用于mvc的模板。

  14. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:39:00

    -0- 太nx了反而让人不知道做啥好

  15. 老赵
    admin
    链接

    老赵 2009-09-27 15:40:00

    @韦恩卑鄙
    话说,你觉得WebForm座视图模板的缺点是什么,有没有想过用其他模板引擎做视图?

  16. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:45:00

    @Jeffrey Zhao
    问住我了-0- 我除了微软方案外啥都不知道。。。 我打酱油。。。

  17. yyliuliang
    *.*.*.*
    链接

    yyliuliang 2009-09-27 15:48:00

    对spark 比较感兴趣, 但也只是个人用用
    涉及到团队开发还是用 webform的viewengine比较保险一点。

  18. poplau
    *.*.*.*
    链接

    poplau 2009-09-27 15:50:00

    刚好,我昨天也做了一下分类,我的做法是在MVC的contraller里生成一个json结构的字符串(自定义的),返回给view,在view中调用js递归生成树。

  19. 老赵
    admin
    链接

    老赵 2009-09-27 15:51:00

    @yyliuliang
    spark是除webform外比较流行的模板引擎了,不过说实话我没有看出它比webform要有优势。
    而且要比可扩展性,那就更不如webform了,我不知道外置的模板引擎,怎么可以超过webform,呵呵。

  20. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:52:00

    Jeffrey Zhao:
    @韦恩卑鄙
    可惜视图中不太方便定义一个这样的类型,我觉得。
    不过如果你可以提取公共部分,转化为一个漂亮的API,那么应该也可以用于mvc的模板。


    把公共部分提取成api 肯定就是你这种拉~~ 核心思想还是把递归的几个步骤抽象出来么

    我当时也是做了那个比较难看的 render 才打算做树枚举器的

    这样得到IEnumerable<stack<Node>> 可以很轻松的生成客户端需要的“给哪个节点增加那些内容” 的脚本数据
    (json也可以 直接生成脚本也可以) 我觉得在服务器render太便宜客户端那些脑满肠肥的浏览器了

  21. 老赵
    admin
    链接

    老赵 2009-09-27 15:52:00

    @poplau
    我们这里还是讨论HTML吧,因为总会遇到除了分类外的需求,JS不是万全之策。
    还有就是SEO……

  22. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:54:00

    poplau:刚好,我昨天也做了一下分类,我的做法是在MVC的contraller里生成一个json结构的字符串(自定义的),返回给view,在view中调用js递归生成树。


    对对 我也是这个想法
    客户端无论是js 还是silverlight 都有直接根据nodeID 构建视图的方法 在服务器和客户端都作一次太亏了点`~~

  23. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 15:55:00

    Jeffrey Zhao:
    @poplau
    我们这里还是讨论HTML吧,因为总会遇到除了分类外的需求,JS不是万全之策。
    还有就是SEO……


    做成页内xml数据岛是不是能帮助seo聂?

  24. 老赵
    admin
    链接

    老赵 2009-09-27 15:56:00

    @韦恩卑鄙
    这我不知道啊,不过不是一直说JSON不适合SEO的吗?
    文字可能容易捕捉,但是比如你用JSON,那些URL如果是拼出来的,那么自然链接就没了。

  25. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 16:04:00

    @Jeffrey Zhao
    -0-作ajax json 肯定不适合seo...
    似乎机器人也都故意绕过 script标记 貌似不会绕过页面内的xml标记
    >_< 也可能只是我良好的愿望而已,,,




    这样的需求确实是很多的
    我觉得有必要在我的枚举器内增加类似 xmlreader 的方式 节点入栈和出栈的时候 都要有个处理才行

  26. peace
    *.*.*.*
    链接

    peace 2009-09-27 16:17:00

    老赵最近是不是在狂补发文率 力争达到1.5篇/日

  27. 老赵
    admin
    链接

    老赵 2009-09-27 16:22:00

    @韦恩卑鄙
    至少Google是不会饶过script的,它会把页面上所有看起来像链接的爬下来。我为什么知道这个呢?因为我看到Google的Web Master Tools里有一些链接说是404,比如是/xxx/yyy/zzz/,为什么会这样呢?后来发现,是因为在JavaScript里写了这样的代码:
    var url = "/xxx/yyy/zzz/" + id;
    搜遍全部文件,只有这一个地方了,嘿嘿。

    其实seo除了关注文字之外还有链接。
    xml里有文字不会被忽略的话,如果不放链接,这效果也是要大打折扣的……

  28. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 16:30:00

    构成带连接的xml才成 这个的确需要注意

    也就是说以"数据尽量接近他被使用时的样子 仅仅进行结构变换"作为原则 就更加seo friendly一些

  29. 老赵
    admin
    链接

    老赵 2009-09-27 16:32:00

    @韦恩卑鄙
    不过,生成带连接的xml和直接生成html的区别在哪里呢?

  30. poplau
    *.*.*.*
    链接

    poplau 2009-09-27 16:37:00

    Jeffrey Zhao:
    @poplau
    我们这里还是讨论HTML吧,因为总会遇到除了分类外的需求,JS不是万全之策。
    还有就是SEO……



    您说的是。随便还有一个问题,那就是,我在view的.cs中写递归函数对model进行解析生成ul和li,这种做法也应该可以吧。

    呵呵,主要目前还只看到您发布的视频第7讲.对html.的用法还不熟练。

  31. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-09-27 16:38:00

    直接生成html好~

  32. 老赵
    admin
    链接

    老赵 2009-09-27 16:39:00

    @poplau
    我倾向于在模板中直接写HTML,而不是在后面的代码里进行拼接……

  33. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-09-27 16:47:00

    挺漂亮的,之前如果是我,我会定义一个PartialView,也就是第一种方法。

    ViewEngine的话,WebFormView已经很让我满意了。

  34. poplau
    *.*.*.*
    链接

    poplau 2009-09-27 16:50:00

    Jeffrey Zhao:
    @poplau
    我倾向于在模板中直接写HTML,而不是在后面的代码里进行拼接……



    恩,学习了。

  35. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 16:53:00

    那个那个。。。
    比如带连接的 xml 是可以

    <cal id="10" father="2">
     http://xxx
    </cal>
    <cal id="11" father="10">
     http://xxx
    </cal>
    

    这样的关系型信息 然后利用客户端dom 一个for进行处理 本身不需要递归


    如果是服务器render就要递归了。。。

  36. 老赵
    admin
    链接

    老赵 2009-09-27 17:03:00

    @韦恩卑鄙
    哦哦,就是生成的XML其实不是嵌套结构,客户端读出来之后再组装。
    那么我觉得就直接在页面上丢不可见的HTML效果也是一样的啊。

  37. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-09-27 17:07:00

    Jeffrey Zhao:
    @韦恩卑鄙
    哦哦,就是生成的XML其实不是嵌套结构,客户端读出来之后再组装。
    那么我觉得就直接在页面上丢不可见的HTML效果也是一样的啊。




    不好意思插一句,韦恩卑鄙的意思是不是在render xml到客户端,然后用js去生成树?

  38. fffff[未注册用户]
    *.*.*.*
    链接

    fffff[未注册用户] 2009-09-27 17:08:00

    http://mikehadlow.blogspot.com/2008/10/rendering-tree-view-using-mvc-framework.html

  39. 老赵
    admin
    链接

    老赵 2009-09-27 17:12:00

    @Ryan Gene
    应该是的。

  40. 老赵
    admin
    链接

    老赵 2009-09-27 17:12:00

    @fffff
    他的做法是在服务器端生成HTML,我不喜欢
    哎,真要说起来,我搞得无数东西都是外国人没写过的。
    因为如果外国人搞过,我就不愿意写了,最多引用一下。

  41. JimLiu
    *.*.*.*
    链接

    JimLiu 2009-09-27 17:14:00

    @Jeffrey Zhao
    我也倾向直接在View里输出HTML,我不愿意绕弯子生成XML或者JSON然后用js转换,HTML有“原生”的url,这个应该对搜索引擎更友好。
    Ajax的情况,没有办法了,打死我也不愿意在服务端生成HTML,所以我统统用JSON了。
    不过有点情况也会有例外,比如我要做一个客户端的树状控件,我也许会考虑服务端只输出JSON,因为这样我可以只做一个Template,就是js的,就能处理页面输出的数据和AJAX得到的数据,而不用js控件一个模板,View里又一个模板。似乎扯远了……

  42. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-09-27 17:17:00

    Jeffrey Zhao:
    @Ryan Gene
    应该是的。



    呵呵,使不得啊。。。

    如果在客户端解析xml,生成树的话,速度非常慢,因为要create无数dom对象。。。

    dhtmlx treeview就用了这种方法,200个节点在客户端就非常慢了,特别是在老浏览器下,例如ie6,记得好像200个节点要6,7秒吧,这是无法接受的。。。

    原来我是用dhtmlx treeview,后来因为这个实在不行,最后自己写算了,哈哈

    没看过的同学可以看下:http://www.geekees.com ,在服务端生成ul, li,最后在客户端绑上事件,几千个节点一起加载没问题 :P

  43. 老赵
    admin
    链接

    老赵 2009-09-27 17:29:00

    @Ryan Gene
    这个项目看上去很不错啊。

  44. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-09-27 17:30:00

    Jeffrey Zhao:
    @Ryan Gene
    这个项目看上去很不错啊。




    呵呵,只是做了个皮而已,里面还没东西呢。。。

  45. Arthraim
    *.*.*.*
    链接

    Arthraim 2009-09-27 17:39:00

    @jrgl
    额…… 他自己交待过……

  46. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-09-27 17:39:00

    Ryan Gene:

    Jeffrey Zhao:
    @Ryan Gene
    这个项目看上去很不错啊。




    呵呵,只是做了个皮而已,里面还没东西呢。。。



    不过这个皮用在实际项目里效果还不错:

  47. W.SiMin[未注册用户]
    *.*.*.*
    链接

    W.SiMin[未注册用户] 2009-09-27 17:44:00

    我习惯用FOR XML AUTO,然后自己转换成需要的东西。。。如果有LINQ就更好了,随时定义也可以

  48. 老赵
    admin
    链接

    老赵 2009-09-27 17:47:00

    @W.SiMin
    你说的不是同一件事情吧。

  49. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 18:51:00

    Ryan Gene:

    Jeffrey Zhao:
    @Ryan Gene
    应该是的。



    呵呵,使不得啊。。。

    如果在客户端解析xml,生成树的话,速度非常慢,因为要create无数dom对象。。。

    dhtmlx treeview就用了这种方法,200个节点在客户端就非常慢了,特别是在老浏览器下,例如ie6,记得好像200个节点要6,7秒吧,这是无法接受的。。。

    原来我是用dhtmlx treeview,后来因为这个实在不行,最后自己写算了,哈哈

    没看过的同学可以看下:http://www.geekees.com ,在服务端生成ul, li,最后在客户端绑上事件,几千个节点一起加载没问题 :P


    呵呵
    你以为服务器render的浏览器就可以少创建dom了? 才没呢 没有id的标记该多少还多少
    性能问题主要在于"不要让浏览器多次无效渲染标记"
    1 可以在展开的时候才生成本层的 算是半延迟加载
    2 全部展开的时候生成整个HTML 的string再加入innerHTML 可以免于多次渲染



  50. 老赵
    admin
    链接

    老赵 2009-09-27 19:05:00

    @韦恩卑鄙
    不过浏览器解析时的开销,和用JavaScript构造对象,可能的确有差别。
    所以设置innerHTML最快,因为它和浏览器解析HTML的性质接近。
    // 我猜得,跑步去了,兄弟们慢聊。

  51. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 19:12:00

    @Jeffrey Zhao
    根源是在于没有像 winform那样 的listbox 有一个 beginedit/endedit的开关
    “听风就是雨”任何修改都会导致重新解析
    以前很流行的 xml绑定dhtml 表格 就是因为这个在ie6时代被人无情的bs了。。。用现在的机器水准在ie6才能做到100条/秒 那叫一个慢。。。

  52. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 19:20:00

    总之我个人建议:
    不管是否递归 从关系数据 组装数据成树数据的逻辑还是扔给客户端比较开心

    我始终觉得 服务器作这种民工活太亏,就算是存在缓存里 服务器保存这种民工产品也太亏。

    再次强调是个人建议


    (可以用用 js的 string数组 假装成stringbuilder 最后join 来提高字符串拼接效率)

  53. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-09-27 19:25:00

    满意WebForm的页面作为视图模板引擎吗?


    尽管也有那么一点不是很满意,但相比其他视图模板,至少有IDE支撑智能提示,所以还是用WebForm作视图。

  54. Ryan Gene
    *.*.*.*
    链接

    Ryan Gene 2009-09-27 20:01:00

    韦恩卑鄙:

    Ryan Gene:

    Jeffrey Zhao:
    @Ryan Gene
    应该是的。



    呵呵,使不得啊。。。

    如果在客户端解析xml,生成树的话,速度非常慢,因为要create无数dom对象。。。

    dhtmlx treeview就用了这种方法,200个节点在客户端就非常慢了,特别是在老浏览器下,例如ie6,记得好像200个节点要6,7秒吧,这是无法接受的。。。

    原来我是用dhtmlx treeview,后来因为这个实在不行,最后自己写算了,哈哈

    没看过的同学可以看下:http://www.geekees.com ,在服务端生成ul, li,最后在客户端绑上事件,几千个节点一起加载没问题 :P


    呵呵
    你以为服务器render的浏览器就可以少创建dom了? 才没呢 没有id的标记该多少还多少
    性能问题主要在于"不要让浏览器多次无效渲染标记"
    1 可以在展开的时候才生成本层的 算是半延迟加载
    2 全部展开的时候生成整个HTML 的string再加入innerHTML 可以免于多次渲染





    首先,我没那样以为,谢谢。

    但是,js创建dom和浏览器渲染dom是两件事情,虽然不知道各个浏览器具体是怎么实现的,我相信浏览器渲染dom的速度比js快,而且可能快很多。

    用innerHTML的确看上去很快,但是,说到底,还是在拼string,可读性和维护性都会有问题,如果树比较复杂的话。即使用了template(例如john resig有个很牛的template脚本),最终代码调试等会是一个问题。

    其次,估计是我孤陋寡闻,还没看到过哪个treeview是用innerHTML做出来的,mztreeview号称很快,估计它是?不过,它的功能并不强大。

  55. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 20:12:00



    我在原来的树的非递归枚举 基础上 增加了两个action 作为参数
    一个是进入这个节点的时候运行 一个是离开这个节点运行

    似乎能解决一般的递归调用生成 html了。。。。。

  56. 老赵
    admin
    链接

    老赵 2009-09-27 20:20:00

    CoolCode:
    尽管也有那么一点不是很满意,但相比其他视图模板,至少有IDE支撑智能提示,所以还是用WebForm作视图。


    哪个地方不满意呢?就这个地方而言,哪个视图模板做得比较不错?如果都有IDE支持,你喜欢哪个模板?

  57. 老赵
    admin
    链接

    老赵 2009-09-27 20:20:00

    @韦恩卑鄙
    你这的确好复杂……

  58. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 20:31:00

        public class RecursionVisitor<TItem> 
        {
    
            public Action<RecursionVisitor<TItem>, TItem> EnteringAction;
    
            public Action<RecursionVisitor<TItem>, TItem> LeavingAction;
    
            public RecursionVisitor(
                IEnumerable<TItem> rootObjects, 
                Func<TItem, IEnumerable<TItem>> childrenSelector,
                Action<RecursionVisitor<TItem>, TItem> enteringAction,
                Action<RecursionVisitor<TItem>, TItem> leavingAction)
            {
    
                ColStack = new Stack<IEnumerator<TItem>>();
                ChildrenSelector = childrenSelector;
                var et = rootObjects.GetEnumerator();
                ColStack.Push(et);
                EnteringAction = enteringAction;
                LeavingAction = leavingAction;
               
            }
    
    
            Func<TItem, IEnumerable<TItem>> ChildrenSelector;
    
            Stack<IEnumerator<TItem>> ColStack;
    
    
            TItem Current
            {
                get { return ColStack.Peek().Current; }
            }
    
    
    
            #region IDisposable Members
    
            public void Dispose()
            {
                ColStack.ToList().ForEach(o => o.Dispose());
                ColStack.Clear();
                ChildrenSelector = null;
                EnteringAction = null;
                LeavingAction = null;
            }
    
            #endregion
    
    
    
    
    
            private bool Started = false;
    
            virtual public bool MoveNext()
            {
    
                if (ColStack.Count > 0)
                {
    
                    var cur = ColStack.Peek();
                    
                    if (Started)  //已经开始
                    {
                        //主动找下一个节点
                        var en = ChildrenSelector(cur.Current).GetEnumerator(); //先看有没有子节点
    
                        if (en.MoveNext()) //有子节点  
                        {
                            // 把这个层加入堆栈 访问第一个节点
                            ColStack.Push(en);
                            EnteringAction(this, Current);//运行进入新节点操作
                            return true;
                        }
                        else //没有子节点 
                        {
                           
                            LeavingAction(this, Current); //离开本节点操作;
                            //进入下一个同层节点
                             
                            while (ColStack.Count > 0)
                            {
                                en = ColStack.Peek();
                              
                                if (en.MoveNext()) //有同层节点
                                {
    
                                    EnteringAction(this, Current);//运行访问时内容
                                    return true;//本次访问这个节点;
    
                                }
                                else//没有同层节点
                                {
                                   
                                    ColStack.Pop().Dispose();// 取消本层堆栈  
                                    if (ColStack.Count > 0) LeavingAction(this, Current); //离开本节点操作;
                                }
    
                            }
                      
                            return false; //完全没有子节点 也没有下一个节点  就无法继续了
                        }
    
    
    
                    }
                    else //第一次访问 直接返回
                    {
                        Started = true;
                        var rval = cur.MoveNext();
                        if (rval) EnteringAction(this, Current);//运行进入新节点操作
                        return rval ;
                    
                    }
    
                }
                return false;
            }
    
    
    
            public void Reset()
            {
                if (ColStack.Count > 0)
                {
                    var emt = ColStack.Last();
                    ColStack.Where(o => !o.Equals(emt)).ToList().ForEach(s => s.Dispose());
    
    
                    emt.Reset();
                    ColStack.Clear();
                    ColStack.Push(emt);
                    Started = false;
                }
    
    
            }
    
            public void Start()
            {
    
                while (MoveNext()) { };
            }
    
        }
    
    


    不知道能不能完整的贴出来

  59. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 20:35:00

                RecursionVisitor<string> dir = new RecursionVisitor<string>(
                    new string[]{@"I:\WorkSpace\个人研究\ConsoleApplication3"},
                    s => Directory.GetDirectories(s),
                    (rv, p) => System.Console.WriteLine("Visiting {0}", p),
                    (rv, p) => System.Console.WriteLine("Visited {0}", p));
    
                dir.Start();
    

    Visiting I:\WorkSpace\个人研究\ConsoleApplication3
    Visiting I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3
    Visiting I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\bin
    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\bin\Debug
    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\bin
    Visiting I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\obj
    Visiting I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\obj\Debug

    Visiting I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\obj\Debug
    \TempPE
    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\obj\Debug\
    TempPE
    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\obj\Debug
    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\obj
    Visiting I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\Propertie
    s
    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3\Properties

    Visited I:\WorkSpace\个人研究\ConsoleApplication3\ConsoleApplication3
    Visited I:\WorkSpace\个人研究\ConsoleApplication3

  60. 老赵
    admin
    链接

    老赵 2009-09-27 20:38:00

    韦恩卑鄙:
    总之我个人建议:
    不管是否递归 从关系数据 组装数据成树数据的逻辑还是扔给客户端比较开心

    我始终觉得 服务器作这种民工活太亏,就算是存在缓存里 服务器保存这种民工产品也太亏。

    再次强调是个人建议


    对了,不过你这样的做法,那个什么就不好了,accessibility?
    如果在美国,你的网站可能会被告,因为歧视盲人,盲人的上网工具是需要标准HTML结构的。
    哈哈。

  61. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 20:40:00

    Jeffrey Zhao:

    韦恩卑鄙:
    总之我个人建议:
    不管是否递归 从关系数据 组装数据成树数据的逻辑还是扔给客户端比较开心

    我始终觉得 服务器作这种民工活太亏,就算是存在缓存里 服务器保存这种民工产品也太亏。

    再次强调是个人建议


    对了,不过你这样的做法,那个什么就不好了,accessibility?
    如果在美国,你的网站可能会被告,因为歧视盲人,盲人的上网工具是需要标准HTML结构的。
    哈哈。


    树型本身就够歧视盲人了我说。。。。
    你是坏人


    盲人的上网工具你在看么?
    在的话希望你等全部数据都load好以后读取浏览器的dom
    拜托你了!

  62. 老赵
    admin
    链接

    老赵 2009-09-27 20:42:00

    @韦恩卑鄙
    啥?没听懂。
    不过的确有这案例,挺有意思的。

  63. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 20:48:00

    @Jeffrey Zhao
    我在向盲人阅读工具呼吁,别没事读http流 读浏览器的document.innerHtml


    这样的案例希望别让我摊上 - -b

  64. 老赵
    admin
    链接

    老赵 2009-09-27 20:53:00

    @韦恩卑鄙
    好像盲人的工具就可以简单的认为是CSS和JS同时裸奔。
    不过我不懂啊,现在的普通网站好像基本上都不考虑noscript等情况了。
    也就写大公司产品会考虑。
    记得前几年我在微软的时候,听说Windows Hotmail国内这边是做downlevel browser的体验,就是js功能和css都很弱的浏览器。
    我心想,那些人一定做的很郁闷。

  65. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-27 21:53:00

    压力大爆肝。。。

  66. 老赵
    admin
    链接

    老赵 2009-09-27 22:42:00

    韦恩卑鄙:压力大爆肝。。。


    像菜名

  67. 老赵问你个问题[未注册用户]
    *.*.*.*
    链接

    老赵问你个问题[未注册用户] 2009-09-29 11:21:00

    老赵,软考对于大学刚毕业的学生找工作有作用没,以及对从事IT有何重要性?

  68. 老赵问你个问题[未注册用户]
    *.*.*.*
    链接

    老赵问你个问题[未注册用户] 2009-09-29 11:22:00

    在线等回复

  69. 老赵
    admin
    链接

    老赵 2009-09-29 11:33:00

    @老赵问你个问题
    这些我倒都不懂……

  70. 老赵问你个问题[未注册用户]
    *.*.*.*
    链接

    老赵问你个问题[未注册用户] 2009-09-29 11:49:00

    @Jeffrey Zhao
    谢谢啊,可能老赵也没考这个吧。我个人觉得没什么用。

  71. 老赵
    admin
    链接

    老赵 2009-09-29 11:58:00

    @老赵问你个问题
    我是一个证都没有考过的,所以那些教材啊,培训啊,什么都没有接触过,呵呵。

  72. 链接

    杜保家 2013-08-20 10:01:33

    我在MVC4中使用最后一种方法时,在view视图中提示不能将void 转换为object,特此请教,忘不吝解答。改了好多次都不成,最好给俺贴代码谢了

  73. swika li
    110.184.127.*
    链接

    swika li 2014-07-13 17:59:30

    请问,最后一种实现,页面语法如果改成razor语法,应该怎么实现?

  74. 老赵
    admin
    链接

    老赵 2014-07-13 18:14:32

    @seika li

    不知道,没搞过……

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我