Hello World
Spiga

ASP.NET WebForm页面内容输出方式

2009-09-16 14:41 by 老赵, 14548 visits

这次我们谈的话题是“Web Form页面上输出内容的方式”。这其实是一个非常旧的话题了,因为本文的内容甚至可以运用于ASP.NET 1.1之上。不过这个话题的适用范围很广,因为即使是目前最新的ASP.NET MVC框架,它的默认视图引擎依旧是基于ASP.NET WebForm的(如Page,Control,MasterPage)。甚至说,由于ASP.NET MVC框架的特性,我们会遇到更多在页面上“直接输出”内容的情况。因此,这个话题在ASP.NET MVC应用中可能由为重要。

那么就拿ASP.NET MVC举例吧。假如,我们在页面上生成一个Partial View,我们可以这么做:

<% Html.RenderPartial("MyPartialView"); %>

然而,在前一篇文章中我们提出了一个新的方法Partial,它返回一个字符串,它可以在页面上这样使用:

<%= Html.Partial("MyPartialView") %>

一个aspx页面会被编译成Page类的一个子类,这个子类的主要“功能”是覆盖了基类的Render方法:

public class MyPage : Page
{
    protected override void Render(HtmlTextWriter writer)
    {
        ...
    }
}

我们平时在aspx页面中编写的大量内容,其实都会变成操作writer的代码。例如使用writer.Write方法输出内容,或者把writer交给子控件的Render方法用于生成内容。那么,以上两种页面上的标记分别又是如何操作writer的呢?

<%= expression %>

首先是<%= %>标记。<%= %>标记内包含的是一个“表达式”,因此它不能以分号结尾。表达式内部的数据就会直接写入writer。例如这样的标记:

<%= DateTime.Now %>

在编译过后就成为:

writer.Write(DateTime.Now)

与<%= %>标记不同,<% %>标记中间其实包含的是“语句”。语句自然可以有多行,自然每行最后需要有分号,这就像我们平时写C#代码那样。不过实际上,语句的功能其实并不是为了“输出内容”,而是用来“控制逻辑”。例如,您在页面上写了这样的代码:

<% Func<int, bool> odd = i => i % 2 != 0; %>

这样就相当于您在Render方法内部声明了一个局部变量odd,它的类型是一个Func<int, bool>委托。而如果您编写这样的代码:

<% for (int i = 0; i < 10; i++) { %>
    <span>
        <%= i + 1 %>
    </span>
<% } %>

则生成的Render方法中就会包含:

for (int i = 0; i < 10; i++)
{
    writer.Write("<span>");
    writer.Write(i + 1);
    writer.Write("</span>");
}

如果是写在页面上的普通HTML标记,编译后就被当作普通字符串来处理了。有些朋友一直谈“客户端控件”等等,其实如果一个元素上没有runat="server"标记,ASP.NET只是把它们当作普通字符串处理,并不会有任何“HTML元素”的概念。当然,上面的代码表现的是“意图”,事实上在编译过后aspx页面中的空格和换行等字符也会包含在输出的内容中1

那么,既然<% %>中包含的是用来控制逻辑的语句,本身不是用来表示输出的,那么为什么刚才代码中的Html.RenderPartial方法也会生成页面内容呢?那是因为RenderPartial方法直接向当前HttpContext.Response.Output里写入字符了。很多朋友经常使用Response.Write来输出内容,其实在Write方法内部就是输出到Output中。

事实上,即使我们的页面中使用了HtmlTextWriter来输出内容,但它内部也是封装了Output所暴露出的TextWriter中。为了验证,您可以在代码中设置断点并观察Render方法的writer参数,在“正常情况下”可以发现writer.InnerWriter属性是一个HttpWriter对象,这是个TextWriter的子类,也是ASP.NET中定义的内部类型。

这便是ASP.NET页面输出的细节。那么请问,以下两种输出方式的区别是什么呢?

<%= "Hello World" %>
<% Response.Write("Hello World"); %>

从效果上看,两者没有任何区别。但是实际上前者是使用页面的HtmlTextWriter对象输出的,而后者则直接向Response.Output里输出内容。这个区别看似不重要,但其实它会涉及到我们很多开发过程中可用的实践方式。在今后的文章中,我会提出生成页面内容的一些准则,解释这些准则的原因,并指出ASP.NET MVC本身是如何破坏这些设计准则的。

自然,修改版本的ASP.NET MVC会发布在MvcPatch项目中。

 

注1:小城故事同学提醒,严格说来,页面中的纯文本会被作为一个Literal控件处理,一段连续的纯文本作为一个Literal控件。在输出时,Literal控件的Render方法会将纯文本输出至HtmlTextWriter中。其效果就等同于writer.Write(...)方式的纯文本输出。

Creative Commons License

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

Add your comment

57 条回复

  1. AlexChen
    *.*.*.*
    链接

    AlexChen 2009-09-16 14:47:00

    沙发?

  2. 键盘上的烟灰
    *.*.*.*
    链接

    键盘上的烟灰 2009-09-16 14:48:00

    好文章,终于坐一次沙发

  3. kenny_guo[未注册用户]
    *.*.*.*
    链接

    kenny_guo[未注册用户] 2009-09-16 14:50:00

    貌似成MVC的文章了

  4. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-09-16 15:03:00

    这篇比较简单。。。

  5. 老赵
    admin
    链接

    老赵 2009-09-16 15:16:00

    @xiao_p
    其实每篇都不难。

  6. 潜水冠军[未注册用户]
    *.*.*.*
    链接

    潜水冠军[未注册用户] 2009-09-16 15:43:00

    真提供连接喽,嘿嘿。。。

  7. 潜水冠军[未注册用户]
    *.*.*.*
    链接

    潜水冠军[未注册用户] 2009-09-16 15:45:00

    读完本文,有温故知故之所以然的感觉。。。

  8. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-09-16 15:51:00

    “一个aspx页面会被编译成Page类的一个子类”

    问个问题,在2003中肯定是这样的,但是2005中,也是这样的吗?
    我记得好像变成了局部类了吧?



  9. 老赵
    admin
    链接

    老赵 2009-09-16 15:57:00

    @xiao_p
    这点从来没变过,否则aspx里又如何使Inherits="..."里面定义的成员呢?

  10. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-16 15:59:00

    两者效率上应该没有什么差别吧?

  11. 老赵
    admin
    链接

    老赵 2009-09-16 16:01:00

    @麒麟.NET
    哪两者?

  12. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-16 16:06:00

    xiao_p:
    “一个aspx页面会被编译成Page类的一个子类”

    问个问题,在2003中肯定是这样的,但是2005中,也是这样的吗?
    我记得好像变成了局部类了吧?





    我认为是编译成MyPage类的子类

  13. WWWWWx[未注册用户]
    *.*.*.*
    链接

    WWWWWx[未注册用户] 2009-09-16 16:07:00

    你天天在写文章,不用上班吗?

  14. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-09-16 16:07:00

    <%= "Hello World" %>和
    <% Response.Write("Hello World") %>
    这两个

  15. 高天乐
    *.*.*.*
    链接

    高天乐 2009-09-16 16:12:00

    感觉老赵写的东西可看可不看

  16. 老赵
    admin
    链接

    老赵 2009-09-16 16:15:00

    @WWWWWx
    一篇文章半小时到45分钟,很快的。

  17. 老赵
    admin
    链接

    老赵 2009-09-16 16:15:00

    @麒麟.NET
    两个的区别就是包了一个HtmlTextWriter而已。

  18. 老赵
    admin
    链接

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

    @高天乐
    天地可鉴,我从来没有说过我的文章是必读的。

  19. Athrun
    *.*.*.*
    链接

    Athrun 2009-09-16 16:27:00

    老赵,不好意思打扰了,经常看你的文章,你的文章的确很值的读,每一篇都分析的很透彻.只是像我这种刚入门的学生来说有一些还真的不理解,不过每天还是坚持看,看不明白找一些资料理解也行.

    今天看到你有谈到ASP.NET WebForm页面,我也刚好有一个小问题想问一下.问题是这样的,我在做Web的时候,通常每一个页面的第一行就有这一句<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="index.aspx.cs" Inherits="Athrun.Web.index" %>
    可是为什么我把网站发布了以后仍然有
    CodeBehind="index.aspx.cs"这一段.
    我们在最后在发布网站的时候是不会把index.aspx.cs文件发布出去的.若CodeBehind="index.aspx.cs"这一句真的有意义,也就是在程序在运行的某个时候肯定是会使用到index.aspx.cs文件.可事实网站上并没有这个文件,如果这样理解的话,不是程序会发生错误吗?所以我想的是这一句发布以后还存在的话是有点多余.
    是否有办法在发布后不留这一句呢?

  20. 东阿王[未注册用户]
    *.*.*.*
    链接

    东阿王[未注册用户] 2009-09-16 16:40:00

    <%= "Hello World" %>和
    <% Response.Write("Hello World") %>
    这两个应该都是在Render中执行的吧?但这两个是有什么优先顺序呢?后者写入OutPut,output有什么作用啊?

  21. 东阿王[未注册用户]
    *.*.*.*
    链接

    东阿王[未注册用户] 2009-09-16 16:41:00

    这篇文章对我有帮助!!

  22. 老赵
    admin
    链接

    老赵 2009-09-16 16:52:00

    @Athrun
    不会出错,具体原因我也不知道,可能是被忽略了吧。

  23. 老赵
    admin
    链接

    老赵 2009-09-16 16:53:00

    @东阿王
    正常顺序,代码什么顺序就什么顺序执行。Output是什么文章里写着了,或者可以看一下MSDN。

  24. Athrun
    *.*.*.*
    链接

    Athrun 2009-09-16 17:14:00

    @Jeffrey Zhao
    非常感谢,只是感到好奇,只是有没有都无所谓.

  25. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 17:19:00

    Jeffrey Zhao:
    @WWWWWx
    一篇文章半小时到45分钟,很快的。



    呵呵,老赵以前两小时一篇,现在效率提高了一倍。

    对页面中的纯文本,倒不是没有Html元素的概念,会被当作LiteralControl控件,因此可以说一个Aspx页面内容都是由页面类的子控件输出的。

  26. 老赵
    admin
    链接

    老赵 2009-09-16 17:21:00

    @小城故事
    你说的没错,我一会儿改一下原文。
    LiteralControl中的Render是输出到writer中的。

  27. 老赵
    admin
    链接

    老赵 2009-09-16 17:28:00

    @小城故事
    改好了,呵呵。
    // 不过思考时间节省的可不是一倍两倍的。

  28. uxspy
    *.*.*.*
    链接

    uxspy 2009-09-16 17:42:00

    关于送显方式,今天我遇到一个问题,
    我给usercontrol做了一个事件,在index页面注册了这个事件,在事件处理代码中我用ClientScript.RegisterStartupScript方法,结果我的页面死活就是不输出js,我想了想应该是跟委托有关吧,委托用完了,就释放资源了?百思不得其解,望指点一二,谢谢

  29. 小城故事
    *.*.*.*
    链接

    小城故事 2009-09-16 17:47:00

    Jeffrey Zhao:
    @小城故事
    改好了,呵呵。
    // 不过思考时间节省的可不是一倍两倍的。


    是啊,看您文章多了,不时怀疑老赵有疏忽,但绝不会怀疑老赵写技术文章的专业态度。

  30. thankzhao[未注册用户]
    *.*.*.*
    链接

    thankzhao[未注册用户] 2009-09-16 17:54:00

    <script runat="server">
    private void Page_Load(object sender, EventArgs e)
    {}
    </script>

    那么,在page中直接写入这种代码的话 和 codebehind中的Page_Load同时并存,会是一个什么顺序呢?有什么区别呢?

  31. 老赵
    admin
    链接

    老赵 2009-09-16 18:00:00

    @uxspy
    你这样子我也没法帮你,信息太少了。

  32. 老赵
    admin
    链接

    老赵 2009-09-16 18:00:00

    @thankzhao
    我记不住,不妨自己尝试一下吧。

  33. 老赵
    admin
    链接

    老赵 2009-09-16 18:10:00

    @uxspy
    不够,asp.net控件很复杂,不是这点代码就够看得,估计这个问题只能你调了吧。
    // 代码太多了,我删了,下次使用博客园插入代码的功能啊。

  34. Ss_Andy
    *.*.*.*
    链接

    Ss_Andy 2009-09-16 18:11:00

    温故而知新!

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

    韦恩卑鄙 2009-09-16 18:13:00

    <%= "Hello World" %>
    <% Response.Write("Hello World") %>
    


    这个混淆是越资深 越容易出错的

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

    韦恩卑鄙 2009-09-16 18:15:00

    又在为一篇长篇大论准备基石了 我替不明真相围观群众bs你

  37. uxspy
    *.*.*.*
    链接

    uxspy 2009-09-16 18:20:00

    Jeffrey Zhao:
    @uxspy
    不够,asp.net控件很复杂,不是这点代码就够看得,估计这个问题只能你调了吧。
    // 代码太多了,我删了,下次使用博客园插入代码的功能啊。


    那好吧,谢谢老赵

  38. 老赵
    admin
    链接

    老赵 2009-09-16 18:21:00

    @韦恩卑鄙
    你太了解我了……不过这次基本上是多个短篇小论。

  39. 知道了[未注册用户]
    *.*.*.*
    链接

    知道了[未注册用户] 2009-09-16 18:23:00

    我知道了,看这:
    <%@ Page language="c#" CodeFile="Default.aspx.cs" Inherits="_Default" %>

    Default.aspx在运行时会被编译成Default.aspx.cs。
    Default.aspx中的
    <script runat="server">
    private void Page_Load(object sender, EventArgs e)
    {}
    </script>
    显然就是Default.aspx.cs中的一个方法啊,重写了其父类_Default.cs中的Page_Load方法。

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

    韦恩卑鄙 2009-09-16 19:02:00

    Jeffrey Zhao:
    @xiao_p
    这点从来没变过,否则aspx里又如何使Inherits="..."里面定义的成员呢?


    因为有一本误人子弟的asp.net 2.0 里面说 由于partial引入导致aspx变成了code behind的partial类

    这本书前几天我一小弟还在看 让我写了几个例子 让他看清楚了书不能全信

  41. 老赵
    admin
    链接

    老赵 2009-09-16 19:06:00

    @知道了
    CodeBehind的确是没有用的,删了也没事,当然我是指运行时。
    编辑期我就没有尝试过了……

  42. 老赵
    admin
    链接

    老赵 2009-09-16 19:06:00

    @韦恩卑鄙
    那个,其实这里有两点:
    1、partial引入,只是“源代码”级别的,运行时丝毫不知情,因此也无法partial给跨程序集的类。
    2、曾经在asp.net 2.0里提出过code beside这个东西,我至今没搞清楚它是什么,后来也就丝毫没有声音了。

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

    韦恩卑鄙 2009-09-16 19:39:00

    Jeffrey Zhao:
    @韦恩卑鄙
    那个,其实这里有两点:
    1、partial引入,只是“源代码”级别的,运行时丝毫不知情,因此也无法partial给跨程序集的类。
    2、曾经在asp.net 2.0里提出过code beside这个东西,我至今没搞清楚它是什么,后来也就丝毫没有声音了。


    那本书也说什么code beside 但是举例全是code behind的 让人费解
    我也是按照1说的给小弟这么讲的

    好多书都是beta 1就开始写 写到一半很多特性就过时了

  44. 老赵
    admin
    链接

    老赵 2009-09-16 19:58:00

    @韦恩卑鄙
    partial是C# 3.0和.NET 3.5的特性,code beside是.net 2.0,其中相差三年,搞毛啊。
    哪本书?曝光曝光。

  45. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-09-16 20:34:00

    Jeffrey Zhao:
    @韦恩卑鄙
    partial是C# 3.0和.NET 3.5的特性,code beside是.net 2.0,其中相差三年,搞毛啊。
    哪本书?曝光曝光。


    搞错了吧?

  46. jalor
    *.*.*.*
    链接

    jalor 2009-09-16 21:33:00

    老赵最近产量很高啊,赞一个.

  47. 老赵
    admin
    链接

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

    @uxspy
    我建议你可以从最简单的代码做起,一点点修改成你项目的样子,哪一个步骤错了,就知道问题所在了。

  48. tshao[未注册用户]
    *.*.*.*
    链接

    tshao[未注册用户] 2009-09-17 00:14:00

    partial class是C# 2.0的,已经很久了,到了C# 3.0引入了partial method

  49. 老赵
    admin
    链接

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

    @tshao
    这样啊……多些提醒,记不清了,呵呵。

  50. tshao[未注册用户]
    *.*.*.*
    链接

    tshao[未注册用户] 2009-09-17 00:33:00

    code beside最早是针对asp.net 1.x模型中的code behind概念提出来的。原先1.x中的code behind,aspx是继承自aspx.cs的类的,因此server control都需要在aspx与aspx.cs中同时定义,而且是protected的,这样维护起来很麻烦。

    当时asp.net 2.0的website中最早使用了partial class的功能,aspx page和aspx.cs文件都定义为partial class,这样aspx page中的server control就不用在aspx.cs中重复定义了,以避免误删除操作。这种方式当时称为code beside。

    后来在群众的呼吁下,微软专门出了一个vs的补丁,加入了web project的项目选项,从这时开始页面模型又叫做code behind了,某些page指令的属性有所改动,但机制和code beside其实是相同的。可能微软觉得还是code behind比较好听吧,其实我也不知道……

    之后vs 2005 sp1就自带web project了,code beside这名词好像也没人提起了。

    以上都是靠记忆写的,也可能有错哈。

  51. hans.hu
    *.*.*.*
    链接

    hans.hu 2009-09-17 08:36:00

    <% Response.Write("Hello World") %>
    缺少分号吧!应该改成
    <% Response.Write("Hello World"); %>

  52. 老赵
    admin
    链接

    老赵 2009-09-17 08:44:00

    @hans.hu
    是,已经补上了。

  53. 老赵
    admin
    链接

    老赵 2009-09-17 08:45:00

    @tshao
    也就是说code beside只是代码级别,开发级别的概念,而code behind是运行时的概念?
    话说我从来不用web site,我讨论web site,我喜欢web application。

  54. pzq
    *.*.*.*
    链接

    pzq 2009-09-17 09:32:00

    很久没试过了,老赵的文章不用怎思考,一下就能看明白。

  55. 老赵
    admin
    链接

    老赵 2009-09-17 10:09:00

    @pzq
    其实我都是设法把问题讲清楚,而不是把一个大问题说得乱七八糟,而让普通人觉得“高深”。
    很反感这种装模做样的写法。

  56. mild
    *.*.*.*
    链接

    mild 2009-09-17 10:46:00

    麒麟.NET:

    xiao_p:
    “一个aspx页面会被编译成Page类的一个子类”

    问个问题,在2003中肯定是这样的,但是2005中,也是这样的吗?
    我记得好像变成了局部类了吧?





    我认为是编译成MyPage类的子类




    有必要这样钻牛角尖吗?

  57. 上山打老虎
    *.*.*.*
    链接

    上山打老虎 2009-09-17 10:56:00

    我思考了半天

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我