读者基础需求:了解ASP.NET 控件设计技巧參考书目:深入剖析ASP.NET 组件设计(碁峯)、
Developing ASP.NET Server Controls And Components(MS Press)
Controls Model 的变革
如你所知晓,ASP.NET 1.x 提供了设计师丰富且易用的控件群,藉由这些控件,设计师可以快速的建构网页程序,但除了这些表面上看得到的控件外,为了实现组件化设计的理想,ASP.NET 1.x 还提供了简单但完整的Controls Model ,让设计师可以自行撰写控件來增加开发速度,并藉由组件化开发模式來简化程序的复杂度与降低出错率。在ASP.NET 2.0 中,这个Controls Model 做了相当大幅度的延伸,除了相容于原來的Controls Model 外,新的Controls Model 还提供了比以往更加丰富的基础类别。在1.x 时代,老实說控件的开发模式并没有太严谨的规则,设计师只要满足最基础的要求,继承至System.Web.UI.Control 或是System.ComponentModel.Component 就能够撰写控件与组件,虽然这给了设计师高度的自由,但也间接的加重组件设计师的工作量,例如撰写资料感知控件(Data Bound Control)这种很常見的想法,每个设计师硬是得先弄清楚DataSource 与DataField 属性该如何与IDE Designer 互动,然后再撰写特定的ControlDesigner 才能完成这样的控件。为了日后不再重新撰写这些变动率低、又常用到的程序代码,有经验的设计师就会架构自己的一群基础类别。在2.0,这件事不用勞设计师费心了,ASP.NET Team 已经为常用、可制式化的控件建立一群基础类别,现在要建立一个资料感知控件,设计师只要继承至DataBoundControl 即可,不需撰写ControlDesigner 了,要建立复合式控件也只要继承至CompositeControl 就行了,这个设计不但简化了设计师的工作,同时也为控件设计模式定下一个基本的标准,可以让初学者更容易上手,让设计师将时间花费在组件真正的功能上。
Adapter Model
Adapter Model 首次出现于ASP.NET Mobile Controls 中,当时主要的设计概念是为了让ASP.NET 网页能够适用于不同的行动装置,图1 是该设计的概念。图1 Mobile Controls
Adapter Model 采取Adapter 设计模式,将原本应该由Control 负责的Rending 动作交给另一个对象,也就是ControlAdapter 來完成,当要求到达服务器端时,ASP.NET 会判断客户端的装置类别,建立对应的ControlAdapter 來绘制控件,当然!在为了达到Rending 的动作前提下,除了真正的Rending 动作外,ControlAdapter 与Control 还建立了其它的通道,例如不同的Rending 动作需要有不同的Initialize 动作,也可能需要不同的Pre-Rending 动作,因此ControlAdapter 中定义了一群与Control 几乎相同的函式,如Init、PreRender、Render 等等。在ASP.NET 2.0 中,Adapter Model 已经被整合入Control 类别中,现在设计师可以为所有的控件建立ControlAdapter,不再仅限于Mobile Control。这个设计的目的分成远景与近需,近的是为了解决不同浏览器需要使用不同的HTML&JavaScript來绘制控件,远的是为了让Mobile与PC共享一个网页,当然!实务上这是很难达到的。
Base Control Classes
那到底有多少基础类别供我们应用呢?图2 列出目前笔者所观察到的类别。图2 ASP.NET 2.0 的控件基础类别Control 与WebControl 都是组件设计师熟悉的类别,接下來的DataSourceControl 是ASP.NET 2.0 新引入的Data-Binding 技术所用的基础类别,在2.0 中,DataSet 已经被SqlDataSource、AccessDataSource、OracleDataSource 等DataSource Control 所取代,而她们的基础类别就是DataSourceControl。BaseDataBoundControl 就是先前所提及的资料感知控件的基础类别,她提供了预定义的DataSourceID 属性,并为设计师预先建构了专属的ControlDesigner,因此只要继承至此类别,设计师只需专心撰写控件的程序代码,不须再耗费时间处理IDE 的相关细节。DataBoundControl 是更具体化些的基础类别,除了原有的DataSourceID 之外,另外还提供了DataMember 属性,她应该是最常用的资料感知控件基础类别,其子嗣CompositeDataBoundControl 则是用于复合式资料感知组件,如DetailsView、FormView及GridView的基础类别都是源自于此,HierarchicalDataBoundControl 是另一个支线,她是TreeView、Menu 的基础类别。最后一个基础类别是CompositeControl,用于撰写简单、不含资料感知能力的复合式控件。
Base Control Designer Classes
基础类别除了提供一致的实作标准外,更好的是她们预先配备了标准的ControlDesigner 來处理IDE 细节,图3 是这些基础类别所配备的基础ControlDesigner 。。图3 ASP.NET 2.0 的控件基础ControlDesigner 读者们应该可以由名称來对应出那个基础类别所用的ControlDesigner。
Designer Actions
初用VWD 或VS.NET 2005 的设计师应该都对其新的设计接口感到方便或是累赘,不管是那种感觉,至少新的设计模式真的让我们减少在WebForm 与属性盘上來回的次数, 如4 是这个新花招的截图。图4 ASP.NET 2.0 的新界面
Microsoft 将此技术称为Smart Task(Smart Tag 之毒?),简单的說就是将常用的属性与工作放到这个容器中,让设计者可以方便的设定她们,不须在属性表及WebForm 上來來去去,当然!这也有缺点,这个窗口显示的速度考验着开发者的计算机速度,何时该缩、何时该展开也考验着接口设计人员的智能。那如何让自己的控件拥有这种效果呢?說來也简单,見程序1。 程序1 建立含有Smart Task 的控件
using System;
using System.Collections.Generic;
using System.Text;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
#endregion
namespace ClassLibrary1{
public class SCCDesignerActionList : DesignerActionList {
private ControlDesigner _designer;
public bool ShowText {
get { return ((SimpleDesignerTest)_designer.Component).ShowText; }
set { ((SimpleDesignerTest)_designer.Component).ShowText = value; _designer.UpdateDesignTimeHtml();}
}
public SCCDesignerActionList(ControlDesigner designer):base() {
_designer = designer;
}
public void FireShowText() {
ShowText = true;
}
public void FireHideText() {
ShowText = false;
}
public override DesignerActionItem[] GetSortedActionItems() {
DesignerActionPropertyItem item = new DesignerActionPropertyItem("ShowText", "Show Text","Appearence");
DesignerActionMethodItem m_item;
if (!ShowText)
m_item = new DesignerActionMethodItem(this, "FireShowText", "Show Text","Actions");
else
m_item = new DesignerActionMethodItem(this, "FireHideText", "Hide Text", "Actions");
return new DesignerActionItem[] {item,m_item};
}
}
public class SCCControlDesigner : System.Web.UI.Design.ControlDesigner {
public SCCControlDesigner() : base() { }
public override DesignerActionListCollection ActionLists {
get
{
DesignerActionListCollection actions = new DesignerActionListCollection();
actions.AddRange(base.ActionLists);
actions.Add(new SCCDesignerActionList(this));
return actions;
}
}
}
[DesignerAttribute(typeof(SCCControlDesigner), typeof(IDesigner))]
public class SimpleDesignerTest:WebControl {
public bool ShowText {
get {
object o = ViewState["ShowText"];if (o != null)
return (bool)o;
return true;
}
set {ViewState["ShowText"] = value; }
}
protected override void Render(System.Web.UI.HtmlTextWriter writer) {
if(ShowText)
writer.WriteLine("TEST"); }
public SimpleDesignerTest() { }
}
}
DesignerActionPropertyItem 指的是一个属性型态的Smart Tas k,IDE 会将指定的属性显示成Smart Task,并套用该属性所有的属性编辑器。DesignerActionMethodItem 指的是一
个选项,供使用者点选后执行某些动作,与以前的DesignerVerbs 功能相同。
New Data Binding System
对于ASP.NET 的使用者而言,新的Data Binding 系统无疑是2.0 中变动最大、影响也最深的设计,原來的DataSet 已经被DataSource Control 所取代,这个变动将ASP.NET 的RAD 设计模式推进一大步,DataSource 模式讲求将资料操作完全封装在DataSource Control 中,控件与其的连结仅止与资料的交换,设计师也不再需要烦恼分页、快取等琐碎的问题,甚至可以使用ObjectDataSource 來建构分布式系统。对于组件设计师來說, 新的Data Binding 模式影响不大,因为2.0 已经建构了完整的基础类别,程序2 是一个简单的Table 控件(因为Beta 1 将DataBoundControlDesigner 标示为抽象类别,因此我们得继承她才能使用)。
程序2 SimpleTable 控件
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.ComponentModel;
using System.Web.UI.Design.WebControls;
#endregion
namespace ClassLibrary1 {
public class SimpleTableDesigner : DataBoundControlDesigner { }
[Designer(typeof(SimpleTableDesigner))]
public class SimpleTable:DataBoundControl {
private IEnumerable _dataRecs;
protected override HtmlTextWriterTag TagKey {
protected virtual void RenderHeader() {
if (_dataRecs != null) {
foreach (object item in _dataRecs)
{
}
Controls.Add(row);
break;
}
}
}
protected virtual void RenderRows()
{ if (_dataRecs != null)
{
foreach (object item in _dataRecs)
{ HtmlTableRow row = new HtmlTableRow();
foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(item))
{ HtmlTableCell col = new HtmlTableCell();
object v = prop.GetValue(item);
if (v != null) col.Controls.Add(new LiteralControl(v.ToString()));
row.Controls.Add(col); }
Controls.Add(row); } }
}
private void OnSelectCallBack(IEnumerable data) {
_dataRecs = data;
}
protected void RetrieveData() {
DataSourceView view = GetData();
if(view != null)
view.Select(DataSourceSelectArguments.Empty, new DataSourceViewSelectCallback(OnSelectCallBack));
}
public override void DataBind() {
ClearChildControlState(); Controls.Clear();
RetrieveData();
RenderHeader();
RenderRows();
ChildControlsCreated = true;
TrackViewState();
}
protected override void OnPreRender(EventArgs e) {
if (_dataRecs == null)
DataBind();
base.OnPreRender(e);
}
public SimpleTable() { }
} }
Non Visual Controls
ASP.NET 2.0 取消了Component Tray ,这代表着Component 在ASP.NET 2.0 中将完全失去RAD 能力,取而代之的是类似DataSource Control 类的非可视型组件,就实务上來說,Non Visual Control 还是一个控件,差别在于IDE 如何看待她,要将某个控件标示为Non Visual Control ,只要配上NonVisualControl Attribute 即可。
截至目前为止,我仍未发现该Attribute 会对IDE 造成何种影响,或许日后正式版时该Attribute 会用于另一种设计,让Non Visual Control 不会影响到设计版面。
Web Parts
2.0 将原本于SharePoint 上的Web Parts 整合进來,许多初次接触Web Parts 的朋友反应都很正面,这个技术可以让我们与组件化设计模式更贴近,在组件设计上,撰写一个自订的Web Parts 也很容易,程序3 是个简单的范例,她也同时展示了如何使用Web Parts 的Connection 技术。程序3
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Permissions;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.ComponentModel;
#endregion
namespace ClassLibrary1{
public class MySimpleWebPart:WebPart
{
public override bool AllowEdit
{
get { return true; }
}
public string Text { get { object o = ViewState["Text"];
}
[WebBrowsable,Personalizable(PersonalizationScope.User)]
public string TestVeriable { get { return "TEST"; }
}
[WebBrowsable,Personalizable,Category("Behavior")]
public string TestVeriable2 { get { return "TEST"; }
}
[ConnectionProvider("provider","provider")]
public object GetData() {
}
[ConnectionConsumer("consumer","consumer")]
Text = (string)data;
}
protected override void RenderContents(HtmlTextWriter writer) {
writer.WriteLine(Text);
}
public MySimpleWebPart() { }
}
Client Callback
ASP.NET 的PostBack 技术使网页程序的功能更贴近窗口程序,但仍然无法避免于PostBack 发生时画面的闪烁情况,其实这个问题一直都困扰着许多的设计师,为了让网页能在最小闪烁情况下更新,XMLHTTP 技术就应运而生,此技术运用了XML 与HTTP协议,再整合JavaScript 让网页可以在某个事件发生时更新一部份的网页,由于并非是重新向Web Server 要求网页,因此画面的闪烁情况可以减到最轻。2.0 整合了此技术,提供了一个标准的JavaScript 及ICallbackEventHandler 接口,控件只要实作此接口,并搭配上适当的JavaScript ,就能达到网页部份刷新的效果,程序10是一个简单的范例,LowcaseTextBox会将所有输入的字串转成小写,读者可以放上她及几个RadioButton或是CheckBox,当焦点由LowcaseTextBox 離开时,就可发现字串都被转成小写,你也会发现,浏览器并未重新刷新网页,也没有闪烁的情况。程序10 LowcaseTextBox
using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
#endregion
namespace ClassLibrary1{
public class LowcaseTextBox:TextBox,ICallbackEventHandler {
private const string SCRIPT =
"<script language='JavaScript'>/n"+
"function SetText(eventarg){/n"+
"document.getElementById('%ID%').value = eventarg;/n"+
"}</script>";
public LowcaseTextBox() { }
protected override void AddAttributesToRender(
HtmlTextWriter writer) {
base.AddAttributesToRender(writer);
writer.AddAttribute(
HtmlTextWriterAttribute.Onchange, Page.GetCallbackEventReference(
this,ClientID+".value","SetText",null)); }
protected override void OnPreRender(EventArgs e) {
base.OnPreRender(e);
Page.ClientScript.RegisterClientScriptBlock( GetType(), ClientID,
SCRIPT.Replace("%ID%",ClientID));
}
#region ICallbackEventHandler Members
public string RaiseCallbackEvent(string eventArgument) {
return eventArgument.ToLower(); }
#endregion
} }
Still Running
ASP.NET 2.0 仍在开发中,可以预期的是Web Parts 部份会持续的加强,许多更方便的基础类别也会一一的加入,相信在2.0 推出后,在控件市场上一定会出现相当多且方便的控件。