转自:http://www.cnblogs.com/tedzhao/archive/2008/05/10/1190772.html
在自定义控件的开发过程中,我们经常要给控件添加一些复杂类型的属性。利用声明持久性(Declarative Persistence)可使得页面开发人员能够让页面开发人员在ASP.NET页面中,声明性地设置这些复杂属性值,而无需编写任何C#或者VB.NET代码。
参见下面的例子:
- GridView的DataKeyNames属性,其数据类型是string[]:
<asp:GridView ID="GridView1" runat="server" DataKeyNames="ID, Title, Author">
</asp:GridView>
GridView的RowStyle属性,其数据类型是System.Web.UI.WebControls.TableItemStyle:
<asp:GridView ID="GridView1" runat="server">
<RowStyle BackColor="Red" ForeColor="Black"/>
</asp:GridView>
GridView的Columns属性,其数据类型是:System.Web.UI.WebControls.DataControlFieldCollection
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1" >
<Columns>
<asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
<asp:BoundField DataField="ID" HeaderText="ID" SortExpression="ID" />
</Columns>
</asp:GridView>
GridView的PagerTemplate属性, 其数据类型是System.Web.UI.ITemplate:
<asp:GridView ID="GridView1" runat="server">
<PagerTemplate>
<div>
<span>Pager Template</span>
</div>
</PagerTemplate>
</asp:GridView>
那如何才能实现在ASPX中声明性地设置这些复杂属性哪?
下面我将逐一讲述这些属性背后的故事,本文的重点不在于如何维护这些属性的状态,而是如何由ASPX Markup到复杂属性的构建。
一、由ASPX Markup 到C# 或者VB.NET class
1.ASP.NET管道
对于ASPX页面的请求,ASP.NET管道的目标是找到一个完全代表被请求页面的托管类,如果该类不存在,则即时创建并且编译。
ASP.NET页面由标记(Markup)和代码(Codebehind)文件组成,整个页面编译过程包含两个主要步骤:首先将ASPX Markup装换成一个适合ASP.NET类层次结构的C#或者VB.NET临时类,我们可以从ASP.NET的临时文件夹中找到包含该类的文件;其次将该临时类编译成一个程序集,最后将得到的程序集装入托管该应用程序的AppDomain中。
对于特定的请求,HttpApplication对象从config文件中获取处理对象以服务该请求,通过下面的代码片断我们可以看出.aspx资源与PageHandlerFactory相关联。 在ASP.NET管道中PageHandlerFactory对象将创建当前请求页面的实例,随后将请求交由该页面处理。
<httpHandlers>
<add path="*.aspx" verb="*" type="System.Web.UI.PageHandlerFactory" validate="True"/>
<add path="*.ashx" verb="*" type="System.Web.UI.SimpleHandlerFactory" validate="True"/>
</httpHandlers>
2.PageHandlerFactory
PageHandlerFactory负责找到包含请求页面类的程序集,如果该程序集还没有被创建,则即时动态创建。请求页面类是通过解析ASPX资源的Markup代码创建的,并且存放在ASP.NET的临时文件夹%AppData%"Local"Temp"Temporary ASP.NET Files中。
3.ControlBuilder
而ControlBuilder类就是负责将ASPX Markup声明解析成为ASP.NET Server控件,通常页面上的每个控件都有一个默认的 ControlBuilder 类相关联。在ASPX页面解析过程中,ASP.NET 页框架首先会生成与页面控件树对应的 ControlBuilder 对象树,然后 ControlBuilder 树用于生成页代码并创建控件树。
ControlBuilder 定义了如何解析控件标记中的内容的,我们可以通过自定义ControlBuilder类来重写此默认行为。
在页面解析过程中,ControlBuilder将会检查ASP.NET Server控件是否标示了ParseChildren(true) Attribute。如果被标示则该控件内部嵌套的子节点将被解析为控件的子属性,否则该节点会被解析为ASP.NET Server控件,并添加到原控件的Controls集合,关于该Attribute见下一节。
二、相关的Attributes
1.ParseChildrenAttribute
ParseChildrenAttribute应用于自定义控件类上,该Attribute将会告诉ASPX页面解析器如何解析自定义控件内部的嵌套节点。
下表详细的描述了ParseChildrenAttribute的用法:
Attribute Usage
描述
ParseChildren(true)
嵌套的子节点必须对应着当前控件的属性,如果找不到对应属性将会产生一个解析错误。另外在当前控件的Tag内部也不允许任何文字节点。
例子:Repeater 以及其他数据绑定控件。
ParseChildrenAttribute(true, "PropertyName")
当前控件必须包含一个Public的属性,属性名等同于参数PropertyName。该属性应该是一个集合类的数据类型。
而嵌套的字节点必须对应着该属性的子Element.
例子:HtmlTable, HtmlTableRow控件。
ParseChildrenAttribute(false)
ParseChildrenAttribute(false, "PropertyName")
ParseChildrenAttribute is not applied to the control.
嵌套的子节点必须是ASP.NET 服务器控件。页面解析器会根据该节点创建一个子控件,然后在当前控件上调用IParserAccessor.AddParsedSubObject方法,该方法的默认实现是将解析到的子控件添加到当前控件的Controls集合。
任何Literal文字节点将被创建为LiteralControl的示例。
例子:Panel控件。
ParseChildrenAttribute (Type childControlType)
嵌套的子节点必须是指定的ASP.NET 服务器控件类型。
例子:MultiView控件。
WebControl类上已经被ParseChildrenAttribute(True)标示了,所以每个直接或者间接从WebControl派生的控件都会默认支持内部属性声明持久性。
在下面的例子中RowStyle节点将被解析为GridView控件的子属性:
<asp:GridView ID="GridView1" runat="server">
<RowStyle BackColor="Red" ForeColor="Black"/>
</asp:GridView>
2. PersistChildrenAttribute
PersistChildrenAttribute应用于自定义控件类上,是一个DesignTime的Attribute。用于指定是否将自定义控件内部的嵌套节点解析为子控件,True将意味着解析该节点为控件。
WebControl类上已经被PersistChildrenAttribute (False)标示了,而Panel类则被PersistChildrenAttribute (True)标示。
示例:
[ParseChildren(false), PersistChildren(true)]
public class MyControl : WebControl
{ }
在设计时添加一个Button控件到MyControl中:
<cc1:MyControl2 ID="MyControl21" runat="server" BorderStyle="Dotted" Height="56px" Width="349px">
<asp:Button ID="Button2" runat="server" Text="Button" />
</cc1:MyControl2>
这个时候用户可以在VS IDE的Design View中选中子Button控件,而如果MyControl被PersistChildrenAttribute (False)标示的话,子控件Button不能被选中。
3. PersistenceModeAttribute
PersistenceModeAttribute应用在ASP.NET 服务器控件属性上,是一个DesignTime的Attribute,用于指定用如何在设计时将ASP.NET Server控件属性(Property)保存到ASP.NET 页面
或者说在 ASPX Mrakup中以何种方式声明该属性。
PersistenceMode.InnerProperty则指定属性在 ASP.NET 服务器控件中保持为嵌套标记。
三、再看例子
1. GridView的DataKeyNames属性,其数据类型是string[]:
<asp:GridView ID="GridView1" runat="server" DataKeyNames="ID, Title, Author">
</asp:GridView>
默认情况下ASP.NET页面解析引擎会将ASPX Markup中赋予DataKeyNames属性的值直接设置到该属性上,由于该属性的数据类型为string[],直接设置将会失败。
那GridView做了些什么哪,见下面的代码片断:
[TypeConverter(typeof(StringArrayConverter))]
public virtual string[] DataKeyNames
{get; set;}
也就是说通过给属性添加特定的TypeConvertor来实现属性的设置,在上面的例子中StringArrayConvter将string装化为string[]后,设置到DataKeyNames属性上。
2. GridView的RowStyle属性,其数据类型是TableRowStyle:
<asp:GridView ID="GridView1" runat="server">
<RowStyle BackColor="Red" ForeColor="Black"/>
</asp:GridView>
由于GridView控件已经标示了ParseChildrenAttribute(True),所以其内部嵌套的节点将被解析为自身的属性。
另外RowStyle属性也添加了PersistenceModeAttribute来指定如何生成ASPX Markup代码。
[PersistenceMode(PersistenceMode.InnerProperty)]
public TableItemStyle RowStyle
{get;}
3. GridView的Columns属性,其数据类型是DataFieldCollection:
<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1" >
<Columns>
<asp:BoundField DataField="Title" HeaderText="Title" SortExpression="Title" />
<asp:BoundField DataField="ID" HeaderText="ID" SortExpression="ID" />
</Columns>
</asp:GridView>
ASP.NET页面是如何来解析上面的一段代码哪?首先Columns节点将被解析为GridView的属性,可是Columns内部的子节点是如何被解析并添加到Columns集合中去的哪?
另外在VS IDE Source View中准备添加Columns属性的子节点代码的时候,为什么VS IDE会帮我们列出所有可实例化的子类型?
让我们来看一下Columns属性的数据类型:DataControlFieldCollection.
ASP.NET页面解析器在解析控件属性的时候,如果发现该属性实现了IList接口,解析完成该属性的子节点后,会去调用该属性的Add方法来添加这些子Element。
另外在设计时也会通过该属性数据类型的Item子属性获取可以实例化的子Element类型,并且通过智能提示表现出来。
也就是说这些集合类属性必须实现IList接口才可以实现属性的声明持久化。
看一下上面示例的Markup代码对应的C#代码,注意代码行16,17。
1[System.Diagnostics.DebuggerNonUserCodeAttribute()]
2private global::System.Web.UI.WebControls.BoundField @__BuildControl__control23()
3{
4 global::System.Web.UI.WebControls.BoundField @__ctrl;
5 @__ctrl = new global::System.Web.UI.WebControls.BoundField();
6 @__ctrl.DataField = "ID";
7 @__ctrl.HeaderText = "ID";
8 @__ctrl.SortExpression = "ID";
9 return @__ctrl;
10}
11
12[System.Diagnostics.DebuggerNonUserCodeAttribute()]
13private void @__BuildControl__control22(System.Web.UI.WebControls.DataControlFieldCollection @__ctrl)
14{
15 global::System.Web.UI.WebControls.BoundField @__ctrl1;
16 @__ctrl1 = this.@__BuildControl__control23();
17 @__ctrl.Add(@__ctrl1);
18}
19
20[System.Diagnostics.DebuggerNonUserCodeAttribute()]
21private global::System.Web.UI.WebControls.GridView @__BuildControlGridView1()
22{
23 global::System.Web.UI.WebControls.GridView @__ctrl;
24 @__ctrl = new global::System.Web.UI.WebControls.GridView();
25 this.GridView1 = @__ctrl;
26 @__ctrl.ApplyStyleSheetSkin(this);
27 @__ctrl.ID = "GridView1";
28
29 this.@__BuildControl__control22(@__ctrl.Columns);
30
31 return @__ctrl;
32}
参照上面说的几点我们就可以写出自定义的集合类属性的声明持久化。
当然了这只是实现了ASPX到CS,我们还需要给集合类以及子Element添加关于状态管理的代码(实现IStateManager接口)才可以在实际项目中使用,这一点我就不再赘述了。
4. GridView的PagerTemplate属性,其数据类型是ITemplate:
其实ITemplate节点内部就是一个控件集合,TemplateBuilder负责将该Tag构建成为一个控件组。
在运行时我们通过ITemplate接口的InstantiateIn方法将一个实例化的Template放到指定的Container中去。
全文完。
我们继续讲解LINQ to SQL语句,这篇我们来讨论Group By/Having操作符和Exists/In/Any/All/Contains操作符。
Group By/Having操作符
适用场景:分组数据,为我们查找数据缩小范围。
说明:分配并返回对传入参数进行分组操作后的可枚举对象。分组;延迟
1.简单形式:
var q =
from p in db.Products
group p by p.CategoryID into g
select g;
语句描述:使用Group By按CategoryID划分产品。
说明:from p in db.Products 表示从表中将产品对象取出来。group p by p.CategoryID into g表示对p按CategoryID字段归类。其结果命名为g,一旦重新命名,p的作用域就结束了,所以,最后select时,只能select g。当然,也不必重新命名可以这样写:
var q =
from p in db.Products
group p by p.CategoryID;
我们用示意图表示:

如果想遍历某类别中所有记录,这样:
foreach (var gp in q)
{
if (gp.Key == 2)
{
foreach (var item in gp)
{
//do something
}
}
}
2.Select匿名类:
var q =
from p in db.Products
group p by p.CategoryID into g
select new { CategoryID = g.Key, g };
说明:在这句LINQ语句中,有2个property:CategoryID和g。这个匿名类,其实质是对返回结果集重新进行了包装。把g的property封装成一个完整的分组。如下图所示:

如果想遍历某匿名类中所有记录,要这么做:
foreach (var gp in q)
{
if (gp.CategoryID == 2)
{
foreach (var item in gp.g)
{
//do something
}
}
}
3.最大值
var q =
from p in db.Products
group p by p.CategoryID into g
select new {
g.Key,
MaxPrice = g.Max(p => p.UnitPrice)
};
语句描述:使用Group By和Max查找每个CategoryID的最高单价。
说明:先按CategoryID归类,判断各个分类产品中单价最大的Products。取出CategoryID值,并把UnitPrice值赋给MaxPrice。
4.最小值
var q =
from p in db.Products
group p by p.CategoryID into g
select new {
g.Key,
MinPrice = g.Min(p => p.UnitPrice)
};
语句描述:使用Group By和Min查找每个CategoryID的最低单价。
说明:先按CategoryID归类,判断各个分类产品中单价最小的Products。取出CategoryID值,并把UnitPrice值赋给MinPrice。
5.平均值
var q =
from p in db.Products
group p by p.CategoryID into g
select new {
g.Key,
AveragePrice = g.Average(p => p.UnitPrice)
};
语句描述:使用Group By和Average得到每个CategoryID的平均单价。
说明:先按CategoryID归类,取出CategoryID值和各个分类产品中单价的平均值。
6.求和
var q =
from p in db.Products
group p by p.CategoryID into g
select new {
g.Key,
TotalPrice = g.Sum(p => p.UnitPrice)
};
语句描述:使用Group By和Sum得到每个CategoryID 的单价总计。
说明:先按CategoryID归类,取出CategoryID值和各个分类产品中单价的总和。
7.计数
var q =
from p in db.Products
group p by p.CategoryID into g
select new {
g.Key,
NumProducts = g.Count()
};
语句描述:使用Group By和Count得到每个CategoryID中产品的数量。
说明:先按CategoryID归类,取出CategoryID值和各个分类产品的数量。
8.带条件计数
var q =
from p in db.Products
group p by p.CategoryID into g
select new {
g.Key,
NumProducts = g.Count(p => p.Discontinued)
};
语句描述:使用Group By和Count得到每个CategoryID中断货产品的数量。
说明:先按CategoryID归类,取出CategoryID值和各个分类产品的断货数量。 Count函数里,使用了Lambda表达式,Lambda表达式中的p,代表这个组里的一个元素或对象,即某一个产品。
9.Where限制
var q =
from p in db.Products
group p by p.CategoryID into g
where g.Count() >= 10
select new {
g.Key,
ProductCount = g.Count()
};
语句描述:根据产品的―ID分组,查询产品数量大于10的ID和产品数量。这个示例在Group By子句后使用Where子句查找所有至少有10种产品的类别。
说明:在翻译成SQL语句时,在最外层嵌套了Where条件。
10.多列(Multiple Columns)
var categories =
from p in db.Products
group p by new
{
p.CategoryID,
p.SupplierID
}
into g
select new
{
g.Key,
g
};
语句描述:使用Group By按CategoryID和SupplierID将产品分组。
说明:既按产品的分类,又按供应商分类。在by后面,new出来一个匿名类。这里,Key其实质是一个类的对象,Key包含两个Property:CategoryID、SupplierID。用g.Key.CategoryID可以遍历CategoryID的值。
11.表达式(Expression)
var categories =
from p in db.Products
group p by new { Criterion = p.UnitPrice > 10 } into g
select g;
语句描述:使用Group By返回两个产品序列。第一个序列包含单价大于10的产品。第二个序列包含单价小于或等于10的产品。
说明:按产品单价是否大于10分类。其结果分为两类,大于的是一类,小于及等于为另一类。
Exists/In/Any/All/Contains操作符
适用场景:用于判断集合中元素,进一步缩小范围。
Any
说明:用于判断集合中是否有元素满足某一条件;不延迟。(若条件为空,则集合只要不为空就返回True,否则为False)。有2种形式,分别为简单形式和带条件形式。
1.简单形式:
仅返回没有订单的客户:
var q =
from c in db.Customers
where !c.Orders.Any()
select c;
生成SQL语句为:
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],
[t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region],
[t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE NOT (EXISTS(
SELECT NULL AS [EMPTY] FROM [dbo].[Orders] AS [t1]
WHERE [t1].[CustomerID] = [t0].[CustomerID]
))
2.带条件形式:
仅返回至少有一种产品断货的类别:
var q =
from c in db.Categories
where c.Products.Any(p => p.Discontinued)
select c;
生成SQL语句为:
SELECT [t0].[CategoryID], [t0].[CategoryName], [t0].[Description],
[t0].[Picture] FROM [dbo].[Categories] AS [t0]
WHERE EXISTS(
SELECT NULL AS [EMPTY] FROM [dbo].[Products] AS [t1]
WHERE ([t1].[Discontinued] = 1) AND
([t1].[CategoryID] = [t0].[CategoryID])
)
All
说明:用于判断集合中所有元素是否都满足某一条件;不延迟
1.带条件形式
var q =
from c in db.Customers
where c.Orders.All(o => o.ShipCity == c.City)
select c;
语句描述:这个例子返回所有订单都运往其所在城市的客户或未下订单的客户。
Contains
说明:用于判断集合中是否包含有某一元素;不延迟。它是对两个序列进行连接操作的。
string[] customerID_Set =
new string[] { "AROUT", "BOLID", "FISSA" };
var q = (
from o in db.Orders
where customerID_Set.Contains(o.CustomerID)
select o).ToList();
语句描述:查找"AROUT", "BOLID" 和 "FISSA" 这三个客户的订单。先定义了一个数组,在LINQ to SQL中使用Contains,数组中包含了所有的CustomerID,即返回结果中,所有的CustomerID都在这个集合内。也就是in。 你也可以把数组的定义放在LINQ to SQL语句里。比如:
var q = (
from o in db.Orders
where (
new string[] { "AROUT", "BOLID", "FISSA" })
.Contains(o.CustomerID)
select o).ToList();
Not Contains则取反:
var q = (
from o in db.Orders
where !(
new string[] { "AROUT", "BOLID", "FISSA" })
.Contains(o.CustomerID)
select o).ToList();
1.包含一个对象:
var order = (from o in db.Orders
where o.OrderID == 10248
select o).First();
var q = db.Customers.Where(p => p.Orders.Contains(order)).ToList();
foreach (var cust in q)
{
foreach (var ord in cust.Orders)
{
//do something
}
}
语句描述:这个例子使用Contain查找哪个客户包含OrderID为10248的订单。
2.包含多个值:
string[] cities =
new string[] { "Seattle", "London", "Vancouver", "Paris" };
var q = db.Customers.Where(p=>cities.Contains(p.City)).ToList();
语句描述:这个例子使用Contains查找其所在城市为西雅图、伦敦、巴黎或温哥华的客户。
总结一下这篇我们说明了以下语句:
Group By/Having
分组数据;延迟
Any
用于判断集合中是否有元素满足某一条件;不延迟
All
用于判断集合中所有元素是否都满足某一条件;不延迟
Contains
用于判断集合中是否包含有某一元素;不延迟
本系列链接:LINQ体验系列文章导航
LINQ推荐资源
LINQ专题:http://kb.cnblogs.com/zt/linq/ 关于LINQ方方面面的入门、进阶、深入的文章。
LINQ小组:http://space.cnblogs.com/group/linq/ 学习中遇到什么问题或者疑问提问的好地方。
作者:李永京(YJingLee's Blog)
出处:http://lyj.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。