老赵点滴


  先做人,再做技术人员,最后做程序员。
  我的理想:“让外国人看中国人写的技术书籍和文章”。Try as I might
posts - 290, comments - 10847, trackbacks - 158, articles - 6
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

方案改进:直接通过User Control生成HTML

Posted on 2008-07-14 13:24 Jeffrey Zhao 阅读(21988) 评论(73)  编辑 收藏 所属分类: ASP.NET

  对于使用User Control生成HTML的方式,大家应该已经比较熟悉了,老赵也曾经写过一篇文章(《技巧:使用User Control做HTML生成》)来描述这个做法。使用User Control进行HTML生成最大的好处就是将表现(Presentation)逻辑集中在一处,并且能够让前台开发人员使用传统的方式参与到页面开发中来。在其他方面,使用User Control生成HTML的做法直接使用了ASP.NET WebForms引擎,在开发时能够利用到ASP.NET的各种优秀实践。

  在“我的衣橱”中大量使用了这种生成HTML的做法。不过当项目达到一定规模之后,这个方法的不足之处也慢慢地体现了出来。就拿《技巧:使用User Control做HTML生成》作为例子来讲,除了显示上必要的Comments.aspx页面和Comments.ascx控件之外,还有一个额外的GetComments.ashx进行客户端与服务器端之间的通信。不过问题就出在这里,当此类做法越来越多时,项目中就会出现大量的此类ashx文件。冗余代码的增加降低了代码的可维护性,试想如果我们需要在某个控件上增加一个额外的属性,就需要去Handler那里编写对应的逻辑。虽不算是繁重的工作,但是如果能解决这个问题,无疑是一个锦上添花的做法。

  如果要避免大量Handler的出现,必然需要找到这些Handler的共同之处。这一点并不困难,因为每个Handler的逻辑的确大同小异:

  1. 使用ViewManager加载User Control
  2. 从QueryString或Form中获取参数,并设置对应属性
  3. 使用ViewManager生成HTML代码,并使用Response.Write输出

  写一个统一的Handler来将User Control转化为HTML是一件轻而易举的事情。不过因为有第2步,我们就必须应对为不同控件赋不同值的情况。这种“描述性”的内容即是该控件的“元数据(metadata)”,而说到“元数据”各位应该就能很快想到自定义属性(Custom Attribute)这个.NET特有的事物。因此我们的解决方案也使用这种方式来对控件的属性进行“描述”,且看该属性的定义:

public enum UserControlRenderingPropertySource
{
    Form,
    QueryString
}
 
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class UserControlRenderingPropertyAttribute : Attribute
{
    public string Key { get; set; }
 
    public UserControlRenderingPropertySource Source { get; set; }
}

  这个自定义属性只能标记在属性上,而它的作用是定义这个属性值的来源(是Query String还是Form)与集合中的键名。而我们的实现还会根据属性上标记的DefaultValueAttribute为属性设定默认值。定义了Custom Attrubte之后,我们就可以编写这个统一的Handler了。为了在客户端“隐藏”直接请求ascx文件的事实,我们只响应对扩展名为“ucr”的请求:

public class UserControlRenderingHandler : IHttpHandler
{
    public void ProcessRequest(HttpContext context)
    {
        string appRelativePath = context.Request.AppRelativeCurrentExecutionFilePath;
        string controlPath = appRelativePath.ToLower().Replace(".ucr", ".ascx");

        var viewManager = new ViewManager<UserControl>();
        var control = viewManager.LoadViewControl(controlPath);
 
        SetPropertyValues(control, context);
       
        context.Response.ContentType = "text/html";
        context.Response.Write(viewManager.RenderView(control));
    }
}

  上面代码中的SetPropertyValues方法便是为User Control实例的属性赋值:

private static void SetPropertyValues(UserControl control, HttpContext context)
{
    var metadata = GetMetadata(control.GetType());
    foreach (var property in metadata.Keys)
    {
        object value = GetValue(metadata[property], context) ?? GetDefaultValue(property);
        if (value != null)
        {
            property.SetValue(control, Convert.ChangeType(value, property.PropertyType), null);
        }
    }
}

  SetPropertyValues方法中会调用三个方法:GetMetadata,GetValue和GetDefaultValue。GetMetadata方法会得到关于这个control的元数据,为PropertyInfo - List<UserControlRenderingPropertyAttribute>的键值对(即Dictionary)。然后将metadata传入GetValue方法,以获取由UserControlRenderingPropertyAttribute标记的值。如果GetValue方法返回null,则调用GetDefaultValue方法获取标记在该属性上DefaultValueAttribute。以下就将其余代码附上,没有技术含量,但做一参考:

[展开代码]

  至此,UserControlRenderingHandler完成了。不过在真正使用时,还需要进行一些配置。例如,您需要在IIS的ISAPI Mapping中将“.ucr”与aspnet_isapi.dll进行映射,并且在web.config中将*.ucr与UserControlRenderingHandler关联起来。当然,对User Control的属性进行标记是必须的。例如还是《技巧:使用User Control做HTML生成》一文中的例子:

public partial class Comments : System.Web.UI.UserControl
{
    protected override void OnPreRender(EventArgs e)
    {
        // ...
    }
 
    [UserControlRenderingProperty(Key = "page", Source = UserControlRenderingPropertySource.QueryString)]
    public int PageIndex { get; set; }
 
    [DefaultValue(10)]
    public int PageSize { get; set; }
 
    // ...
}

  然后,在客户端代码中只要根据路径发起请求即可,UserControlRenderingHandler会在服务器端完成余下的工作。

<script type="text/javascript" language="javascript">
    function getComments(pageIndex)
    {
        new Ajax.Updater(
            "comments",
            "/Controls/Comments.ucr?page=" + pageIndex + "&t=" + new Date(),
            { method: "get" });
       
        return false; // IE only
    }
</script>

  不过,这就够了吗?对于一个例子来说,这已经足够了,不过要在产品环境中使用很可能还略显不够。例如,如果只让用户访问到特定的User Control,或者只有特定权限的用户才能访问,又该如何对UserControlRenderingHandler进行改造呢?相信您了解上述做法之后,这点要求对您一定不成问题。

Feedback

#1楼    回复  引用  查看    

2008-07-13 20:46 by Indigo Dai      
沙发
先顶再看
老赵好久不发问啦

#2楼 [楼主]   回复  引用  查看    

2008-07-13 20:50 by Jeffrey Zhao      
点子不错,实现没啥技术含量——点子是Dflying提出的,老赵负责实现和改进。

#3楼 [楼主]   回复  引用  查看    

2008-07-13 20:50 by Jeffrey Zhao      
@Indigo Dai
忙……
懒……
主要是后者……

#4楼    回复  引用  查看    

2008-07-13 21:24 by 李涛      
抢个地板
代码我就先不看了,知道老赵喜欢耍内功了,呵呵

#5楼 [楼主]   回复  引用  查看    

2008-07-13 21:40 by Jeffrey Zhao      
@李涛
啥叫耍内功呀?

#6楼    回复  引用  查看    

2008-07-13 21:54 by 李涛      
@Jeffrey Zhao
川话中喜欢把玩,用等说成耍,老赵早已经练就北溟神功,故曰:耍内功啊

#7楼    回复  引用  查看    

2008-07-13 22:03 by 醉春风      
看了题目,顿觉自己最近想要的一个东西可以实现了。
这个做法我的好好看看。
thanks

#8楼    回复  引用  查看    

2008-07-13 22:33 by 王孟军!      
好东西,有时间研究研究

#9楼    回复  引用  查看    

2008-07-13 23:09 by 代码乱了      
关注中,明天再研究

#10楼    回复  引用  查看    

2008-07-13 23:44 by Tony Zhou      
网站美女挺多

#11楼    回复  引用  查看    

2008-07-14 01:16 by Clingingboy      
从HttpHandler去取UserControl的数据并不是一种很好的办法。
老赵把请求的参数以暴露控件的属性的方式,然后把参数传给控件.
由于UserControl是必须放在Page里面的,但现在是在HttpHandler中创建一个Page对象,实际中,这个创建出来的Page对象无法获取到请求的参数,这就导致了这个Page只能做为UserControl的一个容器的作用而已,其已经失去了很多功能。如果逻辑有些复杂的话,则需要定义更多的属性,由外界传值进去。
如果单单是应用于分页还好说。个人认为可以通过User Control产生数据,但不应该在HttpHandler里面为了得到User Control的内容而去创建一个Page对象。HttpHandler更适合于传输json和xaml之类的数据。我的做法是先创建一个page页面,由这个已经创建好的页面来负责加载这个User Control.并不会因为其是Page而在速度上比HttpHandler慢。因为User Control就是基于Page的,有了Page做的事情还是很多的

#12楼 [楼主]   回复  引用  查看    

2008-07-14 02:29 by Jeffrey Zhao      
@Clingingboy
哪些请求参数取不到啊?所有的参数都在HttpContext上,和Page有什么关系,现在是为了生成User Control的HTML,所有的逻辑都是在User Control上的,Page对象在这里本就是个Helper而已。如果有任何需要用到Page中逻辑的地方就是设计上的错误了。就像Page本不应该知道它属于哪个特定的master page,control本不应该知道它属于哪个page一样。
再者如果是请求一个页面而输出一个HTML片段的话,这就是个设计上的问题了(UpdatePanel这种为了特殊目的自然是个例外)。就像当年AJAX刚出现时很多人不知道有ashx这回事,就用个几乎为空的aspx来Response.Write数据一样,的确能用,但是不能不说是个问题。
而且HttpHandler本来就是随心所欲地输出各种数据流,何谈适合json/xaml的数据……我还能用它输出图片、输出各种文件呢。

#13楼    回复  引用  查看    

2008-07-14 04:53 by LuChaoShuai      
public void ProcessRequest(HttpContext context)
{
HttpApplication app = context.ApplicationInstance;
string appRelativePath = app.Request.AppRelativeCurrentExecutionFilePath;
string controlPath = appRelativePath.ToLower().Replace(".ucr", ".ascx");
var viewManager = new ViewManager<UserControl>();
var control = viewManager.LoadViewControl(controlPath);
SetPropertyValues(control, context);
app.Response.ContentType = "text/plain";
app.Response.Write(viewManager.RenderView(control));
app.Response.StatusCode = 200;
app.CompleteRequest();
}


Request.AppRelativeCurrentExecutionFilePath;
以前不知道还有这么一个属性.谢谢了.

#14楼    回复  引用    

2008-07-14 04:59 by Alvan [未注册用户]
“control本不应该知道它属于哪个页面”,这句话只说对了一半。它可以不知道它属于哪个页面,但是它一定要知道它不该属于哪个页面——以user control为载体加载异步HTML片段的一个潜在问题就是客户端id冲突,而且在规模稍大的项目中这几乎是一定会出现的问题,除非建立一套完整的server control命名方案或者保证客户端脚本决不使用客户端id。显然这两项要求都是一线程序员最讨厌的东西,所以如果一定要用uc来作为载体,我更倾向于用iframe加载HTML。

#15楼    回复  引用  查看    

2008-07-14 05:03 by 怪怪      
其实把这个和UpdatePanel的优点全都占了并统一化的方案也不是没有, 不过在框架上就需要一整套了, 等真能闲下来, 我也拿出来晒晒。 可惜就是我写不出像你这么好的技术文章, 最后估计又是乱七八糟。

@Alvan
你说的这些, 花点功夫还是能解决的 :)。 还可以实现过在客户端和服务器端统一的FindControl这样的功能: 只要咱们自己在两边都维护了控件树信息。

#16楼    回复  引用  查看    

2008-07-14 08:41 by micenter      
顶一个

#17楼    回复  引用  查看    

2008-07-14 09:03 by Clingingboy      
Page与控件的关系是。Page可以没有控件,但控件不能没有Page.控件可以不知道其放在哪个Page,但应该可以知道Context。
ashx和aspx最终同为url请求,不同的则是aspx需要在项目中建立一个实体的文件而已,给人的感觉就是模块多了,代码就淤泥了,紧紧为了ajax的数据传输而建页面.当然aspx也能产生数据流,很多人用aspx作为一个页面做验证码,技术上可行,但用HttpHandler更合理.
我的做法是依赖于一个Page,在其基础上进行封装.犹如ajaxpro这种做法.
再者如果UserControl职责单一只用于分页,功能确定下来,就可以封装成自定义控件了,建多个UserControl也并不是很好,这个UserControl最后需要封装就是输出HTML的一个模板.

当然这里只讨论后端生成数据,与前端如何去取后端数据无关,两者可以结合也可以分开.

关于控件ID的问题,可以通过控件适配器来解决。当然这是要有一定牺牲的.如果控件仅仅(即失去了webform的一些功能)显示而已的话是可以这么做.

还有如果这个UserControl脱离了这种在HttpHandler下生成HTML的模式.似乎就意义不大了.
个人见解:)

#18楼    回复  引用  查看    

2008-07-14 09:24 by 坏人      
文章还没细读,怀疑需求的存在必要性。

#19楼    回复  引用  查看    

2008-07-14 09:29 by willieQ      
正需要,收下,回去仔细看~~~~

#20楼 [楼主]   回复  引用  查看    

2008-07-14 09:35 by Jeffrey Zhao      
@Clingingboy
在Handler中使用User Control生成HTML意义很大,UserControl在这里就是个独立的模板,而且这种情况下如果要用来生成HTML的User Control,其逻辑一定是内聚的,否则就是设计上的失误。
说到AJAX Pro,我觉得他一大败笔就是把好好的AJAX Method与页面绑定在一起,但是却没有任何必要,也没有带来任何益处。所以ASP.NET AJAX选择了外部Web Service这样的做法,灵活多了。
其实你就这样想,平时请求的是aspx,现在请求的是ascx而已,目标就在aspx和ascx上,没有其他的。

#21楼 [楼主]   回复  引用  查看    

2008-07-14 09:35 by Jeffrey Zhao      
@坏人
放心,这个做法是经过实际项目考验的,从可用性到必要性都没有问题。

#22楼    回复  引用    

2008-07-14 10:47 by 东 [未注册用户]
酷哦....

#23楼    回复  引用    

2008-07-14 10:57 by 子曰2 [未注册用户]
为什么一定要用User Control ,用Page 不行吗。第一次也用异步请求生成Html

#24楼    回复  引用  查看    

2008-07-14 10:59 by Clingingboy      
关于绑定的Method是分类型的,如数据相关的操作可以不放在页面里面,确实绑定起来没必要,抛开这个来讲,其实在Handler同样是需要与方法绑定起来的,这实际上是一个一样的逻辑.同样也可以通过注册的方式在页面上注册Service分离页面与Service.
我确实是这么想的,通过aspx请求,原因是Handler无法满足我的需求。

如果通过传值的话,这个UsrControl功能变的很单调.

#25楼    回复  引用    

2008-07-14 11:01 by opend@live.cn [未注册用户]
通过User Control生成Html,然后通过IHander得到相关Html,然后通过Ajax调用控制。。。为什么不直接使用IHander输出Json数据?效率应该更高。UserControl可能共用。。。JS也可以。。。
不过思路很不错

#26楼    回复  引用  查看    

2008-07-14 11:05 by rockshit      
功能、设计、以及技术实现方面的问题貌似改分开讨论。

#27楼    回复  引用  查看    

2008-07-14 11:09 by rockshit      
ajaxpro的优势的明显的,简单,单纯。ajax.net的使用外部web service的是灵活但是开发有一定的冗余。不可否认的是ajax.net模式在一定程度上也相当简单,但是效率牺牲是一定的。

#28楼 [楼主]   回复  引用  查看    

2008-07-14 11:10 by Jeffrey Zhao      
@Clingingboy
举个Handler不能满足需求的例子吧

#29楼 [楼主]   回复  引用  查看    

2008-07-14 11:11 by Jeffrey Zhao      
@rockshit
ajax.net就是ajaxpro。应该说是asp.net ajax。
你说效率上牺牲什么了?
ajaxpro和asp.net ajax实现机制差不多,效率没有差别。

#30楼    回复  引用    

2008-07-14 11:42 by matta [未注册用户]
好笑.让我想起一个 通过请求自己的页生成html的方法.

#31楼 [楼主]   回复  引用  查看    

2008-07-14 11:58 by Jeffrey Zhao      
@matta
你似乎没有看懂我的做法?

#32楼    回复  引用    

2008-07-14 12:03 by 子曰2 [未注册用户]
有个问题,如果 UserContorl 被 UserControlRenderingHandler 处理了。那么
.ascx的还会不会被HttpForbiddenHandler 处理。如果HttpForbiddenHandler 再处理,启不是加载了2次UserContorl

#33楼    回复  引用    

2008-07-14 12:11 by 子曰2 [未注册用户]
.ascx 默认的 处理是由HttpForbiddenHandler来处理的。
<add path="*.ascx" verb="*" type="System.Web.HttpForbiddenHandler" validate="true"/>

#34楼 [楼主]   回复  引用  查看    

2008-07-14 12:13 by Jeffrey Zhao      
@子曰2
你看我的代码,请求的不是.ascx而是.ucr

#35楼 [楼主]   回复  引用  查看    

2008-07-14 12:14 by Jeffrey Zhao      
@子曰2
一个扩展名不能被两个handler或handlerfactory处理。

#36楼    回复  引用    

2008-07-14 12:14 by 子曰2 [未注册用户]
哦。看到了 。其实也没必要。刚查询到MSDN的一句话了。
在WEBconfig 设置 httphandle 时 。要注意的是相同的后缀名配置多次的话,后面的配置会把前面的覆盖。

#37楼    回复  引用    

2008-07-14 12:15 by 子曰2 [未注册用户]
恩。呵呵。刚刚自己问了个很BC的问题。不好意思!

#38楼 [楼主]   回复  引用  查看    

2008-07-14 12:15 by Jeffrey Zhao      
@子曰2
其实我文章里也写了……是为了“不要太过暴露”……

#39楼 [楼主]   回复  引用  查看    

2008-07-14 12:16 by Jeffrey Zhao      
--引用--------------------------------------------------
子曰2: 恩。呵呵。刚刚自己问了个很BC的问题。不好意思!
--------------------------------------------------------
客气

#40楼    回复  引用    

2008-07-14 12:16 by 子曰2 [未注册用户]
恩,学习!

#41楼    回复  引用    

2008-07-14 12:43 by 子曰2 [未注册用户]
UserControlDirectAccessPropertySource 这个类好像没看到定义的。
是不是写错了。应该是 这个 UserControlRenderingPropertySource枚举类型

#42楼 [楼主]   回复  引用  查看    

2008-07-14 13:21 by Jeffrey Zhao      
@子曰2
没错,谢谢指正。

#43楼    回复  引用  查看    

2008-07-14 15:15 by SZW      
把Form和QueryString区分开来主要用意是什么呢?

#44楼 [楼主]   回复  引用  查看    

2008-07-14 16:56 by Jeffrey Zhao      
@SZW
没啥用意,就是分开了。
既然标记了就详细一些,比如Form的key1里获取或者QueryString的Key2里获取属性值。

#45楼    回复  引用  查看    

2008-07-14 17:27 by 果果’er      
我也是看了这个以后,自己写了个page来按参数载入UserControl,(?uc=ucABC)
其它的参数则让UserControl自己去获取。

感觉这样用UserControl的好处是页面上可以首次有输出,又还可以简单做Ajax。
我甚至把Ajax都写到UserControl自己里面了,可以自己重新载入自己 :)

#46楼    回复  引用  查看    

2008-07-14 19:10 by 随风流月      
@Jeffrey Zhao
Ajax Pro 的 Method 不一定要和 Web Page 绑定的...

#47楼    回复  引用  查看    

2008-07-14 19:28 by airwolf2026      
就像当年AJAX刚出现时很多人不知道有ashx这回事,就用个几乎为空的aspx来Response.Write数据一样,的确能用
-------------------------------------
-_-!!!俺前几天还这样写....还说page为啥把屁股也给输出出去了...哈哈...

#48楼 [楼主]   回复  引用  查看    

2008-07-14 20:24 by Jeffrey Zhao      
@果果’er
我这里UserControl不太自己取,因为页面上也会复用这个控件。

#49楼 [楼主]   回复  引用  查看    

2008-07-14 20:24 by Jeffrey Zhao      
@随风流月
这还差不多

#50楼 [楼主]   回复  引用  查看    

2008-07-14 20:24 by Jeffrey Zhao      
@airwolf2026
屁股?

#51楼    回复  引用    

2008-07-16 21:43 by hotjava [未注册用户]
还是用的您上次发的那种老办法,可是我如果使用的是gridview而不是repeater, 就会出现
Error executing child request for handler 'System.Web.UI.Page'

请问这是什么原因呢。? 多谢

#52楼    回复  引用  查看    

2008-07-21 18:17 by Cat Chen      
如果用RoR的思维来做,应该连ucr => ascx的替换也不用,就让用户请求ascx。因为Ajax.Updater会加上一个header声明这是Ajax请求,你拦截请求然后根据header判断怎么处理就够了,更加简洁。

#53楼    回复  引用    

2008-07-21 20:36 by win [未注册用户]
有个DEMO看过来可能更好
这样看着那些代码有点烦
不过老赵还是很利害地
顶一下

#54楼 [楼主]   回复  引用  查看    

2008-07-21 21:44 by Jeffrey Zhao      
@win
要的就是这个效果,希望读者能够好好阅读代码

#55楼 [楼主]   回复  引用  查看    

2008-07-21 21:46 by Jeffrey Zhao      
@Cat Chen
我就是不希望加特别的header,呵呵。
而且最终还是要写一个Handler(甚至为了看Header还需要一个Module)……

#56楼    回复  引用    

2008-07-24 11:45 by lixyvip [未注册用户]
看了一下 我的衣橱 网站,感觉不错

#57楼    回复  引用  查看    

2008-07-28 11:14 by 凌风      
我倒觉得老赵的做法很合理呀。
支持,同时也希望各位在沟通时先把文章看完。

#58楼    回复  引用  查看    

2008-08-06 09:09 by 假正经哥哥      
@Jeffrey Zhao
UserControl 在做通用的Ajax 效果时候还是很有用的。比如把这里的UserControl 变成Webpart ,那么我们可以定义通用的Webpart基类,抛出回调的JS获取Webpart实际的HTML,可以实现通用的带有ajax功能的Webpart,在实际编码人员开发webpart的时候就不需要考虑ajax的效果,只要去实现webpart本身的逻辑就可以了

#59楼    回复  引用  查看    

2008-08-14 19:53 by Evernory      
太猛了~学习了~

#60楼    回复  引用  查看    

2008-08-17 17:20 by Indigo Dai      
看了文章,第一感觉就是老赵的基本功很强很强,Attribute那段代码博客圆子里能写出的应该为数不多吧。过硬的基本功,再加上其他知识,这才叫程序员啊……

#61楼    回复  引用  查看    

2008-09-01 16:32 by 胡一刀      
如果不用这种 获取Custom Attribute的 方法,反射是不是也可以呢?
看3.5的语法,似乎有点不习惯

#62楼 [楼主]   回复  引用  查看    

2008-09-01 20:18 by Jeffrey Zhao      
@胡一刀
Custom Attribute不就是反射吗?

#63楼    回复  引用    

2008-09-25 05:19 by vilyliao [未注册用户]
一个字:强!
几个字:老赵太强了!
-------------------------
有点疑问:
1、老赵你不是ajax深入浅出的讲师吗?干么不用asp.net ajax框架做啊?
把Usercontrol直接放在一个UpdatePanel中不就得了?
(是不是您只是为了讲解这个技巧才这样做呢?)
2、个人感觉这样做的性能(包括使用UpdatePanel)这样做都不是最佳的,
不如在提交Comments时使用ajax方式提交保存到数据库,客户端直接使用js,AppendChild一个回复就可以了,您以为如何?
(严重佩服您的勤劳与勇敢,我的第2点想法也没有实践,但一定行得通,本人
太懒了,就不去实践啦!)

#64楼    回复  引用  查看    

2008-09-26 21:12 by SuperSaiyan      
我的几点改进意见,
http://www.cnblogs.com/kakrat/archive/2008/09/26/1299824.html

#65楼 [楼主]   回复  引用  查看    

2008-09-27 10:13 by Jeffrey Zhao      
@vilyliao
1、我也不是到处用asp.net ajax阿,ajax其实从来就是我的副业……
2、我的做法有其好处,方便,而且容易维护。

#66楼 [楼主]   回复  引用  查看    

2008-09-27 10:15 by Jeffrey Zhao      
@SuperSaiyan
我现在是能让一个最普通的User Control得到支持,而不是为这个功能进行特殊处理。当然您的做法也可以,我们出发点有点不一样。
至于所谓反射所带来的性能问题,它远不会是性能瓶颈,而且您要知道,您用的asp.net里的各种功能相当部分是基于反射的——其他框架也是。

#67楼    回复  引用  查看    

2008-09-27 11:00 by