下面是Shanku Niyogi和Nikhil Kothari在PDC 2005上演示中展示的Atlas的整体架构图
(偷自Nikhil Kothari的PPT)
据Nikhil Kothari,Atlas的设计宗旨不是要成为普普通通的AJAX类库,而是成为一个跨越客户端和服务器端开发以及整合两者交互的一个整体应用框架(end-to-end application framework),它允许开发人员,既可以用XML声明的方式,也可以用脚本编程的方式,很快地集成组件,并且使得脚本功能与ASP.NET应用的集成既整洁,又不让人觉得唐突。
Atlas提供了一个客户端的框架和服务,主要包括
1。浏览器兼容层,把浏览器之间的差异封装出来,以利于其他层次的功能的浏览器独立性,目前支持IE,Safari和Firefox
2。脚本内核,包括了一个Javascript的整套类体系,允许你使用标准OOP里的构造,譬如,命名空间,类,接口,继承,枚举,代理(delegate)等等。目的是要提供一个机制把数据,逻辑和行为封装成类,使得开发人员可以象和其他编程语言一样来做脚本开发
3。基类库,受.NET框架启发,提供了StringBuilder, Debug, Event,和 IDisposable等方便的基础类型,同时通过WebRequest, WebResponse类提供了一个基于XMLHTTP 的客户端networking层,在上面可以通过MethodRequest 与aspx和asmx等服务端服务进行交互。也提供了序列化,特别是JSON 序列化器。该类库也提供了可以和服务器端相应服务集成的Profile 和认证服务
4。组件模型和UI框架,引进了可以自描述本身对象模型的组件的概念,这些模型可以参与一个顶层的Application类管理的生命周期机制,可以通过以声明或编程的方式建立,可以通过绑定以及事件的形式来传输数据和交互。UI框架提供了与DHTML UI元素相关的控件,以及如何给这些控件附加行为的机制,譬如象Drag/Drop以及对输入控件数据的验证等等。
5。控件和组件,提供了象计时器(Timer)和计数器(Counter)这样的组件以及象ListView和地图控件等的控件
Atlas也提供了相应的服务器端基础设施,
1。服务器控件框架,定义了一个新的控件ScriptManager来管理传回到客户端的脚本以及由服务器控件生成的XML-脚本标识。服务器控件也可以通过实现 IScriptComponent来参与请求处理,同时,还提供了与客户端组件模型类型相对应的服务器端对象
2。网络服务桥,允许客户端脚本访问普通的网络服务(asmx)以及Indigo风格的服务(svc),也可以直接访问网页内声明的WebMethod,这是通过象下面这样的类似声明
<script src="MyService.asmx/js" />
由服务自动产生javacript代理类来实现的,同时提供了实现JSON协议调用服务器端服务的基础设施
3。应用服务桥,通过网络服务桥提供了ASP.NET的几个应用服务,譬如可以通过成员服务做用户认证,以及通过Profile服务访问/更新用户数据等
详见Nikhil Kothari的blog以及下面两个ppt
Atlas Architecture Overview
PRS312 ASP.NET: Future Directions for Developing Rich Web Applications with Atlas (Part 1)
PRS420 ASP.NET: Future Directions for Developing Rich Web Applications with Atlas (Part 2)
DLINQ宣布后,网上有很多争议,其中以Paul Wilson的意见最有代表性
Linq is Really Cool -- But DLinq is a Big Mess
他指出,虽然DLINQ还是早期预览版本,但这已经是微软第三次尝试O/R Mapper了(之前有过ObjectSpaces的2个版本)。没用过O/R Mapper的人也许会认为DLINQ是个很大的进步,但熟悉O/R Mapper的人,却发现DLINQ问题多多,主要包括:
1。这是个基于属性(attribute-based)的方案,比外部映射文件的方法为差,连DLINQ自己文档中说到的non-intrusive的目标都没达到
2。目前只支持SQL Server
3。过分复杂,开发人员要熟练掌握/使用,需要了解的东西很多
4。对存储过程支持不足,现有的支持也要求开发人员写很多编码
5。功能有限,没有服务器端的分页支持,还不支持many-to-many relationship以及inheritance,没有WinFS/OPath集成。。。。
随LINQ一起来的工具里,有个Entity Class Generator Tool,叫SQLMetal,能连接到数据库直接产生跟数据表对应的Entity Class,就象上个帖子里的例子一样
SqlMetal /server:(local) /database:DLINQ /delayfetch /pluralize /namespace:LINQ.Examples.QuickStart /code:User.cs
也可以先生成一个Schema XML文件,譬如
SqlMetal /server:(local) /database:DLINQ /pluralize /xml:User.xml
对应上个贴里的Users表的XML是这样的
<Database xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="DLINQ">
<Schema Name="Dbo">
<Table Name="users" Property="Users" Class="User">
<Column Name="LogonID" Type="System.String" DbType="NVarChar(20) NOT NULL" Nullable="false" IsIdentity="true" />
<Column Name="Name" Type="System.String" DbType="NVarChar(40)" />
<Column Name="Password" Type="System.String" DbType="NVarChar(20)" />
<Column Name="EmailAddress" Type="System.String" DbType="NVarChar(40)" />
<Column Name="LastLogon" Type="System.DateTime" DbType="DateTime" Nullable="true" />
<PrimaryKey Name="PK__users__09DE7BCC">
<Column Name="LogonID" />
</PrimaryKey>
<Index Name="PK__users__09DE7BCC" Style="CLUSTERED" IsUnique="true">
<Column Name="LogonID" />
</Index>
</Table>
</Schema>
</Database>
你可以修改其中内容 (但居然无法修改生成的property name??),然后再运行SQLMetal来产生编码
SqlMetal /namespace:LINQ.Examples.QuickStart /code:User.cs User.xml
注意,生成的Entity Class是个partial class
public partial class User : System.Data.DLinq.IChangeNotifier
{
//....
}
里面是对应数据表字段的变量和property们,你可以在另外的文件里定义该类的操作,譬如
public partial class User
{
public void Test()
{
Console.WriteLine("User:Test():" + Name);
}
//.....
}
这样,即使你用SQLMetal重新生成Entity Class文件,也不会影响你定义操作的文件。将来IDE大概会支持,或者顶多在Build过程中多加一步
以前做过一个NHibernate练习,这里用DLINQ重新来过
想试的话,也许需要改动连接字符串,可惜上面的程序在8月份的CTP上不工作。出錯信息是
Unhandled Exception: System.TypeLoadException: Could not load type 'System.INullableValue' from assembly 'mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'.
at System.Data.DLinq.SqlClient.QueryConverter.IsConstNull(ConstantExpression expr)
at System.Data.DLinq.SqlClient.QueryConverter.VisitConstant(ConstantExpression cons) in c:\PdcBuild\query\DLinq\Engine\Converter.cs:line 500
at System.Data.DLinq.SqlClient.QueryConverter.VisitInner(Expression node) in c:\PdcBuild\query\DLinq\Engine\Converter.cs:line 102......
[来源:Robert McLaws] Bill Gates的keynote speech中播放的一个叫《Bill Gates Goes to College》的录像在网上转播时被过滤掉了,但在现场的LonghornBlogs.com的Robert McLaws把它录了下来,现在他的blog上提供了网址供观看
Exclusive: BillG Goes To College
[来源:Karsten Januszewski ] 另一个录像是North Face公司做的演示,在其中展示了在Windows Vista下使用XAML写的程序的惊人的效果,他们声称,之前,他们只是web developer,从没写过Windows Applications和3D应用
读完文档,I am in awe,虽然感觉这玩意带来的编程模型的变化会很大
对DLINQ的基于属性的做法
[Table(Name="Customers")]
public class Customer
{
[Column(Id=true)]
public string CustomerID;
[Column]
public string City;
}
不是很认同,因为这样把relational database schema与对象模型耦合在一起了,这也是我不喜欢Gentle.NET的原因
比较一下C#,VB的Query syntax,(更正,谢谢Ninputer)
IEnumerable<string> expr = from s in names
where s.Length == 5
orderby s
select s.ToUpper();
Dim expr As IEnumerable(Of String) = Select s.ToUpper() _
From s in names _
Where s.Length = 5 _
Order By s
感觉VB更自然些,也许VB将会成为“programming language of choice”?
Anders Hejlsberg接受Channel 9的采访,谈到如何把数据编程与对象编程统一起来
也参考
The LINQ Project
上面有 LINQ的技术预览,包括示范程序,白皮书文档,hands-on labs以及为使用LINQ技术编程所需的编译器支持,可以在Visual Studio 2005 下使用
MSDN上的C# 将来版本网站罗列了很多资源,包括,
C# 3.0 Language Specification
这篇blog提到了下面这些new features
http://blogs.sarkhouston.com/jrobertson/archive/2005/07/19/2742.aspx
Extension methods
Lambda expressions
Type inference and implicit types
Anonymous types
Expression Trees
Concurrency
Object Initializers
Dynamic Typing
9月22日MSDN将有个聊天活动,讨论C# 3.0的new features
C# 3.0 Language Enhancements
下面是对上贴里的编码的初浅的重构,
1. AuthorList.aspx:
<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="AuthorList.aspx.cs" Inherits="JoyCode.AuthorListPage" %>
<html>
<head>
</head>
<body style="font: 10pt verdana">
<form id="Form1" runat="server">
<h3><font face="Verdana">Updating a Row of Data w/ Validation</font></h3>
<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>
<ASP:DataGrid id="MyDataGrid" runat="server"
Width="800"
BackColor="#ccccff"
BorderColor="black"
ShowFooter="false"
CellPadding=3
CellSpacing="0"
Font-Name="Verdana"
Font-Size="8pt"
HeaderStyle-BackColor="#aaaadd"
>
<Columns>
<asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update" ItemStyle-Wrap="false"/>
<asp:BoundColumn HeaderText="au_id" SortExpression="au_id" ReadOnly="True" DataField="au_id" ItemStyle-Wrap="false"/>
<asp:TemplateColumn HeaderText="au_lname" SortExpression="au_lname">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/>
</ItemTemplate>
<EditItemTemplate>
<nobr>
<asp:TextBox runat="server" id="edit_LName" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/>
<asp:RequiredFieldValidator id="au_lnameReqVal"
ControlToValidate="edit_LName"
Display="Dynamic"
Font-Name="Verdana" Font-Size="12"
runat=server>
*
</asp:RequiredFieldValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="au_fname" SortExpression="au_fname">
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/>
</ItemTemplate>
<EditItemTemplate>
<nobr>
<asp:TextBox runat="server" id="edit_FName" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/>
<asp:RequiredFieldValidator id="au_fnameReqVal"
ControlToValidate="edit_FName"
Display="Dynamic"
Font-Name="Verdana" Font-Size="12"
runat=server>
*
</asp:RequiredFieldValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="phone" SortExpression="phone">
<ItemTemplate>
<asp:Label ID="Label3" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/>
</ItemTemplate>
<EditItemTemplate>
<nobr>
<asp:TextBox runat="server" id="edit_Phone" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/>
<asp:RequiredFieldValidator id="phoneReqVal"
ControlToValidate="edit_Phone"
Display="Dynamic"
Font-Name="Verdana" Font-Size="12"
runat=server>
*
</asp:RequiredFieldValidator>
<asp:RegularExpressionValidator id="phoneRegexVal"
ControlToValidate="edit_Phone"
ValidationExpression="[0-9]{3} [0-9]{3}-[0-9]{4}"
Display="Dynamic"
Font-Name="Arial" Font-Size="11"
runat=server>
* Phone must be in form: XXX XXX-XXXX <br>
</asp:RegularExpressionValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="address" SortExpression="address">
<ItemTemplate>
<asp:Label ID="Label4" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" id="edit_Address" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="city" SortExpression="city">
<ItemTemplate>
<asp:Label ID="Label5" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" id="edit_City" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="state" SortExpression="state">
<ItemTemplate>
<asp:Label ID="Label6" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "state") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList runat="server" id="edit_State">
<asp:ListItem>CA</asp:ListItem>
<asp:ListItem>IN</asp:ListItem>
<asp:ListItem>KS</asp:ListItem>
<asp:ListItem>MD</asp:ListItem>
<asp:ListItem>MI</asp:ListItem>
<asp:ListItem>OR</asp:ListItem>
<asp:ListItem>TN</asp:ListItem>
<asp:ListItem>UT</asp:ListItem>
</asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="zip" SortExpression="zip">
<ItemTemplate>
<asp:Label ID="Label7" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" id="edit_Zip" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/>
<asp:RegularExpressionValidator id="RegularExpressionValidator1"
ASPClass="RegularExpressionValidator" ControlToValidate="edit_Zip"
ValidationExpression="[0-9]{5}"
Display="Dynamic"
Font-Name="Arial" Font-Size="11"
runat=server>
* Zip Code must be 5 numeric digits <br>
</asp:RegularExpressionValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="contract" SortExpression="contract">
<ItemTemplate>
<asp:Label ID="Label8" runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "contract", "{0}") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:CheckBox runat="server" id="edit_Contract" Checked='<%# DataBinder.Eval(Container.DataItem, "contract")
%>'/>
</EditItemTemplate>
</asp:TemplateColumn>
</Columns>
</ASP:DataGrid>
</form>
</body>
</html>
2. AuthorList.aspx.cs:
using System;
using System.Data;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using JoyCode;
namespace JoyCode
{
public class AuthorListPage : System.Web.UI.Page
{
AuthorManager am;
protected HtmlGenericControl Message;
protected DataGrid MyDataGrid;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
this.Load += new EventHandler(Page_Load);
this.MyDataGrid.EditCommand += new DataGridCommandEventHandler(MyDataGrid_Edit);
this.MyDataGrid.UpdateCommand += new DataGridCommandEventHandler(MyDataGrid_Update);
this.MyDataGrid.CancelCommand += new DataGridCommandEventHandler(MyDataGrid_Cancel);
this.MyDataGrid.ItemDataBound += new DataGridItemEventHandler(MyDataGrid_ItemDataBound);
this.MyDataGrid.AutoGenerateColumns = false;
this.MyDataGrid.DataKeyField = "au_id";
am = new AuthorManager();
}
protected void Page_Load(Object Src, EventArgs E)
{
if (!IsPostBack)
BindGrid();
}
public void MyDataGrid_Edit(Object sender, DataGridCommandEventArgs E)
{
MyDataGrid.EditItemIndex = (int)E.Item.ItemIndex;
BindGrid();
}
public void MyDataGrid_Cancel(Object sender, DataGridCommandEventArgs E)
{
MyDataGrid.EditItemIndex = -1;
BindGrid();
}
public void MyDataGrid_Update(Object sender, DataGridCommandEventArgs E)
{
if (Page.IsValid)
{
string id = MyDataGrid.DataKeys[(int)E.Item.ItemIndex].ToString();
String[] cols = { "LName", "FName", "Phone", "Address", "City", "Zip" };
String[] vals = new String[cols.Length];
for (int i = 0; i < cols.Length; i++)
{
String colvalue = ((TextBox)E.Item.FindControl("edit_" + cols[i])).Text;
vals[i] = colvalue;
}
string lname = vals[0];
string fname = vals[1];
string phone = vals[2];
string address = vals[3];
string city = vals[4];
string zip = vals[5];
string state = ((DropDownList)E.Item.FindControl("edit_State")).SelectedItem.ToString();
bool contract = ((CheckBox)E.Item.FindControl("edit_Contract")).Checked;
try
{
am.UpdateAuthor(id, lname, fname, phone, address, city, state, zip, contract);
Message.InnerHtml = "<b>Record Updated</b><br>";
MyDataGrid.EditItemIndex = -1;
}
catch (RecordExistException ex)
{
Message.InnerHtml = "ERROR: " + ex.Message;
Message.Style["color"] = "red";
}
catch(UnknownException ex)
{
Message.InnerHtml = "ERROR: Could not update record, please ensure the fields are correctly filled out";
Message.Style["color"] = "red";
}
BindGrid();
}
else
{
Message.InnerHtml = "ERROR: Please check each field for error conditions.";
Message.Style["color"] = "red";
}
}
private void MyDataGrid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.EditItem)
{
for (int i = 0; i < e.Item.Controls.Count; i++)
{
try
{
if (e.Item.Controls[i].Controls[1] is TextBox)
{
TextBox tb = (TextBox)e.Item.Controls[i].Controls[1];
tb.Text = Server.HtmlDecode(tb.Text);//这里是否算?
}
else if (e.Item.Controls[i].Controls[1] is DropDownList)
{
DropDownList ddl = e.Item.Controls[i].Controls[1] as DropDownList;
string state = (String)((DataRowView)e.Item.DataItem)["state"];
ListItem li = ddl.Items.FindByText(state);
if (li != null)
{
ddl.SelectedIndex = ddl.Items.IndexOf(li);
}
}
}
catch
{
}
}
}
}
public void BindGrid()
{
MyDataGrid.DataSource = am.GetAuthors();
MyDataGrid.DataBind();
}
}
}
3. Author.cs:
using System;
using System.Data;
using System.Web;
using System.Runtime.Serialization;
using System.Data.SqlClient;
namespace JoyCode
{
public class UnknownException : ApplicationException
{
public UnknownException() {}
public UnknownException(string message) : base(message) {}
protected UnknownException(SerializationInfo info, StreamingContext context) : base(info,context) {}
public UnknownException(string message, Exception innerException) : base(message, innerException) {}
}
public class RecordExistException : ApplicationException
{
public RecordExistException() { }
public RecordExistException(string message) : base(message) { }
protected RecordExistException(SerializationInfo info, StreamingContext context) : base(info, context) { }
public RecordExistException(string message, Exception innerException) : base(message, innerException) { }
}
public class AuthorManager
{
SqlConnection myConnection;
public AuthorManager()
{
myConnection = new SqlConnection(System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"]);
}
public DataView GetAuthors()
{
SqlDataAdapter myCommand = new SqlDataAdapter("select * from Authors", myConnection);
DataSet ds = new DataSet();
myCommand.Fill(ds, "Authors");
return ds.Tables["Authors"].DefaultView;
}
public void UpdateAuthor(string id, string lname, string fname, string phone, string address, string city, string
state, string zip, bool contract)
{
String updateCmd = "UPDATE Authors SET au_id = @Id, au_lname = @LName, au_fname = @FName, phone = @Phone, "
+ "address = @Address, city = @City, state = @State, zip = @Zip, contract = @Contract where au_id = @Id";
SqlCommand myCommand = new SqlCommand(updateCmd, myConnection);
myCommand.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 11)).Value = id;
myCommand.Parameters.Add(new SqlParameter("@LName", SqlDbType.NVarChar, 40)).Value =
HttpUtility.HtmlEncode(lname);
myCommand.Parameters.Add(new SqlParameter("@FName", SqlDbType.NVarChar, 20)).Value =
HttpUtility.HtmlEncode(fname);
myCommand.Parameters.Add(new SqlParameter("@Phone", SqlDbType.NChar, 12)).Value = HttpUtility.HtmlEncode(phone);
myCommand.Parameters.Add(new SqlParameter("@Address", SqlDbType.NVarChar, 40)).Value =
HttpUtility.HtmlEncode(address);
myCommand.Parameters.Add(new SqlParameter("@City", SqlDbType.NVarChar, 20)).Value = HttpUtility.HtmlEncode(city);
myCommand.Parameters.Add(new SqlParameter("@State", SqlDbType.NChar, 2)).Value = HttpUtility.HtmlEncode(state);
myCommand.Parameters.Add(new SqlParameter("@Zip", SqlDbType.NChar, 5)).Value = HttpUtility.HtmlEncode(zip);
myCommand.Parameters.Add(new SqlParameter("@Contract", SqlDbType.Int)).Value = contract;
try
{
myCommand.Connection.Open();
myCommand.ExecuteNonQuery();
}
catch (SqlException e)
{
if (e.Number == 2627)
throw new RecordExistException("A record already exists with the same primary key");
else
throw new UnknownException();
}
finally
{
if (myCommand.Connection != null && myCommand.Connection.State == ConnectionState.Open)
myCommand.Connection.Close();
}
}
}
}
4. web.config:
<configuration>
<appSettings>
<add key="ConnectionString" value="server=(local)\\NetSDK;database=pubs;Trusted_Connection=yes"/>
</appSettings>
</configuration>
可以看出AuthorList.aspx.cs里纯粹是操作View里的对象以及Controller的编码,不再包含Model的编码。这些编码已经被移到另一个类,这个类完全可以属于一个新的类库项目。原先对SQL异常的处理,也改成了Model类抛出跟应用有关的异常。
当然,还有很多不满意之处,譬如AuthorList.aspx里的DataBinder.Eval里,还包含着数据库表的字段名(!),AuthorList.aspx.cs里对MyDataGrid里模板里的控件的处理也显得有些不干净。Author.cs类居然依赖System.Web (因为用了System.Web.HttpUtility类),SQL语句的处理也不满意,想象一下假如有很多这样的类的话,编码会有很多重复。依赖于web.config里的设置也不好,因为这样或多或少意味着这个类跟当前的aspx页的部署关系
Bruce Tate 在他的书《Bitter Java》里谈到了Server-side Java 中的 antipatterns ,其中一个叫做“Magic Servlet” 。在这个Servlet 里,混杂了model,view和controller的编码,搞得责任不清,维护或扩展起来很麻烦。
其实在ASP.NET世界里也一样,如果你在论坛上混长了,类似下面的编码随处可见,我们不妨叫它为 “ Magic Page”,
。问题是,这些红字的语句是否应该出现在这里?
(注:我知道这是个样品程序而已,求全责备恐怕是有点荒唐,但类似的编码在论坛上的帖子里经常出现,到底是什么原因呢?是程序员技术不精还是模仿造成的?)
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<head>
<script language="C#" runat="server">
SqlConnection myConnection;
public Hashtable StateIndex;
protected void Page_Load(Object Src, EventArgs E)
{
myConnection = new SqlConnection("server=(local)\\NetSDK;database=pubs;Trusted_Connection=yes");
if (!IsPostBack)
BindGrid();
StateIndex = new Hashtable();
StateIndex["CA"] = 0;
StateIndex["IN"] = 1;
StateIndex["KS"] = 2;
StateIndex["MD"] = 3;
StateIndex["MI"] = 4;
StateIndex["OR"] = 5;
StateIndex["TN"] = 6;
StateIndex["UT"] = 7;
}
public void MyDataGrid_Edit(Object sender, DataGridCommandEventArgs E)
{
MyDataGrid.EditItemIndex = (int)E.Item.ItemIndex;
BindGrid();
}
public void MyDataGrid_Cancel(Object sender, DataGridCommandEventArgs E)
{
MyDataGrid.EditItemIndex = -1;
BindGrid();
}
public void MyDataGrid_Update(Object sender, DataGridCommandEventArgs E)
{
if (Page.IsValid)
{
String updateCmd = "UPDATE Authors SET au_id = @Id, au_lname = @LName, au_fname = @FName, phone = @Phone, "
+ "address = @Address, city = @City, state = @State, zip = @Zip, contract = @Contract where au_id = @Id";
SqlCommand myCommand = new SqlCommand(updateCmd, myConnection);
myCommand.Parameters.Add(new SqlParameter("@Id", SqlDbType.NVarChar, 11));
myCommand.Parameters.Add(new SqlParameter("@LName", SqlDbType.NVarChar, 40));
myCommand.Parameters.Add(new SqlParameter("@FName", SqlDbType.NVarChar, 20));
myCommand.Parameters.Add(new SqlParameter("@Phone", SqlDbType.NChar, 12));
myCommand.Parameters.Add(new SqlParameter("@Address", SqlDbType.NVarChar, 40));
myCommand.Parameters.Add(new SqlParameter("@City", SqlDbType.NVarChar, 20));
myCommand.Parameters.Add(new SqlParameter("@State", SqlDbType.NChar, 2));
myCommand.Parameters.Add(new SqlParameter("@Zip", SqlDbType.NChar, 5));
myCommand.Parameters.Add(new SqlParameter("@Contract", SqlDbType.NVarChar,1));
myCommand.Parameters["@Id"].Value = MyDataGrid.DataKeys[(int)E.Item.ItemIndex];
String[] cols = {"LName","FName","Phone","Address","City","Zip"};
for (int i=0; i<6; i++)
{
String colvalue = ((TextBox)E.Item.FindControl("edit_" + cols[i])).Text;
myCommand.Parameters["@" + cols[i]].Value = Server.HtmlEncode(colvalue);
}
myCommand.Parameters["@State"].Value = ((DropDownList)E.Item.FindControl("edit_State")).SelectedItem.ToString();
if (((CheckBox)E.Item.FindControl("edit_Contract")).Checked = true)
myCommand.Parameters["@Contract"].Value = "1";
else
myCommand.Parameters["@Contract"].Value = "0";
myCommand.Connection.Open();
try
{
myCommand.ExecuteNonQuery();
Message.InnerHtml = "<b>Record Updated</b><br>" + updateCmd;
MyDataGrid.EditItemIndex = -1;
}
catch (SqlException e)
{
if (e.Number == 2627)
Message.InnerHtml = "ERROR: A record already exists with the same primary key";
else
Message.InnerHtml = "ERROR: Could not update record, please ensure the fields are correctly filled out";
Message.Style["color"] = "red";
}
myCommand.Connection.Close();
BindGrid();
}
else
{
Message.InnerHtml = "ERROR: Please check each field for error conditions.";
Message.Style["color"] = "red";
}
}
private void MyDataGrid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.EditItem)
{
for (int i = 0; i < e.Item.Controls.Count; i++)
{
try
{
if (e.Item.Controls[i].Controls[1].GetType().ToString() == "System.Web.UI.WebControls.TextBox")
{
TextBox tb = (TextBox)e.Item.Controls[i].Controls[1];
tb.Text = Server.HtmlDecode(tb.Text);
}
}
catch
{
}
}
}
}
public void BindGrid()
{
SqlDataAdapter myCommand = new SqlDataAdapter("select * from Authors", myConnection);
DataSet ds = new DataSet();
myCommand.Fill(ds, "Authors");
MyDataGrid.DataSource=ds.Tables["Authors"].DefaultView;
MyDataGrid.DataBind();
}
</script>
</head>
<body style="font: 10pt verdana">
<form runat="server">
<h3><font face="Verdana">Updating a Row of Data w/ Validation</font></h3>
<span id="Message" EnableViewState="false" style="font: arial 11pt;" runat="server"/><p>
<ASP:DataGrid id="MyDataGrid" runat="server"
Width="800"
BackColor="#ccccff"
BorderColor="black"
ShowFooter="false"
CellPadding=3
CellSpacing="0"
Font-Name="Verdana"
Font-Size="8pt"
HeaderStyle-BackColor="#aaaadd"
OnEditCommand="MyDataGrid_Edit"
OnCancelCommand="MyDataGrid_Cancel"
OnUpdateCommand="MyDataGrid_Update"
DataKeyField="au_id"
AutoGenerateColumns="false"
OnItemDataBound="MyDataGrid_ItemDataBound"
>
<Columns>
<asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update" ItemStyle-Wrap="false"/>
<asp:BoundColumn HeaderText="au_id" SortExpression="au_id" ReadOnly="True" DataField="au_id" ItemStyle-Wrap="false"/>
<asp:TemplateColumn HeaderText="au_lname" SortExpression="au_lname">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/>
</ItemTemplate>
<EditItemTemplate>
<nobr>
<asp:TextBox runat="server" id="edit_LName" Text='<%# DataBinder.Eval(Container.DataItem, "au_lname") %>'/>
<asp:RequiredFieldValidator id="au_lnameReqVal"
ControlToValidate="edit_LName"
Display="Dynamic"
Font-Name="Verdana" Font-Size="12"
runat=server>
*
</asp:RequiredFieldValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="au_fname" SortExpression="au_fname">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/>
</ItemTemplate>
<EditItemTemplate>
<nobr>
<asp:TextBox runat="server" id="edit_FName" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>'/>
<asp:RequiredFieldValidator id="au_fnameReqVal"
ControlToValidate="edit_FName"
Display="Dynamic"
Font-Name="Verdana" Font-Size="12"
runat=server>
*
</asp:RequiredFieldValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="phone" SortExpression="phone">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/>
</ItemTemplate>
<EditItemTemplate>
<nobr>
<asp:TextBox runat="server" id="edit_Phone" Text='<%# DataBinder.Eval(Container.DataItem, "phone") %>'/>
<asp:RequiredFieldValidator id="phoneReqVal"
ControlToValidate="edit_Phone"
Display="Dynamic"
Font-Name="Verdana" Font-Size="12"
runat=server>
*
</asp:RequiredFieldValidator>
<asp:RegularExpressionValidator id="phoneRegexVal"
ControlToValidate="edit_Phone"
ValidationExpression="[0-9]{3} [0-9]{3}-[0-9]{4}"
Display="Dynamic"
Font-Name="Arial" Font-Size="11"
runat=server>
* Phone must be in form: XXX XXX-XXXX <br>
</asp:RegularExpressionValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="address" SortExpression="address">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" id="edit_Address" Text='<%# DataBinder.Eval(Container.DataItem, "address") %>'/>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="city" SortExpression="city">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" id="edit_City" Text='<%# DataBinder.Eval(Container.DataItem, "city") %>'/>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="state" SortExpression="state">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "state") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:DropDownList runat="server" SelectedIndex='<%# StateIndex[(String)((DataRowView)Container.DataItem)["state"]] %>' id="edit_State">
<asp:ListItem>CA</asp:ListItem>
<asp:ListItem>IN</asp:ListItem>
<asp:ListItem>KS</asp:ListItem>
<asp:ListItem>MD</asp:ListItem>
<asp:ListItem>MI</asp:ListItem>
<asp:ListItem>OR</asp:ListItem>
<asp:ListItem>TN</asp:ListItem>
<asp:ListItem>UT</asp:ListItem>
</asp:DropDownList>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="zip" SortExpression="zip">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:TextBox runat="server" id="edit_Zip" Text='<%# DataBinder.Eval(Container.DataItem, "zip") %>'/>
<asp:RegularExpressionValidator id="RegularExpressionValidator1"
ASPClass="RegularExpressionValidator" ControlToValidate="edit_Zip"
ValidationExpression="[0-9]{5}"
Display="Dynamic"
Font-Name="Arial" Font-Size="11"
runat=server>
* Zip Code must be 5 numeric digits <br>
</asp:RegularExpressionValidator>
</EditItemTemplate>
</asp:TemplateColumn>
<asp:TemplateColumn HeaderText="contract" SortExpression="contract">
<ItemTemplate>
<asp:Label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "contract", "{0}") %>'/>
</ItemTemplate>
<EditItemTemplate>
<asp:CheckBox runat="server" id="edit_Contract" Checked='<%# DataBinder.Eval(Container.DataItem, "contract") %>'/>
</EditItemTemplate>
</asp:TemplateColumn>
</Columns>
</ASP:DataGrid>
</form>
</body>
</html>
跟本帖子的主题不相关,在上面的例程中,还有一个不懂的地方是,每次更新一条数据,为什么需要对整个DataGrid做DataBind()?为什么不能对当前这个DataGridItem做重新DataBind()?(DataGridItem是支持DataBind()的,但无法改变当前这个DataGridItem的ItemType)。。。。。。我的看法是,选择是否对整个DataGrid重新做DataBind()应该是高层次的policy,应该由应用的需求或程序员来决定,而不应该通过控件来强制执行
页面很长的话,要在PostBack后保持PostBack前的页面位置,在ASP.NET 1.*里可以使用SmartNavigation。但SmartNavigation有不少问题,包括在IE外的浏览器里不工作等。对此,解决方案是,在页面PostBack前,通过客户端脚本和隐藏控件记住当前页面位置,在PostBack后,把页面位置赋值给隐藏控件,然后在页面装载后用客户端脚本来设置页面位置,譬如可以参考CodeProejct上的这篇文章
Crossbrowser SmartNavigation Alternative
ASP.NET 2.0里提供了这样的一个方案,可以通过设置
<%@ Page MaintainScrollPositionOnPostBack="true" %>
或在编码里设置
Page.MaintainScrollPositionOnPostBack = true;
或在web.config里设置
<pages maintainScrollPositionOnPostBack="true" />
来达成。
用Reflector查看,在Page类的ProcessRequestMain里有这样的一句
if (this.MaintainScrollPositionOnPostBack)
{
this.LoadScrollPosition();
}
在LoadScrollPosition()里,
internal void LoadScrollPosition()
{
if ((this._previousPagePath == null) && (this._requestValueCollection != null))
{
string text1 = this._requestValueCollection["__SCROLLPOSITIONX"];
if ((text1 != null) && !int.TryParse(text1, out this._scrollPositionX))
{
this._scrollPositionX = 0;
}
string text2 = this._requestValueCollection["__SCROLLPOSITIONY"];
if ((text2 != null) && !int.TryParse(text2, out this._scrollPositionY))
{
this._scrollPositionY = 0;
}
}
}
从提交值里的__SCROLLPOSITIONX和__SCROLLPOSITIONY获取值并且存到成员变量里,然后在BeginFormRender(由HtmlForm的RenderChildren调用)里输出到页面
if (this.MaintainScrollPositionOnPostBack && !this._requireScrollScript)
{
this.ClientScript.RegisterHiddenField("__SCROLLPOSITIONX", this._scrollPositionX.ToString(CultureInfo.InvariantCulture));
this.ClientScript.RegisterHiddenField("__SCROLLPOSITIONY", this._scrollPositionY.ToString(CultureInfo.InvariantCulture));
this.ClientScript.RegisterStartupScript(typeof(Page), "PageScrollPositionScript", "\r\ntheForm.oldSubmit = theForm.submit;\r\ntheForm.submit = WebForm_SaveScrollPositionSubmit;\r\n\r\ntheForm.oldOnSubmit = theForm.onsubmit;\r\ntheForm.onsubmit = WebForm_SaveScrollPositionOnSubmit;\r\n" + (this.IsPostBack ? "\r\ntheForm.oldOnLoad = window.onload;\r\nwindow.onload = WebForm_RestoreScrollPosition;\r\n" : string.Empty), true);
this.RegisterWebFormsScript();
this._requireScrollScript = true;
}
看一下页面输出,有下面这样的语句
<script src="/WebSite5/WebResource.axd?d=oNWBOJESyPoXDbbBArijOg2&t=632619935818125000" type="text/javascript"></script>
......
<div>
<input type="hidden" name="__SCROLLPOSITIONX" id="__SCROLLPOSITIONX" value="0" />
<input type="hidden" name="__SCROLLPOSITIONY" id="__SCROLLPOSITIONY" value="547" />
......
</div>
<script type="text/javascript">
<!--
theForm.oldSubmit = theForm.submit;
theForm.submit = WebForm_SaveScrollPositionSubmit; //<-保存当前页面位置
theForm.oldOnSubmit = theForm.onsubmit;
theForm.onsubmit = WebForm_SaveScrollPositionOnSubmit; //<-保存当前页面位置
theForm.oldOnLoad = window.onload;
window.onload = WebForm_RestoreScrollPosition; //<-设置页面位置
// -->
</script>
WebResource.axd提供了对应的脚本
function WebForm_GetScrollX() {
if (__nonMSDOMBrowser) {
return window.pageXOffset;
}
else {
if (document.documentElement && document.documentElement.scrollLeft) {
return document.documentElement.scrollLeft;
}
else if (document.body) {
return document.body.scrollLeft;
}
}
return 0;
}
function WebForm_GetScrollY() {
if (__nonMSDOMBrowser) {
return window.pageYOffset;
}
else {
if (document.documentElement && document.documentElement.scrollTop) {
return document.documentElement.scrollTop;
}
else if (document.body) {
return document.body.scrollTop;
}
}
return 0;
}
function WebForm_SaveScrollPositionSubmit() {
if (__nonMSDOMBrowser) {
theForm.elements['__SCROLLPOSITIONY'].value = window.pageYOffset;
theForm.elements['__SCROLLPOSITIONX'].value = window.pageXOffset;
}
else {
theForm.__SCROLLPOSITIONX.value = WebForm_GetScrollX();
theForm.__SCROLLPOSITIONY.value = WebForm_GetScrollY();
}
if ((typeof(this.oldSubmit) != "undefined") && (this.oldSubmit != null)) {
return this.oldSubmit();
}
return true;
}
function WebForm_SaveScrollPositionOnSubmit() {
theForm.__SCROLLPOSITIONX.value = WebForm_GetScrollX();
theForm.__SCROLLPOSITIONY.value = WebForm_GetScrollY();
if ((typeof(this.oldOnSubmit) != "undefined") && (this.oldOnSubmit != null)) {
return this.oldOnSubmit();
}
return true;
}
function WebForm_RestoreScrollPosition() {
if (__nonMSDOMBrowser) {
window.scrollTo(theForm.elements['__SCROLLPOSITIONX'].value, theForm.elements['__SCROLLPOSITIONY'].value);
}
else {
window.scrollTo(theForm.__SCROLLPOSITIONX.value, theForm.__SCROLLPOSITIONY.value);
}
if ((typeof(theForm.oldOnLoad) != "undefined") && (theForm.oldOnLoad != null)) {
return theForm.oldOnLoad();
}
return true;
}
这个方案应该对常用的浏览器都有效
ASP.NET 2.0 快速入门教程里很简要地提到了VirtualPathProvider,听上去很powerful,查看了一下文档,大概是说,
“。。。。
The VirtualPathProvider类提供了一套方法以在一个Web 应用里实现虚拟文件系统。在虚拟文件系统里,文件和文件夹是由一个 data store 来管理的,而不是由通常的服务器操作系统的文件系统所管理。譬如,你可以使用虚拟文件系统来在 SQL Server 数据库里存储内容。
在这个虚拟文件系统里,你可以存储下面这些文件
1。ASP.NET 页面, master页面,用户控件以及其他对象
2。以.htm 和.jpg为扩展名的.标准的网页
3。映射到BuildProvider实例的任何自定义扩展文件
4。存在App_Theme文件夹的任何赋名的 theme
但不能存储那些产生application-level assemblies的文件和文件夹,譬如不能存储
1。Global.asax
2。Web.config
3。应用级的文件夹,Bin,App_Code, App_GlobalResources,以及App_LocalResources
4。应用的数据文件夹,App_Data.
。。。”
文档提供了一个例子,通过一个 DataSet 对象里的信息实现一个虚拟文件系统。
看完后并没有留下很深的印象,直到看到了Scott Guthrie的演示里的例子,由一个Access文件提供的文件系统时,真的感觉这东西真的很酷,感兴趣者可以去Scott Guthrie的网站下载对应的PPT和编码例子。
ASP.NET 2.0 Tips/Tricks Slides + Demos
下面是我依样画葫芦写的一个简单的Provider例子,主要是用来验证可以使用codefile。想要试着运行的话,生成一个虚拟目录,然后建立一个App_Code文件夹,把下面内容存成一个.cs文件,然后在浏览器里访问
http://localhost/你的应用名字/abc/def.aspx
应用名字后面的文件夹子,文件名是任意的,只要是以.aspx结尾
===============================
using System;
using System.IO;
using System.Text;
using System.Web.Caching;
using System.Web.Hosting;
using System.Collections;
namespace JoyCode
{
class SimpleVirtualFile : VirtualFile
{
string path;
public SimpleVirtualFile(string virtualPath) : base(virtualPath)
{
path = virtualPath.ToLower();
}
public override bool IsDirectory
{
get
{
if(path.EndsWith(".aspx") || path.EndsWith(".cs"))
return false;
return true;
}
}
public override Stream Open()
{
string Html = @"<%@ Page Language=""C#"" CodeFile=""Test.aspx.cs"" Inherits=""ASPNET20.Test_aspx""%>
hello world: <%=DateTime.Now%><BR>";
string Code = @"using System;
using System.Web.UI;
namespace ASPNET20
{
public partial class Test_aspx : Page
{
void Page_Load(Object sender, EventArgs e)
{
Response.Write(""hello world from Test_aspx.Page_Load:"" + DateTime.Now + ""<BR>"");
}
}
}
";
Stream stream = null;
if (!IsDirectory)
{
if (path.EndsWith(".aspx"))
stream = new MemoryStream(Encoding.ASCII.GetBytes(Html));
else if (path.EndsWith(".cs"))
stream = new MemoryStream(Encoding.ASCII.GetBytes(Code));
}
if (stream != null)
stream.Seek(0, SeekOrigin.Begin);
return stream;
}
}
public class SimpleVirtualPathProvider : VirtualPathProvider
{
public static void AppInitialize()
{
SimpleVirtualPathProvider provider = new SimpleVirtualPathProvider();
HostingEnvironment.RegisterVirtualPathProvider(provider);
}
public override bool FileExists(string virtualPath)
{
virtualPath = virtualPath.ToLower();
if (virtualPath.EndsWith(".aspx") || virtualPath.EndsWith(".cs"))
return true;
else
return false;
}
public override bool DirectoryExists(string virtualDir)
{
return !FileExists(virtualDir);
}
public override VirtualFile GetFile(string virtualPath)
{
return new SimpleVirtualFile(virtualPath);
}
//this prevents an error if you have a path with folder names
//参考Valvert 在下面这个帖子里的答复
//http://forums.asp.net/921762/ShowPost.aspx
public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
{
return null;
}
}
}
看到这个问题,答案是需要在配置文件里修改MembershipProvider的设置,
<connectionStrings>
<add name="MySqlConnection" connectionString="Data Source=MySqlServer;Initial Catalog=aspnetdb;Integrated Security=SSPI;" />
</connectionStrings>
<system.web>
.....
<membership defaultProvider="SqlProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add
name="SqlProvider"
type="System.Web.Security.SqlMembershipProvider"
connectionStringName="MySqlConnection"
applicationName="MyApplication"
minRequiredNonalphanumericCharacters="0"
minRequiredPasswordLength="6"
passwordStrengthRegularExpression="......" />
</providers>
</membership>
参考MSDN文章
How To: Use Membership in ASP.NET 2.0
How To: Use Forms Authentication with SQL Server in ASP.NET 2.0
以及Mark Michaelis这个blog:
Passwords in VS 2005 Beta 2's Personal Website Starter Kit
根据Lutz Roeder的Reflector对System.Web.dll的反编译,下面是HttpApplication,Page和Control对象的主要方法和事件的流程
| | PostBack |
| | Page/Control Event |
| | CallBack |
| HttpApplication | Page | Control |
|
BeginRequest
|
|
|
|
AuthenticateRequest
|
|
|
|
DefaultAuthentication
|
|
|
|
PostAuthenticateRequest
|
|
|
|
AuthorizeRequest
|
|
|
|
PostAuthorizeRequest
|
|
|
|
ResolveRequestCache
|
|
|
|
PostResolveRequestCache
|
|
|
|
MapHttpHandler
|
Construct
|
|
|
PostMapRequestHandler
|
|
|
|
AcquireRequestState (Session)
|
|
|
|
PostAcquireRequestState
|
|
|
|
PreRequestHandlerExecute
|
|
|
|
CallHandler
|
DeterminePostBackMode
|
|
|
LoadScrollPosition
|
|
PerformPreInit
--PreInit
--InitializeThemes
--ApplyMasterPage
|
|
InitRecursive
(--ResolveAdapter
--ApplySkin
--Init
--TrackViewState)
|
ResolveAdapter
|
|
InitRecursive
|
|
ApplySkin
|
|
Init
|
|
TrackViewState
|
|
InitComplete
|
|
LoadAllState
--LoadPageStateFromPersistenceMedium
--LoadViewStateRecursive
|
LoadControlStateInternal
--LoadControlState
|
LoadViewStateRecursive
--LoadViewState
|
|
ProcessPostData
|
|
|
PreLoad
|
|
LoadRecursive
(--Load)
|
LoadRecursive
--Load
|
|
ProcessPostData
|
|
|
RaiseChangedEvents
|
|
|
RaisePostBackEvent
|
|
|
LoadComplete
|
|
RaiseCallbackEvent
--return
|
|
PreRenderRecursiveInternal
(--PreRender)
|
PreRenderRecursiveInternal
--PreRender
|
|
ExecuteRegisteredAsyncTasks **
|
|
PerformPreRenderComplete
--PreRenderComplete
|
|
SaveAllState
--SaveViewStateRecursive
--SavePageStateToPersistenceMedium
|
SaveControlStateInternal
--SaveControlState
|
SaveViewState
--SaveViewStateRecursive
|
|
SaveStateComplete
|
|
|
RenderControl
|
RenderControl
--RenderControlInternal
----Render
------RenderChildren
--------RenderChildrenInternal
|
|
PostRequestHandlerExecute
|
|
|
|
ReleaseRequestState (Session)
|
|
|
|
PostReleaseRequestState
|
|
|
|
CallFilter -- Response.FilterOutput
|
|
|
|
UpdateRequestCache
|
|
|
|
PostUpdateRequestCache
|
|
|
|
EndRequest
|
|
|
** 参考 Jeff Prosise在MSND杂志10月期的文章
Asynchronous Pages in ASP.NET 2.0