这几天做完了一个中型的项目,学到了许多的东西。在做项目的时候遇到问题时都是上csdn看帖子,我想我遇到的问题别人肯定也会碰到,应该说大部分的问题都可以在csdn上找到答案。所以我在csdn学到了很多的知识,谢谢大家!做项目时想,等我做完项目时就总结一些经验,让后来者学习和借鉴或说不再重复我犯的错误,所以就写了这一篇文章,希望对大家有用!
最大的感触就是,csdn上经常有人问,为什么我的dropdownLsit或checkboxlist或其它什么控件不能取到修改后的值,或是不能响应用户的修改(通俗一点的说是老是显示那一个值,不会改变)。其实这个问题最根本的原因是没有完全理解asp.net中页面的执行(处理)过程,今天我们就这个问题说说。
参考msdn中的"web 窗体页处理”(ms-help://MS.VSCC/MS.MSDNVS.2052/Vbcon/html/vbconWebFormsPageProcessingStages.htm),我们先理解一个概念---往返行程:浏览器向用户显示一个窗体,用户与该窗体进行交互,这导致该窗体回发到服务器。但是,因为与服务器组件进行交互的所有处理必须在服务器上发生,这意味着对于要求处理的每一操作而言,必须将该窗体发送到服务器、进行处理、然后返回到浏览器。这一事件序列称作“往返行程”。因此我们不难理解,为什么建议多使用html控件(客户端控件),而不是web控件(服务器端控件);为什么web控件不支持onmouseover、onmouseout事件了。因为它们要每触发一次都要在服务器和客户端间往返一次,也就我们所说的会引起"刷屏" 。但我们可以为web控件增加客户端的事件以支持这些操作,因为有时候必须用到。
理解了"往返行程"后,我们再来理解一下web页面的另一个特性----无状态性:客户端向服务器发送一个请求(譬如你在IE栏输入网址,回车),服务器接到请求,响应请求(处理事件),服务器完成处理后将生成的web页发送回浏览器,然后就清除该页的信息,释放服务器资源。服务再等待下一次请求,即使下一次是请求同一页,服务器重新开始创建和处理该页。服务器就是这样不停的重复这一过程。服务器不记录页面的状态或信息的特性我们就称之为“无状态性”。也许有朋友会问,我的控件每次刷新时会自动保存状态啊,那是因为asp.net引入了视图状态和状态管理,它会自动保持web控件的状态。关于服务器是怎样保存web控件的状态,我想我以后有能力或有时间的时候再写吧。请接着往下看。
理解了这两个基本概念后,我们就来看看asp.net中的服务器处理页面的一个过程,服务器处理页面过程(大概)分以下几个阶段:ASP.NET 页框架初始化(Page_Init() )à 用户代码初始化(page_load()) à 验证(调用任何验证程序 Web 服务器控件的 Validate 方法来执行该控件的指定验证) à 事件处理(处理所引发的特定事件)  à  清除(page_unload()),每一阶段会触发不同的事件,阶段后面的括号内容就是该阶段触发的事件。由此我们知道服务器每次执行页面代码的过程就是:page_init() à page_load() à vlidate函数(web控件的验证事件,应具体到实际的应用中)à 引起页面回发的具体事件(例如button 的click事件等) à page_unload()。如果不考虑具体的页面执行,我们可以看出,页面每次处理时,都要执行Page_Init、Page_load、page_unload事件。回到我们的主题,如要我们把dropdownlist 或其它的控件的初始化代码都放在某些方面page_load事件里面执行,那么意思就是每次页面刷新或被请求时控件都被重新初始化了。因此就有了控件的值永远都不会变的问题了。所以我们应该把初始化控件的代码放在:
if (!Page.IsPostBack){ //放在这里}。
说到这里,我想问题就说清楚了,不知你明白了吗?谢谢大家捧场!!欢迎大家讨论和指正。
如果大家想了解视图状态和状态管理,请参见:
ms-help://MS.VSCC/MS.MSDNVS.2052/Vbcon/html/vbconwebformstate.htm
及下面这篇文章:
http://www.microsoft.com/China/Community/T ... cle/TechDoc/Viewstate.asp
如果你想更深的了解页面或控件的执行过程,请参见:
ms-help://MS.VSCC/MS.MSDNVS.2052/cpguide/html/cpconcontrolexecutionlifecycle.htm

 

在项目实际应用中,常用datagrid控件,datagrid控件非常好用,今天我们来讲讲datagrid的基本的原理和概念性的东西,然后在续篇中我们会介绍datagrid的一些实用技巧。
   首先我们来看一下datagrid的组成,我们可以把datagrid理解成是html里面的一个table,table是由行(tr)和列(td)组成,而datagrid的行其实是datagriditem对象,而列就是datagridcolumn 对象。在datagrid对象的属性集了我们可以看到items和columns 属性,它们分别就是datagrid的行集和列集,也就是datagriditem对象集合及datagridcolumn 对象集合。通过查看asp.net生成的html页的源代码,我们也可以发现,datagrid的表现形式也就是table。
    我们也可以把datagrid理解成数据库里面的表,也是由行和列组成。就像我们建数据库表一样,我们首先要构造表的列,在创建一个datagrid对象时,我们也是要先创建datagrid的列。Datagrid的列有以下几种类型:BoundColumn(绑定列)、ButtonColumn(按钮列)、EditCommandColumn(编辑命令列)、HyperLinkColumn(超链接列)、TemplateColumn(模板列)。每种类型列实现的功能不一样,这里不一一细说了,大家可以在msdn查:ms-help://MS.VSCC/MS.MSDNVS.2052/cpref/html/frlrfSystemWebUIWebControlsDataGridClassTopic.htm。
    说完了datagridcolumn对象,我们再来看看datagriditem对象的类型,datagriditem有以下七种类型(它们组成ListItemType枚举类型的七种类型,不包含Separator,Separator一般用于datalist和repeater控件分隔行,不能绑定数据):Header(标题行) / Item (正常数据绑定行) / AlternatingItem(数据绑定分隔行) / footer(脚注行) / edititem(编辑行) / Pager(分页导航行) / SelectedItem(选中行),通过datagriditem的itemtype属性,我们可以知道某个datagriditem对象是哪一种类型,从而进行不同的处理。这在我们定制多表头及分页导航栏等一些情况下非常有用,后续篇我们会讲到这些应用。
    然后我们看看datagrid支持的一些常用的事件,它们在什么条件下触发?它们执行的顺序是什么?它们会触发时改变了哪些属性和对象?这些问题对编辑人员来说是非常重要的。也是我们用好datagrid 的基础,下面我们分别讲述DataBinding、itemcommand、itemcreated、itemdatabound这几个事件。
    因为datagrid是以列的形式来呈现(表现)数据的,一般情况下我们在声明一个datagrid的时候,就指定了datagrid要显示的列,所以datagrid的创建工作就只是创建行的工作了。当我们设置了datagrid的datasource属性,就调用datagrid的databind()方法,把数据源绑定到datagrid上,这时就触发了databinding事件。因为我们使用datagrid主要就是和数据打交道,我们可以把databinding事件看作是创建datagrid 的入口,也就是datagrid的创建工作是由databinding事件开始,它是创建datagrid最先执行的事件。接着,datagrid根据数据源开始创建行的工作。
    通过监视,我们发现datagird按如下顺序创建行(假设我们先把datagird 的allowpaging属性设为 true):PageràHeaderàItemà AlternatingItemàItemà AlternatingItemà Itemà……(直至创建完所有内容行)àFooteràPager ;如果我们把datagird 的allowpaging属性设为 false;,datagird则按如下顺序创建行: HeaderàItemà AlternatingItemàItemà AlternatingItemà Itemà……(直至创建完所有内容行)àFooter ;如果我们是按下了edit button(或select button) ,datagrid首先要取得edititem或selecteditem的itemindex,然后在创建行的过程中,与正在创建行的itemindex属性比较,如果相等,则这一行按照edititemtemplate或selecteditemtemplate规定的模板创建,那么datagrid按如下顺序创建行:HeaderàItemà AlternatingItemàItemà AlternatingItemà Itemà…àedititem(selecteditem)à…(直至创建完所有内容行)àFooter 。值得注意的是,当我们把datagird 的allowpageing属性设为 true时,最先创建是的是分页导航行(Pager),而且在最后又重新创建了一次于分页导航行(Pager),首先创建pager行,可以理解为datagrid必须先依据Pager行才能知道现在要显示第几页,才好做下面的创建工作。但在最后又重新创建Pager行的原因是什么呢?还是请各位高手解答,我是没有搞清楚为什么要这样做?。另外我们要注意的是,即使我们没有设置datagrid 的AlternatingItemStyle样式,在创建行时,datagrid也是按照itemà AlternatingItem的顺序创建,只不过它把ItemStyle样式应用在AlternatingItem(分隔行)上。并不是datagrid不创建AlternatingItem行,我在开始用datagrid的时候就是这样认为。
创建行时首先触发itemcreated事件,每创建完上面的任何一行,都会触发itemcreated事件,通过itemcreated事件,我们可以定制datagird的任何类型的行。我们常在该事件中定制header(标题行),pager(分页导航行),及footer(脚注行),后续篇会我们讲到。执行完itemcreated事件后,一行的框架就打出来了,无需数据绑定的列都已完全建好了。接着要对需要进行数据绑定的列进行数据绑定,也就是把数据源的值赋到各个列中,也包括执行aspx页面的数据绑定表达式,就是像这种代码:<%#  表达式 %>。完成数据绑定后,这时就触发了itemdatabound事件,在itemdatabound事件里,提供了我们访问datagrid的数据源的最后的机会,在itemcreated和itemdatabound事件里,我们可以通过(DataRowView)(e.Item .DataItem),把e.Item..DataItem强制转换成DataRowView对象,然后我们就可以访问该数据源了。根据数据源中的数据,我们可以定制或改变datagrid某行或某列的显示值或显示样式。但要注意以下两点:一是创建pager行时并不会触发itemdatabound事件,因为pager行不进行数据绑定。二是只有在创建item行、AlternatingItem行、selecteditem行、edititem行时e.Item.DataItem才有值;创建其它类型的行时,e.Item.DataItem的值都为null。所以我们在处理e.Item.DataItem时必须先判断e.Item.itemtype属性为上述类型行时,才能取出数据源的值;否则会抛出“对象不能为null“的异常。
执行完itemdatabound事件后,一行的创建工作就完成了。这时我们发现datagrid的items.count属性的值加了一,表示这一行创建完毕,加入了datagrid.items集合中。
Datagrid循环地调用itemcreated和itemdatabound事件创建完所有的行后,就完成了datarid的创建工作。也就是说,执行完databinding事件后(itemcreated和itemdatabound是嵌套在databinding事件里执行的),整个datagrid就创建完了。如果我们要在这以后再访问datagrid里面的数据或内容,就只有通过datagird的items集合访问行数据,再通过datagirditem的cells集合访问datagrid的列数据或控件。如果此时你想通过datagirditem.dataitem属性访问datagrid的数据源,就会抛出”找不到对象或对象为null”的异常。因为在执行完itemdatabound事件后datagriditem.dataitem属性已经被置为null了。
最后我们说说itemcommand事件,如果我们在datagrid的列模板中设置了buttoncolumn列或者说在itemtemplate中放置了linkbutton控件、checkbox控件、button控件等;所有这些控件引起页面提交时,都会触发datagrid的itemcommand事件,它们并不会触发它们本身的事件,例如button的click事件等。这种事件机制在asp.net里面称之为“冒泡事件“,在msdn上冒泡事件是这样解释的:不同于每个按钮单独引发一个事件,来自嵌套控件(这些控件放在datagrid容器中)的事件是“冒泡的”——也就是说,这些事件都将发送到容器中。该容器又引发一个带有参数的一般事件,名为 ItemCommand,它的参数使您可以发现是哪个控件引发了该事件。通过响应此单个事件(指只响应ItemCommand事件),可以避免不必要地为子控件编写单独的事件处理程序。关于“冒泡事件”请参考msdn:ms-help://MS.NETFrameworkSDK.CHS/cpguidenf/html/cpconwebformseventmodel.htm。
至此,datagrid的原理篇就讲完了,下一个续篇我们具体讲解datagrid控件的应用。

 


 

写这篇文章之前,在csdn上经常看到有关从数据库中读出图片,并显示在网页上的问题,我查了好多资料,只有一种好的方案解决这个问题,就是用一个专门的页来生成图片,然后在目标页(引用图片)中放一个img控件,然后把img控件的src属性设成生成图片页的url,图片就可动态的显示在目标页上了。不知你有更好的方案没有?
    这几天我在做报表,表格的报表一般用datagrid或table web控件生成,因为这两个控件定制起来比较方便,基本上能满足实际需要。但很遗憾的是,我做的项目并不要求打印报表,所以没有经验可谈。做完整个项目后,我会研究一下打印的问题,以后的项目肯定要用,到时再作总结了。希望大家有好的解决打印的方案,贴出来给大家分享。关于用datagrid或table web控件定制报表的方案,在后续的篇章我会讲到,请关注!
    回到我们的主题,看了这篇文章后,也许你会感叹.net确实强大。因为.net里面提供了强大的画图功能,只是我们没有发现而已。有时间大家看看msdn,也许会发现好多好的东西。贴出源码之前,首先说明本源码是我从网上down下来的,我也不记得原作者是谁了。原来是英文的,我把它用中文作了注释,你把源码cut下来,放在某个页面的.vb文件中,就可以浏览其报表效果了。代码如下:

Imports System.Drawing
Imports System.Drawing.Imaging

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim i As Integer
        '创建一个bitmap对象,width=400,height=200像素
        Dim bm As New Bitmap(400, 200)
        Dim g As Graphics
        g = Graphics.FromImage(bm)

        'set canvas color(设置图片的底色)
        g.Clear(Color.Snow)

        'display graph title(在指定位置并且用指定的 Brush 和 Font 对象绘制指定的文本字符串。画图片的标题)
        g.DrawString("Hours per day", New Font("Tahoma", 16), Brushes.Black, New PointF(5, 5))

        Dim yaxis(6) As Integer
        yaxis(0) = 12
        yaxis(1) = 7
        yaxis(2) = 4
        yaxis(3) = 10
        yaxis(4) = 3
        yaxis(5) = 11
        yaxis(6) = 5

        Dim xaxis(6) As String
        xaxis(0) = "Mon"
        xaxis(1) = "Tue"
        xaxis(2) = "Wed"
        xaxis(3) = "Thu"
        xaxis(4) = "Fri"
        xaxis(5) = "Sat"
        xaxis(6) = "Sun"

        'display graph legends(画图例)
        '图例的起始位置
        Dim symbolLeg As PointF = New PointF(335, 20)
        '图例说明文字的起始位置
        Dim descLeg As PointF = New PointF(360, 16)
        '用循环创建图例
        For i = 0 To xaxis.Length - 1
            '画图例,用矩形表示
            '画矩形
            g.FillRectangle(New SolidBrush(GetColor(i)), symbolLeg.X, symbolLeg.Y, 20, 10)
            '画矩形的边框
            g.DrawRectangle(Pens.Black, symbolLeg.X, symbolLeg.Y, 20, 10)
            '画图例的说明文字
            g.DrawString(xaxis(i).ToString, New Font("Arial", 10), Brushes.Black, descLeg)
            '移动图例的坐标值,以例画下一图例
            symbolLeg.Y += 15
            descLeg.Y += 15
        Next i

        '开始画饼图
        Dim totalAng As Integer
        ‘数据所占角度
        Dim currentangle As Single = 0
        '起始角度
        Dim startangle As Single = 0

        '计算总值
        For i = 0 To yaxis.Length - 1
            totalAng = totalAng + yaxis(i)
        Next

        For i = 0 To yaxis.Length - 1
            '计算角度
            currentangle = yaxis(i) / totalAng * 360
            '画扇形
            g.FillPie(New SolidBrush(GetColor(i)), 100, 40, 150, 150, startangle, currentangle)
            '画扇形的边框
            g.DrawPie(Pens.Black, 100, 40, 150, 150, startangle, currentangle)
            '移动扇形的坐标值,以例画下一扇形
            startangle += currentangle
        Next i
        '画整个图的大边框
        Dim p As New Pen(Color.Black, 2)
        g.DrawRectangle(p, 1, 1, 398, 198)

        '输出图片到输出流上,显示在客户端(格式成jpeg文件)
        bm.Save(Response.OutputStream, ImageFormat.Jpeg)
    End Sub

    '获取图例的颜色
    Private Function GetColor(ByVal itemIndex As Integer) As Color
        Dim objColor As Color
        Select Case itemIndex
            Case 0
                objColor = Color.Blue
            Case 1
                objColor = Color.Red
            Case 2
                objColor = Color.Yellow
            Case 3
                objColor = Color.Peru
            Case 4
                objColor = Color.Orange
            Case 5
                objColor = Color.Coral
            Case 6
                objColor = Color.Gray
            Case 7
                objColor = Color.Maroon
            Case Else
                objColor = Color.Green
        End Select
        Return objColor
    End Function

End Class

    在实际的应用中,我们新建一个页面,然后:
    
<%@ Page language="vb"  %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
    <HEAD>
        <title>项目总结之饼图报表源码篇</title>
    </HEAD>
    <body bottomMargin="0" leftMargin="0" topMargin="0" rightMargin="0" MS_POSITIONING="GridLayout">
        <form id="sfgl_report" method="post" runat="server">
            <TABLE cellSpacing="0" cellPadding="0" width="765" align="center" border="0">
                <TBODY>
                    <tr>
                        <td>
                            <br>
                            <div align="center">
                                <img runat="server" id="img_report" src=” 生成图片页的url”>
                            </div>
                            <br>
                        </td>
                    </tr>
</TBODY>
            </TABLE>
    </body>
</HTML>
图片闪是正常的,因为它是用二进制流输出的,我以前做过从数据库读出html、word,显示到网页上,是这样解决的,你看看下面的代码,或许对你有启发,原理是多作了一道转换,我也不知道为什么这样会快,是经验得出来的:
HttpContext.Current.Response.Clear();
                HttpContext.Current.Response.ContentType=open_file_application(file_type(str_table_name,str_blob_column_name,str_select_condition));
                System.IO.Stream fs=HttpContext.Current.Response.OutputStream;
                int fileDataCol = 0;         
                Byte[] b = new Byte[(sdr_file.GetBytes(fileDataCol, 0, null, 0, int.MaxValue))];
                sdr_file.GetBytes(fileDataCol, 0, b, 0, b.Length);
                sdr_file.Close();            
                cyc_data.scn_databaseconnection.Close();
//转换成string
                string s = System.Text.Encoding.Default.GetString(b);
                s = s.Replace(str_keyword,"<font color='red'>" + str_keyword + "</font>");
//再转换成byte
                b=System.Text.Encoding.Default.GetBytes(s);                
                fs.Write(b,0,b.Length);
                fs.Close();
                HttpContext.Current.Response.End();
没有直接写Response.Write(s)是因为这样太慢了,转换成byte[]型后,用文件流来写,速度快多了。

 


datagrid自定义分页是datagrid的一个重要的功能,datagrid自定义分页主要用于数据源较大时。因为数据源大(记录多),加载时间长,反应慢,耗服务器的资源。而且每显示一页就要重新加载所有的数据。而如果我们用自定义分页,则每一次只加载一页的记录,也就是只加载我们要显示的记录。这样加载数据时间短,反应快,节约服务器的资源。在做项目的过程中,我们也经常需要用到自定义功能。今天我们就说说怎样自定义分页。
    先看看datagrid的自定义分页的原理,它主要依据两个主要属性,第一个就是VirtualItemCount属性,它表示datagrid一共要显示多少条记录,它就是的作用是用于生成pager(分页导航行),通过结合另外两个属性pagesize和PageButtonCount,datagrid就知道要分成多少页了及当前要显示多少个分页按钮,我们不难得到总共要显示的总页数=(VirtualItemCount+pagesize-1)/pagesize;如要总页数<PageButtonCount,则显示总页数个按钮;如果总页数>PageButtonCount,则显示PageButtonCount个按钮,当然到了最后一页就只显示VirtualItemCount% pagesize(总记录条数除以每页显示记录的余数)个按钮。另一个重要的属性就是datasource(数据源),自定义分页的一个重要的特点是显示数据源中所有的记录,如果数据源中有一条记录,则显示一条记录;如果数据源中有一万条记录,则它会显示一万条记录,可能你的机子就慢了 : )。所以自定义最重要的一点是如何设置或获取数据源了。
    接下来,我们先说说自定义分页的主要步骤:
1.    设置datagrid的VirtualItemCount属性;
2.    获取datagrid的datasource(数据源);
3.    绑定数据到datagrid;
4.    设置新页的页码(datagrid.currentpageindex属性)。
重复上述2,3,4步。
下面我们以一个例子来讲解datagrid 的自定义过程。
例子要求机子装有ms sql server 7.0 或 2000,当然还要能运行asp.net页了(废话)。
我们先来写一个通用的存储过程,用于分页,返回某页的要显示的记录集,及一个输出参数--总的记录条数,但这个存储过程有缺陷,例如只能用于单表查询,必须要有条件语句等。
CREATE PROCEDURE up_custompage @vc_order_column_name varchar(100),@vc_select_column_list varchar(100),
@vc_select_table_list varchar(100),@vc_condition varchar(100),@page_size int,@current_page int,@total1 int output 
/*
    (
        @vc_order_column_name :表要排序列的列名,只能按一列排序,而且该列必须得在输出列表中;
        @vc_select_column_list :返回列的列名列表;
        @vc_select_table_list:要查询的表名;
        @vc_condition:查询条件的字符串,必须要有查询条列,否则会抛出异常;
        @page_size:每页显示记录的条数;
        @current_page:当前页的页码;
        @total1:所有符合条件的记录的总数。
        构造的sql语句=select top 每页显示记录的条数 * from (select top 每页显示记录的条数 返回列的列名列表 from 要查询的表名 where 要排序列的列名 in (select top 每页显示记录的条数 X 当前页的页码  要排序列的列名  from 要查询的表名 where 查询条件 order by 要排序列的列名) order by 要排序列的列名 desc ) as temp1 order by 要排序列的列名
    )
*/
AS
--声明要用到的变量,@temp1是正常的分页语句字符串,@temp2是最后一页的分页语句字符串,@page_total表一共有几页,@last_page
--是最后一页的页码
declare @temp1 varchar(500),@temp2 nvarchar(500),@page_total int,@last_page int
--构造获得总页的数的检索语句
set @temp2=N'select @total2=count(*) from ' + @vc_select_table_list + ' where ' + @vc_condition
--执行检索语句,取得总的记录条数
exec sp_executesql @temp2,N' @total2 int output ',@total1 output

/*构造分页检索语句,基本原理是先取出@page_size*@current_page条记录,相当于是取出当前页及当前页前面的所有页面的记录然后取出当前面所要显示的记录,也就是反序排序后取前@page_size条记录;最后再反序排序(因为前面的顺序被反排过一次,现在再反排一次,正好是我们要的顺序),最后执行,返回结果集。
*/
if @total1>0
begin
set @page_total=(@total1+@page_size-1)/@page_size
--如果当前页不是最后一页
if @current_page<@page_total
set @temp1='select top ' + cast(@page_size as varchar(4)) + ' * from 
(select top ' + cast(@page_size as varchar(4))  + ' ' + @vc_select_column_list + ' from ' + @vc_select_table_list +' where ' + @vc_order_column_name 
+ ' in (select top ' + cast(@page_size*@current_page as varchar(10)) + ' ' + @vc_order_column_name + ' from ' + 
@vc_select_table_list + ' where '+ @vc_condition  + ' order by ' +
@vc_order_column_name + ') order by ' + @vc_order_column_name  + ' DESC) as temp1 order by ' + @vc_order_column_name 
else
--最后一页只返回分页后的最后几条记录,也就是@total1%@page_size条记录
begin
set @last_page=@total1%@page_size
set @temp1='select top ' + cast(@last_page as varchar(4)) + ' * from 
(select top ' + cast(@last_page as varchar(4))  + ' ' + @vc_select_column_list + ' from ' + @vc_select_table_list +' where ' + @vc_order_column_name 
+ ' in (select top ' + cast(@total1 as varchar(10)) + ' ' + @vc_order_column_name + ' from ' + 
@vc_select_table_list + ' where '+ @vc_condition  + ' order by ' +
@vc_order_column_name + ') order by ' + @vc_order_column_name  + ' DESC) as temp1 order by ' + @vc_order_column_name 
end
--执行检索
exec(@temp1)
end
else
return

-------------------------------------------------------------------------

然后在新建一个aspx页面,代码如下:
<%@ Page language="c#" Codebehind="custompage.aspx.cs" AutoEventWireup="false" Inherits="cyc_test.custompage" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
    <HEAD>
        <title>custompage</title>
        <meta content="Microsoft Visual Studio 7.0" name="GENERATOR">
        <meta content="C#" name="CODE_LANGUAGE">
        <meta content="JavaScript" name="vs_defaultClientScript">
        <meta content="http://schemas.microsoft.com/intellisense/ie5& ... ot;vs_targetSchema">
        <style>A { BEHAVIOR: url(MouseOver.htc) }
    HR { COLOR: black; HEIGHT: 2px }
    .StdText { FONT-WEIGHT: bold; FONT-SIZE: 9pt; FONT-FAMILY: verdana }
    .StdTextBox { BORDER-RIGHT: black 1px solid; BORDER-TOP: black 1px solid; FONT-SIZE: 9pt; FILTER: progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true'); BORDER-LEFT: black 1px solid; BORDER-BOTTOM: black 1px solid; FONT-FAMILY: verdana }
    .Shadow { FILTER: progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color='gray', Positive='true') }
        </style>
    </HEAD>
    <body style="FONT-FAMILY: arial" bgColor="ivory" MS_POSITIONING="GridLayout">
        <form id="custompage" method="post" runat="server">
            <h2>项目总结之datagrid自定义分页篇
            </h2>
            <asp:label id="Label1" runat="server" font-bold="true" cssclass="StdText">当前路径: </asp:label><asp:label id="lblURL" style="COLOR: blue" runat="server" cssclass="StdText"></asp:label>
            <!-- Query --><br>
            <asp:label id="Label2" runat="server" cssclass="stdtext" Text="查询语句:"></asp:label>
<asp:textbox id="Textbox1" runat="server" cssclass="stdtextbox" text="SELECT employeeid, firstname, lastname,title FROM Employees where employeeid>0 order by employeeid" width="765px" Enabled="false"></asp:textbox>
            <hr>
            <!-- Show the information -->
<asp:datagrid id="grid" runat="server" OnPageIndexChanged="PageIndexChanged" AllowCustomPaging="True" AllowPaging="True" PageSize="5" BorderWidth="1" BorderColor="black" BorderStyle="solid" BackColor="White" CssClass="Shadow" GridLines="vertical" CellSpacing="0" CellPadding="2" Font-Names="Verdana" Font-Size="Smaller">
                <PagerStyle Font-Bold="true" Mode="NumericPages" BackColor="palegreen" />
                <AlternatingItemStyle BackColor="#eeeeee" />
                <ItemStyle BackColor="White" />
                <HeaderStyle Font-Bold="True" ForeColor="White" BackColor="Navy" />
            </asp:datagrid></form>
    </body>
</HTML>
再写后台的源代码:
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Data.SqlClient;

namespace cyc_test
{
    /// <summary>
    /// custompage 的摘要说明。
    /// </summary>
    public class custompage : System.Web.UI.Page
    {
        protected System.Web.UI.WebControls.Label Label1;
        protected System.Web.UI.WebControls.Label lblURL;
        protected System.Web.UI.WebControls.Label Label2;
        protected System.Web.UI.WebControls.TextBox Textbox1;
        protected System.Web.UI.WebControls.DataGrid grid;
    
        private void Page_Load(object sender, System.EventArgs e)
        {
            // 第一次加载页时,初始化
            if (!Page.IsPostBack)
            {
                lblURL.Text = Request.Url + "<hr>";    
                createdatasource(1);
            }
        }

        //绑定datagrid的函数
protected void createdatasource(int current_page)
        {
            string str_table_name,str_column_list,str_order_column,str_condition;
            //查询的表名
            str_table_name="employees";
            //返回的列名列表
            str_column_list="employeeid,firstname,lastname,title";
            //排序列的列名
            str_order_column="employeeid";
            //查询的表件
            str_condition="employeeid>0";
            string strConn = "DATABASE=Northwind;SERVER=localhost;UID=sa;PWD=;";
            SqlConnection conn = new SqlConnection(strConn);
            //声明执行存储过程的SqlCommand 
            SqlCommand scd_sel=new SqlCommand("up_custompage",conn);
            scd_sel.CommandType=CommandType.StoredProcedure;
            //给存储过程的参数赋值
            SqlParameter sp_temp;
            sp_temp=scd_sel.Parameters.Add("@vc_order_column_name",SqlDbType.VarChar,100);
            sp_temp.Direction=ParameterDirection.Input;
            sp_temp.Value=str_order_column;
            sp_temp=scd_sel.Parameters.Add("@vc_select_column_list",SqlDbType.VarChar,100);
            sp_temp.Direction=ParameterDirection.Input;
            sp_temp.Value=str_column_list;
            sp_temp=scd_sel.Parameters.Add("@vc_select_table_list",SqlDbType.VarChar,100);
            sp_temp.Direction=ParameterDirection.Input;
            sp_temp.Value=str_table_name;
            sp_temp=scd_sel.Parameters.Add("@vc_condition",SqlDbType.VarChar,100);
            sp_temp.Direction=ParameterDirection.Input;
            sp_temp.Value=str_condition;
            sp_temp=scd_sel.Parameters.Add("@page_size",SqlDbType.Int);
            sp_temp.Direction=ParameterDirection.Input;
            sp_temp.Value=grid.PageSize;
            sp_temp=scd_sel.Parameters.Add("@current_page",SqlDbType.Int);
            sp_temp.Direction=ParameterDirection.Input;
            sp_temp.Value=current_page;
            sp_temp=scd_sel.Parameters.Add("@total1",SqlDbType.Int);
            sp_temp.Direction=ParameterDirection.Output;
            
            //执行存储过程
            SqlDataAdapter sda=new SqlDataAdapter();
            sda.SelectCommand=scd_sel;
            if (conn.State==ConnectionState.Closed)
                conn.Open();
            DataSet ds=new DataSet();
            sda.Fill(ds,"tb1");        
            conn.Close();
            //设置VirtualItemCount属性
            grid.VirtualItemCount=(int)scd_sel.Parameters["@total1"].Value;
            //绑定数据源
            grid.DataSource=ds.Tables["tb1"].DefaultView;
            grid.DataBind();
        }

        #region Web Form Designer generated code
        override protected void OnInit(EventArgs e)
        {
            //
            // CODEGEN:该调用是 ASP.NET Web 窗体设计器所必需的。
            //
            InitializeComponent();
            base.OnInit(e);
        }
        
        /// <summary>
        /// 设计器支持所需的方法 - 不要使用代码编辑器修改
        /// 此方法的内容。
        /// </summary>
        private void InitializeComponent()
        {    
            this.grid.ItemCreated += new System.Web.UI.WebControls.DataGridItemEventHandler(this.grid_ItemCreated);
            this.grid.PageIndexChanged += new System.Web.UI.WebControls.DataGridPageChangedEventHandler(this.PageIndexChanged);
            this.Load += new System.EventHandler(this.Page_Load);

        }
        #endregion
        //datagrid的ItemCreated事件,用于定制分页导航行
        private void grid_ItemCreated(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
        {
            
            ListItemType elemType = e.Item.ItemType;
            //定制分页导航行,样式为[1] [2] 第 3 页 [4]
            if (elemType == ListItemType.Pager) 
            {
                
                TableCell pager = (TableCell) e.Item.Controls[0];
            
                for (int i=0; i<pager.Controls.Count; i+=2) 
                {
                    Object o = pager.Controls[i];
                    if (o is LinkButton) 
                    {
                        LinkButton h = (LinkButton) o;
                        h.Text = "[ " + h.Text + " ]"; 
                    }
                    else
                    {
                        Label l = (Label) o;
                        l.Text = "第" + l.Text + “页”; 
                    }
                }
            }
        }    
        //页选中(分页)事件
        public void PageIndexChanged(object source, System.Web.UI.WebControls.DataGridPageChangedEventArgs e)
        {
            grid.CurrentPageIndex = e.NewPageIndex;
            //页码值是从零开始的,所以要加一
            createdatasource(grid.CurrentPageIndex+1);
        }
    }
}

   要运行上面的示例,请按上面的顺序先创建存储过程,再写页面的代码,最后是源代码。示例参考了《构建Web解决方案---应用ASP.NET和ADO.NET》中的分页思路。在本机调试通过。大家只要明白存储过程分页的思想就可以了。如果大家有更好的方案,请提出来,也许你的更好!
   这一段时间又开始了新的项目,所以这篇文章写得比较仓促,可能有些地方讲得不明白,大家可以提问和讨论。希望对大家有所帮助。
ALTER PROCEDURE up_custompage @vc_order_column_name varchar(100),@vc_select_column_list varchar(100),
@vc_select_table_list varchar(100),@vc_condition varchar(100),@page_size int,@current_page int,@total1 int output 
/*
    (
        @vc_order_column_name :表要排序列的列名,只能按一列排序,而且该列必须得在输出列表中;
        @vc_select_column_list :返回列的列名列表,可以为多个列;
        @vc_select_table_list:要查询的表名及关联表的连接;
        @vc_condition:查询条件的字符串,必须要有查询条列,否则会抛出异常;
        @page_size:每页显示记录的条数;
        @current_page:当前页的页码;
        @total1:所有符合条件的记录的总数。
    )
*/
AS
--声明要用到的变量,@temp1是正常的分页语句字符串,@temp2是最后一页的分页语句字符串,@page_total表一共有几页,@last_page
--是最后一页的页码
declare @temp1 varchar(500),@temp2 nvarchar(500),@page_total int,@last_page int
--构造获得总页的数的检索语句
set @temp2=N'select @total2=count(*) from ' + @vc_select_table_list + ' where ' + @vc_condition
--执行检索语句,取得总的记录条数
exec sp_executesql @temp2,N' @total2 int output ',@total1 output

/*构造分页检索语句,基本原理是先取出@page_size*@current_page条记录,相当于是取出当前页及当前页前面的所有页面的记录
然后取出当前面所要显示的记录,也就是反序排序后取前@page_size条记录;最后再反序排序(因为前面的顺序被反排过一次,
现在再反排一次,正好是我们要的顺序),最后执行,返回结果集。
*/
if @total1>0
begin
set @page_total=(@total1+@page_size-1)/@page_size
--如果当前页不是最后一页
if @current_page<@page_total or @total1%@page_size=0
set @temp1='select top ' + cast(@page_size as varchar(4)) + ' * from 
(select top ' + cast(@page_size as varchar(4))  + ' ' + @vc_select_column_list + ' from ' + @vc_select_table_list +' where ' + @vc_order_column_name 
+ ' in (select top ' + cast(@page_size*@current_page as varchar(10)) + ' ' + @vc_order_column_name + ' from ' + 
@vc_select_table_list + ' where '+ @vc_condition  + ' order by ' +
@vc_order_column_name + ') order by ' + @vc_order_column_name  + ' DESC) as temp1 order by ' + @vc_order_column_name 
else
--最后一页只返回分页后的最后几条记录,也就是@total1%@page_size条记录
begin
set @last_page=@total1%@page_size
set @temp1='select top ' + cast(@last_page as varchar(4)) + ' * from 
(select top ' + cast(@last_page as varchar(4))  + ' ' + @vc_select_column_list + ' from ' + @vc_select_table_list +' where ' + @vc_order_column_name 
+ ' in (select top ' + cast(@total1 as varchar(10)) + ' ' + @vc_order_column_name + ' from ' + 
@vc_select_table_list + ' where '+ @vc_condition  + ' order by ' +
@vc_order_column_name + ') order by ' + @vc_order_column_name  + ' DESC) as temp1 order by ' + @vc_order_column_name 
end
--执行检索
exec(@temp1)
end
else
return

在做项目的过程中看了几本书,包括《asp.net高级编程》(Wrox出版)、《构建Web解决方案---应用ASP.NET和ADO.NET》、《Applied Microsoft .net framework programming》中文版、《javascript 权威指南》(第三版)这几本书,分别说说这几本吧,第一本看了一半(看了前10章及后面的13、15章),至今还没有看完,感觉是讲得比较全,什么东西都说了,但说的不得不够深,不够贴近实际应用,但给我引入了门,知道常用控件的使用及一些常用的类,如dataset、sqldatareader等,所以这本书也值得一看。第二本是个人认为的在做项目中最实用、最有用的一本,我基本上看完了这一本书。看过这本书的人都知道这本书基本上都是围绕着datagrid讲解的,而我们在做项目的时候用得最多的无非也是datagrid,datalist这些与数据库密切关连的控件,所以看了这本书以后,受益匪浅,对datagrid的使用就上了一个档次。Datalist其实和datagrid相差不大,自然也就提升了。上面还结合例子讲了一些其它控件及类的使用。总结下来就是这本书讲得深,结合例子讲(可以在网站上下它的源码),贴近于实际应用。强烈给推荐大家。第三本只看了三分之一,因为看起来实在是太费力了,也许基础比较差,挺费力的看了前面的几章,回过头来想想,好象没什么印象,也不知道书上说了些什么,不知哪位大侠指点一下,如何才能看得懂该书。我想边看英文版,边看中文版,无耐英文实在太差,看到英文就头痛,所以就一直没往下面看。看来得下决心学英文了。所以这本书就不敢提什么建议了。最后一本是因为做web项目,不可能不和客户端打交道,所以javascipt是不可缺少的,这本书是作参考,用到的时候就翻,相当于一本javascript的字典,还是可以的。
    做项目的时候,忙得要命,总是在想,项目完了以后,我要好好的学习自定义控件开发、数据报表、XML、javascrpt、项目管理……
自定义控件:不知大家开发过没有,相信还是有很多人自己研究和开发过,有什么经验,请拿出来和大家一起分享。我计划看msdn学习一下,到时再和大家一起交流,请问大家有什么好书、好的学习网址,有50分奖励哦。
    数据报表:主要是柱形图、曲线图、饼图的开发,这两天都在研究这个,从网上下了一些源码,好象可以通过system.drawing;system.drawing.Graphics类可以画图。学好了再和大家交流。
    XML:因为发现许多的应用中都可以用XML实现,如:不刷新页面在客户端和服务器间来回传递数据,XML作为中间层存储数据等;这是我最想学的东西了,一直没时间,还有就是不知道怎么入门。在网上也下了好多本书,但是希望结合.net来学。所以希望大家推荐好书、好的网站,最好还是书,这样学起来快些。100分赏金。
   Javascript可以慢慢学,用的时候看看书应该没有问题,它是asp.net的基础,当然也不能少了。
   最后是项目管理/软件工程了,做项目有时很气就是,项目需求老是在变,变一次程序就要改一次,挺烦人的。如果在早期作需求的时候充分考虑到需求的变更,或考虑得详细,周全一点,那么在编码时就不必那么费劲了。所以做需求分析及系统分析的工作是非常重要的。大家都说30岁就不能做程序了,我虽然不赞同这种说法,但也赞同不可能一辈子做程序员,毕竟做程序员是靠卖脑细胞谋生的,人老了,脑细胞就不多了,无法再卖了。考虑以后的问题,做项目管理或系统分析是不错的选择。但是难啊!还得学好多的东东,真是学不完啊。最近就买了《人月神话》、《软件工程—实践者的研究方法》看。只看了《人月神话》,感觉还是不错了,上面的话用在实际中还是挺有道理的。引入经典的一句:人/月如果成正比,那是软件工程的神话,天方夜谭。有道理啊。《软件工程—实践者的研究方法》还没开始看,相信还是不错。毕竟是经典之作啊。但我还推荐大家一本好书,自己觉得非常不错,就是电子工业出版社的《软件需求》,国外经典教材系列。结合例子讲。我看了一半,来不及总结。大家可以到书店去翻翻。

电子书下载网址大全:

www.pcbookcn.com  
非常书城:http://www.mayia.com/downshu/bookcity/index.asp  
帝华咨讯:http://pcbook.godcn.net/download/default.asp? ... 33&txtitle=  
海阔天空:http://download.lycos.com.cn/progr ... html#top  
http://wlbookwl.myrice.com/index1.htm  
ebook-博士网图书库  http://www.helpwork.net/book/html/user/index.asp  
ebook电子图书搜索网  http://www.ebooksou.com/  
ebook-电子图书下载中心http://www.epubcn.com/  
ebook-非常书城http://www.yuyux.com/  
ebook-计算机图书大全http://www.vvsoft.net/vvbksd/index ... =visualc  
ebook-互动出版网http://www.china-pub.com/  
ebook论坛-E世代家园论坛--夏季版http://www.downsky.net/cgi-bin/ut/boar ... &age=30  
ebook-山海程序http://www.156ok.com/  
ebook-月夜我飞翔http://www.dingbing.com/  
ebook-中华电脑书库http://www.pcbookcn.com/  
http://www.csdn.net/expert/topic/663 ... p=.7477991  
http://www.csdn.net/expert/topic/598 ... mp=.222912  
http://www.godcn.net/school/  
http://gshulkin.hypermart.net/books/FreeVBBook.html  
http://www.monumental.com/boat/computerbooks.html  
http://ftp.pcworld.com/pub/internet/  
http://www.china-pub.com/computers/Brow ... XiaZhai.htm  
ftp://61.156.23.10/2/.EM/m2w/study/  
http://www.rokinc.cjb.net    
http://books.zone.ru  
http://members.spree.com/education/jackyhft/  
http://www.realvb.bizland.com/  
http://lib.wuhee.edu.cn/book/computer.asp    
http://zhanet.myrice.com/  
http://it.263.net/b/book/  
ftp://win2k:abc123@63.236.67.26/  
ftp://216.233.144.210/%7E/%7Etagge ... sbeller/  
http://www.mycnknow.com/srindex.htm(***)  
http://www.ebooksou.com/     ... nbsp; money  ****)  
http://asm.hyedu.com/index.asp  
http://cyaudit.hz198.net/sddown/index.asp(软件加书籍下载  ****)  
http://ebook.devchina.com  (*)  
http://202.96.70.229/cakk/delphi/(**)  
http://www.china-pub.com/computers(互动出版社,需MONEY)****  
http://zjfeng.csw.cnshare.net  
   http://www.hktk.com/soft/program/book/index.html  
http://www.pcbookcn.com/  
http://www.eshunet.com/a1250/1210.htm  
http://wlbookwl.myrice.com/book.htm  
http://dadao.net/index.php  
http://pcbook.godcn.net/  
http://ctpcyuyustudio.at.china.com/download/53.htm  
http://zhwm.myrice.com/pcbook/  
http://www.ebooksou.com/  
http://goldnets.myrice.com/  
http://www.mayia.com/downshu/bookcity/index.asp  
http://ieq.533.net/book/linux.htm  
http://www.helpwork.net/book/html/user/main.asp  
http://ieq.533.net/book/vc.htm  (专门的书籍下载网  *****)  
http://cyaudit.hz198.net/sddown/index.asp(软件加书籍下载  ****)  
http://www.dangdang.com  (当当网上书店,NEED  MONEY  ****)  
http://goldnets.myrice.com/(黄金书屋  ****)  
http://www.helpwork.net/(博士网****)  
http://pcbook.godcn.net/(***)  
http://www.taishen.net/ebook/(***)  
http://wlbookwl.myrice.com/index1.htm(****)  
www.epubcn.com    (英文斑***)  
http://www.hktk.com  (软件加书籍下载  ***)  
http://www.mycnknow.com/srindex.htm(***)  
http://www.ebooksou.com/     ... nbsp; money  ****)  
http://members.spree.com/education/jackyhft/  
http://www.realvb.bizland.com/  
http://lib.wuhee.edu.cn/book/computer.asp    
http://zhanet.myrice.com/    *****化境  
http://it.263.net/b/book/  
ftp://win2k:abc123@63.236.67.26/  
ftp://216.233.144.210/%7E/%7Etagge ... sbeller/  
http://zjfeng.csw.cnshare.net  
http://vg.dyndns.org/ebook/  
http://www.pchome.net/~/LJ/book/book3.htm  
http://www.mcsebraindumps.com  
http://www.mcsdunion.com  
http://www.kubao.com  
ftp.gargoylewiz.dynip.com  
http://delphideveloper.myrice.com/  
http://www.freedrive.com/ASP/PostFolde ... sc=14365878  
ftp://maestro.den.disa.mil  
http://rokinc.narod.ru  
http://home.cyberarmy.com/micromaniz/  
ftp://209.204.119.253/temp/damoak/  
http://home.wtwh.com.cn/download/ebookshelf.html  
http://www.helpwork.net/    %%%##···博士网  
http://home.online.tj.cn/~cavendish  
http://cheminf.nankai.edu.cn/DynaDocBooks/  
http://www.certificationcorner.com/2000.htm  
http://dzhang.yeah.net  
http://www.chian-pub.com  
ftp://128.32.208.109/.NWNK-FxP/_vti_pvt/_vti_pub  
http://www.freedrive.com/ASP/PostFolde ... sc=15456974  
http://books.gnaritas.net/Books/Exams/  
http://yangnan.126.com/    ****白菜乐园  
http://www.pchome.net/~/LJ/book/book3.htm  
http://www.bruceeckel.com/DownloadSites/  
http://vg.dyndns.org/ebook/    
ftp://141.133.239.239/.%20scanned%20 ... /by%20blue  
ftp://216.167.56.173/tmp/damoak/cisco/  
ftp://216.149.46.228/scan/by/damoak/  
ftp://61.156.23.10/2/.EM/m2w/study/  
ftp://203.198.80.44/test/ilifebackup/24Jun200 ... %20by%20p/Bookz  
http://pclub.technoart.net/books/cpplang/  
ftp://216.233.144.210/%7E/%7Etagge ... sbeller/  
http://members.xoom.com/dgss_02  
www.3322.net/~shii/TNT/crk_a.htm  
ftp://keygen:exam@24.92.14.2:21  
ftp://win2k:win2000@63.236.67.49/  
ftp://209.196.62.156/_vti_pvt/.pmbd  
http://computerbooks.address.com  
http://www.qdit.com.cn/software/programm/  
http://www.startvweb.com/Computers_and_Internet  
ftp://216.0.51.50/%7Etmp/.%20for/%7E/ftf/by%20 ... p;%20TutorialZ=-  
http://www.nopayweb.com  
 
 
《oracle9i初学者指南》  
《oracle8i初学者指南》  
 
下面是一些电子书籍的下载地址  
http://www.nci.ac.cn/cgi-bin/view.c ... ;topic=65  
http://www.pcbookcn.com/content.asp?Nclass ... =3    
http://superexam.myetang.com/ocp/index.html  
http://soft.269.net/SoftWareList.asp?ClasstreePos=24  
http://www.oradb.net/book.htm    
http://flying.swpi.edu.cn/books/dnwl ... ndex.html  
http://easycome.myrice.com/bookstore/oracle.htm  
http://www.kl.gz.cn/~hal/1-1-8.htm  
http://www.ebooksou.com/search_leibie.asp?le ... cle    
http://www.eng.stu.edu.cn/ebook/typ ... ?did=0203  
http://www.helpwork.net/  
http://www.powerba.com/develop/database/oracle/index_12.htm

 



 

在项目中,调试是重要的,作为一个程序员,如果你说你不会调试,那你当然就不是一个合格的程序员,所以我想写一篇文章来总结一下asp.net的调试。正好在做完项目时,上msdn看了一下,就发现msdn杂志上的这篇文章。但却是英文的。我英文本来就很差。所以看英文文章很吃力,但为了学习和提高。也就硬着头皮上了。我是边用金山词霸指着,边翻译。历时一个月才把它翻出来。累啊!可能有许可地方译得不对,希望英文好的同志指正。万分感谢!
    原文网址:http://msdn.microsoft.com/msdnmag/is ... tingEdge/default.aspx
由于在论坛中不能贴图片,所以建议你把所有的文字都拷到word中,把原文的图片下载下来,然后插入到相应的位置,然后再阅读本文。原文还可以下载源代码,下下来后,你可以本机试试。我试了一下,mytracer工具没有应用成功,我也不知道是什么地方没有设置对。希望你看了本文以后,如果用成功了。请说说怎么用。另外就是我在最后把mydebugtool用户控件的源码也帖出来了。希望对你有帮助。
    我在看完本文后,感觉还有些收获的。你看完后,请谈谈你的感想,下面请看文章。

MyTracer跟踪监视器和asp.net的跟踪应用
NET Framework自带了许多跟踪和调试程序的工具。我并不想和大家讲整个的调试工具;我只想说明在开发软件周期中, 跟踪器和调试器与一般的应用程序不一样,因为它们是交互式的工具,它们一般都与开发环境整合在一起,例如vs.net。
    Systems.Diagnostics命名空间包括了两个类,一个是trace,另一个是debug,它们的方法都能用于输出代码执行结果的信息。这种特性对跟踪数据错误,监视数据溢出和判定条件,及收集各种数据是非常有用的。trace和debug类功能相似,都是比较特殊的模块,称为接收器。
    跟踪Windows&reg;程序和asp.net程序的工具有很大的区别。具体的说,Windows Forms程序的跟踪机制是基于可以定制接收者的模块并加以扩展,而asp.net程序的跟踪机制是不可扩展的,ASP.NET子系统只提供了一个可定制的tracing模块。当page在跟踪模式下运行时,将附加的输出各种跟踪信息表。这些表将显示性能、请求和状态等有关信息。不管你是否写了输出跟踪消息,那些表总是会附加的输出到浏览器中。
    Vs.net提供了一个功能强大的整合的调试器,它支持cutting-edge特性,如断点,快速监视及各种通过代码分步执行的方法。调试器是捕获错误及使代码错误最小化的理想的工具。因此,如果你用vs.net创建工程的话,就不必再问什么了。但是如果你不用vs.net的呢?如果你用的是不具备调试工具的编辑器的话,在这种情况下,编辑跟踪调试工具对程序健壮性就非常重要了。
    在这种情况下,我们首先要研究整个ASP.NET应用程序的跟踪子系统,然后写一个window应用程序,以执行一个给定的页和收集它运行时的信息,如页的视图状态(viewstate),请求内容(request context),和全局对象(如cashe,Application,Session)。我把这个应用程序叫MyTracer 。MyTracer应用程序能与vs.net整合应用,在测试时也可以作为浏览器。我要指出的是,在某些地方我运用了没有经过验证的类。它不会影响代码,但是那些类在以后可能会有所改变。

    允许asp.net页面调试
    虽然你可以在web.config文件中增加trace配置段以在配置程序级跟踪(也就是在该程序下的所有网页都具有跟踪功能),但是我们没有必要跟踪每一个页面。对于一个非常大的项目来说,你可以在web.config文件中用下面代码设置trace功能:     
<configuration>
  <system.web>
     <trace enabled="true" pageOutput="true" />
  </system.web>
</configuration>
    enabled属性设置是否允许跟踪,pageOutput属性设置是否允许在页面中显示跟踪输出的信息。如果pageOutput属性设置为false时(默认值),跟踪输出自动发送到asp.net的跟踪工具--- trace.axd中。在项目要结束时,你只要把trace配置段的上述两个属性设为 false,就关闭了各个级别的跟踪,页面就不会再输出跟踪信息了。
    如果你只想跟踪某个页面,你可以用页面的@page指令的trace属性来设置跟踪。trace属性默认值为false。当你把它设为true时,系统和自定义的信息都可以通过单击trace按钮显示在页面上,如图1所示:
 
          Figure 1 System and Custom Tracing

要注意的是,跟踪信息会作为当前页面的一部分输出到访问该页的浏览器中。正如你想的一样,某些跟踪信息表显示由页生成的跟踪信息。另外的一些表显示请求的详细信息、控件树及一些有用的集合,如cookeies,headers(标头集合),form values(窗体集合)和server variables(服务器变量)。如果session和application状态不为空,它们的内容也会显示出来。view state的内容和Cache对象不会直接的被跟踪。在mytracer(作者做的windows应用程序,用于调试跟踪)里面会跟踪他们。
@Page指令支持TraceMode属性,让你可以选择怎样排序显示跟踪信息。两个有用的属性是SortByCategory(按类型排序)及SortByTime(按时间排序)。默认情况下,跟踪信息是按产生的顺序显示的。如果你把TraceMode属性设为SortByCategory,跟踪信息将按类别名排序显示。跟踪信息行是按输出跟踪信息的方法进行分类的。

 输出跟踪信息
 asp.net页面用TraceContext类暴露的方法生成其跟踪信息。在执行一个http请求时,asp.net会创建一个TraceContext类的实例。Tracer对象(TraceContext类的实例)通过httpcontenxt 类的trace属性和page类的trace属性暴露出来。
  TraceContext类有一个很简单的接口,它只有两个属性和一些方法。两个属性分别是IsEnabled 和 TraceMode属性。IsEnabled是一个布尔值的只读属性,用于设置是否允许跟踪。它的值受@page指令的trace属性和web.config配置文件的trace配置段的影响。跟踪信息按TraceMod属性的设置的排序方式排序后显示。TraceMode的属性值是一个TraceMode枚举类型,它包括SortByCategory 和SortByTime这两种枚举值。
 要输出跟踪信息,你可以用TraceContext类的write或warn方法。这两种方法都有三次重载。它们非常相似。只有一点不同,warn方法总是以红色的字输出信息。
   write方法有三次重载:  
Public Sub Write(msg As String)
Public Sub Write(cat As String, msg As String)
Public Sub Write(cat As String, msg As String,e As Exception);
    最简单的重载方法只输出指定的文字信息。在第二个重载方法中,第一个字符型参数表示你想用哪种类型名来输出参数二中指定的信息。类型名用于对跟踪信息排序,你可以用能够清楚表示信息的任何文字作为它的类型名。第三个重载方法增加了一个输出异常信息的对象,在这种情况下,跟踪信息由系统的异常信息及你指定的错误信息串接而成。
虽然通过write和warn方法可以在html页中输出跟踪信息,但是不能对文字应用样式表。信息字符串是以纯文本的形式输出的,如果你试图对它应用任何特殊的格式,例如<b>标签,你将在跟踪信息行上看到<b>标签的字符串,而不是加粗显示效果。
如果没有完全限定名称, aspx页面及其后台类文件都不能使用Asp.net的tracer对象,页面中的自定义控件和它的后台类也不能够直接的访问跟踪子系统,其它的类则更不能。但是可以在你的后台代码类中声明一个外在的类来代理完成跟踪页面的任务。那么代理类怎么在asp.net页中执行跟踪呢?在这些类里,页面的跟踪对象是无效的,至少在外部类中想要在当前的http请求中输出跟踪信息必须要用下面的表达式来调用tracer:
System.Web.HttpContext.Current.Trace.Write(cat, msg)
    注意asp.net的跟踪系统不支持把它自己作为跟踪信息的接收者,也不支持诊断跟踪片断注册。另外也没有办法去修改哪些显示跟踪数据的标准表,如果你想修改或增加其它的跟踪信息表,你只有通过其它的方法了。
跟踪查看器(trace viewer)
    asp.net的trace viewer 支持程序级跟踪。一旦程序允许跟踪,每次请求页面时,页面都会发送指定的跟踪信息到该查看器。你可以通过请求应用程序根目录下的trace.axd页面看到跟踪查看器(如图2)。如前所述,你可以在web.config文件的trace配置段中设置允许跟踪。如下代码所示:  
<configuration>
  <system.web>
     <trace enabled="true" />
  </system.web>
</configuration>
    pageOutput属性为false时,只有跟踪查看器才能收到跟踪信息,每个页面都可以用@Page指令的trace属性覆盖程序级的跟踪设置。跟踪查看器只缓存requestLimit(trace配置段中的一个属性)个请求信息。 
 
Figure 2 Viewing the Trace Viewer

    简单的说,asp.net跟踪查看器担任总控制台的角色,它收集了在应用程序中所有的页面的跟踪信息。每一次客户端的请求--不能大于requestLimit设定的值,都会在跟踪器中生成一条记录以供参考,除非跟踪器的缓存被清除(如图2)。
    你可以通过在应用程序的根目录下请求trace.axd页的url来激活跟踪查看器。AXD是特殊的资源类型,它是aspnet_isapi.dll ISAPI应用程序的扩展名,如图3所示。首先,跟踪查看器显示如图2所示的页,然后,跟踪查看器会自动跟踪所有的请求并缓存请求的页的所有信息直到它们被清除为止。当请求达到上限时(requestLimit设定的值),其它的请求将不会被缓存,除非你手工把它清除。你可以单击每个请求的跟踪信息行中的“查看详细信息”链接查看其详细的跟踪信息。
 
Figure 3 Requesting a Trace

    asp.net跟踪的内部机制是完美的,它提供了很好的跟踪页面运行时信息的办法,并允许你声明和校验动态的内部的值。另一方面,它不会显示所有你想跟踪的信息及令人讨厌的基于文本的界面。跟踪信息是依据页面生命周期中最后的一个事件触发后生成的。众所周知,asp.net页面是从HttpRuntime环境下得到一个请求事件后开始工作的。从Page_Init到Page_Unload事件,也包括Page_Load和Page_PreRender事件记录了页面的整个生存周期。当页面的最后一个事件触发时,浏览器的html还没有被预呈现。在页面呈现时,html代码临时缓存在输出流中,等待注册的处理模块作进一步的处理。这时,页面中的代码不能访问附加输出的跟踪html信息。这种封闭的结构,连同我们不喜欢的基于文本的用户界面一起封装起来,也给了我们另一个可选的方案。
    在讨论mytracer之前,我先说明一点,是关于asp.net 的页面跟踪。你应注意把页面的trace属性设为false,那么页面代码中的调试语句(trace.write或trace.warn)就无效了(不会在页面中输出调试信息)。实际上不管是否允许跟踪,write和warn方法都会执行。这也许在执行程序的时候有影响,但是它可以忽略不计,尤其是在没有用代码进行跟踪时,就更不会有什么影响了。要允许或不允许跟踪最简单的办法是在web.config文件中设置trace配置段,也可通过跟踪查看器设置。当接到用户的反馈时(例如不能停止应用程序)要跟踪应用程序,用上述的方法重新激活跟踪,按条件输出跟踪信息。

MyTracer工具
    在asp.net 1.x可以选择另一种方法来写跟踪工具,你可以把它当成一个http模块来写,及用HttpApplication事件和ASP.NET对象模式去收集程序运行时要显示的信息。另外,你还可以使它暴露方法和事件以便与页面相结合。在本期栏目中,我将用一个不同的方法开发一个的windows应用程序与vs.net集成使用。最后,mytracer是一个windows应用程序,它嵌入了WebBrowser ActiveX&reg;组件用于显示web页面,并且运用了web service作为asp.net和windows的桥梁。图4显示了该工具运行一个示例页的情况:
 
Figure 4 MyTracer Tool

    该工具可以运行及显示指定的页。如果page页已配置了MyTracer(下面会讲怎么在页中配置mytracer,使页面可以被mytracer跟踪),那么页面指定的信息将输出,这些信息是用位于同一虚拟目录web Serveice作为跟踪应用程序生成的。 Windows form应用程序处理DocumentComplete事件,并与web service连接下载已准备好的asp.net运行时的信息,然后它简单的组织生成各种信息表,表中输出各种信息,如:session, Application, Cache, view state, input form, server变量,及页面的控件树等。
    Mytracer由三个部分组成----windows forms 应用程序(如图4所示),前述的web service,以及被跟踪页面必须包含的web 用户控件。用户控件(mydebugtool.ascx)与页面的生存周期中的主要事件关联并把跟踪信息存入dataset对象中。当页面卸载时,dataset被存入数据库中,这类似于asp.net中的用SQL Server™管理session状态。Web service从数据库中重新获得该对象(dataset对象),Windows Forms程序再从web service中下载该对象。数据总是以dataset的形式一级级的传递,直到它显示给用户看。数据库和web service都有比较简单的结构。不同的是,dataset作为一个XML DiffGram对象保存下来的,所以它被当成一个字符串处理和移动。图5描述了该工具的整个框架结构:
      
Figure 5
应用 MyTracer
    要应用MyTracer,页面中必须要包含MyDebugTool用户控件。该控件有一个简单的用户界面,它告诉用户该工具已被激活(可以在页面的看见标题文字)。该控件与Page_Unload和 Page_PreRender事件关联,缓存程序和请求指定的信息,如Application, Cache和 Session。数据被存在dataset对象中,dataset对象在该用户控件的构造函数中被实例化,并在page卸载时被长久的保存在SQL Server 数据库中。要在给定一个的asp.net页中应用MyTracer,必须在页面中注册该控件,可以通过@Register注册用户控件:
     <%@Register TagPrefix="msdn" TagName="debug" Src="MyDebugTool.ascx"%>
     注意,TagPrefix 和TagName属性为任何值,但用户控件必须与应用程序在同一个Web domain中。你可以在页面中的任何地方放置该用户控件的实例,甚至在最顶端。注册完后,你可以用下面的代码声明用户控件的一个实例:
<msdn:debug runat="server" ID="mytracer" UserKey="dino" />
    ID属性不是必须的,但是,在后面你可以看到。如果你想要访问视图状态信息的话,你必须要设置ID属性。用户控件的公共属性UserKey用于唯一标识数据库的行(表的主键),它必须是唯一的。除此以外,就没有其它的限制了。当你完成上述的配置时,在浏览器中打开指定的页面。该页面显示一个蓝底黑字的信息条文字,说明页面应用了MyTracer工具。当页面发送到浏览器,数据库就会被更新。可以通过ConnString属性设置数据库的连接字符串。MyTracer默认调用SQL Server一个名为InternalCache的表。该表有两个字段:UserKey和Data,userkey为字符串型;Data为text型,它们被存在diffgram格式的dataset中,dataset对象包含了所有收集到的信息。写入数据库时,假设数据库中已存在指定的userkey。换种说法,只执行update语句而从不执行insert语句。对任何的数据库服务器,只有用下面的查询语句才能被理解和执行:

SELECT data FROM internalcache WHERE userkey=@TheUser

    用户控件在临时的dataset中创建了许多的表,所有的这些表都有两个string类型的列,分别叫:Key和Value。这些表保留了下面集合的内容:Request.Headers, Request.Form, Request.Cookies, Request.ServerVariables,Application, Cache, Session, 加上页面的view state 和controls。图6中所示的代码演示了关于怎样重新获得Cache和页面控件的信息。特别地,cache对象包含user和system两种信息。如tracer所示,在集合中的一些slot通过服务器的http 运行时和指定的内部类收集运行时的信息。例如,如果session 状态配置成存在本地的asp.net进程中(默认的设置),session集合在缓存中作为一个单独的对象保存。在session集合内部叫SessionStateItem,如图7所示:

Figure 6 Retrieving Cache and Page Control Information

// *****************************************************************
// 从缓存中加载信息
private void LoadFromCache(DataSet info)
{
    DataTable dtCache = CreateKeyValueDataTable("Cache");
    foreach(DictionaryEntry elem in Page.Cache)
    {
        if (ShowAll)
        {
            AddKeyValueItemToTable(dtCache, elem.Key.ToString(), 
                DisplayFormat(elem.Value));
        }
        else
        {    
            string s = elem.Key.ToString();
            if (!s.StartsWith("ISAPIWorkerRequest") && 
                !s.StartsWith("System"))
                AddKeyValueItemToTable(dtCache, elem.Key.ToString(), 
                DisplayFormat(elem.Value)); 
        }
    }

    dtCache.AcceptChanges();
    info.Tables.Add(dtCache);
    return;
}
// *****************************************************************

// *****************************************************************
// 从页面的控件集合中加载信息
private void LoadFromPageControls(DataSet info)
{
    HtmlForm theForm = null;
    
    DataTable dtControls = CreateKeyValueDataTable("Controls");
    for (int i=0; i<Page.Controls.Count; i++)
    {
        if (Page.Controls[i] is HtmlForm)
            theForm = (HtmlForm) Page.Controls[i];
        AddKeyValueItemToTable(dtControls, Page.Controls[i].ToString(), 
            Page.Controls[i].ClientID);
    }
    dtControls.AcceptChanges();
    info.Tables.Add(dtControls);

    if (theForm == null)
        return;    
    
    DataTable dtFormControls = CreateKeyValueDataTable("FormControls");
    for (int i=0; i<theForm.Controls.Count; i++)
        AddKeyValueItemToTable(dtFormControls, 
            theForm.Controls[i].ToString(), 
            theForm.Controls[i].ClientID);
    dtFormControls.AcceptChanges();
    info.Tables.Add(dtFormControls);

    return;
}
// *****************************************************************
Figure 7 System Items Stored in Cache
 
在图7中你可以看到所有保存在cache中的系统项都有一个以"System"或者 "ISAPIWorkerRequest.开头名称。如果你想看到这些项---位于cache中的程序的session变量,设置MyDebugTool的showall属性为false。在图7中,在value列中的一些值用括号括起来,它表示该项包含的是一个对象,括号中的字符串表示该对象的类型名或者是该对象的实际值。对象是通过下面的这个函数显示的:
string DisplayFormat(object o)
{
   //如果o是一个string或者是基元类型,则返回它的实际值,否则返回对象的类型名
 return (o is string || o.GetType().IsPrimitive
       ?Convert.ToString(o) 
       :"{ " + o.ToString() + " }");
}
    如果 Type为基元类型之一,则IsPrimitive属性为 true;否则为 false。基元类型是 Boolean、Byte、SByte、Int16、UInt16、Int32、UInt32、Int64、UInt64、Char、Double 和 Single。注意在这个文本中是不考虑基元类型的,它们把它当成是一个引用类型,并把它当作是system.string类的一个实例。用于输出所有存储在cache中的信息的代码,类似于用于输出application, Session, Request collection的代码,都是很简单的。
    要列出页面中所有的控件,你可以通过遍历Page.Controls集合。注意,该集合只包含的页面中的第一层控件,并不是页面中所有的控件。也就是在整个html模式下的<body>元素(标签)下的所有的直接的子控件。实际上,如果你遍历Page.Controls集合,你将会得到一个非常少的控件---主要是literals和asp.net中唯一的一个form控件。在设计时,所有的服务器端控件都包含在一个服务器端的form控件中。要访问所有的这些控件,你必须对form控件的controls集合执行第二次循环。MyTracer工具显示了每个控件的id。要注意的是,在设计时,所有服务器端控件都必须分配一个客户端id。这个id必须是唯一的,它通常都在基于服务器的id属性值分配的。如果该服务器端控件没有id,asp.net运行时会为它生成一个唯一的值。要访问它的客户端id, 就用它的clientid属性。

辅助的web service
    从架构上讲, web service相当有用,因为它搭建了asp.net与windows应用程序或者asp.net应用程序与asp.net应用程序之间的桥梁。在mytracer工具中,web service担任了连接windows 应用程序与asp.net页面跟踪应用程序的作用。它提供了一个命令界面让windows应用程序获得asp.net应用程序中指定的信息。在这里我用web service是因为它非常容易与其它已存在的程序共存。例如,你可能在程序中用了一个基于windows服务的外部程序,它通过remoting操作,从用户控件中获得数据,并在windows form应用程序中处理该数据。同样的,选择用数据库也是个人喜好,你可以用服务全局变量(内存)替代。总的说来,StateServer 和 SqlServe两项,都能被mytrace复制。
    Web service的编辑非常简单(如图8所示)。Getinfo方法执行一个数据库查询并返回一个描述原始的dataset对象的xml diffgram 格式的string。GetInfo方法序列化该string,然后用dataset对象的readxml方法重建一个有效的dataset对象,接着,返回新建的dataset对象,注意,如果userkey在数据库中不存在,那么web service返回的值可能不对,理想情况是你应该过滤userkey字段值,也可以用参数化的查询。

Figure 8 Web Service Programming Interface
<%@ WebService language="C#" class="MyDebugTool" %>

using System;
using System.Web.Services;
using System.Data;
using System.IO;
using System.Data.SqlClient;

[WebService(Namespace="MsdnMag.CuttingEdge")]
public class MyDebugTool 
{
    [WebMethod]
    public DataSet GetInfo(string connString, string userKey)
    {
        SqlDataAdapter adapter = new SqlDataAdapter(
            "SELECT data FROM internalcache WHERE userkey='" + 
            userKey + "'", connString);
        DataTable tmp = new DataTable();
        adapter.Fill(tmp);

        DataSet ds = new DataSet();
        StringReader reader = new 
            StringReader(tmp.Rows[0]["data"].ToString());
        ds.ReadXml(reader);        
        ds.AcceptChanges();
        return ds;
    }
}

基于windows的应用程序
    MyTracer应用程序使用了一个tabstrip控件,以及WebBrowser ActiveX控件以显示页面,这样来触发用户控件。当页面下载完时,该应用程序调用web service,web service的getinfo 方法绑定数据到tabstrip控件的tab页中。下面的代码演示了怎样结合WebBrowser的StatusTextChange及DocumentComplete事件写处理程序。

// Hook up the StatusTextChange event(订阅StatusTextChange事件)
DWebBrowserEvents2_StatusTextChangeEventHandler e1;
e1 = new 
  DWebBrowserEvents2_StatusTextChangeEventHandler(StatusTextChange);
webBrowser.StatusTextChange += e1;

// Hook up the DocumentComplete event
DWebBrowserEvents2_DocumentCompleteEventHandler e2;
e2_= new
  DWebBrowserEvents2_DocumentCompleteEventHandler(DocumentComplete);
webBrowser.DocumentComplete += e2;

    在自定义的事件数据类中,它根据名字匹配处理事件,对事件有用的信息被返回。例如,类名称是DWebBrowserEvents2_DocumentCompleteEvent对应于DocumentComplete事件。
    所有的tab页都有一个通用的结构,都包含一个或多个datagrid控件,它们绑定到由web service返回的dataset中的某个表中。
    Tracer应用程序也返回页面的view state(如图9)。这个特性超越了asp.net默认的tracer(跟踪查看器)的功能。asp.net默认的tracer局限于只显示页面中每个控件的view state。Mytracer 只提供显示存储在页面中的view state元素的信息。它不包括特殊控件的view state,就如我在msdn 杂志2003年第二期中所讲的一样,asp.net的view state通过委托它的主页代管view state集合。页面或控件的view state是一个statebag对象,它通过viewstate属性暴露出来。ViewState属性是受保护的级别(protected),这使得外部的组件不能改变view state的信息。
    那么用户控件是怎样返回主页的view state呢?只有一种可能就是我也没有充分的研究映射(reflection)。从理论上来说,在公共语言运行时中的API映射允许你通过编程访问类内部的(internal)和非公共的(non-public)元素。可是,它也受级别的保护和限制,只能在asp.net应用程序的安全内容范围内进行处理。
 
Figure 9

    MyTracer运用了一个很直接但又不强的方法读页面的view state。用户控件简单的使页面特意的暴露它的view state给tracer工具。用户控件有一个BindViewStatey方法,被跟踪的页面能够在任何时候调用并传回它的view state对象,如下面所示:

void Page_Load(object sender, EventArgs e) {
   •••

   // Bind the viewstate to display 
   mytracer.BindViewState(ViewState);
}
    这样,页面也可以传递任何它拥有的view state对象,例如,一个自定义的控件也可以在开发时输出用于调试的信息。一个简单的技巧,你可以深入的应用控件的view state创建一个包装类专门用于暴露控件的viewstate属性。例如:
public class MyDataGrid : DataGrid
{
    public StateBag ExternalViewState
    {
        get {return ViewState;}
    }


    然后,在页面中用一个新的控件,并绑定暴露view state属性到mytracer应用程序。如果该控件类被密封了(声明为sealed),这个技巧就没有用了,也不能得到任何改进。所以,如果你开发了一个商业的控件不想让用户跟踪控件的状态,密封类也是一个技巧。然而,这样即有优点也有缺点。例如,我不想买第三方控件,也不想从它们哪里继承。注意,我只想简单的说说关于view state的侦察,并不想修改它。修改view state 实际上是不可能的(这就是它的缺点)。就如我在msdn 杂志2003年第二期专栏中所说的哪样。
整合整个应用程序
    当你要开发和测试asp.net页面时, mytracer是非常有用的。这个工具运行页面,捕获运行时的任何信息,然后用于友好的方式显示它们。比你用传统的方法跟踪页面,它可以提供更多的跟踪信息。这个版本的代码不支持输出自定义的信息。也不会捕获你用trace对象输出的信息。可是,增加一个message container就向为用户控件增加一个新的write方法一样容易,message container用于收集的所有传送过来的string对象,然后输出到另外一张表中。
    Mytrace可以作为一个辅助的工具,在你测试asp.net时它能代替浏览器。甚至可以把在集成在vs.net中。在你的vs.net项目配置中,在解决方案资源管理器窗口的项目名中右键,单击“属性”,选择配置“属性”,选择“调试”,选择“启动应用程序”,指定MyTracer应用程序所属的路径,并把页面的url作为该工具的命令行参数(如图10所示).
Figure 10 Setting Up MyTracer in Visual Studio .NET
 
Figure 10

    当你应用mytracer应注意,最好不要让asp.net的调试器工作.如你需要逐步的了解的代码.没有什么比调试工具更好的了.快速监视特性允许你查看应用程序中的任何值.如果你不需要调试, MyTracer当然也比浏览器好用.要重新允许调试,单击选择”启动应用程序”(和图10一样)。
    在本栏的下载源码中,你可以从源码中找到mytracer示例项目,你可以试试。
下面是用户控件的源码:

<%@ Control ClassName="MyDebugTool" Language="C#"  AutoEventWireup="false" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.IO" %>
<script runat="server">
//*****************************************************************
// Run when the host page is unloading and caches info
//当主页卸载时运行,缓存信息。
private void Page_Unload(object sender, EventArgs e)
{
    GrabAndStoreContextInfo();
}          
//*****************************************************************


//*****************************************************************
//Run when the host page is going to render
//当主页呈现时运行
private void Page_PreRender(object sender, EventArgs e)
{
    GrabAndStoreRequestInfo();
}          
//*****************************************************************


//Key for the database
//数据库的主键
public string UserKey = "__MyDebugTool";     

//Connection string for the database
//数据库连接字符串
public string ConnString = "SERVER=localhost;DATABASE=MyTracer;UID=sa;";        

//Should load all possible info
//是否加载所有的可能的信息
public bool ShowAll = true;        

//Bind to the page's view state (necessary because viewstate is protected)
//绑定页面的view state(必须的,因为 viewstate受保护)
private StateBag m_boundViewState = null;
public void BindViewState(StateBag state)
{
    m_boundViewState = state;
}

//*****************************************************************
//Internals
//声明dataset为内部的对象
private DataSet info = new DataSet("MyTracer");
//*****************************************************************


//*****************************************************************
//Collect all the information from the HTTP context
//从http context中收集所有的信息Application 、Session、Cache
private void GrabAndStoreContextInfo()
{
    //Application
    LoadFromApplication(info);

    //Session
    LoadFromSession(info);
    
    //Cache
    LoadFromCache(info);

    //Make the DataSet publicly available
    PublishDataSet(info);
}
//*****************************************************************

//*****************************************************************
//Collect all the request information from the HTTP context
//从http context中收集所有请求的信息
private void GrabAndStoreRequestInfo()
{
    //视图
    LoadFromViewState(info);

    //控件
    LoadFromPageControls(info);

    //Form + QueryString
    LoadFromRequest(info);

    //Request Headers
    LoadFromRequestHeaders(info);

    //Server Variables
    LoadFromServerVariables(info);
    
    //Cookies
    LoadFromCookies(info);
}

//Load information from Cache
//从cache中加载信息
private void LoadFromCache(DataSet info)
{
    DataTable dtCache = CreateKeyValueDataTable("Cache");
    //Page.Cashe对象为DictionaryEntry (字典对象)结构集,该结构只有key和value两个属性
    foreach(DictionaryEntry elem in Page.Cache)
    {
        if (ShowAll)
        {
            AddKeyValueItemToTable(dtCache, elem.Key.ToString(), DisplayFormat(elem.Value));
        }
        else
        {    
            string s = elem.Key.ToString();
            if (!s.StartsWith("ISAPIWorkerRequest") && !s.StartsWith("System"))
                AddKeyValueItemToTable(dtCache, elem.Key.ToString(), DisplayFormat(elem.Value)); 
        }
    }

    dtCache.AcceptChanges();
    info.Tables.Add(dtCache);
    return;
}
//*****************************************************************

//*****************************************************************
//Load information from page's controls
//从页面的控件集合中加载信息(page中有两层控件集,一个htmlform及htmlform下的子控件集)
private void LoadFromPageControls(DataSet info)
{
    HtmlForm theForm = null;
    
    DataTable dtControls = CreateKeyValueDataTable("Controls");
    for (int i=0; i<Page.Controls.Count; i++)
    {
        //如果控件为htmlform控件,输出控件的类型,及其的ClientID
        if (Page.Controls[i] is HtmlForm)
            theForm = (HtmlForm) Page.Controls[i];
        AddKeyValueItemToTable(dtControls, Page.Controls[i].ToString(), 
            Page.Controls[i].ClientID);
    }
    dtControls.AcceptChanges();
    info.Tables.Add(dtControls);
    //如果不存在htmlform控件,返回空值
    if (theForm == null)
        return;    
    //存在htmlform控件,输出htmlform的子控件集
    DataTable dtFormControls = CreateKeyValueDataTable("FormControls");
    for (int i=0; i<theForm.Controls.Count; i++)
        AddKeyValueItemToTable(dtFormControls, theForm.Controls[i].ToString(), 
            theForm.Controls[i].ClientID);
    dtFormControls.AcceptChanges();
    info.Tables.Add(dtFormControls);

    return;
}
//*****************************************************************

//*****************************************************************
//Load information from Application
//从application中加载信息,application对象为HttpApplicationState 类。
private void LoadFromApplication(DataSet info)
{
    DataTable dtApp = CreateKeyValueDataTable("Application");
    for (int i=0; i<Application.Count; i++)
        AddKeyValueItemToTable(dtApp, Application.Keys[i].ToString(), 
            DisplayFormat(Application[i]));

    dtApp.AcceptChanges();
    info.Tables.Add(dtApp);
    return;
}
//*****************************************************************

//*****************************************************************
//Load information from Session
//从Session中加载信息,Session对象为HttpSessionState 类。
private void LoadFromSession(DataSet info)
{
    DataTable dtSession = CreateKeyValueDataTable("Session");
    for (int i=0; i<Session.Count; i++)
        AddKeyValueItemToTable(dtSession, Session.Keys[i].ToString(), 
            DisplayFormat(Session[i]));

    dtSession.AcceptChanges();
    info.Tables.Add(dtSession);
    return;
}
//*****************************************************************

//*****************************************************************
//从视图中加载信息(ViewState)
private void LoadFromViewState(DataSet info)
{
    if (m_boundViewState == null)
        return;
        
    DataTable dtViewState = CreateKeyValueDataTable("ViewState");
    string [] keys = new string[m_boundViewState.Count]; 
    m_boundViewState.Keys.CopyTo(keys, 0);
    StateItem [] values = new StateItem[m_boundViewState.Count];
    m_boundViewState.Values.CopyTo(values, 0);
    
    for (int i=0; i<m_boundViewState.Count; i++)
    {
        AddKeyValueItemToTable(dtViewState, keys[i].ToString(), 
            DisplayFormat(values[i].Value));
    }

    dtViewState.AcceptChanges();
    info.Tables.Add(dtViewState);
    return;
}
//*****************************************************************


//*****************************************************************
//Load information from Request.Headers
//从Request.Headers中加载信息
private void LoadFromRequestHeaders(DataSet info)
{
    DataTable dtHeaders = CreateKeyValueDataTable("Headers");
    for (int i=0; i<Request.Headers.Count; i++)
        AddKeyValueItemToTable(dtHeaders, Request.Headers.Keys[i].ToString(), 
            DisplayFormat(Request.Headers[i]));

    dtHeaders.AcceptChanges();
    info.Tables.Add(dtHeaders);
    return;
}
//*****************************************************************

//*****************************************************************
//Load information from Request.ServerVariables
//Request.ServerVariables中加载信息
private void LoadFromServerVariables(DataSet info)
{
    DataTable dtServerVars = CreateKeyValueDataTable("ServerVariables");
    for (int i=0; i<Request.ServerVariables.Count; i++)
        AddKeyValueItemToTable(dtServerVars, Request.ServerVariables.Keys[i].ToString(), 
            Request.ServerVariables[i]);

    dtServerVars.AcceptChanges();
    info.Tables.Add(dtServerVars);
    return;
}
//*****************************************************************

//*****************************************************************
//Load information from Request.Cookies
// Request.Cookies中加载信息
private void LoadFromCookies(DataSet info)
{
    DataTable dtCookies = CreateKeyValueDataTable("Cookies");
    for (int i=0; i<Request.Cookies.Count; i++)
        AddKeyValueItemToTable(dtCookies, Request.Cookies.Keys[i].ToString(), 
            Request.Cookies[i].Value);

    dtCookies.AcceptChanges();
    info.Tables.Add(dtCookies);
    return;
}
//*****************************************************************

//*****************************************************************
//Load information from Request.Form
//从Request.Form中加载信息  
private void LoadFromRequest(DataSet info)
{
    DataTable dtForm = CreateKeyValueDataTable("Form");
    for (int i=0; i<Request.Form.Count; i++)
    {
        string key = Request.Form.Keys[i].ToString();
        if (key == "__VIEWSTATE")
            AddKeyValueItemToTable(dtForm, key, "{ view state }"); 
        else
            AddKeyValueItemToTable(dtForm, key, Request.Form[i]); 
    }

    dtForm.AcceptChanges();
    info.Tables.Add(dtForm);
    return;
}
//*****************************************************************


//*****************************************************************
//Create the table with the expected Key/Value structure
//创建表函数
private DataTable CreateKeyValueDataTable(string tableName)
{
    DataTable dt = new DataTable(tableName);
    dt.Columns.Add("Key", typeof(string));
    dt.Columns.Add("Value", typeof(string));
    return dt;
}
//*****************************************************************

//*****************************************************************
//Add a row to a key/value table
//增加数据到表的函数
private void AddKeyValueItemToTable(DataTable dt, string key, string value)
{
    DataRow row = dt.NewRow();
    row["Key"] = key;
    row["Value"] = value;
    dt.Rows.Add(row);
}
//*****************************************************************

//*****************************************************************
//Serialize the overall DataSet to the database
//把dataset对象中的数据保持到数据库
private void PublishDataSet(DataSet info)
{
    //Serialize to a diffgram
    StringWriter writer = new StringWriter();
    info.WriteXml(writer); 

    //Assume a record with the specified UserKey is already in the db
    SqlConnection conn = new SqlConnection(ConnString);
    SqlCommand cmd = new SqlCommand("UPDATE InternalCache SET Data=@TheData WHERE UserKey=@TheUser", conn);
    cmd.Parameters.Add("@TheData", SqlDbType.Text).Value = writer.ToString();
    cmd.Parameters.Add("@TheUser", SqlDbType.VarChar).Value = UserKey;
    conn.Open();
    cmd.ExecuteNonQuery();
    conn.Close();
}
//*****************************************************************


//*****************************************************************
//Format strings and object references
//格式化显示的字符串
private string DisplayFormat(object o)
{
    return (o is string || o.GetType().IsPrimitive
        ?Convert.ToString(o) :"{ " + o.ToString() + " }");
}
//*****************************************************************
</script>
<table style="font-family:verdana;font-size:8pt;border:solid 1px;" width="100%" bgcolor="cyan">
    <tr>
        <td>
            This page is traced using "<b>MyTracer</b>", courtesy of Cutting Edge
        </td>
    </tr>
</table>

 

前言:本文从msdn 2002年第一期的cutting-edge栏目中翻译而来,它讲述的asp.net中的模板(template)的应用。在项目上非常有用,我在项目中应用datagrid、datalist控件时,从这篇文章中学到了许多的东西,应该说它包括了大部分的datagrid或datalist控件的实际应用。同上篇总结一样,建议你把所有的文字都拷到word中,把原文的图片下载下来,然后插入到相应的位置,然后再阅读本文。原文还可以下载源代码,下下来后,你可以本机试试。希望英文好的同志指正。万分感谢!

原文见: 
http://msdn.microsoft.com/msdnmag/ ... cutting/default.aspx
    在asp.net中我们可以通过设置属性或css样式来定制Web服务器控件。有一些控件允许你通过asp.net模板来定制它们。一个asp.net模板是由html元素和asp.net控件组成的混合体,它把这些元素包装成一个特殊的控件。模板具有不同的样式。样式主要是指css样式表和影响图形的属性,例如颜色、字体、边框样式、单元格边距及其它。控件在构造了时是不变的,但结合样式,控件在显示时可以修改。
    相对来说,模板可以任意的组合其它控件及改变它包含的控件的用户界面。例如,Repeater控件允许你组合html元素和asp.net web控件,并把控件绑定到数据源,以行形式的呈现。同样的datagrid控件让你运用任何有效的asp.net控件格式化所有的列中的单元格。除此以外,datagrid控件还支持通过应用不同的模板来自定义普通行,分隔行,选中行等等。
asp.net整个模板的后台机制完全不同于windowxp或windows的后台模板机制,最重要的一点是,理解包含哪些用户界面的元素可以给用户定制。win32平台有客户端和非客户端的普通控件,Windows xp有独特样式的控件,ASP.NET 又有它独特的模板控件。在win32平台,程序员通过消息调用相关绘画进程,以呈现某个控件。在window xp平台下,控制台调用某块代码注册重绘事件,然后触发该事件绘制某个控件。在asp.net中,控件类都继承ITemplate接口, ITemplate接口能动态的创建pagelets和插入html 代码到控件所属的页面中。
     样式表和模板不是互斥的。你可以同时运用样式表和模板,或者分别使用它们,以控制呈现定义在你的模板里面的元素。当某个被模板化的控件(例如说datagrid)被处理时,模板就会在包含它的页中被实例化然后呈现为html。
在这个专栏,我将襄括asp.net 的三个模板化控件:Repeater、DataLis和DataGrid来讲解模板的应用。它们都支持扩展模板,可以让你根据你自己的数据自定义其呈现方式。此外,我将举例讲解怎样编程动态地加载模板,及怎样应用模板创建一个可以多选的datagrid控件。在所有的例子中,我将要用到sql server 2000的Northwind数据库中的Employees表。
什么是模板,在什么地方呢
通俗的讲,模板是用来说明在运行时怎样把某个元素呈现出来。在asp.net 中,模板是服务器控件的一个属性,它包含了静态的html代码、控件以及用于在某个特定区域呈现控件的脚本。例如,一个Repeater控件有一个HeaderTemplate属性,它定义了repeaper控件的头部内容。通常你会运用asp.net的语法定义一个模板。例如下面的代码说明了怎样指定模板以画出Repeater的头和每一行的内容。 
<asp:repeater runat="server">
  <HeaderTemplate>
    <h2>Employees</h2>
    <table border="0">
  </HeaderTemplate>

  <ItemTemplate>
    <tr>...</tr>
  </ItemTemplate>
</asp:repeater>
当它开始呈现Repeater控件的内容时,ASP.NET用在模板中定义的内容和进程一起绑定数据,创建描述数据的html代码。所有的服务器控件都包含一个可以用html描述并呈现它们自己的模板。
.NET Framework框架运用ITemplate接口在运行时处理模板中的每层控件,这些控件能够绑定到数据然后呈现为该模板的一部分。如果你仅运用inline tags在aspx页面中声明了一个公共样式的模板,你不必了解ITemplate 接口。只有当你需要更进一步的了解和研究怎样创建模板和动态的用程序声明模板时, 充分的理解ITemplate 接口就很重要了。但是在我们了解它之前,让我们一起来看一下模板在datagrid控件中的应用。

     DataGrid的模板列
基于模板的列在datagrid控件中扮演了一个很重要的角色,它们允许你增加任意一种类型的列到datagrid,datagrid通过纯文本的形式或者某种预定义的列类型显示内容。然而,有时预定义的列类型并不能实现我们想要的东西。模板列有四种不同的模板,如图1
Figure 1 DataGrid Column Templates 
Name    Description
ItemTemplate    Contains the template for the items in a DataGrid's column. 
<ItemTemplate>
  <asp:label runat="server" text= '<%# ... %>'...>
</ItemTemplate>
You can use any combination of HTML text and ASP.NET controls to populate the column.
EditItemTemplate    Controls the contents of the item selected for editing in the column of the DataGrid control. Place the controls you need for editing the cell between the opening and closing EditItemTemplate tags. 
<EditItemTemplate>
  <asp:textbox runat="server" text= '<%# ... %>'...>
</EditItemTemplate>
HeaderTemplate    Contains the template for the heading section. 
<HeaderTemplate>
  <asp:label runat="server" text= "Header"...>
</HeaderTemplate>
If you omit this template, the column header is rendered with a label or a hyperlink if sorting is enabled. By specifying a custom template, you make yourself responsible to provide the user interface needed to enable sorting on the column.
FooterTemplate    Contains the template for the footer section of the column. The default value is a null reference. 
<FooterTemplate>
  <asp:label runat="server" text= "..."...>
</FooterTemplate>
The footer is displayed only if the ShowFooter property of the DataGrid is set to True.

你可能会频繁的使用模板列(ItemTemplate)。它定义了怎么样显示列中的单元格及由哪些元素组成控件的内容。HeaderTemplate 和 FooterTemplate我们就不说了,相信大家都知道是干什么用的。当列的所属行进入编辑模式时,EditItemTemplate属性指明单元格应该怎样变化。但要注意的是,它和datalist控件不一样,datagrid没有选中模板。
当你需要用不规范的方式显示整个列的时候,你的DataGrid应该用基于模板的列。如果你需要显示数据的数据无法用datagrid 提供的普通的模板显示时,这时,模板就是你的最佳选择了。
下面的代码演示了怎样绑定数据到datagrid控件的模板列。  
   <asp:TemplateColumn runat="server" 
   HeaderText="heading">        
     <itemtemplate>
       ...ASP.NET layout goes here...
     </itemtemplate>
   </asp:TemplateColumn>
注意模板列和其它类型的列一样,也有一个可用于排序的标题文字。模板列没有直接用于绑定数据源字段的属性。也就是在TemplateColumn类的成员里面,你找不到任何DataField或DataTextField属性。
    缺少直接绑定数据源字段的属性是为了更灵活的处理列的显示布局。要呈现一个列,你可以用label控件的text属性,当然也可以用dropdownlist控件或者image控件,这两个控件都没有类似text的属性。结果一样,你都必须用一个数据绑定表达式来绑定数据。在我看来,虽然要写冗长的数据绑定表达式,但它却给了你极大的灵活性。
     下面的代码片断演示怎样正确绑定内容到item template:
   <asp:label runat="server" 
     Text='<%# DataBinder.Eval(Container.DataItem, 
          "lastname") %>' 
   />        
通过用DataBinder.Eval方法,你能够访问当前数据源的任何一个字段。除此以外,你还可以结合任何的排序表达式来对字段进行排序。这是用其它简单的模板列无法实现的。

 
Figure 2 Column

在图2中,你能看到一个基于模板列实现的例子,它演示了怎样在一列中显示数据源中两个字段。在不是基于模板的列中只能获得(显示)一个字段。这个模板的代码如下所示:
<itemtemplate>
<%#
  "<b>" + 
  DataBinder.Eval(Container.DataItem, "lastname") +
  "</b>, " + 
  DataBinder.Eval(Container.DataItem, "firstname") 
%>
</itemtemplate>
    如果你需要结合更多的字段在同一个列中显示,模板列是唯一的方法。如果你需要对某个单元格应用一种特殊的格式,你最好就用datagrid的ItemCreated 事件或者ItemDataBound事件。例如,如果你要改变某个单元格的背景色,或者你要根据某种条件来对单元格应用其它的样式表,你可以在ItemCreated的处理事件中,确定正在创建的项(item)是你需要处理的类型(item 或者AlternatingItem类型),然后应用新的或已存在的样式表。在ItemCreated事件触发时,仍不能保证那一项(item)已经绑定了数据。通过ItemCreated事件的参数e的dataitem属性可以得到触发该事件的datagriditem对象。对于datagrid的ItemCreated事件,它的事件参数是DataGridItemEventArgs类型:
void ItemCreated(Object sender, DataGridItemEventArgs e)
  e.Item.DataItem的值在项被创建时根据数据源的不同赋予不同值。如果datagrid绑定的是一个datatable,那么DataItem就是一个DataRow对象。正如我刚才所说,虽然大多数情况下数据绑定(data binding)事件在ItemCreated触发时还没有触发。但实际上,数据绑定事件(data binding)通常发生在ItemDataBound事件触发时。这个规则只有一种情况下例外,就是在基于模板的列中。一个模板列不止能绑定数据源中的某个字段;它还能访问整个的数据源。这使我们可以在ItemCreated事件中做一些处理工作,而不必等ItemDataBound事件触发,因为itemdatabound事件在ItemCreated事件之后触发。对于bound、button以及 hyperlink列,你可能需要ItemDataBound事件来获得datagrid将要显示的数据绑定的文字。
模板列的头
既然TemplateColumn类给了HeaderTemplate(FooterTemplate)属性,你就可以定制给定列的头(header)和尾(footer)。说到定制,它在这里是非常重要的一点。因为在普通的数据绑定列中没有header模板和footer模板。HeaderTemplat只能应用在TemplateColumn 类的一个实例中。对于这个实例,如果你想用一个非标准的方法编辑该列的内容(例如说你想添加对内容的验证),你可以用headertemplate,也可以用一个简单的BoundColumn类来呈现。
真的可以改变列的header布局吗?如果你需要根据某个表达式对列进行排序,排序机制是datagrid自动在列的头部插入一个hyperlink控件,当用户单击hyperlink控件时,asp.net会根据hyperlink控件的href属性触发一个回发事件,从而对该列进行排序操作。就算你不需要排序你也可以很自由的更改列的header。如果你需要用ItemCreated事件在header中增加其它的控件及脱离datagrid控件做其它的任何事情。下面请看一个例子:
Figure 3 Adding the Sort Dropdown 
public void ItemCreated(Object sender, DataGridItemEventArgs e)
{
    ListItemType lit = e.Item.ItemType;
    if (lit == ListItemType.Header)
    {
        // Create and fill a dropdown list control
        DropDownList dd = new DropDownList();
        dd.ID = "ddSort";
        ListItem li1, li2, li3;

        // ListItem constructor takes Text and Value for the item
        li1 = new ListItem("Title of courtesy", "titleofcourtesy");
        dd.Items.Add(li1);

        li2 = new ListItem("Last Name", "lastname");
        dd.Items.Add(li2);

        li3 = new ListItem("First Name", "firstname");
        dd.Items.Add(li3);

        // Selects the item, if any, that was selected last time
        dd.SelectedIndex = Convert.ToInt32(grid.Attributes["FieldIndex"]);

        // Add the dropdown list to the header of the 2nd column
        TableCell cell = (TableCell) e.Item.Controls[1];
        cell.Controls.Add(dd);
    }
}

public void SortCommand(Object sender, DataGridSortCommandEventArgs e)
{
    // Code that retrieves the grid's data source GOES HERE
    •••

    // Sort by the specified expression or figure it out 
    if (e.SortExpression != "*")
        dv.Sort = e.SortExpression;
    else
    {
        // Retrieves the dropdown list control through its ID 
        DataGridItem dgi = (DataGridItem) e.CommandSource;
        DropDownList dd = (DropDownList) dgi.FindControl("ddSort");

        // Retrieves the sorting expression from the list
        dv.Sort = dd.SelectedItem.Value; 

        // Persists the currently selected dropdown item
        grid.Attributes["FieldIndex"] = dd.SelectedIndex.ToString();
    }
    
    // Refreshes the grid
    grid.DataBind();
}

如图3所示,我们在模板列的header中动态的添加了一个dropdownlist控件,让用户可以选择排序的表达式。当你用模板列组织显示多个数据源字段时定制header就非常有用了。在这种情况下,你可能想允许客户可以自己选择按哪个字段排序,如图4所示。
  
 
Figure 4 Sort by Field
    图3所示的代码动态的增加一个dropdownlist控件用于选择排序的表达式。当用户单击头部的hyperlink控件时,从dropdownlist控件中获得当前用户选择的排序表达式。模板列中的头部文字(headertext属性)必须设置为一个非空的字符,以使排序工作能正常的进行。
<asp:TemplateColumn runat="server" 
    HeaderText="Sort by" 
    SortExpression="*">
     注意上面的SortExpression属性的值设为“*”,它使排序处理事件(如图3)知道客户要求对这一列进行排序。实际上SortExpression属性只是你单击的列中一个元素。

 
Figure 5 Grouping Columns
    在图5中,你可以看到另外一种特殊类型的列头,它合并了两列或更列列的列头。当你想用模板列显示组合的几列数据的时候,这个技巧是非常有用的。特别是,这个例子利用一个技巧来使名和姓按风俗习惯排列(外国人是名在前,姓在后)。表格包含两个明显不同的列:一个用于显示称呼BoundColumn列,一个用于显示雇员姓名的列。两个列有它们各自的单元格,但是在ItemCreated事件中,我们删除了其中的一个列的标题单元格,另一个单元通过其columnspan属性合并了被删除的哪个单元格。
// 1 表示表中的第二列
cell = (TableCell) e.Item.Cells[1];            
e.Item.Cells.Remove(cell);
cell = (TableCell) e.Item.Cells[1]; 
cell.ColumnSpan = 2;
改变用户界面的数据
    基于模板的列可以帮助你模仿控件的用户界面来显示实际数据。如果所有datagrid控件中的数据都以纯文本形式呈现,可能不便于用户阅读和理解。比较明显的是布尔型数据、图像和数组。根据它们在程序中要表达的意思,布尔型数据最好的显示成一对相反(是/不是)的文字或者用一个小图片来表示选中(是)或未选中(不是)状态。用图片来替代checkbox控件是很有效的,因为图片是不能被单击,它也永远不会获得焦点,其应用显示界面如图6所示:
  
 
Figure 6 Column with Checkboxes
    <asp:image>标签的imageurl属性可以用数据绑定表达式来绑定数据源。
   <itemtemplate>
     <asp:image runat="server" 
       imageurl='<%# GetProperFile( (int) 
       DataBinder.Eval(Container.DataItem, 
       "paid")) %>' 
     />
   </itemtemplate> 
 在上述代码里,数据绑定表达式由用户自己定义函数组成,它把布尔型字段值作为函数的参数。函数返回相应的GIF文件路径用于HTML <img>标签imageurl属性中。
但是有时候我们也没必要用两个互斥文字或图像来显示布尔型数据。例如,某列要表示是否付款,就可以用一个检查标志来表示,其中钩表示已付,空白表示没付。在这种简单的情况下, 最好的解决方案是在ItemDataBound事件中重新设置单元格的显示值.代码如下所示:
Figure 7 Intercepting Cell Creation Event 
/*ItemDataBound 事件在一个item被数据绑定到期datagrid控件时触发。Datalist和repeater控件也有的ItemDataBound事件。*/
public void ItemDataBound(Object sender, DataGridItemEventArgs e)
{
    // 获得源数据行(强制转换成DataRowView对象),确定它不为null object
    DataRowView drv = (DataRowView) e.Item.DataItem;
    if (drv == null)
        return;

  // 访问我们要的列数据,如果得到的值>=0,则用webdind字体的a作为单元格的显示值。 
    if (((int) drv["boss"]) >0)
    {
        e.Item.Cells[3].Font.Name = "Webdings";
        e.Item.Cells[3].Text = "a";  
    }
    else
        e.Item.Cells[3].Text = "";
}

,显示结果如图8 所示:
 
Figure 8 Column with Checks 
    一般情况下,你可以用不是基于模板的列来显示数据,但这并不意味着所有的都这样做。模板列是强大和有效的,但是它们总和一些事件挂钩,如ItemDataBound 和 ItemCreated。在执行期间,这两种方法的不同之处在于对大多数的应用程序不是非常有意义。它们也有缺点就是都必须与事件挂钩。
图7的演示代码有一点问题,就是如果要在给定的列中显示图片而不是一个字符呢?你要怎么实现呢?图片对一行来说是不是无效的呢?你也许会想到显示一些交替的文字或简单级别的空格。要在一列中插入图片有两种方法:你可以写一个新的列对象(column object)或者用模板列(在模板列中用<asp:image>控件来显示图片)。用模板列的方法更好,因为所有的详细内容将在视图中隐藏,放在后台代码中。也可以用一个新类型的列来处理在图片不存在或不能显示时替换显示的内容。同样的,一个image列也能实现直接从数据库的blob字段中读出表示图像的二进制流,而不是通过读出图像路径来显示图片。我将在以后的专题的详细讲解这个技巧。在本文中,你将看到怎样用模板列在同一个列中显示图片和文字。你可以从中学到一个技巧就是以运行时的值为条件,动态的从两个或两个以上列布局中选择其中的一个来显示列数据。
解决方案是在同一个模板列中定义两个布局,但在同一时刻只显示其中的一个。显示的布局可以在ItemCreated处理事件中选择或通过一个简单的数据绑定表达式来选择。图9显示了以datagrid控件为例的模板列的代码,模板列中有一个<img>和一个<asp:label>元素。它们都明显的设置了它们的visible属性。Label控件用于显示文字。通常你应该避免用label控件显示静态的文字。当你的文字无须动态的改变时,最好用客户端控件如<span>控件,它们不会在服务器端工作(它们没有runat=server属性).在当前情况下,因为visible属性要在服务器端才能设置,而且它所显示文字也不是静态的,所以必须用 <asp:label>控件。
下面的代码是用于控制在列中显示哪个布局的函数:
bool IsImageAvailable(String strLastName)
{
  String strImageFile = "images\\";
  strImageFile = strLastName + ".bmp";
  return File.Exists(
      Server.MapPath(strImageFile));
}
Figure 9 Two Templates 
<asp:DataGrid id="grid" runat="server"  
    AutoGenerateColumns="false"
    CssClass="Shadow" BackColor="white"
    CellPadding="2" CellSpacing="0" 
    BorderStyle="solid" BorderColor="black" BorderWidth="1"
    font-size="x-small" font-names="verdana"
    AllowPaging="true"
    PageSize="4"
    DataKeyField="employeeid"
    OnPageIndexChanged="PageIndexChanged">

<AlternatingItemStyle BackColor="palegoldenrod" />
<ItemStyle BackColor="beige" VerticalAlign="top" />
<EditItemStyle BackColor="yellow" Font-Bold="true" />
<PagerStyle Mode="NumericPages" />
<HeaderStyle ForeColor="white" BackColor="brown" HorizontalAlign="center" 
    Font-Bold="true" />

    <columns>

   <asp:BoundColumn runat="server" DataField="employeeid" 
                    HeaderText="ID">    
    <itemstyle backcolor="lightblue" font-bold="true" 
               HorizontalAlign="right" />
   </asp:BoundColumn>

   <asp:TemplateColumn runat="server" HeaderText="Employee Name">        
    <itemtemplate>
        <asp:label runat="server" 
            Text='<%# DataBinder.Eval(Container.DataItem, 
            "TitleOfCourtesy") + "<b> " + 
            DataBinder.Eval(Container.DataItem, "LastName") + 
            "</b>" + ", " + 
            DataBinder.Eval(Container.DataItem, "FirstName") %>' />            
    </itemtemplate>
   </asp:TemplateColumn>

       <asp:TemplateColumn runat="server" HeaderText="Photo">
    <itemtemplate>
        <img runat="server" width="50"  
           visible='<%# IsImageAvailable(DataBinder.Eval
           (Container.DataItem, "lastname")
           ToString()) %>'    
           src='<%# "images\\" + DataBinder.Eval(Container.DataItem, 
           "lastname") + ".bmp" %>' />
        <asp:label runat="server" text="<i><small>No picture available.
                   </small></i>" 
           visible='<%# !IsImageAvailable(DataBinder.Eval
           (Container.DataItem, 
           "lastname").ToString()) %>' />
    </itemtemplate>
       </asp:TemplateColumn>

</columns>
</asp:DataGrid>
 
    函数假设要显示的图片为bmp类型,它位于应用程序根目录的images子目录下,而且图片文件的名称为雇员的名字。当然,这是武断的。在你自己的应用程序里,你也许会用其他更聪明的办法来解决。图10显示了当某些图片不存在的时候,页面将显示: 
 
Figure 10 Missing Image
编程加载模板
    通常模板列在设计时就会定义它们的显示布局。但有时候在设计时就定义好布局并不能解决我们的实际问题。如果你预先知道必须在运行时通过ItemCreated 和ItemDataBound事件改变许多的东西,那就没有理由定义一个静态的模板,强制控件改变两次,先处理模板,然后又改变模板。动态加载模板的另一个常用的应用是对同样的数据用不同的视图(布局)显示。
    你至少可以有两种方法动态的加载模板。第一种方法是把模板代码存成一个子user control 文件(以ascx为扩展名),然后通过page对象的loadtemplate方法加载该模板。第二种方法要求你量身订做一个实现了ITemplate接口的类。让我们详细的看看这两种实现方法。
    要动态创建datagrid的列,首先要声明一个类的实例,然后设置其的属性,最后把该实例对象添加到datagrid的列集合中。下面的代码演示了这个过程:
TemplateColumn bc = new TemplateColumn();
bc.HeaderText = "Template Column";
bc.ItemTemplate = Page.LoadTemplate(TEMPLATEFILE);
grid.Columns.Add(bc);
    首先,你创建一个TemplateColumn类的实例,然后设置它的header text及其它的应用程序要求的属性。比较复杂的是如何指定template的ItemTemplate模板及任何你需要的其它的模板属性。简单的方法是通过一个扩展的ASCX文件设置模板,ascx文件是用户控件文件,它声明了在template中的html 代码(文字)和asp.net控件。下面的代码是ascx文件的典型结构:
<%@ Language="C#" %>
<%# layout goes here %>
    在ascx文件的布局中显式的声明language是非常重要的。不管你的ascx文件中是否有代码你都要声明language。你可以声明为任何.net支持的language(可以编译成MSIL的语言)即使这种语言与其所在页的语言不一样。
    Page.LoadTemplate方法能用于加载任何类型列的template布局代码,包括EditItemTemplate 和 HeaderTemplate。当你调用LoadTemplate方法的时候,你不需要用完整的web路径,因为该方法需要一个虚拟的路径。你不能指定一个带有盘符和目录的绝对路径。

    从字符串加载模板
    与其它加载模板的方法不同,page.loadtemplate方法不支持流(stream)和写(writer)。如果它支持stream和writer,那么就可以用内存中的字符串来动态的创建模板。如果你不想或不能接受你的应用程序依赖外部的ascx文件,那要怎样动态的创建模板呢?典型的情况就是你不想用死板的ascx文件,而要把布局代码作为应用程序的一个参数存在数据库或xml文件中,然后动态的从数据库或xml文件中取出并显示。
    是否有办法从字符串中动态的创建模板呢?我察看了有关的类的编程接口,没能找到答案。不过,没有什么可以阻止你创建一个临时文件,并从它哪里加载模板。当你在asp.net应用程序中创建临时文件时,要确保该文件的文件名对每一个并发的会话都是唯一的。为此,使用session id 或者调用静态方法Path.gottempfilename的返回值为文件名来创建临时文件。要注意的是LoadTemplate方法假设它得到的是一个虚拟路径。而stream和writer类要求的是绝对路径,它们不知道如何处理虚拟路径。因此,用下面的代码创建并加载基于字符串的模板。
  
TemplateColumn bc = new TemplateColumn(); 
String tmp = Session.SessionID + ".ascx";
StreamWriter sw;
sw = new StreamWriter(Server.MapPath(tmp));
sw.Write(strLayoutCode);
sw.Close();
bc.ItemTemplate = Page.LoadTemplate(tmp);
grid.Columns.Add(bc);
File.Delete(Server.MapPath(tmp));
你需要使用server.mappath将虚拟中径映射到物理路径。只有在处理stream和文件时才需要这样做。
实现ITemplate
    如果你想完全地在内存中创建一个的模板,你不得不首先编码,然后实例化一个类,该类实现ITemplate接口. ITemplate接口只有一个方法,名为InstantiateIn. asp.net中所有具有template属性的服务器控件都通过这个属性暴露实现了ITemplate接口的类.这个接口简单地定义了用于组合与模板一致的子控件的实例的方法来填充容器控件.图11演示了实现itemplate接口大部分的代码,它为一个datagrid在内存中创建了一个的模板列.同样,也可以把这些代码用于datalist和repeater控件中。唯一不同的是在OnDataBinding事件处理上。类的结构你可以写成类似下面:
class LastFirstNameTemplate : ITemplate
{
  public void InstantiateIn(Control container) 
  {...}

  private void BindLastName(Object s, EventArgs e)
  {...}

  private void BindFirstName(Object s, EventArgs e)
  {...}
}

Figure 11 In-memory Templated Column 
// 动态的创建模板列函数
public void AddTemplateColumnFromITemplate(String strHeader)
{
    TemplateColumn bc = new TemplateColumn();
    bc.HeaderText = strHeader;
    bc.ItemTemplate = new LastFirstNameTemplate();
    grid.Columns.Add(bc);
}

// 这个类实现了一个自定义模板。新控件与容器本身的控件组合创建一个新的模板类,如// 果控件要绑定数据,你可以注册你自己的绑定处理事件(OnDataBinding事件)
public class LastFirstNameTemplate : ITemplate
{

    // Instantiate the elements of the template in the given
    // container. In this case, a DataGridItem element.

    public void InstantiateIn(Control container) 
    {
        container.Controls.Add(new LiteralControl("<b>"));

        Label lblLastName = new Label();
        lblLastName.DataBinding += new EventHandler(this.BindLastName);
        container.Controls.Add(lblLastName);

        container.Controls.Add(new LiteralControl("</b>, "));

        Label lblFirstName = new Label();
        lblFirstName.DataBinding += new EventHandler(this.BindFirstName);
        container.Controls.Add(lblFirstName);
    }

    // Handler of the OnDataBinding event for the Label element
    // that renders the lastname column in the template.

    private void BindLastName(Object sender, EventArgs e)
    {
            Label l = (Label) sender;
            DataGridItem container = (DataGridItem) l.NamingContainer;
            l.Text = ((DataRowView)container.DataItem)
                ["lastname"].ToString();
    }

    // Handler of the OnDataBinding event for the Label element
    // that renders the firstname column in the template.

    private void BindFirstName(Object sender, EventArgs e)
    {
            Label l = (Label) sender;
            DataGridItem container = (DataGridItem) l.NamingContainer;
            l.Text = ((DataRowView)container.DataItem)
                ["firstname"].ToString();
    }
}
这个类可以在asp.net页的<script>部分中定义,也可以在一个单独的类文件中定义.另一种的好的方法是把这定义类的代码放在asp.net页的后台代码源文件中. 在InstantiateIn方法中,简单地创建控件的实例并把它们添加到容器控件中。对于datagrid控件来说,容器控件是一个datagriditem对象。datalist 容器控件是DataListItem对象。容器可以是任何一个实现了INamingContainer接口的类.
Label lblName = new Label();
lblName.DataBinding += new EventHandler(this.BindName);
container.Controls.Add(lblName);
  如果添加到的容器的控件集合中的控件要绑定数据源字段,那么还要为注册您自己的databinding事件处理程序。当databinding事件触发时,从数据源检索数据生成文本并刷新控件的用户界面.如果是自定义服务器控件,databinding事件处理程序需要解析服务器控件以及它所有的子控件中的所有数据绑定表达式。
void BindName(Object sender, EventArgs e)
{
  Label l = (Label) sender;
  DataGridItem container;
  container = (DataGridItem) l.NamingContainer;

  DataRowView drv;
  drv = ((DataRowView) container.DataItem);
  l.Text = drv["lastname"].ToString();
}
databinding事件处理程序完成两项任务。如果编写正确,它应该首先提取并且保持底层数据项;其次,刷新绑定控件的用户界面以反映出数据绑定。
    通过sender参数可以获得相关对象的引用。包含该控件的容器可以由控件本身的namingcontainer属性返回.这时,你就有了设置和使用另一个asp.net表达式Container.DataItem所需要的所有内容。数据项的类型取决于与datagrid关联的数据源。在大多数的实际应用中,它会是DataRowview。剩下的就是访问行中的特定的列并设置控件的绑定属性。

可以多选的datagrid
    在讨论完asp.net的模板时,让我举一个实际的例子详细的说明,这个例子是动态的创建一个模板列,用于多选.
<%@ Register TagPrefix="expo" 
Namespace="BWSLib" 
Assembly="MultiGrid" %>
•••
<expo:MultiGrid id="grid" runat="server" 
    AutoGenerateColumns="false"
    AllowMultiSelect="true"
    AllowMultiSelectFooter="true"
    font-size="x-small" font-names="verdana"
    BorderStyle="solid" BorderWidth="1"
    GridLines="both">
它是一个新的控件,继承自datagrid控件。此外,它也能被实例化及像datagrid一样的配置. 这个新的控件添加了一个新的列,放了一个checkbox控件以允许选择。另外, footer被修改成用一个链接按钮用于取消选定的行。
    图12列举了MultiGrid控件的新方法和属性定义。图13演示了应用该控件的情况。
Figure 12 Methods and Properties 
Property/Method    Description
AllowMultiSelect    Boolean property that enables or disables the multi-selection feature. Defaults to False
AllowMultiSelectFooter    Boolean property that enables or disables the tailor-made footer for the grid. Defaults to False. If enabled, it overrides the default footer. If disabled but the grid has a footer anyway, then the contents of this special footer are displayed as the footer of the checkbox column
SelectedItems    Read-only property that returns an array with the DataGridItem objects currently selected
ClearSelection    Method that clears all the currently selected items

Figure 13 The Control in Action
 
 
最后,在图14中,我写了该控件的一些关键的代码。这些是结合ITemplate类来提供ItemTemplate和FooterTemplate属性的.

这是支持多选的datagrid控件的源代码,只包含关键的源代码,所有的源码请到msdn下载:

namespace BWSLib
{
    public class MultiGrid : DataGrid
    {
        // Constructor that sets some styles and graphical properties    
        public MultiGrid()
        {
            AllowMultiSelect = false;
            AllowMultiSelectFooter = false;

            // Set event handlers
            Init += new EventHandler(OnInit);
            ItemCreated += new DataGridItemEventHandler(OnItemCreated);
        }

        // PROPERTY: SelectedItems
        public ArrayList SelectedItems 
        {
            get 
            {
                if (!AllowMultiSelect)    return null;

                ArrayList a = new ArrayList();
                foreach(DataGridItem dgi in Items)
                {
                    CheckBox cb = (CheckBox) dgi.Cells[0].Controls[0];
                    if (cb.Checked)
                        a.Add(dgi);
                }
                return a;
            }
        }

        // PROPERTY: AllowMultiSelect 
        public bool AllowMultiSelect = false;

        // PROPERTY: AllowMultiSelectFooter 
        public bool AllowMultiSelectFooter = false;

        // METHOD: ClearSelection
        public void ClearSelection()
        {
            foreach(DataGridItem dgi in Items)
            {
                CheckBox cb = (CheckBox) dgi.Cells[0].Controls[0];
                cb.Checked = false;
            }
        }

        ///
        // Event Handlers


        // EVENT HANDLER: Init            
        private void OnInit(Object sender, EventArgs e)
        {
            // Add a templated column that would allow for selection.
            // The item template contains a checkbox. It also features a 
            // templated footer containing links for Unselect/Select all 
            if (AllowMultiSelect)
                AddSelectColumn();
        }

        // EVENT HANDLER: Deselect
        private void OnDeselect(Object sender, EventArgs e)
        {
            ClearSelection();
        }

        // EVENT HANDLER: ItemCreated            
        private void OnItemCreated(Object sender, DataGridItemEventArgs e)
        {
            // Get the newly created item
            ListItemType itemType = e.Item.ItemType;

            ///
            // FOOTER
            if (itemType == ListItemType.Footer && AllowMultiSelectFooter 
                && AllowMultiSelect)
            {
                // Look for a link button called "lnkSelect" in the context 
                // of the grid item that represents the footer
                LinkButton lb = (LinkButton) 
                    e.Item.FindControl("lnkDeselect");

                // Now you hold the living instance of the link 
                // button in the footer and can bind it to any code in the 
                // context of the MultiGrid control
                lb.Click += new EventHandler(OnDeselect);
                
                // Force ShowFooter to true
                ShowFooter = true;

                // Removes all the cells but the first 
                TableCell cell = e.Item.Cells[0];
                for (int i=1; i<Columns.Count; i++)
                {
                    e.Item.Cells.RemoveAt(1);
                }
                cell.ColumnSpan = Columns.Count;
            }
        }

        ///
        // Helper Functions

        private void AddSelectColumn()
        {
            // Create the new templated column
            TemplateColumn tc = new TemplateColumn();
            tc.ItemStyle.BackColor = Color.SkyBlue;
            tc.ItemTemplate = new SelectColumnTemplate();
            tc.FooterTemplate = new SelectFooterTemplate();
            Columns.AddAt(0, tc);
        }
    }

    ///
    // Template Classes

    public class SelectColumnTemplate : ITemplate
    {
        public void InstantiateIn(Control container)
        {
            CheckBox cb = new CheckBox();
            container.Controls.Add(cb);
        }
    }

    public class SelectFooterTemplate : ITemplate
    {
        public void InstantiateIn(Control container)
        {
            LinkButton lb = new LinkButton();
            lb.Text = "Deselect all";
            lb.ID = "lnkDeselect";
            container.Controls.Add(lb);
        }
    }
}

 


 

原文地址:http://msdn.microsoft.com/msdnmag/is ... -WorldXML/default.aspx
本文从msdn 2003年第五期中的文章翻译而来,应该不算是项目总结了,因为这段时间在学XML,所有翻译了这篇文章,从这篇文章,你可以学到在.net中简单的操作xml文档,我觉得非常不错。同上篇总结一样,建议你把所有的文字都拷到word中,把原文的图片下载下来,然后插入到相应的位置,然后再阅读本文。原文还可以下载源代码,下下来后,你可以本机试试。希望英文好的同志指正。万分感谢!

本文假设你已熟悉XML和.NET Framework

    前言   在.NET Framework中,XmlTextReader和XmlTextWriter类提供了对xml数据的读和写操作。在本文中,作者讲述了XML阅读器(Reader)的体系结构及它们怎样与XMLDOM 和SAX 解释器结合。作者也演示了怎么样运用阅读器分析和验证XML文档,怎么样创建格式良好的XML文档,以及怎么样用函数读/写基于Base64和BinHex编码的大型的XML文档。最后,作者讲了怎么样实现一个基于流的读/写分析器,它把读写器都封装在一个单独的类里。
    大概三年前,我参加了一个软件研讨会,主题是“如果没有XML,就没有编程的未来”。XML确实也在一步一步的发展,它已经嵌入到. NET Framework中了。在本文中,我将讲解. NET Framework中用于处理XML文档的API的角色和它的内部特性,然后我将演示一些常用的功能。
    从MSXML到.net的XML
    在. NET Framework出现之前,你习惯使用MSXML服务----一个基于COM的类库---写windows的XML的驱动程序。不像. NET Framework中的类,MSXML类库的部分代码比API更深,它完全的嵌在操作系统的底层。MSXML的确能够与你的应用程序通信,但是它不能真正的与外部环境结合。
MSXML类库能在win32中被导入,也能在CLR中运用,但它只能作为一个外部服务器组件使用。但是基于.NET Framework的应用程序能直接的用XML类与.NET Framework 的其它命名空间整合使用,并且写出来的代码易于阅读。
作为一个独立的组件,MSXML分析器提供了一些高级的特性如异步分析。这个特性在.NET Framework中的XML类及.NET Framework的其它类都没有提供,但是,NET Framework中的XML类与其它的类整合可以很轻易的获得相同的功能,在这个基础上你可以增加更多的功能。
.NET Framework中的XML类提供了基本的分析、查询、转换XML数据的功能。在.NET Framework中,你可以找到支持Xpath查询和XSLT转换的类,及读/写XML文档的类。另外,.NET Framework也包含了其它处理XML的类,例如对象的序列化(XmlSerializer和the SoapFormatter类),应用程序配置(AppSettingsReader类),数据存储(DataSet类)。在本文中,我只讨论实现基本XML I/O操作的类。
XML分析模式
既然XML是一种标记语言,就应该有一种工具按一定的语法来分析和理解存储在文档中信息。这个工具就是XML分析器---一个组件用于读标记文本并返回指定平台的对象。
所有的XML分析器,不管它属于哪个操作平台,不外乎都分以下的两类:基于树或者基于事件的处理器。这两类通常都是用XMLDOM(the Microsoft XML Document Object Model)和SAX(Simple API for XML)来实现。XMLDOM分析器是一个普通的基于树的API---它把XML文档当成一个内存结构树呈现。SAX分析器是基于事件的API----它处理每个在XML数据流中的元素(它把XML数据放进流中再进行处理)。通常,DOM能被一个SAX流载入并执行,因此,这两类的处理不是相互排斥的。
总的来说,SAX分析器与XMLDOM分析器正好相反,它们的分析模式存在着极大的差别。XMLDOM被很好的定义在它的functionalition集合里面,你不能扩展它。当它在处理一个大型的文档时,它要占用很大内存空间来处理functionalition这个巨大的集合。
SAX分析器利用客户端应用程序通过现存的指定平台的对象的实例去处理分析事件。SAX分析器控制整个处理过程,把数据“推出”到处理程序,该处理程序依次接受或拒绝处理数据。这种模式的优点是只需很少的内存空间。
.NET Framework完全支持XMLDOM模式,但它不支持SAX模式。为什么呢?因为.NET Framework支持两种不同的分析模式:XMLDOM分析器和XML阅读器。它显然不支持SAX分析器,但这并不意味它没有提供类似SAX分析器的功能。通过XML阅读器SAX的所有的功能都能很容易的实现及更有效的运用。不像SAX分析器,.NET Framework的阅读器整个都运作在客户端应用程序下面。这样,应用程序本身就可以只把真正需要的数据“推出”,然后从XML数据流中跳出来。而SAX分析模式要处理所有的对应用程序有用和无用的信息。
阅读器是基于.NET Framework流模式工作的,它的工作方式类似于数据库的游标。有趣的是,实现类似游标分析模式的类提供对.NET Framework中的XMLDOM分析器的底层支持。XmlReader、XmlWriter两个抽象类是所有.NET Framework中XML类的基础类,包括XMLDOM类、ADO.NET驱动类及配置类。所以在.NET Framework中你有两种可选的方法去处理XML数据。用XmlReader和XmlWriter类直接处理XML数据,或者用XMLDOM模式处理。更多的关于在.NET Framework中读文档的介绍可以参见MSDN 2002 年八月刊的Cutting Edge栏目文章。
XmlReader类
    XML阅读器支持一个编程接口,接口用于连接XML文档,“推出”你要的数据。如果你更深入去了解阅读器,你会发现阅读器工作原理类似于我们的桌面应用程序从数据库中取出数据的原理。数据库服务返回一个游标对象,它包含所有查询结果集,并返回指向目标数据集的开始地址的引用。XML阅读器的客户端收到一个指向阅读器实例的引用。该实例提取底层的数据流并把取出的数据呈现为一棵XML树。阅读器类提供只读、向前的游标,你可以用阅读器类提供的方法滚动游标遍历结果集中的每一条数据。
    从阅读器中看XML文档不是一个标签文本文件,而是一个序列化的节点集合。它是.NET Framework中的一种特殊的游标模式;在.NET Framework中,你找不到其它的任何一个类似的API函数。
    阅读器和XMLDOM分析器有几点不同的地方。XML阅读器是只进的,它没有父、子、祖宗、兄弟节点的概念,而且是只读的。在.NET Framework中,读写XML文档是分为两种完全不同的功能,分别由XmlReader和XmlWriter类来完成。要编辑XML文档,你可以用XMLDOM分析器,或者你自己设计一个类来实现这两种功能。让我们开始分析阅读器的程序功能。
    XmlReader是一个抽象类,你可以继承并扩展它的功能。用户程序一般都基于下面的三种类:XmlTextReader、XmlValidatingReader或者 XmlNodeReader类。所有的这些类都有如图一的属性和图二的方法。要注意的是,某些属性的值实际上依赖于实际的某个阅读器类,不同的类与基类可能不同。因此,在图一中每个属性的说明都是以基类为准的。例如,CanResolveEntity属性在XmlValidatingReader类中只返回true;而在其它的阅读器类中它却可以设为false。同样的,在图二中的某些方法的实际返回值对不同的类可能不同。例如,如果节点类型不是元素节点(element node),所有包含Atrributes的方法的返回值类型都是void。
    XmlTextReader类用只进,只读的方式快速访问XML数据流。阅读器先验证XML文档是否是格式良好的,如果不是则抛出一个异常。XmlTextReader 检查 DTD 的格式是否良好,但不使用 DTD 对文档进行验证。XmlTextReader通过XML文档的文件名,或它的URL,或者从文件流中载入XML文档,然后快速的处理XML文档数据。如果你需要对文档的数据进行验证,你可以用XmlValidatingReader类。
    可以用多种方法创建XmlTextReader类的实例,从硬盘中加载文件,或从URL地址中加载,流(streams)中加载,还有就是从文本中读入XML文档数据:
XmlTextReader reader = new XmlTextReader(file);
    注意,所有XmlTextReader类的公共(public)构造函数都要求你指定数据源,数据源可以是stream、文件或者其它。XmlTextReader默认的构造函数是受保护的(protected),所以不能直接使用。像.NET Framework中所有的阅读器类一样(如SqlDataReader类),一旦阅读器对象连接并打开,你就可以用Read方法去访问数据了。开始的时候只能用Read方法把指针移到第一个元素;然后我们可以用Read方法或其它方法(如Skip, MoveToContent和ReadInnerXml)移动指针到下一个节点元素。要处理整个XML文档的内容,可以根据Read方法的返回值用一个循环遍历文档内容,因为Read方法返回一个布尔值,当读到文档的尾节点时,Read方法返回false,否则它返回true。

Figure 3 Outputting an XML Document Node Layout
string GetXmlFileNodeLayout(string file)
{
    // 创建一个XmlTextReader类使它指向目标XML文档
    XmlTextReader reader = new XmlTextReader(file);

// 循环取出节点的文本并放入到StringWriter对象实例中
StringWriter writer = new StringWriter();
    string tabPrefix = "";

    while (reader.Read())
    {
        // 写开始标志,如果节点类型为元素
        if (reader.NodeType == XmlNodeType.Element)
        {
            //根据元素所处节点的深度,加入reader.Depth个tab符,然后把元素名写入到<>中。
        tabPrefix = new string('\t', reader.Depth);
            writer.WriteLine("{0}<{1}>", tabPrefix, reader.Name);
        }
        else
        {
            //写结束标志,如果节点类型为元素
            if (reader.NodeType == XmlNodeType.EndElement)
            {
                tabPrefix = new string('\t', reader.Depth);
                writer.WriteLine("{0}</{1}>", tabPrefix, reader.Name);
            }
        }
    }

    // 输出到屏幕
    string buf = writer.ToString();
    writer.Close();

    // 关闭流
    reader.Close();

   return buf;
}

    图三演示了一个简单的用于输出一个给定的XML文档的节点元素的函数。该函数先打开一个XML文档,然后用循环处理XML文档中所有的内容。每次调用Read方法,阅读器的指针都会向下移一个节点。大部分情况下,用Read方法可以处理的元素节点,但有时候,当你从一个节点移动到下一个节点时,可能是在两个不同类型的节点间移动。但是Read方法不能在属性节点之间移动。阅读器的MoveToContent方法可以让指针从头部节点位置跳到第一个内容节点位置。在ProcessingInstruction, DocumentType, Comment, Whitespace和SignificantWhitespace类型节点中也可以用Skip方法移动指针。
    每个节点的类型是XmlNodeType枚举值中的一种,在如图三所示的代码中,我们只用了其中的两种类型:Element 和 EndElement。输出源码重新定制了原始的文档结构,它丢弃或者说是忽略了XML元素的属性和节点内容,只输出了元素节点名。假设我们运用了下面的XML片断:
<mags>
   <mag name="MSDN Magazine">
   MSDN Magazine
   </mag>
   <mag name="MSDN Voices">
   MSDN Voices
   </mag>
</mags>
用上面的程序输出的结果如下: 
<mags>
   <mag>
   </mag>
   <mag>
   </mag>
</mags>
子节点的缩进量是根据阅读器的深度属性(Depth属性)设置的,Depth属性返回一个整形的数据,它表示当前节点的嵌套层次。所有文本都放在StringWriter对象中(一个非常方便的基于流的封装了StrigBuilder类的类)。
如前所述,阅读器不会自动通过Read方法访问属性节点。要访问当前元素的属性节点集合,必须用一个简单的用MoveToNextAttribute方法的返回值控制的循环去遍历该集合。下面的代码用于访问当前节点的所有属性,并把属性的名称和它的值用逗号分开组合成一个字符串:
if (reader.HasAttributes)
while(reader.MoveToNextAttribute())
   buf += reader.Name + "=\"" + reader.Value + "\",";
reader.MoveToElement();
当你完成对属性集的处理时,调用MoveToElement方法使指针返回到属性所属的元素节点。准确的说,MoveToElement方法并不是真正的移动指针,因为在处理属性集时指针从来就没有从元素节点中移开。MoveToElement方法只不过指向某个内部成员,并依次取得成员的值。例如,用Name属性获得某个属性的属性名,然后调用MoveToElement方法把指针移到其所属的元素节点处。但是当你不需要继续处理别的节点时,就不必再调用MoveToElement方法了。
分析属性值
大部分情况下,属性值都是一个简单的文本字符串。然而,这并不意味着实际应用中的属性值都是字符型的。有时候,属性值是由许多种类型的数据组合而成的,例如Date或Boolean,这时,你就要用XmlConvert或System.Convevt类的方法把这些类型转换成原来的类型。XmlConvert和System.Convevt类都能实现数据类型的转换,但是XmlConvert类依据XSD中指定的数据类型进行转换,而不管它现在是什么类型。
假设你有以下的XML数据片断:
    <person birthday="2-8-2001" />
    让我们先确认,birthdaay属性值是February 8, 2001,如果你用System.Convert类把该字符串转换成.NET Framework中的DateTime类型,这样,我们就可以把它当成date类型使用了。相比下,如果你用XmlConvert类来转换字符串,你将看到一个分析错误,因为XmlConvert类不能正确解释这个字符串中的日期。因为在XML中,日期型数据的格式必须是YYYY-MM-DD形式的。XmlConvert类担任CLR类型与XSD类型之间的相互转换工作。当转换工作发生时,转换结果是局部的。
    在某些解决方案中,属性值是由纯文本和实体共同组成的。在所有的阅读器类中,只有XmlValidatingReader类能处理实体。XmlTextReader虽然不能处理实体,但它们同时出现在属性值中的时候,它只能把文本值取出来。出现这种情况,你必须用ReadAttributeValue方法替代简单的读方法来分析属性值的内容。
    ReadAttributeValue方法分析属性值,然后把各个组成的要素分隔开(如把纯文本和实体分开)。你可以用ReadAttributeValue方法的返回值作为循环条件,遍历整个属性值中的要素。既然XmlTextReader类不能处理实体,那么你可以自己写一个用于处理实体的类。下面的代码片断演示了怎么调用一个自定义的处理类:
while(reader.ReadAttributeValue())
{
   if (reader.NodeType == XmlNodeType.EntityReference)
// Resolve the "reader.Name" reference and add
// the result to a buffer
buf += YourResolverCode(reader.Name);
   else
    // Just append the value to the buffer
buf += reader.Value;
}
当属性值全部被分析后,ReadAtributeValue方法返回False, 从而结束循环。属性值的最终结果就是全局变量buffer的值了。
处理XML文本(Text)
当我们在处理XML标签文本时,如果不能正确的处理,它的错误原因能很快地确定。例如一个字符转换错误,它必然是传输了非XML文本到一个XML数据流中。不是所有在给定的平台中有效的字符都是有效的XML字符。只有在XML规范(www.w3.org/TR/2000/REC-xml-20001006.html)中规定的有效的字符才能安全的用作元素和属性名。
XmlConvert类提供了把非XML标准的命名转换成标准的XML命名的功能。当标签名中包含有无效的XML字符时,EncodeName 和 DecodeName方法能把它们调整成符合Schema的XML命名。包括SQL Server™ 和Microsoft Office,这些应用程序允许及支持Unicode文档,然而,这些文档中的字符有些也不是有效的XML命名。典型的情况是在你处理数据库中包含空格的列名时。虽然SQL Server允许长列名,但这对XML流来说可能就不是有效的命名。空格会被十六进制代码Invoice_0x0020_Details替代。下面的代码演示了怎么样在程序中获得该字符串:
XmlConvert.EncodeName("Invoice Details");
    与此相反的方法是DecodeName。该方法把XML文本转换成其原始的格式。要注意的是它只能转换完整的十六进制代码,只有_0x0020_才被当成一个空格,而_0x20_就不是了:
XmlConvert.DecodeName("Invoice_0x0020_Details");
    在XML文档中的空格即重要也不重要。说它重要,是当它出现在元素的内容中或者它在注释语句中时,它能表示实际意义。例如下面的情况:
 
<MyNode xml:space="preserve">
<!-- any space here must be preserved -->
•••
</MyNode>
    在xml中,空格不只是代表空格(空白),也代表回车、换行和缩进。
通过XmlTextReader类的WhiteSpaceHandling属性你可以处理空格。这个属性接受及返回一个WhiteSpaceHandling枚举值(该枚举类有三种可选值)。默认值是All,它表示有意义和无意义的空格都会作为节点返回---- 分别为SignificantWhitespace和Whitespace节点。 另一个枚举值是None,它表示对任何空格都不作为节点返回。最后,就是Signficant枚举值,它表示忽略没有意义的空格,而只返回节点类型为SignficantWhitespace的节点。注意WhiteSpaceHandling属性是少数阅读器属性中的一个。它能被改变在任何时候和给Read操作带来影响。而Normalization及 XmlResolver属性是“Sensitive”的。
String和Fragment
程序员把在MSXML的程序剪切下来,会发现在COM和.NET Framework XML API 之间的差别很大。.NET Framework类本身没有提供方法去分析存储在字符串中XML数据。不像MSXML分析器对象,XmlTestReader类没有提供任何一种LoadXML方法从一个格式良好的字符中创建阅读器。没有提供类似LoadXML的方法因为你可以用特殊的text reader---StringReader类来获得同样的功能。
XmlTextReader其中一个构造函数接受一个TextReader派生对象和一个XML reader作参数(该阅读器以text reader的内容为基础创建)。一个text reader类是一个流,这个流是输入的字符经优化生成的。StringReader类继承TextReader类,并用一个内存中字符串作为其输入流。下面的代码片断演示了怎样初始化一个XML reader,用一个格式良好的XML 字符串作为其输入:
string xmlText = "...";
StringReader strReader = new StringReader(xmlText);
XmlTextReader reader = new XmlTextReader(strReader);

另外,用StringWriter类代替TextWrite类,你可以从内存字符中创建一个XML文档。
一个指定类型的XML字符串是一个XML片断(fragment). XML片断由XML文本构成,但没有根节点的XML文档不是格式良好的XML文档,所以不能被应用。一个XML片断是原始的文档的一部分,所以它可能缺少根节点。例如,下面的XML文本是一个有效的XML  片断,但不是一个有效的XML文档,因为它没有根节点:
<firstname>Dino</firstname>
<lastname>Esposito</lastname>
.NET Framework XML API允许程序员把XML片断与一个分析器内容结合使用,分析器内容由类似encoding字符集,DTD文档,命名空间,语言和空格处理程序构成:  
public XmlTextReader(
   string xmlFragment,
   XmlNodeType fragType,
   XmlParserContext context
);
xmlFragment参数包括了XML字符串分析。FragType参数表示fragment的类型,它给出了fragment根节点的类型。只有element,attibute和document类型的节点才能作为fragment的根节点,分析器的内容才能被XmlParserContext类解释。
带验证的阅读器
    XmlValidatingReader类实现了XmlReader类,它提供了支持多种类型的XML验证:DTD,XML-Data Reduced(XDR)架构,以及XSD,DTD和XSD都是W3C官方推荐的。而XDR是Microsoft早期用于处理XML构架的一种格式。
    你可以用XmlVlidatingReader类去验证XML文档和XML片断。XmlValidatingReader类工作在XML阅读器上面---是一个典型的XMLTextReader类实例。XMLTextReade用于读取文档的节点,但是XmlVlidatingReader依据需要的验证类型去验证每一个XML块。
    XmlVlidatingReader类只实现了非常小的XML阅读器必备的一个功能子集。该类总是工作在一个已存在的XML阅读器上面,它监视方法和属性。如果你深入该类的构造函数,你会发现它很明显的依靠一个已存在的文本阅读器。带验证的XML阅读器不能直接的从一个文件或一个URL序列化。该类的构造函数列表如下:
public XmlValidatingReader(XmlReader);
public XmlValidatingReader(Stream, XmlNodeType, XmlParserContext);
public XmlValidatingReader(string, XmlNodeType, XmlParserContext);
    带验证的XML阅读器能分析任何的XML片断,XML片断通过一个string或者一个stream提供,也可以分析任何阅读器提供的XML文档。
    XmlVlidatingReader类中有重大改变的方法非常少(相对其它reader类来说),另外对 Read,它有Skip和ReadTypedValue方法。Skip方法跳过当前节点所有的子节点(你不能跳过不良格式的XML文本,它是相当有用的算法),Skip方法也验证被跳过的内容。ReadTypedValue方法返回指定 XML 架构 (XSD) 类型对应的CLR类型。如果该方法找到了XSD类型对应的CLR类型,则返回CLR的类型名。如果找不到,则把该节点的值作为一个字符串值返回。
    带验证的XML阅读器正如其名,它是一个基于节点的阅读器,它验证当前节点的结构是否符合当前的schema。验证是增量式的;它没有方法返回表示文档是否有效的布尔值。通常你都是用Read方法去读输入的XML文档。实际上,你也可以用带验证的阅读器去读XML文档。在每一步中,当前被访问的节点的结构是否与指定的schema符合,如果不符合,抛出一个异常。图四是一个控制台应用程序,它有一个要输入文件名的命令行,最后输出验证结果。
    Figure 4 Console App
using System;
using System.Xml;
using System.Xml.Schema;

class MyXmlValidApp
{
    public MyXmlValidApp(String fileName)
    {
        try {
            Validate(fileName);
        }
        catch (Exception e) {
            Console.WriteLine("Error:\t{0}", e.Message);
            Console.WriteLine("Exception raised: {0}",
                e.GetType().ToString());
        }
    }

    private void Validate(String fileName)
    {
        XmlTextReader xtr = new XmlTextReader(fileName);
        XmlValidatingReader vreader = new XmlValidatingReader(xtr);
        vreader.ValidationType = ValidationType.Auto;
        vreader.ValidationEventHandler += new
            ValidationEventHandler(this.ValidationEventHandle);

        vreader.Read();
        vreader.MoveToContent();

        while (vreader.Read()) {}

        xtr.Close();
        vreader.Close();
    }

    public void ValidationEventHandle(Object sender,
        ValidationEventArgs args)
    {
        Console.Write("Validation error: " + args.Message + "\r\n");
    }

    public static void Main(String[] args)
    {
        MyXmlValidApp o = new MyXmlValidApp(args[0]);
        return;
    }
}

ValidationType属性设置验证的类型,它可以是:DTD, XSD, XDR或者none。如果没有指定验证的类型(用ValidationType.Auto选项),阅读器将自动的根据文档用最适合的验证类型。在验证过程中出现任何错误,都会触发ValidationEventHandler事件。如果未提供事件ValidationEventHandler事件处理程序,则抛出一个XML异常。定义ValidationEventHandler事件处理程序是用于捕捉任何在XML源文件中存在错误而引发XML异常的一种方法。要注意的是阅读器的原理是检查一个文档是否是格式良好的,以及检查文档是否与架构吻合。如果带验证的阅读器发现一个有严重的格式错误的XML文档,只会触发XmlException异常,它不会触发其它的事件。
    验证发生在用户用Read方法向前移动指针时,一旦节点被分析和读取,它获得传送过来的处理验证的内部的对象。验证操作是基于节点类型及被要求的验证类型。它确认节点所有的属性和节点包含的子节点是否符合验证条件。
    验证对象在内部调用两个不同风格的对象:DTD分析器和架构生成器(schema builder)。DTD分析器处理当前节点的内容和不符合DTD的子树。架构生成器根据XDR或者XSD架构对当前的节点构建一个SOM(schema object model)。架构生成器类实际上是所有指定为XDR和XSD架构生成器的基类。为什么呢,虽然XDR和XSD架构的许多相同的方法被加工处理过,但是它们在执行时的性能没有区别。
    如果节点有子节点,用另一个临时的阅读器收集子节点信息,因此节点的架构信息能被完全地验证。你可以看图五:        

Figure 5 Using A Temporary Reader on Child Nodes
   注意,尽管XmlValidatingReader类的构造函数可以接受一个XmlReader类作为其阅读器,但是该阅读器只能是XmlTextReader类的一个实例或者是它的一个派生类的实例。这意味着你不能用其它从XmlReader派生的类(例如一个自定义的XML阅读器)。在XmlValidatingReader类的内部,它假设阅读器是一个子XmlTextReader对象及把传入的阅读器显式的转换成XmlTextReader类。如果你用XmlNodeReader或者自定义的阅读器器,程序在编译时会出错,运行时抛出一个异常。
节点阅读器
XML阅读器提供一种增量式的方法(一个一个节点的读)来处理文档的内容。到目前为止,我们假设源文件是一个基于硬盘的流或者是一个字符串流,然而,我们不能保证在实际中会提供一个源文件的XMLDOM对象给我们。在这种情况下,我们需要一个带有特别的读方法的特别的类。对这种情况,.NET Framework提供了XmlNodeReader类。
就像XmlTextReader访问指定XML流中所有节点一样,XmlNodeReader类访问XMLDOM子树的所有节点。XMLDOM类(在.NET Framework中的XmlDocument类)支持基于Xpath的方法,例如SelectNodes方法和SelectSingleNode方法。这些方法的作用是把匹配的节点放在内存中。如果你需要处理子树中的所有节点,节点阅读器比用增量式方法处理节点的阅读器具有更高的效率: 
// xmldomNode is the XML DOM node
XmlNodeReader nodeReader = new XmlNodeReader(xmldomNode);
while (nodeReader.Read())
{
    // Do something here
}
    当你要在配置文件(例如web.cofig文件)中引用自定义的数据时,先把这些数据填充到XMLDOM树中,然后用XmlNodeReader类与XMLDOM类结合处理这些数据。这也是高效的。
XmlTextWriter类
    用在本节中的方法创建XML文档显然并不困难。多年以来,开发者都是通过在缓存在连接一些字符串,连接好以后再把缓存中字符串输出到文件的方式来创建XML文档。但是以这种方式创建XML文档的方法只有在你保证字符串中不存在任何细小的错误的时候才有效。.NET Framework通过用XMLwriter提供了更好的创建XML文档的方法。
    XML Writer类以只前(forward-only)的方式输出XML数据到流或者文件中。更重要的是,XML Writer在设计时就保证所有的XML数据都符合W3C XML 1.0推荐规范,你甚至不用担心忘记写闭标签,因为XML Writer会帮你写。XmlWriter是所有 XML writer的抽象基类。.NET Framework只提供唯一的一个writer 类----XmlTextWriter类。
    我们先来看看XML writers和旧的writers的不同点,下面的代码保存了一个string型的数组:
StringBuilder sb = new StringBuilder("");
sb.Append("<array>");
foreach(string s in theArray) {
   sb.Append("<element value=\"");
   sb.Append(s);
   sb.Append("\"/>");
}
sb.Append("</array>");
    代码通过循环取出数据中的元素,写好标签文本并把它们累加到一个string中。代码保证输出的内容是格式良好的并且注意了新行的缩进,及支持命名空间。当创建的文档结构比较简单时,这种方法可能不会有错误。然而,当你要支持处理指令,命名空间,缩进,格式化以及实体的时候,代码的数量就成指数级增长,出错的可能性也随之增长。
    XML writer写方法功能对应每个可能的XML节点类型,它使创建xml文档的过程更符合逻辑、更少的信赖于繁琐的标记语言。图六演示了怎么样用XmlTextWriter类的方法来连接一个string数据。代码很简洁,用XML writer的代码更容易读、结构更好。
 Figure 6 Serializing a String Array
void CreateXmlFileUsingWriters(String[] theArray, string filename)
{
    // Open the XML writer (用默认的字符集)
    XmlTextWriter xmlw = new XmlTextWriter(filename, null);
    xmlw.Formatting = Formatting.Indented;

    xmlw.WriteStartDocument();
    xmlw.WriteStartElement("array");
    foreach(string s in theArray)
    {
        xmlw.WriteStartElement("element");
        xmlw.WriteAttributeString("value", s);
        xmlw.WriteEndElement();
    }
    xmlw.WriteEndDocument();

    // Close the writer
    xmlw.Close();
}
    然而XML writer并不是魔术师----它不能修复输入的错误。XML writer不会检查元素名和属性名是否有效,也不保证被用的任何的Unicode字符集适合当前架构的编码集。如上所述,为了避免输出错误,必须要杜绝非XML字符。但是writer没有提供这种方法。
    另外,当创建一个属性节点时,Writer不会检验属性节点的名称是否与已存在的元素节点的名称相同。最后,XmlWriter类不是一个带验证的Writer类,也不保证输出是否符合schema或者DTD。在.NET Framework中带验证的writer类目前来说还没有提供。但是在我写的《Applied XML Programming for Microsoft .NET (Microsoft Press&reg;, 2002)》书中,我自己写了一个带验证的Writer组件。你可以到下面的网址去下载源码:http://www.microsoft.com/MSPress/books/6235.asp.
    图七列出了XML writer的一些状态值(state)。这些值都源于WriteState枚举类。当你创建一个Writer,它的初始状态为Start,表示你将要配置该对象,实际上writer没有开始。下一个状态是Prolog,该状态是当你调用WriteStartDocument方法开始工作的时候设置的。然后,状态的转换就取决于你的写的文档及文档的内容了。Prolog状态一直保留到当你增加一个非元素节点时,例如注释元素,处理指令及文档类型。当第一个节点也就是根节点写完后,状态就变为Element。当你调用WriterStartAtribute方法时状态转换为Attribute,而不是当你调用WriteAtributeString方法写属性时转换为该状态。如果那样的话,状态应该是Element。当你写一个闭标签(>)时,状态会转换成Content。当你写完文档后,调用WriteEndDocument方法,状态就会返回为Start,直到你开始写另一个文档或者把Writer关掉。
Figure 7 States for XML Writer
State    Description 
Attribute    The writer enters this state when an attribute is being written 
Closed    The Close method has been called and the writer is no longer available for writing operations
Content    The writer enters this state when the content of a node is being written 
Element    The writer enters this state when an element start tag is being written 
Prolog    The writer is writing the prolog of a well-formed XML 1.0 document 
Start    The writer is in an initial state, awaiting for a write call to be issued
Writer 把输出文本存在内部的一个缓冲区内。一般情况下,缓冲区会被刷新或者被清除,当Writer被关闭前XML文本应该要写出。在任何时你都可以通过调用Flush方法清空缓冲区,把当前的内容写到流中(通过BaseStream属性暴露流),然后释放部分占用的内存,Writer仍保持为打开状态(open  state),可以继续操作。注意,虽然写了部分的文档内容,但是在Writer没有关闭前其它的程序是不能处理该文档的。
    可以用两种方法来写属性节点。第一种方法是用WriteStartAtribute方法去创建一个新的属性节点,更新Writer的状态。接着用WriteString方法设置属性值。写完后,用WriteEndElement方法结束该节点。另外,你也可以用WriteAttributeString方法去创建新的属性节点,当writerr的状态为Element时,WriterAttributeString开始工作,它单独创建一个属性。同样的,WriteStartElement方法写节点的开始标签(<),然后你可以随意的设置节点的属性和文本内容。元素节点的闭标签都带”/ >”。如果想写闭标签可以用WriteFullEndElement方法来写。
应该避免传送给写方法的文本中包含敏感的标记字符,例如小于号(<)。用WriteRaw方法写入流的字符串不会被解析,我们可以用它来对xml文档写入特殊的字符串。下面的两行代码,第一行输出的是”&lt”,第二行输出”<”:
writer.WriteString("<");
writer.WriteRaw("<");
读写流
有趣的是,reader(阅读器)和writer类提供了基于Base64 和BinHex编码的读写数据流的方法。WriteBase64 和 WriteBinHex方法的功能与其它的写方法的功能存在着细微的差别。它们都是基于流的,这两个方法的功能像一个byte数组而不是一个string。下面的代码首先把一个string转换成一个byte数组,然后把它们写成一个Base64 编码流。Encoding类的GetBytes静态方法完成转换的任务: 
writer.WriteBase64(
   Encoding.Unicode.GetBytes(buf),
   0, buf.Length*2);
图八中代码演示了把一个string数据转换为Base64 编码的XML流。图九是输出的结果。

Figure 8 Persisting a String Array as Base64
using System;
using System.Text;
using System.IO;
using System.Xml;

class MyBase64Array
{
    public static void Main(String[] args)
    {
        string outputFileName = "test64.xml";
        if (args.Length > 0)
            outputFileName = args[0];    // file name

        // 把数组转换成XML
        String[] theArray = {"Rome", "New York", "Sydney", "Stockholm",
                             "Paris"};

        CreateOutput(theArray, outputFileName);
        return;
    }

    private static void CreateOutput(string[] theArray, string filename)
    {
        // 打开XML writer
        XmlTextWriter xmlw = new XmlTextWriter(filename, null);
    //使子元素根据 Indentation 和 IndentChar 设置缩进。此选项只对元素内容进行缩进
        xmlw.Formatting = Formatting.Indented;
    //书写版本为“1.0”的 XML 声明
        xmlw.WriteStartDocument();
    //写出包含指定文本的注释 <!--...-->。
        xmlw.WriteComment("Array to Base64 XML");
    //开始写出array节点
        xmlw.WriteStartElement("array");
//写出具有指定的前缀、本地名称、命名空间 URI 和值的属性
        xmlw.WriteAttributeString("xmlns", "x", null, "dinoe:msdn-mag");
    // 循环的写入array的子节点
            foreach(string s in theArray)
        {
        //写出指定的开始标记并将其与给定的命名空间和前缀关联起来
xmlw.WriteStartElement("x", "element", null);
//把S转换成byte[]数组, 并把byte[]数组编码为 Base64 并写出结果文本,要写入的字节数为s总长度的2倍,一个string占的字节数是2字节。
xmlw.WriteBase64(Encoding.Unicode.GetBytes(s), 0,               s.Length*2);   
//关闭子节点
            xmlw.WriteEndElement();
        }
    //关闭根节点,只有两级
        xmlw.WriteEndDocument();

        // 关闭writer
        xmlw.Close();

        // 读出写入的内容
        XmlTextReader reader = new XmlTextReader(filename);
        while(reader.Read())
        {
        //获取节点名为element的节点
            if (reader.LocalName == "element")
            {
        
                byte[] bytes = new byte[1000];
                int n = reader.ReadBase64(bytes, 0, 1000);
                string buf = Encoding.Unicode.GetString(bytes);

                Console.WriteLine(buf.Substring(0,n));
            }
        }
        reader.Close();

    }
}

 
Figure 9 String Array in Internet Explorer
    Reader类有专门的解释Base64和BinHex编码流的方法。下面的代码片断演示了怎么样用XmlTextReader类的ReadBase64方法解析用Base64和BinHex编码集创建的文档。
XmlTextReader reader = new XmlTextReader(filename);
while(reader.Read()) {
  if (reader.LocalName == "element") {
    byte[] bytes = new byte[1000];
int n = reader.ReadBase64(bytes, 0, 1000);
string buf = Encoding.Unicode.GetString(bytes);
    Console.WriteLine(buf.Substring(0,n));
  }
}
reader.Close();
    从byte型转换成string型是通过Encoding类的GetString方法实现的。尽管我只介绍了基于Base64编码集的代码,但是可以简单的用BinHex替换方法名就可以实现读基于BinHex编码的节点内容(用ReadBinHex方法)。这个技巧也可以用于读任何用byte数据形式表示的二进制数据,尤其是image类型的数据。
设计XmlReadWriter类
    如前面所说,XML reader和Writer是各自独立工作的:reader只读,writer只写。假设你的应用程序要管理冗长的XML文档,且该文档有不确定的数据。Reader提供了一个很好的方法去读该文档的内容。另一方面,Writer是一个非常有用的用于创建XML文档片断工具,但是如果你想要它即能读,又能写,那么你就要用XMLDOM了。如果实际的XML文档非常庞大,又会出现了一个问题,什么问题呢?是不是把这个XML文档全部加载到内存中,然后进行读和写呢?让我们先看一下怎么样建立一个混合的流分析器用于分析大型的XMLDOM。
    像一般的只读操作一样,用普通的XML reader去顺序的访问节点。不同的是,在读的同时你可以用XML writer改变属性值以及节点的内容。你用reader去读源文件中的每个节点,后台的writer创建该节点的一个拷贝。在这个拷贝中,你可以增加一些新的节点,忽略或者编辑其它的一些节点,还可以编辑属性的值。当你完成修改后,你就用新的文档替换旧的文档。
    一个简单有效的办法是从只读流中拷贝节点对象到write流中,这种方法可以用XmlTextWriter类中的两个方法:WriteAttributes方法和WriteNode方法。 WriteAttributes方法读取当前reader中选中的节点的所有有效的属性,然后把属性当作一个单独的string拷贝到当前的输出流中。同样的,WriteNode方法用类似的方法处理除属性节点外的其它类型的节点。图十所示的代码片断演示了怎么用上述的两个方法创建一个源XML文档的拷贝,有选择的修改某些节点。XML树从树根开始被访问,但只输出了除属性节点类型以外的其它类型的节点。你可以把Reader和Writer整合在一个新的类中,设计一个新的接口,使它能读写流及访问属性和节点。
Figure 10 Using the WriteNode Method
XmlTextReader reader = new XmlTextReader(inputFile);
XmlTextWriter writer = new XmlTextWriter(outputFile);

// 配置 reader 和 writer
writer.Formatting = Formatting.Indented;
reader.MoveToContent();

// Write根节点
writer.WriteStartElement(reader.LocalName);

// Read and output every other node
int i=0;
while(reader.Read())
{
    if (i % 2)
        writer.WriteNode(reader, false);
    i++;
}

// Close the root
writer.WriteEndElement();

// Close reader and writer
writer.Close();
reader.Close();
    我的XmlTextReadWriter类并没有从XmlReader或者XmlWriter类中继承。取而代之的是另外两个类,一个是基于只读流(stream)的操作类,另一个是基于只写流的操作类。XmlTextReadWriter类的方法用Reader对象读数据,写入到Writer对象。为了适应不同的需求,内部的Reader和Writer 对象分别通过只读的Reader和Writer属性公开。图十一列出了该类的一些方法:
Figure 11 XmlTextReadWriter Class Methods
Method    Description
AddAttributeChange    Caches all the information needed to perform a change on a node attribute. All the changes cached through this method are processed during a successive call to WriteAttributes.
Read    Simple wrapper around the internal reader's Read method.
WriteAttributes    Specialized version of the writer's WriteAttributes method, writes out all the attributes for the given node, taking into account all the changes cached through the AddAttributeChange method.
WriteEndDocument    Terminates the current document in the writer and closes both the reader and the writer. 
WriteStartDocument    Prepares the internal writer to output the document and add a default comment text and the standard XML prolog.
这个新类有一个Read方法,它是对Reader的read方法的一个简单的封装。另外,它提供了WriterStartDocument和WriteEndDocument方法。它们分别初始化/释放(finalize)了内部Reader和writer对象,还处理所有I/O操作。在循环读节点的同时,我们就可以直接的修改节点。出于性能的原因,要修改属性必须先用AddAttributeChange方法声明。对一个节点的属性所作的所有修改都会存放在一个临时的表中,最后,通过调用WriteAttribute方法提交修改,清除临时表。
图十二所示的代码演示了客户端用XmlTextReadWriter类在读操作的同时修改属性值的优势。在本期的msdn中提供了XmlTextReadWriter类的C#和VB源代码下载(见本文开头提供的链接)。
Figure 12 Changing Attribute Values
private void ApplyChanges(string nodeName, string attribName,
    string oldVal, string newVal)
{
    XmlTextReadWriter rw = new XmlTextReadWriter(InputFileName.Text,
                               OutputFileName.Text);
    rw.WriteStartDocument(true, CommentText.Text);

    // 手工修改根节点
    rw.Writer.WriteStartElement(rw.Reader.LocalName);

    // 开始修改属性
    // (可以修改更多节点的属性)
    rw.AddAttributeChange(nodeName, attribName, oldVal, newVal);

    // 循环处理文档
    while(rw.Read())
    {
        switch(rw.NodeType)
        {
            case XmlNodeType.Element:
                rw.Writer.WriteStartElement(rw.Reader.LocalName);
                if (nodeName == rw.Reader.LocalName)
                    // 修改属性
                    rw.WriteAttributes(nodeName);
                else
                    // deep copy
                    rw.Writer.WriteAttributes(rw.Reader, false);

                if (rw.Reader.IsEmptyElement)
                    rw.Writer.WriteEndElement();
                break;
        }
    }

    // Close the root tag
    rw.Writer.WriteEndElement();

    // Close the document and any internal resources
    rw.WriteEndDocument();
}

XmlTextReadWriter类不仅可以读XML文档,也可以写XML文档。你可以它来读XML文档的内容,如果需要,你还可以用它来做一些基本的更新操作。基本的更新操作在这里是指修改某个已存在的属性的值或者某个节点的内容,又或者是增加一个新的属性或节点。对于更复杂的操作,最好还是用XMLDOM分析器。
总结
Reader和Writer是.NET Framework中处理XML数据的根本。它们提供了对所有XML数据访问功能的原始的API。Reader像一个新的分析器类,它即有XMLDOM的强大,又有SAX的快速简单。Writer为简单的创建XML文档而设计。虽然Reader和Writer都是.NET Framework中的一小块,但是它们是相互独立的API。在本文中,我们只讨论了怎么样用Reader和Writer完成一些主要的工作, 介绍了验证分析器的原理机制,并把Reader和writer整合在一个单独的类中。上述所有的这些类都是轻量级的,类似于游标式的XMLDOM分析器。

 

此文章来源于网络,如果未属名,可能因为此文被转摘多次,原作者不详,如果您认为侵权,请联系我。我将在第一时间按要求做出处理,并消除影响。