2011年5月31日 星期二

如何建立具有樣板(Template)的伺服器控制項

先看一下我們期望做出的效果

在網頁中,如果有使用SuccessTemplate樣板的話,顯示原來網頁上的內容。

<cc1:TheControl ID="TheControl1" runat="server" >
    <SuccessTemplate runat="server" >
    This is the success template.
    </SuccessTemplate>
</cc1:TheControl >

呈現出來的效果如下:


image


在網頁中,如果"沒有"使用SuccessTemplate樣板的話,使用內定的DefaultSuccessTemplate樣板內容。


<cc1:TheControl ID="TheControl2" runat="server" />
呈現出來的效果如下:
image


控制項間的結構如下:


image


控制項間的結構與網頁上標籤結構上有一些不同,在於我們並沒有在網頁上看到SuccessContainer這個容器,而是直接看到SuccessTemplate。


MSDN上說『宣告其容器控制項的基底型別』就是這個意思。


image

核心的步驟可以簡化如下:

CreateChildControls =>
    CreateSuccessViewControls =>
        建立空樣板 =>
        建立樣板容器 =>
        決定使用哪一種樣板 =>
        將樣板放入容器內,並建立子控制項 =>
        將容器放入控制項中

 

程式碼已改善MSDN範例不易擴充的問題,重新改寫如下:

 

[ToolboxData("<{0}:TheControl runat=server></{0}:TheControl>")]
[ParseChildren(true)]
public class TheControl : Control, INamingContainer
{
    private ITemplate _successTemplate;
    //InnerProperty:指定該屬性保存在 ASP.NET 伺服器控制項中做為巢狀標記。
    //TemplateContainer:宣告其容器控制項的基底型別
    //注意:預設的樣板行為由TemplateBuilder來決定
    [PersistenceMode(PersistenceMode.InnerProperty)]
    [TemplateContainer(typeof(TheControl))]
    public virtual ITemplate SuccessTemplate
        {
            get { return this._successTemplate; }
            set
            {
                this._successTemplate = value;
                base.ChildControlsCreated = false;
            }
        }
    private SuccessContainer _successContainer;
    //注意:樣板容器(SuccessContainer)只有定義樣板使用的"資料",樣板行為由樣板(DefaultSuccessTemplate)決定
    internal sealed class SuccessContainer : Control,INamingContainer
    {            
        private Literal _title;
        internal Literal Title
        {
            get { return this._title; }
            set { this._title = value; }
        }
        public SuccessContainer(TheControl owner) { }
    }
    //如果使用者沒有使用樣板的話,使用這個內定的樣板
    //注意:樣板行為由樣板(DefaultSuccessTemplate)決定,樣板容器(SuccessContainer)只有定義樣板使用的"資料"
    private sealed class DefaultSuccessTemplate : ITemplate
    {
        private TheControl _owner;
        public DefaultSuccessTemplate(TheControl owner)
            {
                this._owner = owner;
            }
        
        public void InstantiateIn(Control container)
            {
                SuccessContainer successContainer = container as SuccessContainer;
                this.CreatControls(successContainer);
                this.LayoutControls(successContainer);
            }
        private void CreatControls(TheControl.SuccessContainer container)
            {
                container.Title = new Literal();
                container.Title.Text = "This is the \"default\" success template.";
            }
        private void LayoutControls(TheControl.SuccessContainer container)
            {
                Table table = new Table();
                TableRow tr = new TableRow();
                TableCell tc = new TableCell();
                tc.Controls.Add(container.Title);
                tr.Cells.Add(tc);
                table.Rows.Add(tr);
                container.Controls.Add(table);
            }
    }        
    private void CreateSuccessViewControls()
    {
        ITemplate template = null;
        this._successContainer = new SuccessContainer(this);
        this._successContainer.ID = "SuccessContainerID";
        //網頁上有樣板,型態為System.Web.UI.CompiledTemplateBuilder
        if (this.SuccessTemplate != null)
        {
            template = this.SuccessTemplate;
        }
        else //網頁上沒有樣板,使用內定的模板
        {
            template = new DefaultSuccessTemplate(this);
        }
        //定義子控制項和樣板所屬的控制項物件
        template.InstantiateIn(this._successContainer);
        this.Controls.Add(this._successContainer);
    }
    protected override void  CreateChildControls()
    {
        CreateSuccessViewControls();
    }
}
參考資料:

 

2011年5月29日 星期日

實作資料來源控制項(DataSourceControl)

今天我們要做的是以CSV為資料來源的資料來源控制項。我簡化了MSDN的範例,讓我們專注在資料來源控制項的最根本機制上。

資料來源控制項是一種沒有視覺化界面的控制項,也就是說,它並不需要使用者介面來作呈現,因為他只負責對其他控制項提供資料來源。

當我們要對多個物件做協調或溝通的時候,需要一個管理的介面,在這邊分別是IDataSource.GetViewNames和IDataSource.GetView。

IDataSource.GetViewNames 負責取得資料來源的各個物件名稱。

IdataSource.GetView負責取得指定的資料來源。

View實際上指的是DataSourceView的,這是一個抽象類別,你必須實作你想要提供的動作,這邊我們實作ExecuteSelect。

CsvDataSource的屬性可以供開發人員來做設定,而真正對資料來源操作的是CsvDataSourceView,我們把這兩項屬性的存取交給他來負責。

CsvDataSource只負責由CsvDataSourceView中取得屬性值,本身並不負責保存這些屬性值。

image

 

[ToolboxData("<{0}:CsvDataSource runat=server></{0}:CsvDataSource>")]
public class CsvDataSource : DataSourceControl
{
    public CsvDataSource() : base() { }
    public string SourceFile
    {
        get { return (this.GetView(string.Empty) as CsvDataSourceView).SourceFile; }
        set
        {
            if ((this.GetView(string.Empty) as CsvDataSourceView).SourceFile != value)
            {
                (this.GetView(string.Empty) as CsvDataSourceView).SourceFile = value;
                RaiseDataSourceChangedEvent(EventArgs.Empty);
            }
        }
    }
    public bool IncludesColumnNames
    {
        get { return (this.GetView(string.Empty) as CsvDataSourceView).IncludesColumnNames; }
        set
        {
            if ((this.GetView(string.Empty) as CsvDataSourceView).IncludesColumnNames != value)
            {
                (this.GetView(string.Empty) as CsvDataSourceView).IncludesColumnNames = value;
                RaiseDataSourceChangedEvent(EventArgs.Empty);
            }
        }
    }
    private CsvDataSourceView view = null;
    protected override DataSourceView GetView(string viewName)
    {
        if (null == view)
        {
            view = new CsvDataSourceView(this, string.Empty);
        }
        return view;
    }
    protected override System.Collections.ICollection GetViewNames()
    {
        ArrayList al = new ArrayList(1);
        al.Add(CsvDataSourceView.DefaultViewName);
        return al as ICollection;            
    }
}
在繼承的DataSourceView中,我們使用ExecuteSelect來取得資料。在MSDN中,是如此描述ExecuteSelect和Select:
ExecuteSelect:從基礎資料儲存區取得資料清單。
Select:從基礎資料儲存區非同步取得資料清單。
其他的Update、Insert、Delete也一樣是在 DataSourceView 物件表示的資料清單上,執行『非同步』作業。
public class CsvDataSourceView : DataSourceView
{
    public CsvDataSourceView(IDataSource owner, string viewName): base(owner, DefaultViewName)
    { }
    public static string DefaultViewName = "CommaSeparatedView";
    private string sourceFile = string.Empty;
    internal string SourceFile
    {
        get { return sourceFile; }
        set
        {
            sourceFile = HttpContext.Current.Server.MapPath(value);
        }
    }
    private bool includesColumnNames = false;
    internal bool IncludesColumnNames
    {
        get { return includesColumnNames; }
        set { includesColumnNames = value; }
    }
    protected override System.Collections.IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
    {
        if (File.Exists(this.SourceFile))
        {
            DataTable dt = new DataTable();
            using (StreamReader sr = new StreamReader(SourceFile)) {
                string[] dataValues;
                DataColumn col;
                dataValues = sr.ReadLine().Split(',');
                foreach (string token in dataValues)
                {
                    col = new DataColumn(token, typeof(string));
                    dt.Columns.Add(col);
                }
                if (!IncludesColumnNames)
                {
                    dt.Rows.Add(dataValues);                        
                }
                string s = string.Empty;
                while(( s = sr.ReadLine()) != null)
                {
                    dataValues = s.Split(',');
                    dt.Rows.Add(dataValues);      
                }
            }
            dt.AcceptChanges();
            DataView dv = new DataView(dt);
            if (arguments.SortExpression != string.Empty)
            {
                dv.Sort = arguments.SortExpression;
            }
            return dv;    
        }
        return null;
        
    }
}


測試的檔案(sample.csv):



ID,Title,Channel,Program
1,台灣奇案,第一頻道,大目神
2,戲說台灣,第二頻道,蓋杯娘



網頁中的使用方式:

<asp:GridView ID="GridView1" DataSourceID="CsvDataSource1" runat="server">
</asp:GridView>
<cc1:CsvDataSource ID="CsvDataSource1" runat="server"
     IncludesColumnNames="true" SourceFile="sample.csv">
</cc1:CsvDataSource>        


參考資料:



2011年5月27日 星期五

動態載入jQuery與CSS

動態載入jQuery,可以使用Firebug來輸入:

var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", "http://code.jquery.com/jquery-1.6.1.js");
$ = jQuery;
動態載入CSS
var fileref=document.createElement("link");
fileref.setAttribute("rel", "stylesheet");
fileref.setAttribute("type", "text/css");
fileref.setAttribute("href", "http://www.fiddler2.com/Fiddler/Fiddler.css");

動態載入其他的資料

document.getElementsByTagName("head")[0].appendChild(URL);

應用


可以在不破壞Cookie(通常會存有SessionID)的狀況下,進行與伺服器的交互運作。


當然,Fiddler的RequestBuilder也可以達到相同的功能,不過就沒有jQuery那麼好用了。


$.post("URL",{Key:"Value",...},function(data){alert(data);})


參考資料:


Dynamically loading an external JavaScript or CSS file


2011年5月26日 星期四

最具實用性的伺服器控制項互動介面–ICallbackEventHandler

如果你很懶,覺得Server Control的東西很多,老闆的要求也很多,時間也很趕,如果用的是Web Form,那就使用這個介面吧。

前台可以使用JavaScript來搞定(如果你會jQuery那就更好了),後台回傳JSON形式的結構給前台呈現,絕妙的組合。

至於設計時的操作、瀏覽器的相容性、資料持續性等等的議題,就需要好好地給他系統分析一下,隨便亂搞是不行的啊。

Callbackc和一般Postback在網頁生命週期上有部分不同:

Postback & Callback Page LifeCycle COmparision

1. Server Control需要繼承ICallbackEventHandler,並實作GetCallbackResult與RaiseCallbackEvent。

ICallbackEventHandler讓你不必PostBack,就可以處理複雜的結構或許多的資料。

GetCallbackResult送出計算結果到前台。

RaiseCallbackEvent從前台接收資料資料。

至於OnLoad則是將處理的相關JavaScript寫到前台,這邊的函式命名與前台有關,需要注意一下。

    [ToolboxData("<{0}:CallbackControl runat=server></{0}:CallbackControl>")]
    public class CallbackControl : Control,ICallbackEventHandler
    {
        protected override void OnLoad(EventArgs e)
        {
            // 建立用戶端指令碼函式
              // http://msdn.microsoft.com/zh-tw/library/ms178208.aspx
            ClientScriptManager cm = Page.ClientScript;
            String cbReference = cm.GetCallbackEventReference(this, "arg", "ReceiveServerData", "");
            String callbackScript = "function CallServer(arg, context) {" + cbReference + "; }";
            cm.RegisterClientScriptBlock(this.GetType(), "CallServer", callbackScript, true);
            base.OnLoad(e);
        }
        public string GetCallbackResult()
        {
            return "GetCallbackResult: 123456"; //fromServerData
        }
        public void RaiseCallbackEvent(string eventArgument)
        {
            //eventArgument = "toServerData"   
        }
    }
2. 網頁的部分有兩個函式,一個送出資料到伺服器,另一個則是負責接收資料。與ICallbackEventHandler中時做的函式剛好互相對應。
    <cc1:CallbackControl ID="CallbackControl1" runat="server" />
    <div id="cbDiv" onclick="CallServer('toServerData', alert('Callback'))">
        伺服器傳回的資料會顯示在這裡。
    </div>
    <script type="text/javascript">
        function ReceiveServerData(fromServerData, context) {            
            document.getElementById("cbDiv").innerHTML = "從伺服器傳回的資料是=>" + fromServerData;
        }
    </script>
3. 執行前的畫面:
image
4. 點擊Div的區塊後,產生的執行後畫面:
image
參考資料:


 

2011年5月17日 星期二

[jQuery]contents、find與filter的不同

測試代碼

<div id="d1" class="fi">
  <div id="d2" class="fi">
    <div id="d2_1" class="fi">
      <p>測試1</p>
    </div>
  </div>
  <div id="d3" class="fi">
    <div id="d3_1" class="fi">
      <p>測試2</p>
    </div>
  </div>
</div>

使用Firebug測試並觀察


image


結論:


contents:可以使用元素或類別來選擇,選擇結果與find是一樣的,但是為廣度優先。


find:選擇的結果只有容器內的元素,不包含容器。選擇結果與contents是一樣的,但是為深度優先。


filter:選擇的結果包含容器。

2011年5月16日 星期一

[EL5]使用Logging Application Block 將記錄寫入資料庫

1. 加入參考

image

2. 加入資料庫的資料來源。注意:目前版本不支援Entity Framework的連結形式。

<connectionStrings>
  <add name="LoggingConnectionString" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Logging;Persist Security Info=True;User ID=admin;Password=pw"
    providerName="System.Data.SqlClient" />
<connectionStrings>

3. 在專案的Web.config上按右鍵,選擇[Edit Enterprise Library V5 Configuration]來進行編輯。

image

4. 新增Logging Target Listeners為Database Trace Listener,在Database Instance選擇剛剛設定的連線字串。

image

5. 將訊息寫入資料庫

using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Database;
LogEntry le = new LogEntry();
le.TimeStamp = DateTime.Now; //原設定非local,建議加入
le.Message = "測試訊息 - 資料變更成功";
//加入額外屬性
le.ExtendedProperties.Add("Controller", "Channel");
le.ExtendedProperties.Add("Action", "Edit");
//將變更的物件屬性也記錄到資料庫
le.ExtendedProperties = Utility.GetDictionary(x);
Logger.Write(le);

取得物件屬性的部分如下:

//將物件中的屬性(Property)放入字典
public static IDictionary<string, object> GetDictionary(object obj)
{
    IDictionary<string, object> dict = new Dictionary<string, object>();
    PropertyInfo[] pis = obj.GetType().GetProperties();
    foreach (PropertyInfo pi in pis)
    {
        dict.Add(pi.Name, pi.GetValue(obj, null).ToString());
    }
    return dict;
}
參考資源