2009年8月18日 星期二

在應用程式中動態的載入Assembly完成自動更新佈署1-2

RunPC 128期:利用.NET技術實現零接觸佈署(二)

晚期繫結(Late Binding)
System.Reflection命名空間除了可以讓我們在執行期間取得型別資訊外,而另一個功能也是本篇文章的重點所在,利用反射機制達成晚期繫結的功能,使您可以在執行期間才去動態載入組件,解析型別與其成員是否存在,然後就可以動態地叫用其方法,讀寫屬性值和維護您的欄位。
談到晚期繫結它有一關鍵類別System.Activator類別,而它的核心方法即是Activator.CreateInstance方法,它可以在執行時期替某個型別建立一個執行個體,程式碼如下:

Dim PType As Type = Asm.GetType("DynamicAssembly.Person")
Dim objPerson As Object = Activator.CreateInstance(PType, New Object() {"ML", "Chen"})

Activator.CreateInstance第一個參數即是執行時期替PType型別(即Person類別)建立一個執行個體,而Person類別建構式有兩個參數First、Last,您可以在Activator.CreateInstance方法的第二個參數中以物件陣列的方式輸入,即可在記憶體中建立一PType的執行個體,再將它指給objType變數。
於記憶體中建立一個執行個體後,接下來讓我們看看該如何呼叫該型別提供的方法,程式碼如下:

Dim miShowName As MethodInfo = PType.GetMethod("ShowName")
miShowName.Invoke(objPerson, Nothing)

如先前的說明,我們可經由型別的GetMethod方法,取出各別的方法給MethodInfo;再透過其Invoke方法來叫用型別的方法,而Invoke方法的第一個參數即在記憶體中建立的執行個體變數,第二個變數為該呼叫方法輸入的參數,可如前建構式方式輸入參數值,此範例的ShowName方法無參數則是Nothing傳入。

了解如何呼叫方法後,接下來為各位介紹如何讀取、改變設定欄位成員值,Person類別內有二個欄位成員:FirstName與LastName,於建構式中我們已分別指定為"ML", "Chen"字串,下面程式碼分別列出二欄位成員改變前與改變後的值:

Console.WriteLine("FirstName成員改變之前內容值:{0}", PType.InvokeMember("FirstName", BindingFlags.GetField, Nothing, objPerson, Nothing))
PType.InvokeMember("FirstName", BindingFlags.SetField, Nothing, objPerson, New Object() {"Good"})
Console.WriteLine("FirstName成員改變之後內容值:{0}", PType.InvokeMember("FirstName", BindingFlags.GetField, Nothing, objPerson, Nothing))
Console.WriteLine("LastName成員改變之前內容值:{0}", PType.InvokeMember("LastName", BindingFlags.GetField, Nothing, objPerson, Nothing))
PType.InvokeMember("LastName", BindingFlags.SetField, Nothing, objPerson, New Object() {"Man"})
Console.WriteLine("LastName成員改變之後內容值:{0}", PType.InvokeMember("LastName", BindingFlags.GetField, Nothing, objPerson, Nothing))

此處用到最主要的為Type的InvokeMember方法,它的作用是使用指定的繫結條件約束並符合指定的引數清單,來叫用指定的成員。此方法有三種多載,我們針對我們該範例使用到的InvokeMember方法多載加以介紹,其餘二個多載請參考MSDN說明,結構如下:

Overloads Public Function InvokeMember( _
ByVal name As String, _
ByVal invokeAttr As BindingFlags, _
ByVal binder As Binder, _
ByVal target As Object, _
ByVal args() As Object _
) As Object

參數:
Name:為要叫用的建構函式、方法、屬性或欄位成員的名稱。
invokeAttr:可由多個 BindingFlags 組成,BindingFlags分類如下:

下列 BindingFlags 篩選旗標可用來定義要包含在搜尋中的成員:



下列 BindingFlags 修飾詞旗標可用來變更搜尋作業的方式:



下列 BindingFlags 引動過程旗標可用來表示要對成員採取哪種動作:


binder:該範例我們傳入Nothing即可,除非您有多載方法的選擇、引數型別的轉換…等需求。
Targer:要叫用指定成員的 Object。
Args:包含引數的陣列,這個引數要傳遞到要叫用的成員中。
上面的程式碼是針對欄位成員加以維護,是故我們用到BindingFlags的GetField與SetField成員,若要讀取Person類別的Name成員則請用BindingFlags的GetProperty成員即可,此處就不再加以說明。

動態載入組件
列舉類別的成員的範例中我們使用Assembly.Load靜態方法傳入組件名稱而傳回一Assembly的實體物件,而我們在此又為各位介紹兩個Assembly載入組件的方法,一為Assembly.LoadFile方法其傳入參數為該組件的絕對路徑,此種做法只適用VS.NET 2003或.NET Framework 1.1環境;二為Assembly.LoadFrom方法其傳入的參數為該組件的位置,除可同為Assembly.LoadFile的絕對路徑外亦可為相對路徑或URL路徑、Ftp檔案路徑…等,且不限於VS.NET 2003或.NET Framework 1.1環境,故接下來的實作範例我們會以Assembly.LoadFrom方法來當動態載入的範本。
Assembly.Load方法:

Asm = System.Reflection.Assembly.Load("DynamicAsm ") ‘只可為組件名稱

Assembly. LoadFrom方法:

Asm = System.Reflection.Assembly.LoadFrom("C:\inetpub\wwwroot\ DynamicAsm.exe") ‘絕對路徑(○)
Asm = System.Reflection.Assembly.LoadFrom("DynamicAsm.exe ") ‘相對路徑(○)
Asm = System.Reflection.Assembly.LoadFrom("http://192.168.1.1/ DynamicAsm.exe") ‘URL路徑(○)
Asm = System.Reflection.Assembly.LoadFrom("ftp:// 192.168.1.1/mlftp/DynamicAsm.exe")‘Ftp路徑(○)

Assembly.LoadFile方法:

Asm = System.Reflection.Assembly.LoadFile("C:\inetpub\wwwroot\ DynamicAsm.exe ") ‘絕對路徑(○)
Asm = System.Reflection.Assembly.LoadFile("DynamicAsm.exe") ‘相對路徑(〤)
Asm = System.Reflection.Assembly.LoadFile("http:// 192.168.1.1/DynamicAsm.exe ") ‘URL路徑(〤)
Asm = System.Reflection.Assembly.LoadFile("ftp:// 192.168.1.1/mlftp/DynamicAsm.exe")‘Ftp路徑(〤)

當我們了解Load、LoadForm、LoadFile三種方法的差異處後,與它們各自的運用時機,最後讓我們建立一個以最友善的動態載入組件方法LoadForm為主自動更新佈署範例。

LoadForm實現自動更新佈署範例

Imports System.Reflection
Imports System.Net
Imports System.io

Public Class Form1
Inherits System.Windows.Forms.Form

#Region " Windows Form 設計工具產生的程式碼 "

#End Region

Private Sub btnShowForm_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnShowForm.Click
Dim strUrlFile, strMsg, strOldFile, strNewFile As String
strUrlFile = "http://192.168.1.1/Com/Version.dll"
strMsg = ""
strNewFile = "Temp.dll"
strOldFile = "Version.dll"

Dim bolContinue As Boolean = True
Dim myRequest As HttpWebRequest
Try
myRequest = CType(WebRequest.Create(strUrlFile), HttpWebRequest)
Catch ex1 As NotSupportedException
strMsg = "NotSupportedException" & ex1.Message
bolContinue = False
Catch ex2 As ArgumentException
strMsg = "ArgumentException" & ex2.Message
bolContinue = False
Catch ex3 As UriFormatException
strMsg = "UriFormatException" & ex3.Message
bolContinue = False
Catch ex As Exception
strMsg = "Exception" & ex.Message
bolContinue = False
End Try
If bolContinue = True Then
MessageBox.Show("發生內部錯誤:" & strMsg)
Exit Sub
End If

Dim myResponse As HttpWebResponse
Try
myresponse = CType(myRequest.GetResponse, HttpWebResponse)
Catch ex1 As WebException
strMsg = "未找到該檔或連線失敗:" & ex1.Message
If File.Exists(strOldFile) Then
strMsg &= "載入現有檔案."
MessageBox.Show(strMsg)
LoadMeDLL(strOldFile)
Else
strMsg &= "不存在檔案: " & strOldFile
strMsg &= "請檢查…."
MessageBox.Show(strMsg)
End If
Catch ex As Exception
strMsg = "Exception" & ex.Message
MessageBox.Show(strMsg)
End Try

Dim receiveStream As Stream = myresponse.GetResponseStream
Dim ofStream As FileStream = New FileStream(strNewFile, FileMode.Create)

Dim streamBytes As Byte()
Dim iLength As Int32 = CType(myresponse.ContentLength, Int32)

If receiveStream.CanRead Then
receiveStream.Read(streamBytes, 0, iLength)
ofStream.Write(streamBytes, 0, iLength)
Else
MessageBox.Show("串流讀取失敗。")
End If
ofStream.Close()
myresponse.Close()

Try
If File.Exists(strOldFile) Then
File.Delete(strOldFile)
End If
File.Move(strNewFile, strOldFile)
Catch ex As Exception
MessageBox.Show(ex.ToString())
File.Delete(strNewFile)
End Try
LoadMeDLL(strOldFile)
End Sub
Public Sub LoadMeDLL(ByVal strFile As String)
Dim simpAsm As [Assembly] = System.Reflection.Assembly.LoadFrom(strFile)
Dim frmTemp As Form = CType(simpAsm.CreateInstance("Version.F01"), Form)
frmTemp.Show()
End Sub

此範例是個簡易的Sample,說明每次執行時會從Web站台上下載最新版本的Version.dll暫儲存為Temp.dll,再將覆蓋成本機端的Version.dll檔案,最後呼叫LoadMeDLL方法,載入該組件與建立一Version.F01型別的表單實體,再將其呈現;當Version.dll組件版本改變後,下次再執行時版本即會自動完成更新佈署,請參考(圖五)。

(圖五)範例示範圖

而筆者實務上也使用該機制建立一『Simple Smart Platform(SSP)』系統,除俱備No-Touch Deployment (NTD)功能外也結合Single Sign On (SSO:單一登錄)功能,使該系統達成On Demand Software Services,讓使用軟體似使用水電服務般的方便即時,架構圖如(圖六),而操作畫面的簡圖如(圖七)。

(圖六) Simple Smart Platform架構圖


(圖七) Simple Smart Platform操作簡圖


結語:
本篇文章筆者一一為各位介紹了,基本核心的System.Type、System.Reflection反射機制,也透過該機制逐一列舉出組件類別內的成員;除此之外,我們也為各位介紹本章的重點所在『動態載入組件』的方法以及晚期繫結(Late Binding),Invoke、InvokeMember來呼叫動態載入組件的方法與相關的成員設定;而我們以一個簡易的範例達成系統自動更新佈署介紹;最後,也為各位簡略介紹筆者實務上建立的SSP系統。在NTD這一系列的文章中已為各位介紹以網址的方式來實現零接觸佈署和本篇的.NET如何動態的載入Assembly,接下來幾篇會為各位介紹BITS、.NET Application Updater與VS.NET 2005重要新增功能之Click Once。

2009年8月12日 星期三

在應用程式中動態的載入Assembly完成自動更新佈署1-1

RunPC 128期:利用.NET技術實現零接觸佈署(二)

利用.NET技術實現零接觸佈署的第一篇文章中,我們說明了如何利用.NET的NTD技術和如何設定程式碼存取安全(Code Access Security,CAS)以及它在.NET安全性上扮演的角色,我們也舉了二個例子加以說明,其一經由URL執行.NET應用程式,利用Download Cache機制佈署、更新程式,讓.NET的NTD應用系統俱備了Web (Thin Clinet)與Windows (Rich Client)應用程式的優點;其二建立一個相似於早期ActiveX Document的NTD應用程式內嵌於Internet Explorer中,並使用Client Script與該.NET控制項互動。
接下來我們會為各位介紹.NET的動態載入組件(Assembly)完成自動更新與佈署作法,此種作法與上篇之差異在於,上篇中針對一應用程式來作更新且完全依賴.NET提供的機制來完成,而本篇可細分至針對Assembly來作更新,甚可加入系統權限角色功能等相關判來完成本身自訂的特殊需求;該篇文章依序會為各位介紹:定義在System命名空間(namespace)下的Type類別、定義於System.Reflection命名空間下的反射機制(Reflection)服務、用來作晚期繫結(Late Binding)的Activator類別…等相關技術來完成動態載入組件自動更新的功能。

System.Type類別
Type代表型別的宣告,它提供了許多方法成員可用來找尋項目後的細節,而許多定義在System.Reflection命名空間裡的項目都用到抽象System.Type類別;換句話說Type 是 System.Reflection 功能的根,也是存取中繼資料 (Metadata) 的主要方法,使用 Type 的成員可以很簡易的取得型別宣告的資訊,如(表一)所示,而Reflection和CLR型別系統請參考(圖一)。

(表一)Type類別成員


(圖一)Reflection和CLR型別系統

取得Type物件的方式與Type的使用
Type類別是個抽象類別,是故您不可以利用『New』關鍵字來建立Type的實體物件,以下筆者提供您三種方式取得Type物件。

1、 GetType運算子:傳回指定型別的 Type 物件。
Dim t As Type=GetType(Integer)
‘註:Integer為型別名稱
2、 靜態Type.GetType方法:取得具有指定名稱的Type物件。
Dim t As Type = Type.GetType("System.Int32")
‘注意:此處"System.Int32"字串參數大小有別。
3、 Object.GetType方法:取得目前執行個體的Type物件。
Dim str As New String("NewString")
Dim t As Type = str.GetType

除非必要否則我們並不建議您使用第三種方式來取得Type物件,因為此處您必須先建立一個物件執行個體,方能取得您所要的Type物件;當然在取得一個Type物件的參考後接下來讓我們用個很簡單的對應關係圖來說明執行時期的型別資訊。

(圖二) 執行時期的型別資訊對應關係圖


System.Reflection命名空間的探索
在探索System.Reflection命名空間首要談的就是Assembly類別,我們可以經由使用該型別動態的載入組件、在執行時期叫用(Invoke)類別成員(稱之為『晚期繫結』),和取得組件本身的屬性資訊。
列舉類別的成員
接著讓我們利用隻簡單的範例程式來瀏灠類別定義的資訊。首先,我們建立一個新的[Visual Basic Windows應用程式專案],取名為[DynamicAssembly],再於Form1程式碼視窗中輸入下面這段程式碼,用於灠瀏組件中所有成員,最後於表單載入事件中呼叫WalkAssembly函式,並傳入[DynamicAssembly]組件名稱(註1),執行後即可在輸出視窗中看見如[圖三]的內容列出本身組件的所有成員型別、名稱…等。
註1:您可於方案總管中找到該DynamicAssembly專案,點選右鍵選[屬性],即可叫出該專案的屬性頁,內有一組件名稱項目可正確找出該專案的組件名稱。

Public Sub WalkAssembly(ByVal assemblyName As String)
Dim Asm As [Assembly] = System.Reflection.Assembly.Load(assemblyName)
For Each m As [Module] In Asm.GetModules
For Each t As Type In m.GetTypes
For Each mi As MemberInfo In t.GetMembers
Console.WriteLine("{0}->{1}.{2}",mi.MemberType,t, mi.Name)
Next
Next
Next
End Sub

程式碼說明如下:
 Dim Asm As [Assembly] = System.Reflection.Assembly.Load(assemblyName)
此處我們呼叫Assembly的靜態方法Load,再傳入一組件顯示名稱(註2),則可傳回一Assembly的實體物件,再使用三個For迴圈一一找出相關的成員。
註2:組件的顯示名稱可能會包含易記名外,還可以選擇指定它的地區設定、版本號碼、公鑰值、和強式名稱…等。如:Name (,Loc=CultureInfo)(,Ver=Major.Minor.Build.Revision)(,SN=StrongName)。
 [Asm.GetModules]:透過Asm組件的GetModules方法取出在該組件內的所有模組,此範例只有一個模組名稱為『DynamicAssembly.exe』,您可使用Console.WriteLine(m.Name)來取得。
 [m.GetTypes]:透過m模組物件的GetTypes方法取出該模組內所有型別,此處亦只有一個型別名稱為『Form1』,可使用Console.WriteLine(t.Name)取得;但若您在此專案內加入圖二的Person類別,則此處會多一型別『Person』。
 [t.GetMembers]:透過t型別物件的GetTypes方法取出該型別內的所有成員。
 [mi.MemberType]:找出mi成員型態,如:Method、Property、Constructor、Event…等。
 [t]:相當於t.ToString為型別的名稱,此處為『DynamicAssembly.Form1』。
 [mi.Name]:即為成員的名稱。

(圖三)組件成員輸出列表


列舉方法的參數
在得知如何列舉類別成員後,接下來讓我們取出圖二Person類別內Save方法的參數成員,請參考下面程式碼,而輸出如圖四:

Private Sub btnListMethods_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnListMethods.Click
Dim PType As Type = Asm.GetType("DynamicAssembly.Person")
Dim saveMi As MethodInfo = PType.GetMethod("Save")
Dim savePars() As ParameterInfo = saveMi.GetParameters
Console.WriteLine("{0}方法參數成員有{1}個", saveMi.Name, savePars.Length)
Dim par As ParameterInfo
For Each par In savePars
Console.WriteLine("參數名稱:{0}的型別是{1},位於第{2}個", par.Name, par.ParameterType, par.Position)
Next
End Sub
End Class

程式碼說明如下:
 Dim PType As Type = Asm.GetType("DynamicAssembly.Person")
經由Assembly物件的GetType得到命名空間DynamicAssembly下的Person型別。
 PType.GetMethod("Save"):經由型別的GetMethod方法得到指定的方法成員。
 saveMi.GetParameters:經由MethodInfo的GetParameters方法得到參數集合。

(圖四)

至此我們了解如何使用定義在System.Reflection命名空間的核心項目,且利用它取得執行期間取得相關的資訊,若您曾使用過.NET Framework提供的工具:ILDasm.exe,我們已完成它提供的大部份瀏灠組件的功能,差別只在ILDasm.exe是顯示樹狀結構,而我們是呈現在輸出視窗中。

2009年8月8日 星期六

經由Internet Explorer以網址的方式來實現零接觸佈署1-2

RunPC 125期:利用.NET技術實現零接觸佈署(一)

實作第一隻NTD技術的.NET應用程式
在了解Web應用程式與NTD技術的優點、CAS與環境要求後,接著讓我們來實作一隻NTD技術的.NET應用程式,步驟如下:
1、 建立一Windows應用程式:
如同在VS.NET IDE內建立Windows應用程式一樣,於工具列上選『檔案』-->『新增』-->『專案』於新增專案對話盒中,選擇專案類型『Visual Basic專案』,範本選擇『Windows應用程式』,名稱內輸入『NTD_App_01』(如圖一)後按確定鈕。

(圖一)建立NTD_App_01Windows應用程式專案

接著更改表單的Text屬性為『NTD_App_01』,且在表單上建立一Label(Label1)與一Button(Button1)控制項,設定二控制項的屬性使其(如圖二)樣式:

(圖二)未修改前表單樣式

在Button1的Click事件內輸入以下的程式碼,它最主要的作用是在C:\Temp\目錄是建立一個MyTest.txt檔案,讓該NTD_App_01的Windows應用程式來操作檔案的建立、讀取、刪除…等動作,讓其每點一下後計數加一且加完的值呈現於Label1上。

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim path As String = "c:\temp\MyTest.txt"
Dim sw As StreamWriter
Dim fs As FileStream
Dim I As Int16
If File.Exists(path) = False Then
sw = File.CreateText(path)
sw.WriteLine("1")
sw.Flush()
sw.Close()
I = 1
Else
Dim sr As StreamReader = File.OpenText(path)
If sr.Peek() >= 0 Then
I = CType(sr.ReadLine(), Int16) + 1
End If
sr.Close()
File.Delete(path)
sw = File.CreateText(path)
sw.WriteLine(I)
sw.Flush()
sw.Close()
End If
Label1.Text = I.ToString
End Sub

接著請您按F5執行該程式,點選『計數』鈕可見(如圖三),開始計數…,同時可在C:\Temp目錄下找到MyTest.txt檔案,開啟該檔其內容值如Label1控制項呈現值,至此我們已完成一隻簡單的Windows應用程式開發,
2、 建立一虛擬目錄
於IIS管理工具預設的網站上按右鍵,『新增』-->『虛擬目錄』下一步,別名設為『NTD』下一步,目錄筆者設為C:\NTD資料夾內,下一步完成即可。
3、 複製Widows應用程式
將剛建立的Windows建立專案下的bin目錄找到『NTD_App_01.exe』檔案,將其複製到C:\NTD資料夾內。
4、初始化環境
刪除C:\Temp\MyTest.txt檔案。
點選:開始-->所有程式-->Microsoft Visual Studio .NET 2003-->Visual Studio .NET 工具-->開啟Visual Studio .NET 2003 命令提示字元,輸入gacutil /cdl命令(圖四)刪除所有Download Cache內的檔案(註3)。

(圖四) gacutil /cdl刪除Download Cache

註3:您也可以利用gacutil /ldl列出Download Cache 內的所有檔案;當然您也可以利用檔案總管連到C:\WINDOWS\assembly\Download資料夾下去觀看Download Cache內的檔案,因已下gacutil /cdl命令是故C:\WINDOWS\assembly\Download資料夾應用空資料夾。
5、用戶端透過IE執行該該程式
開啟IE於網址列上輸入http://172.16.70.123/NTD/ NTD_App_01.exe按Enter,會出現(如圖五),因筆者還未設定安全性原則,是故會呈現安全性資料警告,接著我們點選『計數』鈕,會出現(如圖六)例外視窗告知目前該程式並無檔案IO的權限,檢查C:\Temp資料夾,MyTest.txt並未建立。

(圖五)未設定CAS前出現的Microsoft .NET 安全性資訊警告


(圖六)無FileIO權限警告視窗

6、設定安全性原則
開啟控制項內的系統管理工具內的Microsoft .NET Framework 1.1 組態視窗,展開『.NET Framework 1.1』-->『我的電腦』-->『Runtime安全性原則』-->『電腦』-->『程式碼群組』-->『All_Code』點選右鍵新增程式碼群組-->名稱『Web123』-->選擇條件類型『URL』,URL輸入『http://172.16.70.123/NTD/*』-->指派使用權限集合給程式碼群組:我們使用現在的使用權限集合『FullTrust』-->完成後即可在程式碼群組下找到『Web123』的程式碼群組。
7、用戶端再次透過IE執行該該程式
同第五步驟,但圖已為(如圖七)且沒有安全性資料警告,點選『計數』鈕亦可正常執行;再檢查C:\Temp資料夾,MyTest.txt已被建立且內容值與Lable1控制項值相同,由此可見透過CAS的設定可加強.NET應用程式執行的安全性。接著您可以透過註3的說明由下gacutil /ldl命令(或瀏灠C:\WINDOWS\assembly\Download)即可以找出在Download Cache內的檔案,如圖八我們可以看出組件名稱、類型、版本、文化特性、程式碼基底…等資訊。
至此我們看見了NTD技術的佈署功能與如何設定程式碼存取安全,接下來讓我們看看NTD組件更新的功能。

(圖七)設定完CAS後執行視窗


(圖八)利用檔案總管觀察Download Cache內容

8、組件更新
於專案『NTD_App_01』上加入一按鈕,呈現目前總數,建立後將NTD_App_01.exe複製蓋掉C:\NTD\NTD_App_01.exe檔。如第七步驟用戶端再次透過IE執行該該程式,即可見(如圖九)程式已更新,而Download Cache的資料夾組件版本有二個(如圖十)。

(圖九)更新後表單執行視窗


(圖十) 利用檔案總管觀察Download Cache內容與多個版本

看過一個簡單的透過URL的NTD應用程式後,接著讓我們建立一個像早期VB6時ActiveX Document的NTD應用程式。

實作一個似ActiveX Document的NTD應用程式
這個主題描述如何在 Internet Explorer (IE) 中成功執行 Windows Form 控制項。在 IE 中 Windows Form 控制項不需使用者提示也不需登錄便能啟動,並能利用 Common Language Runtime (CLR) 的程式碼存取安全性。
在 Internet Explorer 中啟動 Windows Form 控制項有四個步驟:

• 建立 Windows Form 控制項。
• 建立含有 object 標記的 HTML 文件。
• 建立虛擬目錄並設定使用權限。
• 設定程式碼存取安全性。
• 執行控制項。

1、建立 Windows Form 控制項:
在專案『NTD_App_01』上加入一新專案『NTD_App_02』,專案類型請選擇『Windows控制項程式庫』,將NTD_App_01專案上的控制項與程式碼複製到NTD_App_02專案的UserControl1上,建立即可。
2、建立含有 object 標記的 HTML 文件

< html>
< script language="JScript">

function ChangeText() {
simpleControl1.LabelText = text1.value;
simpleControl1.Invalidate();
}
< /script>
< body>
< p>NTD_App_02
< br>< br>
< /body>
< object id="simpleControl1"
classid="http:NTD_App_02.dll#NTD_App_02.UserControl1"
height="300" width="300" VIEWASTEXT>
< param name="LabelText" value="0">
< /object>
< br>
< br>
< input type="text" id="text1">
< input type="button" value="Change Text" onclick="ChangeText()">
< /html>

3、建立虛擬目錄並設定使用權限
HTML 網頁必須位於您 Web 伺服器的 IIS 虛擬目錄中,並具有適當的使用權限。在這個範例中,Windows Form 控制項位於相同的目錄中,但它也可以被安裝於共用組件快取中。虛擬目錄上的執行使用權限必須設定為「指令」-- 如果執行使用權限設定為「指令與執行檔」,將無法正常啟動。
4、設定程式碼存取安全性
當您還未設定程式碼存取安全權限時,您將得到(如圖六)的警告訊息,設定方式有二步驟:
A、調整安全性:
開啟控制項內的系統管理工具內的Microsoft .NET Framework 1.1 組態視窗,展開『.NET Framework 1.1』-->『我的電腦』-->『Runtime安全性原則』點選右鍵選『調整安全性』在『安全性調整精靈』中(如圖十一)選擇『對這部電腦進行變更』下一步-->(如圖十二)調整『信任的網站』為完全信任,最後按完成。

(圖十一)安全性調整精靈


(圖十二)信任的網站調整

B、新增信任網站:
開啟Internet Explorer選『工具』-->『網際網路選項』-->於網網路選項對話盒中選取『安全性』頁纖,選擇『信任的網站』選擇『網站…』於信任網站的對話盒中加入『http://172.16.70.123/NTD』且將此區域內的所有網站需要伺服器驗證(https:)去除勾選,按確定即可。
5、 執行控制項
若要執行控制項,只需在您的虛擬目錄中將 Internet Explorer 指向 HTML 網頁。如果控制項未正常啟動,則可能需要重新啟動 Internet Explorer。若要檢視和啟動這個範例,請連結至http://172.16.70.123/NTD/NTD_App_02.html網頁。

總結:
這篇文章中我們介紹了.NET的NTD技術,且說明了如何設定程式碼存取安全,我們舉了二個例子加以說明,經由URL執行.NET應用程式,利用Download Cache機制佈署、更新程式,讓.NET的NTD應用系統俱備了Web(Thin Clinet)與Windows(Rich Client)應用程式的優點,與建立一個相似於早期ActiveX Document的NTD應用程式,再NTD這一系列的文章中的下幾篇中會為各位介紹,在應用程式中如何動態的載入Assembly、BITS、.NET Application Updater與VS.NET 2005重要新增功能之Click Once。

2009年8月4日 星期二

經由Internet Explorer以網址的方式來實現零接觸佈署1-1

RunPC 125期:利用.NET技術實現零接觸佈署(一)

相信絕大多數利用.NET開發的技術人員,一定是熟悉Web與Windows應用程式開發的兩棲人員,不再像以往僅懂ASP Web應用程式開發和VB Windows應用程式開發系統各有一定程度的門檻,踏入彼此領域皆需有一段調適期,而今在VS.NET IDE上開發系統,不僅整合Web、Windows應用程式,甚至Mobile相關(Pocket PC、Smart Phone…)的應用程式開發、Office與下一代資料庫Yunkon…等應用程式的開發,其開發環境幾乎無異,而其應用程式語言更是無異,學會VB.NET或C#…等任一種.NET程式語言,您就可以開發各平台上的應用程式;談了這麼多.NET開發應用程式的好處後,接著我們把焦點放在Web(Thin Client)與Windows(Rich Client)應用程式上;幾年前筆者剛接觸.NET不久,曾向同事斷言『瀏灠器將死』而引來一陣嘩然,或許當時太過武斷,但過了些時侯相關的文章與資料也陸續出來,當然不是很明確的指明瀏灠器將被淘汰,但也說明了Windows應用程式最難克服的佈署問題(也是WEB應用程式最為人所樂道的優點)已逐漸有解決方式,我們終於可以大聲和煩人不俱人性化的JavaScrip說NO了(註1)。
註1:筆者最近看了幾篇文章,產業都必須朝向以人本為中心的思考模式走,一些不俱人性化的事物都將被淘汰,程式設計的模式亦同。

重歸Windows應用程式(Rich Client)懷抱
在過去的幾年時間內,我們看見了軟體發展團隊從Windows開發平臺Client/Server到Web開發技術(包括HTML、ASP、JSP、PHP、ColdFusion和DHTML…等等)的大規模轉移,一下子似乎Web Solution成了唯一的解決方案,主管們似乎更順這個潮流繁事要求Web化,最大的原因莫過於佈署問題無法得到有效的解決與Dll Hell問題,而今,微軟在.NET平臺上發佈了一個名為零接觸佈署(No Touch Deployment,NTD)的新技術。這項技術會讓大批開發者又從Web開發重回Windows表單開發模式;而在開始談什麼是NTD應用程式時,我們先回顧一下Web應用程式的優點與缺點。

Web應用程式的優點與缺點
Web應用程式最大的優點莫過於安裝佈署、維護的簡易性、用戶端需求小(只要俱備瀏灠器即可)和開發平台無關等三個優點;與Windows應用程式比較起來,Web的佈署只要針對Server端的機器加以佈署更新新的應用程式即可,而不像Windows應用程式需要針對每台用戶端機器加以佈署更新,佈署上的成本顯而易見Web應用程式遠少於Windows應用程式,這也是為什麼企業主願意犧牲Windows應用程式上那麼多優點而選擇Web應用程式的主因;其二Web應用程式對用戶端的需求甚低,只要用戶端俱備瀏灠器即可在一來一回間執行Web應用程式;而最後一點隨著技術的延伸與擴充(ActiveX、XML Island…等),這個優勢也應不俱備了,除非您的Web Site全部使用純HTML來架設,否則您必須針對特定的流灠器來開發,若各位是Client Script的撰寫者,您一定痛恨一個簡單的檢查,卻需複雜判斷瀏灠器的種類接著判斷瀏灠器的版本…等。接著讓我們看看Web應用程式的優點背後它帶給我們的限制,首先不外是使用者介面的不友善性和用戶端與Web Server間一來一回效能上的遺失、無離線執行能力、無法安全、有效的利用Client端上的資源;而Web應用程式的缺點正是Windows應用程式的優點,接下來讓我們開發探討同時俱備Thin Cleint和Rich Client優點的應用程式的作法—零接觸佈署。

零接觸佈署(No Touch Deployment,NTD)
何為零接觸佈署呢?它是微軟在.NET上佈署的新技術,相似於今時Web應用程式的佈署方式,它可以在Internet上執行而使用者無需感知應用程式佈署安裝到用戶機器,即是使用者可以透過IE經由URL或一個連結連結到Web Servcer上執行.NET應用程式,而不需手動安裝它,它會自動於背景中下載,然後執行於使用者的電腦上;而NTD在更新方面就如同Web應用程式一樣簡便,只需將新版的程式蓋掉Web站台上舊的程式即可,每個用戶端再次執行時它會自動比較版本時間戳記,若有新版本一樣會自動下載執行,是故用戶端會如同Web程式一樣執行到最新的應用程式。NTD技術它俱備了Web開發的所有優點而無需犧牲用戶介面設計,也沒有Web開放所帶來的困難。當然有些了解Java的程式設計師喜歡拿Java Web Star來與NTD技術比較,雖然有些地方它們確有相似之處,但安全性方面NTD技術提供了更完整的程式碼存取安全機制(Code Access Security,CAS),這一點來說NTD技術比Java Web Star強大許多。為了充分習得NTD技術的優點,了解CAS是必然的,這是因為在網路上的應用程式要在用戶端上執行,必須要取得適當的權限。

程式碼存取安全機制(Code Access Security,CAS)
程式碼存取安全性是協助限制程式碼存取受保護的資源和作業的一項機制。系統管理者依程式碼的來源不同分別給予程式碼不同的信任等級,各等級可以存取不同的資源,在 .NET Framework 中,程式碼存取安全性會執行以下的功能:

• 定義使用權限和使用權限集合以表示存取各種系統資源的權限。
• 使管理員可以依據程式碼群組的使用權限集合來配置安全性原則。
• 使程式碼會要求執行時所需的使用權限,以及可以用來協助它執行的使用權限,並指定程式碼不能擁有的使用權限。
• 依據程式碼所要求的使用權限以及安全性原則允許的作業,授與使用權限給每一個已載入的組件。
• 使程式碼可以要求它的呼叫端具有特定使用權限。
• 使程式碼要求它的呼叫端處理數位簽章,而只允許特定組織或站台的呼叫端可以呼叫受保護的程式碼。
• 比較呼叫堆疊上每一個呼叫端授與的使用權限和呼叫端必須擁有的使用權限,來加強 Run Time 時的程式碼限制。

其應用原理不外乎是先透過安全性原則(.NET提供四種層次:企業、電腦、使用者和Application Domain)設定一組組的使用權限集合(Permission Set),再把這些權限集合授予至那些符合識別項(Evidence)註2的應用程式群組。經由權限授予後,應用程式才知道它被管理者授予了那些權限,有了權限後應用程式才得以執行某些動作與存取某些特殊的資源;即應用程式要存取某些資源時.NET Framework會循整個程式呼叫鏈來檢查每個物件的甬有權限,皆擁有存取該資源的權限時,應用程式才可以正確執行。

註2:可以當作應用程式的識別項分二大類:一、針對某個組件:(1)組件的強勢名稱(Assembly Strong Name)、(2)以某個應用程式的密碼編譯雜湊來區別。二、多個組件標識:(1)軟體供應商的簽章、(2)組件來源的區域(Internet、Intranet…等)、(3)組件來源位置(URL或UNC路徑下)。

用戶端環境需求
介紹完何謂NTD技術與CAS後,接著讓我們談談在用戶端要執行NTD的.NET應用程式的需求,既是.NET程式.NET Framework自然是不可或缺的,且至少要是.NET Framework 1.0 SP2,建議.NET Framework 1.1環境更佳;當然若要透過URL來執行請安裝Internet Explorer 5.0.1以上版本;而伺服器端只要支援HTTP的Web Server即可,當然還是建議您使用IIS 5.0以上會較佳。

2009年7月30日 星期四

探討.NET Configuration Files 1-3

RunPC 112、113期:在.NET上保存應用程式設定(四)

ConfigurationSettings類別
ConfigurationSettings是一個類別提供在指定配置區段中對配置設定的存取,它擁有一個靜態方法(Static Method)GetConfig與一個靜態屬性(Static Property) AppSettings,是故可在不建立類別實體(Instance)的情況下即可使用該靜態方法與屬性,如上範例我們並未建立ConfigurationSettings實體而直接使用;AppSettings屬性會取得一個唯讀的NameValueCollection,含有配置區段中設定的名稱/值對;若在配置檔案中並未指定則會傳回empty;而GetConfig靜態方法的簽名碼(Signature)中指定傳回的是Object,但一般會是NameValueCollection,而實際上在區段中取得的會是ReadOnlyNameValueCollection,詳細的ConfigurationSettings類別請見configurationsettings.cs附檔。
為自訂標記(Tag)自訂一個處理器類別(Handler)
上面我們各舉了一個利用電腦配置檔系統事先已宣告的appSettings區段,和一個利用系統提供的處理器類別宣告一個自訂的區段,而這都不外乎是一種簡單的Key/Value配對機制,對於在共通上保留應用程式的設定值已經非常完美,但若遇到複雜的儲存值時卻也有點顯的不符使用,是故我們在這系列文章中的第二篇為各位介紹怎麼自訂一個XML的配置檔案,當然這是一個解決方式,但若可以利用.NET所提供的機制來解決這方面的問題那就更加的完美。接下來我們將為各位介紹怎麼為自訂標記自訂一個處理器類別,我們舉個簡單的例子,若有某甲公司想將有關產品分類、產品分類網址與產品項目的列表要保留於配置檔案中,其步驟如下:
1、 定義配置檔案內的XML結構描述
依上述需求我們會規劃其XML格式片段如下,顯然地我們沒法使用系統提供的處理器類別來加以剖析處理,接著我們將下面XML片斷加入Web.Config的根項目下,接著執行會發生如(圖一)的錯誤。

2、 建立自訂處理器
建立一個自訂處理器並不困難,但它必需是一個獨立的專案。
A、 在ProWebConfig專案內選擇功能表上的[專案]-->[新增]-->[專案],在新增專案對話盒中選[Visual Basic專案]-->[類別庫],名稱改為[ProProductHandler],記得點選[加入至方案]後按確定。
B、 在專案總管中刪除Class1.vb,後再專案上按右鍵選[加入新項目],選[類別]更名為[ProductTagHandler.vb]。
C、 自訂處理器類別必須實作IConfigurationSectionHandler介面,該介面只有一個Create的方法,由所有配置區段處理器實作以剖析配置區段的 XML。傳回的物件會加入至置配集合並且由 GetConfig進行存取。它帶有三個參數:parent、configContext、section,當然系統提供的處理器類別依舊是必需實作該介面[註3],請參考下面程式碼:

當應用程式剖析時,它會建立ProductTagHandler實體和呼叫Create方法,而通常我們只會用到section這個XmlNode參數,它包含了整個您要處理的配置檔區段內容,在這個例子上是指的區段內容;而configContext(HttpConfigurationContext)是提供目前的內容資訊給 ASP.NET 應用程式中的配置區段處理器,最主要是用來取得Web.config的虛擬路徑;parent則是在對應的父代配置區段中的配置設定。
[註3]:請參考附件系統提供處理器的原始碼檔案:dictionarysectionhandler.cs、ignoresectionhandler.cs、namevaluefilesectionhandler.cs、namevaluesectionhandler.cs、singletagsectionhandler.cs皆實作了IConfigurationSectionHandler介面。
D、 撰寫相關剖析程式碼,如下:

Imports System.Configuration
Imports System.Collections
Imports System.Xml
Public Class ProductTagHandler
Implements IConfigurationSectionHandler

Public Function Create(ByVal parent As Object, ByVal configContext As Object, ByVal section As System.Xml.XmlNode) As Object Implements System.Configuration.IConfigurationSectionHandler.Create
Dim xnProduct, xnRelProduct As XmlNode
Dim ProductNodes, RelProductNodes As XmlNodeList
Dim RelProducts, Prices As ArrayList
Dim PTitle, PDescUrl As String
Dim Products As New ArrayList()
'取得分類產品節點列表
ProductNodes = section.SelectNodes("Product")
'取得每個分類產品節點
For Each xnProduct In ProductNodes
'取得分類產品節點的Title、DescUrl屬性值
PTitle = xnProduct.Attributes.GetNamedItem("Title").Value
PDescUrl = xnProduct.Attributes.GetNamedItem("DescUrl").Value
RelProducts = New ArrayList()
Prices = New ArrayList()
'取出產品節點下的相關產品列表
RelProductNodes = xnProduct.SelectNodes("RelProducts//RelProduct")
If Not RelProductNodes Is Nothing Then
'取出相關產品的名稱與價格
For Each xnRelProduct In RelProductNodes
RelProducts.Add(xnRelProduct.InnerText)
Prices.Add(xnRelProduct.Attributes.GetNamedItem("Price").Value)
Next
End If
'將Product實體放入Products ArrayList內
Products.Add(New Product(PTitle, PDescUrl, RelProducts, Prices))
Next
'傳回Products ArrayList
Return Products
End Function
End Class

Public Class Product
Private m_Title, m_DescUrl As String
Private m_RelProducts, m_Prices As ArrayList
Public Sub New(ByVal PTitle As String, ByVal PDescUrl As String, ByVal RelProducts As ArrayList, ByVal Prices As ArrayList)
m_Title = PTitle
m_DescUrl = PDescUrl
m_RelProducts = RelProducts
m_Prices = Prices
End Sub

Public ReadOnly Property Title() As String
Get
Return m_Title
End Get
End Property
Public ReadOnly Property DescURL() As String
Get
Return m_DescUrl
End Get
End Property
Public ReadOnly Property RelProducts() As ArrayList
Get
Return m_RelProducts
End Get
End Property
Public ReadOnly Property Prices() As ArrayList
Get
Return m_Prices
End Get
End Property
End Class

在Create方法內撰寫對section剖析的程式碼,section的內容是來自配置檔案內的標記,剖析後將值用來建立Product的實體,再將Products ArrayList一一放入對Product實體的參照,最後將該ArrayList傳回,但在IConfigurationSectionHandler介面內的Create方法的簽名碼傳回的資料型別是Object,是故在呼叫者必需轉換型態為ArrayList。
3、 參考自訂處理器類別
在ASP.NET Web應用程式專案(ProWebConfig)中加入參考,在加入參考對話盒中選擇[專案]頁籤,加入參考[ProProductHandler]專案。
4、 宣告配置區段(Products)

項目下宣告,其section屬性名稱設為Products,而type屬性指定為處理器類別的組件(Assembly)和類別名稱。
5、 取得配置內容與資料呈現
在RootForm.aspx上增加一按鈕[Button1],在屬性視窗中改Text為[GetProductsList],在該Button的Click事件中撰寫下面程式碼:

Dim Products As ArrayList
Dim anProduct As ProProductHandler.Product
Dim i As Integer
Products = CType(System.Configuration.ConfigurationSettings.GetConfig("Products"), ArrayList)
If Not Products Is Nothing Then
For Each anProduct In Products
Response.Output.WriteLine("< a href=""" & anProduct.DescURL & """" & ">" & anProduct.Title & "< /a>")
If Not anProduct.RelProducts Is Nothing Then
For i = 0 To anProduct.RelProducts.Count - 1
Response.Output.WriteLine(anProduct.RelProducts.Item(i) & ":$" & anProduct.Prices.Item(i))
Next
End If
Next
End If

在System.Configuration.ConfigurationSettings.GetConfig("Products")會呼叫處理器類別的Create方法,因簽名碼的關係取回的是Object型態,此處借由型別轉換回真正傳回的ArrayList型態;而後續再取出ArrayList內的Product一一組成我們所要呈現的介面,執行結果呈現如下(圖三)。

(圖三) 取得配置內容的Web資料呈現

結論:
這篇文章是這系列文章中的最後一篇,我們對電腦(machine)、應用程式(application)和安全性(security) 各配置檔案做了簡單的介紹;當然我們特別著重在應用程式配置檔案中怎麼自訂配置區段的部份,一般有二個步驟:先宣告配置區段指定區段名稱和處理器類別,再依宣告名稱設定配置區段的內容。若無特殊需求一般可利用已在電腦配置檔案宣告的appSettings來做區段配置;當然您若要自訂區段名稱則可視自己所需在項目下宣告區段名稱,指定.NET Framework所提供的處理器類別,讓我們的區段結構描述是要以XML Node還提XML Attribute鍵/值成對為主;最後若鍵/值成對無法滿足您配置需求時,我們為各位解說怎麼利用實作IConfigurationSectionHandler介面來撰寫自訂的處理器類別,來剖析處理特殊的配置區段內容。
若各位讀者希望能夠撰寫較佳的處理器類別,對於進一步研究.NET上的XML操作是不可或缺的,畢竟配置檔案是XML Base的,當然各位亦可參考附件的dictionarysectionhandler.cs、ignoresectionhandler.cs、namevaluefilesectionhandler.cs、namevaluesectionhandler.cs、singletagsectionhandler.cs系統提供的處理器類別原始碼亦是以操作XML為主。
參考內容:
MSDN線上說明文件
Configuring Applications in .NET(http://www.c-sharpcorner.com/Code/2002/June/ConfigApp.asp)
Creating Custom Configuration Section Handlers(http://www.dotnetbips.com/displayarticle.aspx?id=127)
Specifying Configuration Settings in Web.config(http://aspnet.4guysfromrolla.com/articles/053102-1.aspx)

2009年7月22日 星期三

探討.NET Configuration Files 1-2

RunPC 112、113期:在.NET上保存應用程式設定(四)

應用程式配置檔案(Application Configuration Files)
應用程式配置檔案顧名思義它是用來配置應用程式特有的配置檔案,它的名稱和位置取決於應用程式的主應用程式 (Host),可以是下列其中一個:可執行檔裝載的應用程式、ASP.NET 裝載的應用程式、Internet Explorer 裝載的應用程式。
1、可執行檔裝載的應用程式
可執行檔裝載的應用程式它包含了視窗表單(Windows Froms)和主控台(Console-Base)的應用程式,可執行的主應用程式裝載的應用程式配置檔案位在與應用程式相同的目錄。配置檔案的副檔名為 .config,而其名稱為應用程式名稱(含.exe)加上.config。例如,稱為MyApp.exe 的應用程式可以和稱為 MyApp.exe.config 的配置檔案相關聯。
2、ASP.NET 裝載的應用程式
ASP.NET 裝載的應用程式它包含了ASP.NET Web 應用程式和ASP.NET Web服務,ASP.NET 配置檔案稱為 Web.config它位於虛擬目錄下的根節點或其子目錄下,在一個ASP.NET Web 應用程式中可以有多個 Web.config檔案,若您要求執行子目錄下的aspx頁面,它會先參考較靠近該aspx頁面下的web.config檔案內的設定值,若找不到相關設定值才會往上一層目錄參考其Web.Config檔案內的設定值直至虛擬目錄下的Web.Config檔案,若依舊沒有相關的設定值則會尋找電腦配置檔案內是否有提供其配置值,若皆沒有則出現圖一的錯誤。換言之,ASP.NET應用程式中頁面所參考的配置檔案是結合電腦配置檔案和各層目錄下的Web.Config檔案,若設定值有衝突時則以較靠近頁面的Web.Config設定值為主,請參考範例。
3、Internet Explorer 裝載的應用程式
如果裝載於 Internet Explorer 的應用程式具有配置檔案,這個檔案的位置會以下列語法在 < link> 標記中指定: < link rel="ConfigurationFileName" href="location"> 這個標記中,location 為配置檔案的 URL。這會設定應用程式基底。配置檔案必須位在與應用程式相同的 Web 網站。
範例:
1、 建立ASP.NET Web應用程式專案[ProWebConfig]。
2、 將專案中WebForm1.aspx更名為RootForm.aspx。
3、 在專案中建立Sub1與Sub2子資料夾,又在Sub1中建立Sub1_1子資料夾。
4、 在各子資料夾中加入新項目Web配置檔案(Web.config),在各配置檔中配置appSettings區段,而區段內加入一項目key為dsn,其值為Root_DSN、Sub1_DSN、Sub2_DSN、Sub1_1_DSN。
5、 在各子資料夾中加入Web Form,其名稱各為frmSub1.aspx、frmSub2.aspx、frmSub1_1.aspx。
6、 在各表單的Form_Load事件中加入Response.Output.WriteLine(ConfigurationSettings.AppSettings("dsn"))這行程式碼。
7、 在RootForm.aspx中加入三個HyperLink,其NavigateUrl屬性各指定為Sub1/frmSub1.aspx、Sub2/frmSub2.aspx、Sub1_1/frmSub1_1.aspx。
8、 執行測試,各表單取得的dsn鍵其值皆為最靠近表單的Web.config設定值。
9、 您可試將Sub1資料夾下的appSettings區段拿掉,再看frmSub1.aspx中取得該dsn鍵的值會是如何呢?
使用AppSettings來自訂配置檔案設定
當對各配置檔案做簡單說明後,接下接我們要為各位介紹配置檔案內的配置區段宣告與自訂的應用程式設定的關係,對它們的結構描述與各屬性所代表的意義來作簡單的介紹。一般我們會將對應用程式的喜愛設定和相關的狀態資訊存放在配置檔案的項目區段內,如:資料庫的連結設定等。其結構描述如下:

如前所述在所有配置檔案中皆是以< configuration>為根節點,是故< appSettings>項目區段亦是包含在其內。< add>項目的加入是表加入一個key/value成對的自訂的應用程式設定;< remove>項目是表來從您的應用程式中移除已在配置檔案階層架構較高層級中定義的設定,如:在應用程式配置檔案中移除您於電腦配置檔案中的設定;< clear>項目是表從您的應用程式中移除所有已在配置檔案階層架構較高層級中定義的設定。而我們可以利用< appSettings>項目來自訂的應用程式設定,是因為現存的電腦配置檔案中已對< appSettings>項目作宣告,若宣告的配置區段與配置區段內容沒有對應上則會發生如(圖一)的錯誤。您可參考下面電腦配置檔(machine.config)的部份片斷如下:

電腦配置檔案依舊是以< configuration>為根節點,而< configSections>項目是用來對配置區段和命名空間宣告,要特別注意的是當這個項目位於配置檔案中,則它必須是位於 < configuration> 項目的第一個子項目,其子項目請參考(表三)。

表三:< configSections>下的子項目

在程式中我們可利用ConfigurationSettings.AppSettings(key)[註1]來取得該key所對應的value,若您要將在該區段中的Key/Value值列出,請參考下面的程式碼:

利用For Each取得區段中的每一個鍵的名稱,再利用ConfigurationSettings.AppSettings帶入鍵的名稱取得對應的配對值,字串組合後用Response.Output.WriteLine[註2]列印於網頁上,當完成使用AppSettings來自訂配置檔案設定後,接下來讓我們在應用程式配置檔案中自訂一個配置區段。

註1:請參考下面ConfigurationSettings類別的說明。
註2:此處您亦可利用Respose.Write將結果印於網頁上,但若要分行則必需於每個字串結尾中串接< Br>字串以利分行,實為繁複,是故我們可利用在Global.asax的BeginRequest中加入Response.Output.NewLine = "< br>"該行程式碼,以後在該應用系統中即可使用Response.Output.WriteLine來輸出俱有分行效果的字串喔!
應用程式配置檔案中自訂一個配置區段
我們以ASP.NET Web應用程式為例,在Web.config配置檔案中使用非事先定義的AppSettings區段:
在Web.config中指定新定義的區段MySingleSection

區段宣告中的type可以有NameValueSectionHandler、NameValueFileSectionHandler、SingleTagSectionHandler、DictionarySectionHandler、IgnoreSectionHandler…等系統中預先定義好的區段處理類別,來針對所對應的區段配置內容來做剖析,其相關的說明請見表四。此處我們定義一個區段名稱為MySingleSection,利用SingleTagSectionHandler區段處理類別來做剖析區段內容,而MySingleSection區段內容帶Key/Value成對屬性三對,是因為要配合選用SingleTagSectionHandler的處理器類別,您可以利用下面的程式碼取出Key/Value列表值:

ConfigurationSettings.GetConfig("MySingleSection")傳回一雜湊表,而Hashtable 類別以 IDictionary 介面為基礎,當然MySS的宣告亦可改為Dim MySS As HashTable,詳細的程式碼請見附件範例。

表四:預先定義好的區段處理類別

配置檔案群組化
當在多人團隊開發的環境中或整合系統專案時,我們在配置檔案的應用上一般會將其配置區段群組化,以確保不會與其他人團隊成員所定義的配置區段發生命名衝突,其定義說明您可以參考表三。而下面為群組化的配置檔案部份內容,首先宣告一區段群組MyGroup後在其子項目中宣告一區段MySection其type改為NameValueSectionHandler處理器類別,注意其區段內容結構描述因處理器類別不同而有所差異,必須採用XML節點(node) 結構描述而非原XML屬性(Attribute) 結構描述。

而在群組區段中我們可以使用下面的程式碼取得您所要的Key/Value列表值:

此處因為我們宣告區段MySection其type已改為NameValueSectionHandler處理器類別,是故它的回傳值為NameValueCollection物件,請見表四說明,它是取回MyGroup群組MySection區段配置下集合,詳細的程式碼請見附件範例。

2009年7月15日 星期三

探討.NET Configuration Files 1-1

RunPC 112、113期:在.NET上保存應用程式設定(四)

在過去這一系列的文章中我們為各位介紹了,怎麼利用DynamicProperties快速設定來保留應用程式;怎麼規劃、自訂自己所屬的XML應用程式設定檔案;以及怎麼使用PropertyGrid控制項物件來讓應用程式的設定更加的方便,將設計時期的屬性視窗延伸到執行時期來對整個應用程式的環境來作配置;而本期我們將帶領各位讀者深入探討.NET Framework所提供的配置檔案(machine、security、application),當然為配合主題,我們將會把焦點放在Application配置檔案上,我們會為各位讀者說明利用DynamicProperties設定後產生的app.config內的appSettings配置區段(Configuration Section)是怎麼來的,以及key和value的配對關係;如何利用.NET Framework所提供的事先定義好的區段處理器(Section Handlers)來建立一個新的配置區段;如何將配置檔案內的Section群組化以利團隊模式開發;和要如何實作IConfigurationSectionHandler介面來自訂自己的區段處理器…等。首先讓我們對.NET Framework上的配置檔案做個概念性的解說。
配置檔案(Configuration Files)
在.NET Framework提供了三種基本型態的配置檔案資訊:電腦(machine)、應用程式(application)和安全性(security)。它們各自是為了不同目的而產生的,但它們皆是XML-Base具有相同的結構描述(Schema),例如:它們皆以這個項目為配置檔案的最上層項目,在配置檔案內根項目內包含了一些子項目(表1),而這些項目是使用標記(Tag)來標記項目的開頭和結尾,而項目的XML標記(Tag)和屬性是有大小寫區分的。當然若您使用事先定義好的區段來定義一個新的配置區段,則您必需要使用預先定義的屬性(項目之開始標記內的key/value配對)來指定配置設定。

表1:項目下的子項目

我們會將重心放置在二個子項目來探討,在開始這二個子項目介紹之前讓我們先對電腦(machine)和安全性(security)的配置檔案做個基本性介紹。
電腦配置檔案(Machine Configuration Files)
電腦配置檔案它的名稱為machine.config,位在.NET Framework安裝路徑的Config子目錄下,一般會在C:\WINDOWS\Microsoft.NET\Framework\v1.0.3705\CONFIG目錄下,它包含套用於整個電腦的設定值,如:電腦範圍組件繫結、內建遠端通道和 ASP.NET 的配置設定。配置系統首先會在電腦配置檔案中尋找 項目和開發人員可能定義的其他配置區段。它接著查看應用程式配置檔案(app.config、AppName.exe.config或web.config)。在電腦配置檔所定義的配置區段可在該部電腦的任一配置檔案上使用,除非該配置區段被應用程式配置檔案所覆寫,是故我們可以想像所有應用程式的配置檔案是繼承電腦配置檔案,而應用程式配置檔案可以覆寫電腦配置檔案的配置區段。若要讓電腦配置檔案易於管理,最好將這些設定值放在應用程式配置檔案中,以免喪失應用程式配置檔案的獨立性。然而,若該部電腦共通的設定值會置於電腦配置檔案中,如此做可讓我們的系統更容易維護。例如,如果我們在一台電腦上佈署多個應用程式,而各應用程式皆會連結到同一個單一登錄(Single Sign On)的資料庫伺服器以達到單一登錄,那我們就可將該連結參數設定值放置於電腦配置檔案上是比較適當的位置,而不用在各個應用程式配置檔案上設定,所以您在兩個不同檔案中沒有相同的設定值。但在佈署上若使用 XCOPY 部署應用程式將不會複製電腦配置檔案中的設定值此點要請讀者注意。下面是machine.config的部份內容:

而在先前文章中為各位介紹的『DynamicProperties』是利用DynamicProperties設定後產生的app.config內的appSettings配置區段(Configuration Section)是從電腦配置檔案(Machine.config)內的區段指定配對而來:

您可以將其appSettings區段名稱更名為appSettings1執行,則系統會發生如(圖一)的錯誤,請選擇[否],若要觀察錯誤所在位置,請按『Ctrl+F5』或點選功能表中的[偵錯]-->[啟動但不偵錯],即可見如圖二的配置錯誤畫面,告訴您在配置檔案中發生錯誤的位置,和錯誤的原因,顯而易見的它是告訴我們無法辨認配置區段appSettings,由此可知appSettings配置區段是必預由machine.config電腦配置檔案中事先宣告過。

(圖一)系統配置檔錯誤對話盒


(圖二)配置錯誤畫面

安全性配置檔案(Security Configuration Files)
安全配置檔案包含程式碼群組階層架構和與原則層級相關聯的使用權限集合的資訊。安全性配置檔案包含有三種原則配置檔案:企業(Enterprise)原則配置檔案、電腦(Machine)原則配置檔案、使用者(User)原則配置檔案,各所存在的路徑與檔名如(表2),我們強烈建議讀者使用 .NET Framework 配置工具 (Mscorcfg.msc位在%Runtime install path%下) 或程式碼存取安全性原則工具 (Caspol.exe位在%Runtime install path%下) 來修改安全性原則,確保原則變更不會損毀安全配置檔案。企業原則配置檔案是設定全部企業的安全性配置;電腦原則配置檔案只定義本機的安全性配置,此二者只可被具有administrator權限的使用者來設定配置,而使用者原則配置檔案是用來設定配置目前登錄使用者的相關安全性配置。

表2安全性配置檔案的位置

註:%Runtime install path%為.NET Framework安裝路徑;
vxx.xx.NET Framework的版本,目前一般為v1.0.3705。

2009年7月10日 星期五

PropertyGrid控制項1-2

RunPC 111期:在.NET上保存應用程式設定(三)

利用PropertyGrid呈現自訂類別屬性內容
假設我們自訂的類別為呈現系統相關的環境設定,其自訂屬性和分類規劃如下:

外觀分類屬性{表單顏色(AppColor)、應用程式游標樣式(AppCursor)、表單字型(AppFont)、標頭(Title)};
行為分類屬性{可見與否(Show)};
其他分類屬性{語言(AppLangugage)};
配置分類屬性{表單位置(AppLocation)、表單大小(AppSize)};
視窗樣式分類屬性{表單縮圖(AppIcon)}。



(圖九)自訂類別屬性內容

希望方格依照我們規劃的分類來呈現類別的屬性(圖九),我們運用.NET CategoryAttribute成員提供預設有十三種分類屬性(Action:動作;Appearance:外觀;Behavior:行為;Data:資料;Default:其他(即不設時);Design:設計;DragDrop:拖放;Focus:焦點;Format:格式;Key:索引鍵;Layout:配置;Mouse:滑鼠;WindowStyle:視窗樣式)來做自訂類別的屬性分類,當然規劃時若此十三種預設的屬性分類不能滿足您需求時,您亦可自訂分類;規劃完成後接下來讓我們開始建立自訂類別,其步驟如下:

1、 建立自訂類別:
在原專案[PropertyGrid_WinAp]的方案總管中選右鍵,於快顯功能表中選[加入][加入新項目],在範本中選擇[類別],更名為[AppSettings],按[確定]建立新類別,鍵入下面程式碼。

Imports System.ComponentModel
< DefaultPropertyAttribute("Title")> _
Public Class AppSettings
Private _Title As String = "自訂類別"
Private _Show As Boolean=True
Private _AppCursor As Cursor
Private _AppFont As Font
Private _AppIcon As Icon
Private _AppLangugae As System.Globalization.CultureInfo
Private _AppSize As Size
Private _AppColor As Color
Private _AppLocation As Point
< Category("Appearance"), _
BrowsableAttribute(True), _
ReadOnlyAttribute(False), _
BindableAttribute(False), _
DefaultValue("自訂類別"), _
DesignOnlyAttribute(False), _
DescriptionAttribute("【標頭】:請輸入應用程式的標頭")> _
Public Property Title() As String
Get
Return _Title
End Get
Set(ByVal Value As String)
_Title = Value
End Set
End Property
< CategoryAttribute("Behavior"), _
Browsable(True), _
[ReadOnly](False), _
BindableAttribute(False), _
DefaultValueAttribute("True"), _
DesignOnly(False), _
DescriptionAttribute("【可見與否】:設定應用程式介面可見與否")> _
Public Property Show() As Boolean
Get
Return _Show
End Get
Set(ByVal Value As Boolean)
_Show = Value
End Set
End Property
< CategoryAttribute("Appearance"), DefaultValueAttribute(""), _
DescriptionAttribute("【游標】:選擇應用程式介面上要呈現的游標樣式")> _
Public Property AppCursor() As System.Windows.Forms.Cursor
Get
Return _AppCursor
End Get
Set(ByVal Value As System.Windows.Forms.Cursor)
_AppCursor = Value
End Set
End Property
< CategoryAttribute("Appearance"), DefaultValueAttribute(""), _
DescriptionAttribute("【字型】:設定應用程式介面上的字型")> _
Public Property AppFont() As System.Drawing.Font
Get
Return _AppFont
End Get
Set(ByVal Value As System.Drawing.Font)
_AppFont = Value
End Set
End Property
< CategoryAttribute("WindowStyle"), DefaultValueAttribute(""), _
DescriptionAttribute("【圖示】:設定應用程式介面上呈現的小圖示")> _
Public Property AppIcon() As System.Drawing.Icon
Get
Return _AppIcon
End Get
Set(ByVal Value As System.Drawing.Icon)
_AppIcon = Value
End Set
End Property
< DefaultValueAttribute(""), _
DescriptionAttribute("【語言】:設定應用程式的語言")> _
Public Property AppLangugae() As System.Globalization.CultureInfo
Get
Return _AppLangugae
End Get
Set(ByVal Value As System.Globalization.CultureInfo)
_AppLangugae = Value
End Set
End Property
< CategoryAttribute("Layout"), DefaultValueAttribute(""), _
DescriptionAttribute("【大小】:設定應用程式介面的大小")> _
Public Property AppSize() As Size
Get
Return _AppSize
End Get
Set(ByVal Value As Size)
_AppSize = Value
End Set
End Property
< Category("Appearance"), DefaultValueAttribute(""), _
DescriptionAttribute("【字型顏色】:設定應用程式的字型顏色")> _
Public Property AppColor() As System.Drawing.Color
Get
Return _AppColor
End Get
Set(ByVal Value As System.Drawing.Color)
_AppColor = Value
End Set
End Property
< Category("Layout"), DefaultValueAttribute(""), _
DescriptionAttribute("【位置】:設定應用程式介面呈現的位置")> _
Public Property AppLocation() As Point
Get
Return AppLocation
End Get
Set(ByVal Value As Point)
_AppLocation = Value
End Set
End Property

程式碼說明:
Imports System.ComponentModel
因為要變更該自訂類別某些屬性的顯示方式,可以套用不同的特性(Attribute) 至這些屬性。所以需匯入 System.ComponentModel該命名空間,特性(Attribute) 是用於標記附註程式的設計項目,在執行時期時使用反映擷取類別、類型、欄位、方法及屬性的宣告式標記,此處我們只用到類別和屬性的宣告式標記,其下列是部分清單:
類別宣告式標記:
< DefaultPropertyAttribute("Title")> _
Public Class AppSettings
AppSettings類別的宣告式標記[DefaultPropertyAttribute]:表該類別預設的屬性。此例”Title”屬性為該類別之預設屬性,即該類別被載入方格時最先取得焦點位置的屬性。
屬性宣告式標記:

< Category("Appearance"), _
BrowsableAttribute(True), _
ReadOnlyAttribute(False), _
BindableAttribute(False), _
DefaultValue("自訂類別"), _
DesignOnlyAttribute(False), _
DescriptionAttribute("【標頭】:請輸入應用程式的標頭")> _
Public Property Title() As String
Get
Return _Title
End Get
Set(ByVal Value As String)
_Title = Value
End Set
End Property

 Category或CategoryAttribute:該屬性宣告式標記是用來設定屬性在方格中所屬的分類,其分類可參考上所述的.NET CategoryAttribute成員預設的十三種分類屬性,其分類亦可自訂,只要將其內含值改為要分類的名稱即可,當然若屬性未設定該Category屬性宣告式標記則該屬性會歸納於Default(其他)分類中。
 Browsable或BrowsableAttribute:該屬性宣告式標記是用來設定屬性是否在方格中呈現,若未設定該屬性宣告式標記預設為True,一律會將公用屬性呈現於方格中。
 ReadOnly或ReadOnlyAttribute(註2):該屬性宣告式標記是用來設定屬性是否唯讀,若未設定該屬性宣告式標記預設為False,一律會將俱有Get和Set子函式的公用屬性可於方格中編輯修改;若為True則該屬性於方格中以灰白呈現。
註2:一般宣告式標記的Attribute可省略,而此處省略後ReadOnly為關鍵字,則需將其以中括號含括。
 Bindable或BindableAttribute:該屬性宣告式標記是用來設定屬性可以被Bind到資料來源,若未設定該屬性宣告式標記預設為False。
 DefaultValue或DefaultValueAttribute:該屬性宣告式標記是用來指定屬性的預設值,當屬性值不等於屬性的預設值時,方格內的該屬性值將以粗體來表示。
 DesignOnly或DesignOnlyAttribute:該屬性宣告式標記是用來設定屬性在執行時期是否為唯讀,若為True則該屬性只可在設計時期被設定。
 Description或DescriptionAttribute:該屬性宣告式標記是用來設定屬性方格下描述說明窗格中所顯示之屬性的文字,即在方格中取得焦點的屬性相關描述說明。
類別AppSettings內除了Title和Show為簡單型別的屬性,其餘皆為複雜屬性,由.NET Framework提供一些具有特殊顯示功能的資料型別,讓類別中的屬性更容易在方格中被使用與設定,如:表單顏色(AppColor)、應用程式游標樣式(AppCursor)、表單字型(AppFont)、語言(AppLangugage)表單位置(AppLocation)、表單大小(AppSize)、表單縮圖(AppIcon)…等皆為複雜屬性在方格中各有特殊顯示功能。


2、 建立呈現視窗:
在原專案[PropertyGrid_WinAp]的方案總管中選右鍵,於快顯功能表中選[加入][加入視窗表單],名稱為[SelfProperty],按[確定]建立新表單後,拖曳PropertyGrid控制項物件至SelfProperty表單上名稱為PropertyGrid1。
3、 撰寫相關程式碼:

Private Sub SelfProperty_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim SelfCls As New AppSettings()
PropertyGrid1.SelectedObject = SelfCls
PropertyGrid1.Refresh()
End Sub

上面的程式碼是於SelfProperty_Load事件中(即表單載入時),建立一AppSettings的類別物件的執行個體SelfCls,而PropertyGrid1方格物件的SelectedObject指定為SelfCls物件,如此即可呈現如(圖九)的樣式,而在末行PropertyGrid1.Refresh()方法是用來重新整理屬性。
4、 設定啟始物件
在執行之前我們需改變它的啟始物件,如(圖七)選擇啟始物件為[SelfProperty],後按確定,執行即可見著如(圖九)之畫面。
到目前為止我們還停留在怎麼樣利用PropertyGrid控制項物件方格來呈現自訂或系統類別屬性,和怎麼利用宣告式標記來套用不同的特性(Attribute) 至這些屬性上與怎麼在方格中對屬性來加分類等議題,接下來我們將探討如何結合這系列文章第一篇(Run PC 109期)所提到的DynamicProperties之Config檔來保存PropertyGrid控制項物件的設定值。

利用DynamicProperties保存PropertyGrid控制項物件的設定值
依附上例,我們各舉二個簡單型別{(標頭(Title)、可見與否(Show) }與二個複雜誌型別{表單位置(AppLocation)、表單大小(AppSize)}來加以保存。

1、 建立Config檔
選擇表單SelfProperty後於DynamicProperties內任一設定一對應屬性,使其由系統快速自動建立app.config檔,再將其檔案內容修改如下:



Height和Width是用來保存表單大小(AppSize);而X和Y是用來保存表單位置(AppLocation);Title和Show即是用來保存其餘二個簡單型別。
2、 讀取Config檔與初始設定
在表單SelfProperty的New建構式中呼叫SetApp()

Public Sub New()
MyBase.New()
InitializeComponent()
SetApp()
End Sub
Sub SetApp()
Dim MyConfigReader As System.Configuration.AppSettingsReader = New System.Configuration.AppSettingsReader()
Dim x, y, width, height As Integer, title As String, show As Boolean
x = CType(MyConfigReader.GetValue("X", GetType(System.Int32)), Integer)
y = CType(MyConfigReader.GetValue("Y", GetType(System.Int32)), Integer)
width = CType(MyConfigReader.GetValue("Width", GetType(System.Int32)), Integer)
height = CType(MyConfigReader.GetValue("Height", GetType(System.Int32)), Integer)
title = MyConfigReader.GetValue("Title", GetType(System.String))
show = CType(MyConfigReader.GetValue("Show", GetType(System.Boolean)), Boolean)
Me.Location = New System.Drawing.Point(x, y)
Me.Size = New Size(width, height)
Me.Text = title
Me.Visible = show
SelfCls.AppLocation = Me.Location
SelfCls.AppSize = Me.Size
SelfCls.Title = Me.Text
SelfCls.Show = Me.Visible
End Sub

這段程式碼是利用AppSettingsReader類別物件來讀取Config檔內的設定值,再一一將讀取出來的值設定給應用程式來反應舊有保存值和自訂類別的執行個體(SelfCls)來立即呈現相關設定於PropertyGrid類別物件方格內。
再來需將SelfProperty_Load事件中建立自訂類別物件的執行個體,由事件程序提昇至類別層級,宣告如下:Dim WithEvents SelfCls As New AppSettings()
3、 修改自訂類別來保存設定值
因為我們希望在PropertyGrid類別物件方格內修改屬性值的同時能即時將設定值反應至應用系統環境中,是故我們在自訂類別AppSettings內加入四個事件宣告。

Public Event SetAppSize(ByVal SetValue As Size)
Public Event SetAppLocation(ByVal SetValue As Point)
Public Event SetTitle(ByVal SetValue As String)
Public Event SetShow(ByVal SetValue As Boolean)

當相關的屬性值修改時立即觸發該事件,是故我們選擇在Title、Show、AppLocation、AppSize四個屬性的Set子函式內分別加入RaiseEvent SetTitle(Value)、RaiseEvent SetShow(Value)、RaiseEvent SetAppLocation(Value)、RaiseEvent SetAppSize(Value)敘述。
接下來於自訂類別中撰寫相關儲存設定程式碼,於設定屬性值的同時亦將屬性值直接存入Config檔內,程式碼參考如下:

Private Sub SaveValue()
Dim RA As System.Reflection.Assembly = System.Reflection.Assembly.GetExecutingAssembly
Dim StrCnfgPath As String = RA.Location & ".config"
Dim XmlDoc As New System.Xml.XmlDocument()
XmlDoc.Load(StrCnfgPath)
Dim AddNode As System.Xml.XmlNode
For Each AddNode In XmlDoc.Item("configuration").Item("appSettings")
If AddNode.Name = "add" Then
Select Case AddNode.Attributes.GetNamedItem("key").Value
Case "X"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppLocation.X, System.String)
Case "Y"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppLocation.Y, System.String)
Case "Width"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppSize.Width, System.String)
Case "Height"
AddNode.Attributes.GetNamedItem("value").Value = CType(_AppSize.Height, System.String)
Case "Title"
AddNode.Attributes.GetNamedItem("value").Value = CType(_Title, System.String)
Case "Show"
AddNode.Attributes.GetNamedItem("value").Value = CType(_Show, System.String)
End Select
End If
Next
XmlDoc.Save(StrCnfgPath)
End Sub

是利用XML相關的類別物件將值存入Config檔內,相關細節說明請見(Run PC 109期DynamicProperties),而我們必須在RaiseEvent之前來呼叫SaveValue副程式來做儲存設定值的動作。
4、 反應設定值於應用系統中
當我們有RaiseEvent且在建立自訂的類別執行實體時的WithEvents時,即可在SelfProperty中找到SelfCls類別物件的SelfCls_SetAppSize、SelfCls_SetAppSize、SelfCls_SetAppSize、SelfCls_SetAppSize四個事件,程式碼撰寫如下來反應設定值於應用系統中:

Private Sub SelfCls_SetAppSize(ByVal SetValue As System.Drawing.Size) Handles SelfCls.SetAppSize
Me.Size = New Size(SetValue.Width, SetValue.Height)
End Sub
Private Sub SelfCls_SetAppLocation(ByVal SetValue As System.Drawing.Point) Handles SelfCls.SetAppLocation
Me.Location = New Point(SetValue.X, SetValue.Y)
End Sub
Private Sub SelfCls_SetShow(ByVal SetValue As Boolean) Handles SelfCls.SetShow
Me.Visible = SetValue
End Sub
Private Sub SelfCls_SetTitle(ByVal SetValue As String) Handles SelfCls.SetTitle
Me.Text = SetValue
End Sub


5、 執行測試:
在VS.Net IDE中按F5執行,修改屬性值儲存,停下重新執行是否發現並未如預期的將設定值儲存下來呢?那是因為在開發環境中每次執行會將app.config內容蓋回我們儲存的設定檔中,在佈署時就不會有此問題。請找到Bin資料夾下的PropertyGrid_WinAp.exe執行即可正常操作。


結論:

這篇文章我們大致上為各位介紹怎麼樣將設計時期的IDE中屬性視窗延伸到我們程式執行時期(Run-Time),讓我們更有彈性的設計我們的系統,使其有更豐富的變化;當然接著我們也為各位介紹怎麼將自訂類別物件呈現在PropertyGrid類別物件方格內,而不單單只可呈現系統的類別物件,進而將建立一個自訂獨立的類別,而非使用各別控制項,使其設定較簡單且容易管理維護,將不必要或較細節的部分隱藏起來,而後半部為各位解說怎麼利用DynamicProperties的Config檔來保存PropertyGrid類別物件方格屬性值,細節部份各位可參考(Run PC 109 DynamicProperties)。
.NET內的PropertyGrid類別物件是個非常豐富的控制項,當然還有許多議題可以加以探討,如怎麼樣在.NET Framework型別使用TypeConverter和UITypeEditor類別,將自訂型別於呈現在PropertyGrid類別物件方格內,怎樣利用繼承ExpandableObjectConverter來提供可擴充物件的支援,怎樣提供下拉屬性的支授…等,若各位讀者有興趣可參考Shawn Burke 的文章《Make Your Components Really RAD with Visual Studio .NET Property Browser》;若您對利用PropertyGrid類別物件方格保存設定值想進一步研究,您可參考Resource Editor .NET (http://www.codeproject.org/cs/miscctrl/resource_editor.asp),它說明怎麼利用資源檔來保存設定值,包含圖檔等資料。

2009年6月23日 星期二

RunPC172期:Gadget小工具設計實務:以台灣高鐵乘車資訊全方位指南為例(下)

ASP.NET 2.0 CallBack機制撰寫AJAX程式

ASP.NET 2.0中提供有別於PostBackCallBack功能,它讓我們可以在ASP.NET 2.0上撰寫AJAX程式,要在ASP.NET 2.0上撰寫CallBack程式,要先了解 ICallbackEventHandler這個Interface,該Interface有兩個方法:GetCallbackResult ()RaiseCallbackEvent(ByVal eventArgument As String),所以我們可介由繼承ICallbackEventHandler這個Interface並且實作這兩個方法,來完成CallBack機制撰寫AJAX程式,以下是我們Sever-Side的完整程式碼:

 

程式八:小工具CallBack程式Sever-Side程式碼

Partial Class ThsrcDB

    Inherits System.Web.UI.Page

    Implements System.Web.UI.ICallbackEventHandler

    Dim rtnTime As String  '要回傳的時刻字串

    Dim rtnPrice As String  '要回傳的票價字串

 

    Public Function GetCallbackResult() As String Implements System.Web.UI.ICallbackEventHandler.GetCallbackResult

        Return rtnTime & "^" & rtnPrice'時刻字串與票價字串由『^』分隔,查詢後傳回Client-Side

    End Function

'eventArgumentClient-Side傳入

    Public Sub RaiseCallbackEvent(ByVal eventArgument As String) Implements System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent

        Dim A() As String

        A = eventArgument.Split("^")

        Dim SSiteID As String = A(0)

        Dim ESiteID As String = A(1)

        Dim SSiteTime As String = A(2)

        Dim ESiteTime As String = A(3)

        Dim SEType As String = A(4)

        rtnTime = GetTimeData(SSiteID, ESiteID, SSiteTime, ESiteTime, SEType)

        rtnPrice = GetPriceData(SSiteID, ESiteID)

    End Sub

 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Dim cbReference As String

        '建立 Call back程式碼-指定回傳函式為 ReceiveServerData

        Dim strArg As String = ""

        strArg &= "document.all." + ddlTimeFrom.ClientID + ".value +'^'"

        strArg &= "+document.all." + ddlTimeTo.ClientID + ".value +'^'"

        strArg &= "+document.all." + ddlSTime.ClientID + ".value +'^'"

        strArg &= "+document.all." + ddlETime.ClientID + ".value +'^'"

        strArg &= "+document.all." + ddlSE.ClientID + ".value"

        cbReference = Page.ClientScript.GetCallbackEventReference(Me, strArg, "ReceiveServerData", "")

        '註冊

        btnSearch.Attributes("onclick") = cbReference

 

        '設計一個函式(名稱為ReceiveServerData),接收 Server Call Back 回來的值

        Dim ReceiverScript As String = ""

        ReceiverScript += vbCrLf + "<script>"

        ReceiverScript += vbCrLf + " function ReceiveServerData(DataFromServer)"

        ReceiverScript += vbCrLf + "{"

        ReceiverScript += vbCrLf + "var a=DataFromServer.split('^');"

        ReceiverScript += vbCrLf + "document.all.divTime.innerHTML=a[0];"

        ReceiverScript += vbCrLf + "document.all.divPrice.innerHTML=a[1];"

        ReceiverScript += vbCrLf + "document.all.fdsResult.style.display ='block';"

        ReceiverScript += vbCrLf + "document.all.imglodaing.style.display ='none';"

        ReceiverScript += vbCrLf + "}"

        ReceiverScript += vbCrLf + "</script>"

        '在頁面上註冊(寫入)這個函式

        Page.ClientScript.RegisterClientScriptBlock(GetType(String), "ReceiverScript", ReceiverScript)

    End Sub

 

    Function GetTimeData(ByVal SSiteID As String, ByVal ESiteID As String, ByVal SSiteTime As String, ByVal ESiteTime As String, ByVal SEType As String) As String

        Dim sp As New StringBuilder

        Dim dv As DataView

        With Me.sqldsTime

            .SelectParameters(0).DefaultValue = SSiteID

            .SelectParameters(1).DefaultValue = ESiteID

            .SelectParameters(2).DefaultValue = SSiteTime

            .SelectParameters(3).DefaultValue = ESiteTime

            .SelectParameters(4).DefaultValue = SEType

            dv = .Select(New DataSourceSelectArguments)

        End With

 

        For Each dr As DataRowView In dv

            If CType(dr(2), String) <> "" Then

                sp.Append(String.Format("<span style='color: red' alt='車次:{0}({1}行駛)' altbg='red' altcolor='yellow' altborder=yellow>{2}</span>", dr(0), dr(2), dr(1)))

            Else

                sp.Append(String.Format("<span alt='車次:{0}'>{1}</span>", dr(0), dr(1)))

            End If

        Next

        If sp.ToString = "" Then

            Return ""

        Else

            Return sp.ToString.Substring(0, sp.ToString.Length - 1)

        End If

    End Function

 

    Function GetPriceData(ByVal SSiteID As String, ByVal ESiteID As String) As String

        Dim sp As New StringBuilder

        Dim dv As DataView

 

        With Me.sqldsPrice

            .SelectParameters(0).DefaultValue = SSiteID

            .SelectParameters(1).DefaultValue = ESiteID

            dv = .Select(New DataSourceSelectArguments)

        End With

 

        For Each dr As DataRowView In dv

            sp.Append(String.Format("●【{0}:{1}<br>", GetTypeStr(dr(0)), CType(dr(1), String).PadLeft(10, " ")))

        Next

        If sp.ToString = "" Then

            Return ""

        Else

            Return sp.ToString.Substring(0, sp.ToString.Length - 4)

        End If

    End Function

 

    Function GetTypeStr(ByVal str As String) As String

        Dim RTN As String = ""

        Select Case str

            Case "0"

                RTN = "標準車廂-全票"

            Case "1"

                RTN = "商務車廂-全票"

            Case "2"

                RTN = "標準車廂-優待票"

            Case "3"

                RTN = "商務車廂-優待票"

            Case "4"

                RTN = "自由座-全票"

            Case "5"

                RTN = "自由座-優待票"

            Case "6"

                RTN = "標準車廂-團體票"

            Case "7"

                RTN = "商務車廂-團體票"

        End Select

        Return RTN.PadRight(8, " ")

    End Function

 

End Class

運作流程如下:在Page_Load事件中指定會觸發CallBack物件與事件(btnSearch物件的onclick事件),且該CallBack會傳送什麼前端資訊(strArg)至伺服器端,同時指定伺服端執行完成後會呼叫前端ReceiveServerData函式;該函式為CallBack程式碼指定回傳函式,接收 Server Call Back 回來的值,且將該函式註冊寫入在前端頁面上,而程式碼中會將從伺服器端接收回來的DataFromServer參數分割再放入對應呈現時刻(divTime)與票價(divPrice)的物件中。

而實作ICallbackEventHandlerGetCallbackResult方法是伺服端傳給客戶端資料,它會對應至前面所述的ReceiveServerData函式中的DataFromServer參數;而實作ICallbackEventHandlerRaiseCallbackEvent方法,其中eventArgument是由客戶端傳入,亦就是前面所說的strArg參數,因為該客戶端傳入的參數只有一個,固我們利用組字串時加入『^』來分隔,在伺服端再以^來分割成各變數,再透過切割後的變數,傳入GetTimeDataGetPriceData兩個方法,分別呼叫GetTimeGetPrice預存程序來取得時刻與票價相關資料,整個完成後,即會觸發ICallbackEventHandlerGetCallbackResult方法將存放時刻rtnTime與票價rtnPrice的字串組合後傳至前端ReceiveServerData函式中的DataFromServer參數。

 

AJAX程式包裹成Web Gadget

至此我們完成小工具資料庫的設計、高鐵資料的取得、UrMap地圖的套用、AJAX程式的撰寫,它是個完整獨立的網頁小程式,在MicrosofWeb GadgetsGoogleUniversal Gadgets都提供單獨網頁嵌入Gadget小工具的方式,在實作MicrosofWeb Gadgets的包裹作法前,需先了解一個基本的Gadget包含下面4個檔案:

  1. Gadget Manifest(配置檔;附檔名:xml):必要檔案,其檔案採用XML結構的形式,檔案裡定義一些屬性與指定所參考的Gadget JavaScriptGadget Style Sheet和圖檔檔案的位置。
  2. Gadget JavaScript(邏輯程式檔;附檔名:js):必要檔案,這個檔案包含一些邏輯程式碼、屬性與定義 Gadget該如何動作。
  3. Gadget Style Sheet(樣式表;附檔名:css):非必要檔案,該CSS檔案為該Gadget的樣式檔,設定它如何呈現外觀與介面。
  4. Gadget 圖檔(附檔名:jpgpng):非必要檔案,指定該Gadget的圖示。

 

本範例的Gadget Manifest配置檔如下,指定它的標題、說明與所參考的Gadget JavaScript(gadget.js)和它的圖示(pic.jpg),要特別注意的是binding:type要與Gadget JavaScript內的建構元件對應起來。

程式九:小工具的Gadget Manifest(gadget.xml)

<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0" xmlns:binding="http://www.live.com">

     <channel>

           <title>台灣高鐵乘車資訊全方位指南</title>

           <description>提供台灣高鐵乘車資訊全方位指南,除可查各站點之票價、時間、轉乘、訂票資訊與週遭景點資訊外且結合UrMap地圖可友善設定啟到站與查啟到站時間。</description>

           <language>zh-tw</language>

           <binding:type>MLChen.Spaces.Gadget.TaiwanThsrc</binding:type>

           <item>

                <link>gadget.js</link>

           </item>

           <icons>

                <icon height="32" width="32">pic.jpg</icon>

           </icons>

     </channel>

</rss>

至於Gadget JavaScript程式碼如下,除了名稱空間與建構式等要特別注意外,在單獨網頁嵌入Gadget小工具中僅要將url網址改成您所要嵌入Gadget小工具的單獨網頁即可,而其它只要將所屬的Gadget檔案,本例中的gadget.xmlgadget.jspic.jpg壓成一個壓縮檔zip,接著即可至http://gallery.live.com,將辛苦製作的小工具上傳至 Windows Live Gallery透過Gadget的方式分享給大家。

程式十:小工具的Gadget JavaScript

    // 註冊小工具的名稱空間 (namespace)

    registerNamespace("MLChen.Spaces.Gadget");

 

    //// 定義此小工具的建構元 。

    //(名稱必須相同於清單檔(XML)的設定)

    MLChen.Spaces.Gadget.TaiwanThsrc = function(p_elSource, p_args, p_namespace) {

    // 做任何事之前必須先呼叫 initializeBase 函式。

     MLChen.Spaces.Gadget.TaiwanThsrc.initializeBase(this, arguments);

 

    // 設定一些私有成員變數 (private member variables)

     var m_el = p_elSource;

     var m_module = p_args.module;

      

     /****************************************

     **          initialize Method (初始化方法)                 

     ****************************************/

     // 當你的物件開始執行時,這個 initialize 函式會永遠立即被呼叫。

     this.initialize = function(p_objScope)                       

     {                                                                       

           // 總是先呼叫 Base 物件的 initialize 函式。   

           MLChen.Spaces.Gadget.TaiwanThsrc.getBaseMethod(this, "initialize", "Web.Bindings.Base").call(this, p_objScope);

                                                                                                                  

           //  小工具背景透明化 。                                               

           if (window.parent != window.self) {                                         

                document.body.style.backgroundColor = "transparent";

           }                                                                                

          

           var url ="http://www.abc.com.tw/Thsrc.aspx";

           m_el.innerHTML = "<iframe src=\"" + url + "\" frameborder=\"0\" scrolling=\"auto\" height=\"500\" width=\"466\"/>"

          

     };                                                                       

     MLChen.Spaces.Gadget.TaiwanThsrc.registerBaseMethod(this, "initialize");

      

     

     /****************************************      

     **           dispose Method (解除方法)                    

     ****************************************/

     this.dispose = function(p_blnUnload) {      

           // 清除所有的成員變數                          

           m_this = null;                         

           m_el = null;                         

           m_module = null;                         

                                               

           // 最後總是呼叫 base 物件的 dispose 函式

           MLChen.Spaces.Gadget.TaiwanThsrc.getBaseMethod(this, "dispose", "Web.Bindings.Base").call(this, p_blnUnload);

     };                                                                             

         MLChen.Spaces.Gadget.TaiwanThsrc.registerBaseMethod(this, "dispose");

    };

    MLChen.Spaces.Gadget.TaiwanThsrc.registerClass("MLChen.Spaces.Gadget.TaiwanThsrc", "Web.Bindings.Base");

 

總結:

網路邁入Web 2.0的時代,有別於Web 1.0網友僅能單項接收資訊或服務,此處我們借由MicrosoftGoogle提供的平台,依各平台規格撰寫服務或小工具程式,即可提供我們開發的服務給其它人使用,而該台灣高鐵乘車資訊全方位指南小工具又以混搭(mashup)的方式,使用到地圖供應商UrMap提供的地圖服務,且查詢方式又以AJAX方式呈現,豐富使用者體驗,故我們慢慢可以了解Yahoo!奇摩總經理鄒開蓮表示:『小工具正是Web 2.0精神的展現』這句話的意思。

這篇Gadget小工具設計實務是以筆者參加台灣微軟所主辦、捨得資訊協辦的2007 Windows Live Spaces Gadgets部落格小工具設計大賽的佳作作品,唯筆者在看這場競賽時,實覺十分可惜,在一再強調使用者體驗(User Experience)Gadget小工具上,反諷的是協辦單位所開發舉辦Gadget競賽活動平台竟沒有讓筆者有絲毫感到使用者體驗的部分,光作品需上傳至Windows Live Gallery外還要再上傳一次至競賽網站這一點筆者就十分不能理解,更不用說該競賽平台竟有一堆Bug,反應協辦單位後亦沒有多大改善,或許這只是個小活動、小競賽,無須太過認真,但如此就會失去辦這活動的意義了。

 

參考資料:

Live.com Windows Live Spaces 網頁小工具 Gadget 開發手冊 (上)

Live.com Windows Live Spaces 網頁小工具 Gadget 開發手冊 (中)

Live.com Windows Live Spaces 網頁小工具 Gadget 開發手冊 (下)

英文版 Web Gadget SDK 文件集

UrMap API v1.06 說明文件

台灣高鐵官方網站

相關連結:

台灣高鐵乘車資訊全方位指南Gadget小工具位址:http://gallery.live.com/LiveItemDetail.aspx?li=1cc833af-4048-4d25-aad5-2280948bfa59

台灣高鐵乘車資訊全方位指南Gadget小工具使用簡報:

http://cid-f315ca8945fc025a.skydrive.live.com/self.aspx/%e5%85%ac%e9%96%8b/TwThsrc.ppt

加入書籤: MyShare HemiDemi Baidu Google Bookmarks Yahoo! My Web PChome Del.icio.us Digg technorati furludn bookmark 其他更多書籤

BOOKS:New and Upcoming