Spiga

深入理解 ASP.NET 动态控件 (Part 2 - 编译过程)

2006-11-05 20:55 by Cat Chen, 6914 visits, 收藏, 编辑

前言

要深入理解ASP.NET动态控件,首先就要深入理解整个ASP.NET对页面的处理过程,由你书写好一个ASPX文件(可能还有一个code-behind文件)到你在浏览器中看到的HTML页面,这中间到底发生了什么事。这其中的第一步就是解释ASPX文件并进行编译,也就是这篇文章要讨论的内容。

由于ASP.NET编译本身就是一个大话题,所以我决定在本系列文章把这个题目再细分成几篇文章来写。开头第一篇简单叙述编译过程中涉及的各个步骤,让大家了解ASPX中的声明性代码和C#/VB.NET代码如何合并在一起并编译成assembly。在这篇文章之后,再深入了解编译过程中的一些细节,看看一个ASPX中声明性定义的静态控件到底是如何运行起来的。

鸟瞰

开始讲编译过程了,首先大家来看两张图,这张是ASP.NET 1.x的编译流程图:

接下来这张是ASP.NET 2.0的编译流程图:

这两张图来自官方文档ASP.NET 2.0 的内部变化,大家要注意到代码嵌入(code-beside, inline)与代码隐藏(code-behind)的编译模式是不同的:代码嵌入仅进行一次编译,声明性代码与C#/VB.NET代码都一起编译到一个类里面;代码隐藏则将声明性代码与C#/VB.NET代码分开几次进行翻译/编译,这些代码之间是局部与局部(partial)的关系或是基类与派生类的关系。

着陆

我们现在着陆到图上的某一点,来看清一个编译步骤是如何执行的。

ASP.NET 1.x

图上引人关注的地方就是代码隐藏编译时存在两次的“继承自”关系。第一次继承是很好理解的,用过VS2002/2003的人都记得代码中明确声明本页面的类继承自Page类,那么第二次继承又是怎么来的呢?

先把上面的问题放一边,我们换一种思路来思考,重新想一想我们的C#/VB.NET代码有什么。如果我们在ASPX中放上了一个TextBox,那么两边的代码都会出现它的定义,ASPX代码是<asp:TextBox id="myTextBox" runat="server" />,C#代码是TextBox myTextBox = new TextBox();myTextBox.ID = "myTextBox";。然后我们在此TextBox的后面用HTML写上<div>Please write down something</div>,那么这段HTML仅在ASPX中存在定义,而不在C#代码中存在定义。

接下来我们将C#代码给编译了,然后用ASP.NET引擎运行它(确实能够如此运行,但这不是我们当前关心的事),你猜我们能够看到什么?我们应该能够看到一个TextBox。至于后面那段文字呢,聪明的你应该马上想到它没在C#代码中被定义的,所以不可能被看到。

现在我们明白到了,有一部分逻辑是仅仅在ASPX中有所定义,我们需要将它们添加到C#编译结果上。如何添加这部分的逻辑?ASP.NET选择了继承机制,从C#编译结果的那个类继承,然后在派生类中加入仅在ASPX中定义的逻辑。至于作为声明性语言的ASPX如何编译成MSIL,则属于下一篇文章讨论的内容,在这里就不解释了。

需要说明的是,这两次编译中的第一次必须手动进行的,例如在VS2002/2003中执行编译;第二次编译在运行时进行自动进行。因此改动了ASPX无需重新手动编译,而改动了C#/VB.NET代码则需要手动编译。

ASP.NET 2.0

上面我们解释ASP.NET 1.1的代码隐藏编译时也提到了其中的问题,一个TextBox控件要在两边同时声明,这明显违反了DRY(Don't Repeat Yourself)原则。ASP.NET 2.0为了解决这个问题而引入了新的机制。

所谓的新机制就是C#代码中的那个partial关键字,大家可能都习惯了它的存在,但有没有人曾经想过一个这样的Page继承类的其他partial在哪里呢?如果你在VS2005中作一次项目内搜索,就会发现这个类的其它partial是不存在的,这时候你就该去看看官方文档(例如我上面给出那个)。官方文档会告诉你,另外一个partial就是ASPX,它们会好像两个普通的partial文件那样合并编译,所以在ASP.NET 2.0中我们仅需要一次合并编译就解决了所有问题。然后我要告诉你,官方文档所说的是错误的,ASP.NET 2.0的编译还是好像ASP.NET 1.1那样,只不过根据ASPX中的控件定义生成对应C#定义的工作由IDE转交给了ASP.NET编译器,至于细节你可以去参考我之前写的两篇文章:《ASP.NET 2.0 解决了 Code-Behind 需要控件声明同步的问题》与《ASP.NET 2.0 的编译模型并非完全像 MS 说的那样》。

在ASP.NET编译器捡起了定义同步这项工作后,整个编译过程就都在它的职责范围内了,不再好像ASP.NET 1.x那样先由C#/VB.NET编译器负责隐藏代码的编译,再由ASP.NET编译器负责二次编译。既然ASP.NET编译器同时负责两次编译,那就能够省去第一次编译手工进行的麻烦,编译工作都由它在运行时负责就好了。

下一步

现在我们已经对整个编译过程有了了解,大多数编译步骤都很容易理解,无非是叫C#/VB.NET编译器出来做些本职工作,只有一个除外:仅在ASPX中声明的逻辑是如何被编译为MSIL的,因为我们将此作为下一步深入理解的目标,并在下一篇文章中讨论。

问题与实验

这里有一些简单的问题或者是小实验,通过它们可以加深大家对文章的理解,大家可以将答案直接写在文章评论中。

  1. 我在Web应用的根目录新建了一个用户控件MyUserControl.ascx,隐藏文件中定义类名称为MyUserControl,我现在需要在页面上动态加载此用户控件,请问以下哪种方法正确?为什么?(提示:ASCX的编译方式与ASPX类似)
    1. this.Page.Controls.Add(new MyUserControl());
    2. this.Page.Controls.Add(this.Page.LoadControl("~/MyUserControl.ascx"));
  2. 在讨论ASP.NET 1.1编译的时候,我说到可以直接运行隐藏代码编译出来的类,并且说应该能看到一个TextBox。事实上这个TextBox可能也无法看到,不过我手上没有VS2002/2003,所以没办法验证。大家有兴趣的话,可以自己去动手做一下实验看看那个TextBox到底是否会出现。在实验之前,让我先说说如何让隐藏代码编译结果直接运行:
    1. 打开MSDN,找到IHttpHandler这个条目,然后看看它的示例代码,以及如何在web.config中配置一个路径使用特定的IHttpHandler。
    2. 由于Page类本身实现了IHttpHandler,所以隐藏代码编译后的Page继承类也一定是IHttpHandler,在web.config中配置一个使用IHttpHandler的路径,并指向你要测试的隐藏代码类。
    3. 在浏览器中访问你配置的路径,你就能够看到纯隐藏代码编译后的执行结果。
Add your comment

46 条回复

  1. #1楼 ocean2000[匿名][未注册用户]2006-11-05 21:04
    hehe,有种豁然开朗的感觉,期待你的下篇文章。继续关注
     回复 引用   
  2. #2楼 Dflying Chen      2006-11-05 21:15
    非常不错!
     回复 引用 查看   
  3. #3楼[楼主] Cat Chen      2006-11-05 23:07
    @Dflying Chen
    首次尝试在文章的最后加上“问题与实验”部分,因为我觉得作为深入理解系列的文章就应该鼓励读者自己往更深层次去探索。如果读者都是看完了也就看完了,这样的收获不会太大,最多就是需要用到的时候想起来,然后在bookmark内做一次搜索找回这篇文章,这没办法让人对问题有深入的理解,文章也成了应急时的工具。

    我会在这个系列中继续发几篇有“问题与实验”部分的文章,然后发一张意见反馈专贴了解大家对这种写作风格的看法,如果大家都不乐意积极思考而更喜欢拿来主义的话,那我就再换别的写作风格吧。
     回复 引用 查看   
  4. #4楼 Dflying Chen      2006-11-05 23:59
    @Cat Chen
    写Blog是给自己看的,也没有必要考虑太多,保持自己的风格就好。
    当然我还是很喜欢针对读者写的。
     回复 引用 查看   
  5. #5楼[楼主] Cat Chen      2006-11-06 00:42
    @Dflying Chen
    我这个blog在开始写的时候就考虑作为一个个人的小小KB或者是FAQ。

    在各大ASP.NET论坛上,每天你都能看到很多人遇到各种不同的问题,有一部分最终是要用ViewState或其它的控件底层原理来解释,如果每次都从头解释就需要打很多很多的字。所以我就想做一个自己的KB,解答问题时先将基础解释以链接的形式给出,再补充针对该问题特有的解释,这样我才能有效地针对该问题思考,而不是思考之前先将一些熟悉的文字默写一遍。

    在blog方面,我的想法是要增强作者读者之间的互动,我希望能够有多一些评论是提出新问题的,然后我们能够一起再去研究。或者简单点说,要好像英文技术社区那么活跃,大家都有自己的观点,而不是读者就简单地接受作者的观点,这样才能达到互相促进的效果。
     回复 引用 查看   
  6. #6楼 Jeffrey Zhao      2006-11-06 02:10
    @Cat Chen
    说的好,我们需要的是交流,而不是简单的写和看。
    我在这点上作的蛮失败的。
     回复 引用 查看   
  7. #7楼 buliangdedeng      2006-11-06 08:39
    支持
     回复 引用 查看   
  8. #8楼 adonio      2006-11-06 09:58
    你的那个类似LIVE.com的东东可不可以提供一个可以演示的下载..我现在很需要这个东西..看过Dflying的使用ASP.NET Atlas实现拖放(Drag & Drop)效果,但是一直没有实现如何保存,对这个比较需要,先谢过了
     回复 引用 查看   
  9. #9楼[楼主] Cat Chen      2006-11-06 12:59
    @adonio
    那个东西我也仅仅是做到和Dflying的例子差不多,不过继承了DragDropList和ITemplate,做了一些扩展。详细你可以看我的《Microsoft Ajax Beta1 - 边学边用边补充》系列的文章,事例代码里面也有的了。

    我现在没想好的一件事是拖动后的位置以何种数据结构保存到Profile,在我没有一个很好的idea之前我把它先放一边了。我有想过用纯客户端Object来表述,保存到服务器端时就存JSON String,然后不在服务器端读取,但这意味着用户每次重新打开时都要在客户端启动Profile读取(Live.com可能也是这样),之后再进行客户端布局。但Google Homepage做得更好,第一次的布局由服务器端提供,我在想这个如果做,如果用服务器端控件支持的话,复杂性就加大了不少。
     回复 引用 查看   
  10. #10楼 newsjobs      2006-11-06 13:19
    支持,对.net内部机制的讲解一直是我在找的,关注,另外也想问是Cat chen
    是怎么研究出来这些原理性的知识?还有为什么微软的官方文档会出现你所说的错误?
     回复 引用 查看   
  11. #11楼 newsjobs      2006-11-06 13:51
    我用的是.net2003 在你的第一个试验中,开头的this.Page应改为Form1
    是第二个才有显示控件,可是我不明白啊,原因及原理请赐教,呵呵
     回复 引用 查看   
  12. #12楼[楼主] Cat Chen      2006-11-06 13:57
    @newsjobs
    用Reflector来看ASP.NET的源代码,很动手做实验。前者对思维能力可能有一定要求,因为你要在不能动态调试一段代码的情况下去理解这段代码,比较难跟踪的是通过Reflection执行的代码,virtual函数有时候也比较容易让人迷失,因为你要清楚当前真正执行的是哪一个派生类的override版本。后者则是人人都能做到的,只是你是否能够有心思去设计实验,遇到无法解释的问题时如何向一个更好的实验方案来探索未知项,这和解方程组或者追踪电路故障点差不多。

    例如你想知道为什么静态的TextBox和在Page_Load创建的动态TextBox不同,是不是ViewState引起的,那么你可以继承TextBox,然后override一些你想跟踪的事件,在里面执行Trace.Write。之后你就可以根据Trace来看看两个TextBox的行为有什么不同。可能一次两次Trace你还无法得到合理的解释,多做几次改进你总能从实验中得出一个让人满意的结论。
     回复 引用 查看   
  13. #13楼[楼主] Cat Chen      2006-11-06 14:08
    @newsjobs
    官方文档那个,MS自己或许不会承认是错误,而叫做“比较好听的解释”。就好像Windows上除了IP还有计算机名,MS有时候把那个叫做“比较好听的名字”。

    至于这样做的动机,也就是MS成功的根本——“易用性”。MS不希望开发人员知道太多,希望尽可能实现“开盒即用”,所以有必要对大家隐瞒细节。隐瞒细节这个没什么不好,我们用别人做好的服务器端控件,不用管它怎么写的,它要输出JavaScript可是我可以不懂JavaScript,这很好。只不过MS有时候也会踩过界,变成了隐瞒真相,这是为了吸引更多的程序员到MS的平台上来——看看我们的ASP.NET 1.0 WebForm概念多好,好象WinForm那样简单;看看我们的ASP.NET 2.0编译模型多好,双重继承编译变成了直接合并编译。

    当你明白到这种事情时有发生,也就习惯了去面对。对于有一定深度的文章,按照可信程度来排,MSDN是最差的,通常都过多隐瞒,甚至存在误导性;在MSDN上的专栏作家发表的好一些,这些文章能说真话,但是能在MSDN专栏上发表意味着不可能太深入;最具有探索性的文章往往来自MSDN之外的一些个人blog,有时候是.NET领域的知名贡献者(例如一些MVP),有时候则是一些你听都没听过的人写的。
     回复 引用 查看   
  14. #14楼[楼主] Cat Chen      2006-11-06 14:12
    @newsjobs
    谢谢你指出的错误,我一时忘记了很多控件在Render时会检查自己是不是在HtmlForm内,如果不是则抛出异常。

    至于解释,先看看有没有其它人愿意写出来,如果没有我迟点回复你(或者在本系列的下一篇写上)。
     回复 引用 查看   
  15. #15楼 newsjobs      2006-11-06 17:01
    感动中,非常感谢这么详细的回复,也相信cat chen能写出更好的文章,继续关注
     回复 引用 查看   
  16. #16楼 isMe[未注册用户]2006-11-27 09:48
    第一个问题应该是用第2种方式,因为采用第一种方式的话asp.net运行时是没有加载MyUserControl.ascx中的内容的,不知道是不是这样:)
     回复 引用   
  17. #17楼 kai[匿名][未注册用户]2006-12-22 11:40
    很明显,第一个问题要用第二种方式
    LoadControl就是标准的动态载入控件方法,它同时载入ascx和behind代码
    运行时由ASP.NET编译器一次性编译ascx和它的behind代码。
     回复 引用   
  18. #18楼 yyy[未注册用户]2007-06-01 10:24
    您好,看了你的文章很有启发
    但是最近在项目里遇到一个很棘手的问题,
    是关于自定义控件动态加载用户控件,
    项目中有个页面使用了自定义控件,
    但是运行以后发现在控件里定义的事件处理函数不响应,
    看了网上一些文章,初步认定是因为创建的控件刷新时根本获得不了正确的值和事件,动态控件经常需要两次创建。
    但是却找不到解决方案,请问你能给出点提示吗?
     回复 引用   
  19. #19楼 阿多斯2007-06-14 13:30
    然后我们在此TextBox的后面用HTML写上<div>Please write down something</div>,...至于后面那段文字呢,聪明的你应该马上想到它没在C#代码中被定义的,所以不可能被看到。

    怎么可能!
    难道Html代码在浏览器中居然看不到???换句话说,我们去掉div标签,只在页面上写Please write down something这几个单词,通过浏览器访问页面,会看不到麽??
     回复 引用   
  20. #20楼[楼主] Cat Chen      2007-06-14 16:42
    @阿多斯
    我的意思是,纯html内容并不包含在你的C#代码中,引起你去思考到底这段html保存到哪里了,从而理解为什么new一个UserControl是不可行的。这和是否存在div标签无关,我们讨论的是任何html代码。
     回复 引用 查看   
  21. #21楼 netkey码      2008-03-25 00:24
    看过支持一下!
     回复 引用 查看   
  22. #22楼 orichisonic[未注册用户]2008-03-27 15:22
    真的是技术无止精,本来以为自己对webform很精通,读了你的文章,看来还有待提高
     回复 引用   
  23. #23楼 emass[未注册用户]2008-09-26 21:55
    有深度,有点看不懂,不过很有兴趣研究
    第一个问题用第二个方法
    第一个我自己也试了,没用,不显示
    我也做了这方面的学习,还是没能理解,希望能加深学习,谢谢
     回复 引用   
  24. #24楼 coder_wang      2009-07-27 16:42
    看的第一遍,理解不是很深刻,但也受益匪浅.确实对这些东西太忽略了.强烈关注.
     回复 引用 查看   
  25. #25楼 纯粹舞蹈[未注册用户]2009-10-18 09:19
    哎,简直是在误导人!

    2.0的继承模式跟1.1是一样的,你没看到每个.aspx文件都一个_inherits选项?都是继承于.aspx.cs文件的类名的!

    net 2.0的预编译有两种,一种是把.aspx文件也编译进去,一种是只编译.aspx.cs文件:


    前者,就形成了完整的一个类,asp.net框架解析时就直接实例化。

    后者,asp.net框架要解析解析.aspx形成一个类,进而实例化。


     回复 引用   
  26. #26楼 纯粹舞蹈[未注册用户]2009-10-18 09:23
    至于你所说的利用IHttpHandler运行.dll看文本框,那不是搞笑吗,controls必须执行render()才能显示,不render成HTML,怎么在浏览器中显示?

    你把基本概念搞清楚了,再发表吧,否则只能是舞蹈人!
     回复 引用   
  27. #27楼[楼主] Cat Chen      2009-10-18 11:02
    @纯粹舞蹈
    如果你看的是图片,那么我后面已经说明了——图片源自MSDN,MSDN是在误导人。

    至于为什么我说2.0编译和1.1编译有区别,是因为2.0利用了partial特性,编译器从aspx自动生成了部分cs代码。如果你不清楚这一点,请自行用Reflector看看编译后的代码。
     回复 引用 查看   
  28. #28楼[楼主] Cat Chen      2009-10-18 11:04
    @纯粹舞蹈
    你知道Render就好了,请问你知道Page.Render是在何时被调用的吗?

    Page本身就是一个IHttpHandler,它实现了IHttpHandler.ProcessRequest,里面会调用Page.Render。因此,只要你把一个Page当作IHttpHandler来用,它自然会Render,而无论它是否完备。
     回复 引用 查看   
  29. #29楼 纯粹舞蹈[未注册用户]2009-10-18 21:03
    今天实际上试了下。发现跟 IHttpHandler.ProcessRequest 没有关系。可以把IHttpHandler.ProcessRequest删除了一样运行,我想应该是Page已经实现了这个接口,无需再重重复实现。

    如何在DLL的类中显示控件,主要是重写这个函数和创建控件树。

    protected override void FrameworkInitialize()
    {
    // base.FrameworkInitialize();
    this.__BuildControlTree(this);
    }
    关键是创建树之后,放到FrameworkInitialize()里面。之后就可以显示控件了。
     回复 引用   
  30. #30楼 请 教[未注册用户]2009-10-18 23:00
    请教几个问题:

    1: protected override void Render(HtmlTextWriter writer)

    这个Render方法有个HtmlTextWriter类型的参数writer;像那么web控件、自定义空间,用户控件以及最终的page的render都是由框架如何调用的,writer参数是怎么产生进而传入render方法的,调用的过程究竟是怎么样的。

    2:为什么实现了IHttpHandler的接口就可以接受到请求context的Response,Request等等对象。接口不是一个空类吗,里面只有定义的空方法,什么功能也没有。为什么实现了接口就可以使用context的这些对象了?在ASP.NET以至C#的接口究竟是如何实现其功能的。

    3:.net 2.0可以把整个ASPX网页也编译进.DLL里面,是用什么工具编译进去的?而且运行时,又是如何通过.compiled运行的。

     回复 引用   
  31. #31楼[楼主] Cat Chen      2009-10-19 00:01
    @纯粹舞蹈
    Page当然实现了IHttpHandler.ProcessRequest,这个你可以用Reflector看到,虽然里面逻辑挺绕的,不过都是为一些诸如Server.Transfer的tricky事情准备的,你可以简单地把它理解为直接调用Page.Render。
     回复 引用 查看   
  32. #32楼[楼主] Cat Chen      2009-10-19 00:27
    @请 教
    1.你用Reflector看Page代码吧,Page.ProcessRequest负责创建HtmlTextWriter给Render用,并且把Render的结果输出到HttpResponse。

    2.是ASP.NET引擎本身根据web.config找到你指定的类,实例化这个类,并且给你传递这些信息。曾经有人如此说过,“给你代码调用的叫做库,调用你代码的叫做框架”,就算这句话不完全正确,至少解释了你所提的问题。

    3.ASPX.CS是partial class,它的另外一半由编辑器根据ASPX生成,然后就是标准的C#编译流程了。
     回复 引用 查看   
  33. #33楼 请 教[未注册用户]2009-10-19 10:41
    1:不知道你所指的Page是指Aspx生成的Page,还是指System.Web.UI.Page的Page.

    我看了后者,其ProcessRequest方法为以下代码,并没有看到HtmlTextWriter;前者也没有;我疑惑的是着个writer到底是在那里声明并且进入函数调用的,是不是由编译器隐性声明了这个变量?

    [EditorBrowsable(EditorBrowsableState.Never)]
    public virtual void ProcessRequest(HttpContext context)
    {
    if (HttpRuntime.NamedPermissionSet != null)
    {
    if (HttpRuntime.ProcessRequestInApplicationTrust)
    {
    if (base.NoCompile)
    {
    HttpRuntime.NamedPermissionSet.PermitOnly();
    }
    }
    else if (!base.NoCompile)
    {
    this.ProcessRequestWithAssert(context);
    return;
    }
    }
    this.ProcessRequestWithNoAssert(context);
    }


     回复 引用   
  34. #34楼[楼主] Cat Chen      2009-10-19 10:47
    @请 教
    当然是Page类本身啊,所有派生类都继承了它,你看基类就行了。你既然看到ProcessRequest了,就顺着调用链追寻下去吧,你能够找到调用Render得地方的。
     回复 引用 查看   
  35. #35楼 请 教[未注册用户]2009-10-19 10:48
    另外你所说的“因为2.0利用了partial特性,编译器从aspx自动生成了部分cs代码”不明白是什么意思,能否解释下

    同时,有个疑问,如果我们直接把.aspx.cs的partial修饰符去掉,运行.aspx页面,会出现“ 类型“_Default”的声明上缺少 partial 修饰符;存在此类型的其他分部声明”,但是假如此时又把.aspx.cs文件移动进App_Code文件夹,那么运行.aspx页面又会正常,为什么?其中的原理以及差别究竟在那里?如果MS所说的图是错误的,那么为什么运行的时候会出现“存在此类型的其他分部声明”这样的错误?
     回复 引用   
  36. #36楼[楼主] Cat Chen      2009-10-19 11:13
    @请 教
    例如你写了这样的代码:
    ASPX - <asp:TextBox ID="Email" />
    ASPX.CS - Email.Text = ""

    那么你在ASPX.CS里面引用的Email成员从哪而来呢?你从来都没有声明过它哦!ASP.NET编译器会自动根据ASPX里面那句话,生成一句TextBox Email = new TextBox();,并且方在同类名的另一个partial文件里面。
     回复 引用 查看   
  37. #37楼 请 教[未注册用户]2009-10-19 11:49
    ASP。NET框架是用什么工具把.asp也编译进.DLL的,我们知道编译.cs文件用csc.exe,那么asp.net是用什么工具把aspx和aspx.cs一起编译进一个DLL的?
     回复 引用   
  38. #38楼[楼主] Cat Chen      2009-10-19 11:52
    @请 教
    用aspnet_compiler.exe,通过分析aspx文件自动生成cs,然后就是csc.exe的工作了。
     回复 引用 查看   
  39. #39楼 请 教[未注册用户]2009-10-19 12:07
    aspnet_compiler.exe我看了,没发现那个参数命令能分析ASPX,能说下是那个吗?
     回复 引用   
  40. #40楼[楼主] Cat Chen      2009-10-19 13:07
    @请 教
    这是用来编译站点的,单个ASPX编译对一般开发人员来说是没有意义的。如果你要看看单个ASPX怎么编译,你只能把它放到目录里当作站点来编译。因为ASPX生成的CS也不是一般开发人员需要关注的,所以只是作为临时文件存在,编译完就会删掉。如果你需要看这些临时文件,你可以搜索一下,网上能找到方法让你查看这些临时文件。
     回复 引用 查看   
  41. #41楼 请 教[未注册用户]2009-10-19 15:04
    请教一个实际项目中的问题:数据表格式如下,需求是:要从两个数据库表中取得数据,其中第2个数据表中的数据和第一个数据库表有主-外键的依赖关系,故可以根据第一个表中的某个字段,譬如ID字段取得第二个表中的某个字段值。

    表格格式:
    <table width="736" height="241" border="1" style="text-align: center">
    <tr>
    <td>标题名</td>
    <td>标题名</td>
    <td colspan="2">标题名</td>
    <td>标题名</td>
    <td>标题名</td>
    <td>操作</td>
    </tr>
    <tr>
    <td width="107" rowspan="4">第1条记录</td>
    <td width="120" rowspan="4">表1 字段1 </td>
    <td width="29">1</td>
    <td width="107">表2 字段1 </td>
    <td width="108" rowspan="4">表1 字段1</td>
    <td width="108" rowspan="4">表1 字段1</td>
    <td width="111" rowspan="4">编辑 删除</td>
    </tr>
    <tr>
    <td>2</td>
    <td>表2 字段1 </td>
    </tr>
    <tr>
    <td>3</td>
    <td>表2 字段1 </td>
    </tr>
    <tr>
    <td height="30">4</td>
    <td>表2 字段1 </td>
    </tr>
    <tr>
    <td rowspan="2">第2条记录</td>
    <td rowspan="2">表1 字段1 </td>
    <td>1</td>
    <td>表2 字段1 </td>
    <td rowspan="2">表1 字段1</td>
    <td rowspan="2">表1 字段1</td>
    <td rowspan="2">编辑 删除</td>
    </tr>
    <tr>
    <td>2</td>
    <td>表2 字段1 </td>
    </tr>
    <tr>
    <td>第3条记录</td>
    <td>表1 字段1 </td>
    <td>1</td>
    <td>表2 字段1 </td>
    <td>表1 字段1</td>
    <td>表1 字段1</td>
    <td>编辑 删除</td>
    </tr>
    </table>

     回复 引用   
  42. #42楼 请 教[未注册用户]2009-10-19 15:09
    请问怎么用数据绑定控件能实现,而且要求编辑的时候,第二个数据库表中出来的数据亦可以选择编辑项。当然删除第一个,第二个的也可以删除。

    这只是两个表中的数据,一个扩展的想法是,如果从3个表中呢,也存在这样的依赖关系,是不是也可以用一个服务器控件实现。
     回复 引用   
  43. #43楼[楼主] Cat Chen      2009-10-19 15:56
    @请 教
    建议你用Typed DataSet或者是Linq to Sql、Entity Framework这样的,你可以自定这样一个类,包含来自多个表的数据,但是UPDATE语句必须你自己给出,不可能通过SELECT自动生成。接下来页面就好解决了,用ObjectDataSource就可以了。
     回复 引用 查看