自定义服务器控件(控件状态和事件)
2015-01-15 09:35
337 查看
转自:http://www.cnblogs.com/SkySoot/archive/2012/12/04/2801424.html
ASP.NET使用Web控件在HTTP和HTML的底层细节之上创建了一个面向对象的抽象层。这个抽象的两个基础是视图状态(多次请求之间保存信息的机制)和回传(使页面把表单数据集合回传到相同URL的技术)。
一个常用的Web控件设计模式是:在属性过程中访问ViewState集合。
以我们前面所说的LinkWebControl为例,那个控件不使用视图状态,如果通过编程改变了它的Text和HyperLink属性,这些修改在后续的回传过程中将会丢失。(注意,样式属性又不同,它们会被自动保存在视图状态里)
为了修改这个控件以使用试图状态,下面重写这两个属性:
[/code]
[/code]
现在,LinkWebControl控件可以使用ViewState视图状态了,通过编程修改的属性在后续的回传之中不会丢失了。
在控件初始化的时候,通过调用Page.RegisterRequiresViewStateEncryption()方法可以请求页面加密视图状态信息,这在需要保存敏感信息时很有用:
[/code]
[/code]
有一点很重要,控件的ViewState属性和页面的ViewState属性是完全分开的。换言之,这两者的ViewState是互不干扰的。
控件里的视图状态很容易使用,但仍需考虑许多问题:
不要在视图状态里保存大型对象。例如,支持数据绑定的ASP.NET控件不会在ViewState中保存DataSource属性。这些数据只是存放在内存中,直到调用DataBind()方法。你不得不在每次回传之后重新绑定数据控件。虽然这样在编程上显得有些笨拙,但却确保了网页不至于变得过分臃肿。
它受包容它的页面的限制。如果页面将控件的EnableViewState设置为false,则控件视图状态信息在每次回传之后都将丢失。如果你有确保控件能正常工作的重要信息,应该保存在控件状态信息里(下面会介绍控件状态信息)。
你不能假设数据就在ViewState中。如果尝试读取一个不存在的项你会得到一个异常。你需要在Onit()方法中或自定义控件的构造函数中检查该项是否为null值或者设置默认的视图状态信息。(LinkWebControl控件不会得到一个空引用,因为它使用Onit()设置了初始的视图状态信息)
注解1
即使EnableViewState属性被设置为false,ViewState集合仍能正常工作。唯一的区别在于,一旦控件被处理完,并且页面被呈现完,你设置在该集合中的信息就会被丢弃。
注解2
尽管WebControl类提供了ViewState属性,但它并未提供Cache、Session、Application等属性。但如果需要这些对象来存取数据,你可以通过静态的HttpContext.Current属性来访问这些属性。
偶尔,你会需要更大的灵活性来定制怎么存储视图状态信息。你可以通过覆盖LoadViewState()和SaveViewState()来实现。
SaveViewState()总是在控件被呈现成HTML之前被调用,它返回一个单一的可序列化的对象,这个对象被保存在视图状态里。
LoadViewState()在后续的回传中,控件被创建时被调用。它接收一个参数保存的对象,用这个对象来配置控件的属性。
简单的控件里并不需要覆盖这些方法,然而,有时覆盖这些方法又是很有用的。
当你开发了一个在视图状态中使用单一对象保存多项信息的更紧凑的方式时;当你从一个现存的控件派生新控件,并且想阻止其保存它的状态的时候;当你管理如何保存一个复杂控件的内嵌子控件的状态时;你需要覆盖这两个方法。
实际上,ASP.NET把视图状态信息和控件状态信息放在同一个隐藏字段里。
控件状态不受EnableViewState属性设置的影响,即使为false,控件仍能从控件状态里存取信息。
因为控件状态不能被禁用,所以应该认真的限制允许保存在其中的信息量。通常,仅限于关键信息,例如当前页面的索引或者数据的键值。要使用控件状态,必须以覆盖OnInit()为开端,并调用Page.RegisterRequiresControlState()来表明你的控件需要访问控件状态。
[/code]
[/code]
与视图状态不同,你不能通过一个集合来直接访问控件状态(这个设置比较恶心,可能是出于不想让开发者滥用控件状态的考虑)。你必须再覆盖两个方法SaveControlState()和LoadControlState()。这些方法使用了一种稍微不寻常的模式。基本思想是,你想要得到被基类序列化的任意控件状态,并且将这些来自基类的控件状态与包含你的新的可序列化对象的对象合并,通过System.Web.Pair类实现:
[/code]
[/code]
这个方法只允许你存储单个对象。如果需要存储很多信息,可以考虑使用封装所有信息的自定义类,并确保它包含Serializable属性。
也可以创建许多的Pair对象:
[/code]
[/code]
在LoadControlState()中,你把基类的控件状态传入,并把Pair对象的一部分强制转换为合适的类型:
[/code]
[/code]
为了在自定义控件里处理被传送到页面的数据,需要实现IPostBackDataHandler借口。通过实现这个接口,你向ASP.NET标明,当一个回传发生的时候,控件需要一个时机来检查回传数据,你的控件将会得到这个机会,无论实际上触发这次回传的是哪个控件。
IPostBackDataHandler定义了两个方法:
LoadPostData():当页面回传时,ASP.NET在任何控件事件被激发之前调用这个方法。这允许你检查被回传的数据,并且相应的更新控件的状态。不过,你不应该在这个时候激发change事件,因为其他控件还不会被更新。
RaisePostDataChangedEvent():在页面上所有输入控件都被初始化了之后,如果必要,ASP.NET会调用这个方法来给你机会机会激发change事件。
理解这个原理的最好方式是通过一个简单的示例。
下一个控件模仿一个基本的TextBox控件。这里给出一个基本的控件定义:
[/code]
[/code]
RaisePostDataChangedEvent()的任务相对简单些,就是激发事件。然而,大多数ASP.NET控件使用另外一层,通过这一层,RaisePostDataChangedEvent()方法调用OnXXX()方法来引发事件。这一层让其他开发人员可以从你的控件派生一个新控件,并且通过覆盖OnXXX()方法来改变控件的行为。
[/code]
[/code]
但是,如果想触发回传,该怎么做呢?
类似最简单的例子就是Button控件,以HTML表单标准,“提交”按钮总是回传页面,但是许多其他富控件如Calendar和GridView都允许你通过单击呈现出的HTML中的一个元素或者某个链接来触发一个回传。
这可以通过另一个ASP.NET机制提供:JavaScript函数_doPostBack()。_doPostBack()函数接受2个参数:触发回传的控件的名字和一个代表额外回传数据的字符串。
ASP.NET提供了Page.ClientScript.GetPostBackEventReference()方法来简化对_doPostBack()函数的访问。这个方法创建一个对客户端的_doPostBack()函数的引用,这样你就可以把这个引用呈现到控件里。通常,你会将这个函数引用放置在控件的HTML元素的onClick特性中。这样,当那个HTML元素被单击时,_doPostBack()函数就被触发了。
下面,创建一个简单的例子来理解回传机制。这个例子展示一个可单击的图像,单击它页面就会回传,没有任何额外的数据:
[/code]
[/code]
你唯一需要定制的工作是添加一些要呈现出来的特性,包括唯一的控件名称、图像的URL和一个将图像与_doPostBack()函数关联起来的onClick特性:
[/code]
[/code]
这对于触发回传是足够的,但是为了参与回传并激发一个事件,还需要多做几步。这次,需要实现IPostBackEventHandler接口,实现其定义的RaisePostBackEvent():
[/code]
[/code]
当页面被回传的时候,ASP.NET判断哪一个控件触发了回传(通过查看每个控件的UniqueID),并且,如果那个控件实现了IPostBackEventHandler事件处理程序,ASP.NET就以事件数据调用RaisePostBackEvent()方法。此时,页面上所有控件都被初始化了,这时触发一个事件很安全:
[/code]
[/code]
创建测试页面,效果如下:
这个控件本身并未提供现存的ASP.NETWeb控件没有的功能。例如,ImageButton。然而,这对于创建一些更加有用的控件来说是一个好的起点。
另外,很多时候并不需要把整个页面都回传,可以使用回调从服务器端取回一些特定的信息。
ASP.NET使用Web控件在HTTP和HTML的底层细节之上创建了一个面向对象的抽象层。这个抽象的两个基础是视图状态(多次请求之间保存信息的机制)和回传(使页面把表单数据集合回传到相同URL的技术)。
视图状态
控件需要保存状态信息,就像网页所做的那样。所有的控件都提供了ViewState属性来让你存取信息,就像你对一个网页所做的那样。在一次回传之后,你需要使用ViewState集合来恢复私有信息。一个常用的Web控件设计模式是:在属性过程中访问ViewState集合。
以我们前面所说的LinkWebControl为例,那个控件不使用视图状态,如果通过编程改变了它的Text和HyperLink属性,这些修改在后续的回传过程中将会丢失。(注意,样式属性又不同,它们会被自动保存在视图状态里)
为了修改这个控件以使用试图状态,下面重写这两个属性:
[code] [code]publicstringText
{
get{return(string)ViewState["Text"];}
set{ViewState["Text"]=value;}
}
publicstringHyperLink
{
get{return(string)ViewState["HyperLink"];}
set
{
if(value.IndexOf("http://")==-1)
{
thrownewApplicationException("SpecifyHTTPastheprotocol");
}
else
{
ViewState["HyperLink"]=value;
}
}
}
protectedoverridevoidOnInit(EventArgse)
{
base.OnInit(e);
if(ViewState["HyperLink"]==null)
ViewState["HyperLink"]="http://www.google.com";
if(ViewState["Text"]==null)
ViewState["Text"]="Clicktosearch";
}
[/code]
[/code]
现在,LinkWebControl控件可以使用ViewState视图状态了,通过编程修改的属性在后续的回传之中不会丢失了。
在控件初始化的时候,通过调用Page.RegisterRequiresViewStateEncryption()方法可以请求页面加密视图状态信息,这在需要保存敏感信息时很有用:
[code]
[code]protectedoverridevoidOnInit(EventArgse)
{
base.OnInit(e);
Page.RegisterRequiresViewStateEncryption();
......
}
[/code]
[/code]
有一点很重要,控件的ViewState属性和页面的ViewState属性是完全分开的。换言之,这两者的ViewState是互不干扰的。
控件里的视图状态很容易使用,但仍需考虑许多问题:
不要在视图状态里保存大型对象。例如,支持数据绑定的ASP.NET控件不会在ViewState中保存DataSource属性。这些数据只是存放在内存中,直到调用DataBind()方法。你不得不在每次回传之后重新绑定数据控件。虽然这样在编程上显得有些笨拙,但却确保了网页不至于变得过分臃肿。
它受包容它的页面的限制。如果页面将控件的EnableViewState设置为false,则控件视图状态信息在每次回传之后都将丢失。如果你有确保控件能正常工作的重要信息,应该保存在控件状态信息里(下面会介绍控件状态信息)。
你不能假设数据就在ViewState中。如果尝试读取一个不存在的项你会得到一个异常。你需要在Onit()方法中或自定义控件的构造函数中检查该项是否为null值或者设置默认的视图状态信息。(LinkWebControl控件不会得到一个空引用,因为它使用Onit()设置了初始的视图状态信息)
注解1
即使EnableViewState属性被设置为false,ViewState集合仍能正常工作。唯一的区别在于,一旦控件被处理完,并且页面被呈现完,你设置在该集合中的信息就会被丢弃。
注解2
尽管WebControl类提供了ViewState属性,但它并未提供Cache、Session、Application等属性。但如果需要这些对象来存取数据,你可以通过静态的HttpContext.Current属性来访问这些属性。
偶尔,你会需要更大的灵活性来定制怎么存储视图状态信息。你可以通过覆盖LoadViewState()和SaveViewState()来实现。
SaveViewState()总是在控件被呈现成HTML之前被调用,它返回一个单一的可序列化的对象,这个对象被保存在视图状态里。
LoadViewState()在后续的回传中,控件被创建时被调用。它接收一个参数保存的对象,用这个对象来配置控件的属性。
简单的控件里并不需要覆盖这些方法,然而,有时覆盖这些方法又是很有用的。
当你开发了一个在视图状态中使用单一对象保存多项信息的更紧凑的方式时;当你从一个现存的控件派生新控件,并且想阻止其保存它的状态的时候;当你管理如何保存一个复杂控件的内嵌子控件的状态时;你需要覆盖这两个方法。
控件状态
控件状态用于存储控件正在使用的数据。从技术上讲,控件状态的工作方式和视图状态相同。控件状态保存那些可序列化的信息,这些信息在呈现页面的时候被塞入到一个隐藏字段。实际上,ASP.NET把视图状态信息和控件状态信息放在同一个隐藏字段里。
控件状态不受EnableViewState属性设置的影响,即使为false,控件仍能从控件状态里存取信息。
因为控件状态不能被禁用,所以应该认真的限制允许保存在其中的信息量。通常,仅限于关键信息,例如当前页面的索引或者数据的键值。要使用控件状态,必须以覆盖OnInit()为开端,并调用Page.RegisterRequiresControlState()来表明你的控件需要访问控件状态。
[code]
[code]protectedoverridevoidOnInit(EventArgse)
{
base.OnInit(e);
Page.RegisterRequiresControlState(this);
}
[/code]
[/code]
与视图状态不同,你不能通过一个集合来直接访问控件状态(这个设置比较恶心,可能是出于不想让开发者滥用控件状态的考虑)。你必须再覆盖两个方法SaveControlState()和LoadControlState()。这些方法使用了一种稍微不寻常的模式。基本思想是,你想要得到被基类序列化的任意控件状态,并且将这些来自基类的控件状态与包含你的新的可序列化对象的对象合并,通过System.Web.Pair类实现:
[code]
[code]stringsomeData;
protectedoverrideobjectSaveControlState()
{
//Getthestatefromthebaseclass.
objectbaseState=base.SaveControlState();
//Combineitwiththestateobjectyouwanttostore,
//andreturnfinalobject.
returnnewPair(baseState,someData);
}
[/code]
[/code]
这个方法只允许你存储单个对象。如果需要存储很多信息,可以考虑使用封装所有信息的自定义类,并确保它包含Serializable属性。
也可以创建许多的Pair对象:
[code]
[code]stringstringData;
intintData;
protectedoverrideobjectSaveControlState()
{
objectbaseState=base.SaveControlState();
Pairpair1=newPair(stringData,intData);
Pairpair2=newPair(baseState,pair1);
returnpair2;
}
[/code]
[/code]
在LoadControlState()中,你把基类的控件状态传入,并把Pair对象的一部分强制转换为合适的类型:
[code]
[code]protectedoverridevoidLoadControlState(objectsavedState)
{
Pairp=savedStateasPair;
if(p!=null)
{
//Givethebaseclassitsstate
base.LoadControlState(p.First);
//Nowyoucanprocessthestateyousaved
Pairpair1=p.SecondasPair;
stringData=(string)pair1.First;
intData=(int)pair1.Second;
}
}
[/code]
[/code]
回传数据和Change事件
视图状态和控件状态可以帮助跟踪控件中的内容,但对于输入控件,这些还不够。这是因为输入控件允许用户更改它们的数据。例如,考虑代表<input>标签的表单中的文本框。当页面回传发生的时候,<input>标签里的数据是控件集合的一部分。TextBox控件需要取得这个信息并且相应的更新它的状态。为了在自定义控件里处理被传送到页面的数据,需要实现IPostBackDataHandler借口。通过实现这个接口,你向ASP.NET标明,当一个回传发生的时候,控件需要一个时机来检查回传数据,你的控件将会得到这个机会,无论实际上触发这次回传的是哪个控件。
IPostBackDataHandler定义了两个方法:
LoadPostData():当页面回传时,ASP.NET在任何控件事件被激发之前调用这个方法。这允许你检查被回传的数据,并且相应的更新控件的状态。不过,你不应该在这个时候激发change事件,因为其他控件还不会被更新。
RaisePostDataChangedEvent():在页面上所有输入控件都被初始化了之后,如果必要,ASP.NET会调用这个方法来给你机会机会激发change事件。
理解这个原理的最好方式是通过一个简单的示例。
下一个控件模仿一个基本的TextBox控件。这里给出一个基本的控件定义:
[code]
[code]publicclassCustomTextBox:WebControl,IPostBackDataHandler
{
//构造函数里初始化为空字符串,将基本标签设置为<input>
publicCustomTextBox()
:base(HtmlTextWriterTag.Input)
{
Text="";
}
//这个控件仅仅需要一个属性Text,值被保存在视图状态里
publicstringText
{
get{return(string)ViewState["Text"];}
set{ViewState["Text"]=value;}
}
//为<input>标签添加特性type和value,你就能够处理所有的事情了
//必须使用name特性为控件添加UniqueID,ASP.NET将这个字符串与回传数据做比较
//如果不添加这个UniqueID,LoadPostData()方法将不会被调用!
protectedoverridevoidAddAttributesToRender(HtmlTextWriterwriter)
{
writer.AddAttribute(HtmlTextWriterAttribute.Type,"text");
writer.AddAttribute(HtmlTextWriterAttribute.Value,Text);
writer.AddAttribute("name",this.UniqueID);
base.AddAttributesToRender(writer);
}
//参数一:当前控件数据的键值
//参数二:回传到页面的值的集合
publicboolLoadPostData(stringpostDataKey,System.Collections.Specialized.NameValueCollectionpostCollection)
{
//Getthepostedvalueandthemostrecentviewstatevalue.
stringpostedValue=postCollection[postDataKey];
stringviewstateValue=Text;
//Ifthevaluechanged,thenresetthevalueofthetextproperty
//andreturntruesotheRaiseDataChangedEventwillbefired.
if(viewstateValue!=postedValue)
{
Text=postedValue;
returntrue;
}
else
{
returnfalse;
}
}
publicvoidRaisePostDataChangedEvent()
{
thrownewNotImplementedException();
}
}
[/code]
[/code]
RaisePostDataChangedEvent()的任务相对简单些,就是激发事件。然而,大多数ASP.NET控件使用另外一层,通过这一层,RaisePostDataChangedEvent()方法调用OnXXX()方法来引发事件。这一层让其他开发人员可以从你的控件派生一个新控件,并且通过覆盖OnXXX()方法来改变控件的行为。
[code]
[code]publiceventEventHandlerTextChanged;
publicvoidRaisePostDataChangedEvent()
{
//Callthemethodtoraisethechangeevent
OnTextChanged(newEventArgs());
}
publicvirtualvoidOnTextChanged(EventArgse)
{
//Checkforatleastonelistener,andthenraisetheevent.
if(TextChanged!=null)
{
TextChanged(this,e);
}
}
[/code]
[/code]
触发回传
通过实现IPostBackDataHandler接口,你就能够参与每一个回传活动,并且读取你的控件的回传数据。但是,如果想触发回传,该怎么做呢?
类似最简单的例子就是Button控件,以HTML表单标准,“提交”按钮总是回传页面,但是许多其他富控件如Calendar和GridView都允许你通过单击呈现出的HTML中的一个元素或者某个链接来触发一个回传。
这可以通过另一个ASP.NET机制提供:JavaScript函数_doPostBack()。_doPostBack()函数接受2个参数:触发回传的控件的名字和一个代表额外回传数据的字符串。
ASP.NET提供了Page.ClientScript.GetPostBackEventReference()方法来简化对_doPostBack()函数的访问。这个方法创建一个对客户端的_doPostBack()函数的引用,这样你就可以把这个引用呈现到控件里。通常,你会将这个函数引用放置在控件的HTML元素的onClick特性中。这样,当那个HTML元素被单击时,_doPostBack()函数就被触发了。
下面,创建一个简单的例子来理解回传机制。这个例子展示一个可单击的图像,单击它页面就会回传,没有任何额外的数据:
[code]
[code]publicstringImageUrl
{
get{return(string)ViewState["ImageUrl"];}
set{ViewState["ImageUrl"]=value;}
}
publicCustomImageButton()
:base(HtmlTextWriterTag.Img)
{
ImageUrl="";
}
[/code]
[/code]
你唯一需要定制的工作是添加一些要呈现出来的特性,包括唯一的控件名称、图像的URL和一个将图像与_doPostBack()函数关联起来的onClick特性:
[code]
[code]protectedoverridevoidAddAttributesToRender(HtmlTextWriterwriter)
{
writer.AddAttribute("name",this.UniqueID);
writer.AddAttribute("src",this.ImageUrl);
writer.AddAttribute("onClick",
Page.ClientScript.GetPostBackEventReference(this,string.Empty));
}
[/code]
[/code]
这对于触发回传是足够的,但是为了参与回传并激发一个事件,还需要多做几步。这次,需要实现IPostBackEventHandler接口,实现其定义的RaisePostBackEvent():
[code]
[code]publicclassCustomImageButton:WebControl,IPostBackEventHandler
{...}
[/code]
[/code]
当页面被回传的时候,ASP.NET判断哪一个控件触发了回传(通过查看每个控件的UniqueID),并且,如果那个控件实现了IPostBackEventHandler事件处理程序,ASP.NET就以事件数据调用RaisePostBackEvent()方法。此时,页面上所有控件都被初始化了,这时触发一个事件很安全:
[code]
[code]publiceventEventHandlerImageClicked;
publicvoidRaisePostBackEvent(stringeventArgument)
{
OnImageClicked(newEventArgs());
}
publicvirtualvoidOnImageClicked(EventArgse)
{
if(ImageClicked!=null)
{
ImageClicked(this,e);
}
}
[/code]
[/code]
创建测试页面,效果如下:
这个控件本身并未提供现存的ASP.NETWeb控件没有的功能。例如,ImageButton。然而,这对于创建一些更加有用的控件来说是一个好的起点。
另外,很多时候并不需要把整个页面都回传,可以使用回调从服务器端取回一些特定的信息。
相关文章推荐
- 自定义服务器控件(控件状态和事件)
- ASP.NET2.0服务器控件之自定义状态管理
- 自定义服务器控件开发(2)--事件和事件处理
- ASP.NET 2.0自定义服务器控件---事件冒泡
- ASP.NET2.0服务器控件之自定义状态管理
- 自定义服务器控件开发(3)--视图状态与控件状态
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- 给自定义服务器控件或用户控件增加事件处理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- ASP.NET2.0服务器控件之自定义状态管理
- 自定义服务器控件中的事件
- [经验]自定义ASP.NET服务器控件属性的状态不能保存的问题
- 给web用户控件自定义后台事件