2010年3月11日 星期四

Free ASP.NET MVC eBook Tutorial

Free ASP.NET MVC eBook Tutorial ,
有免費的學習資源喔!
有興趣的可以看一下喔! ^_^

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是顯示樹狀結構,而我們是呈現在輸出視窗中。
加入書籤: MyShare HemiDemi Baidu Google Bookmarks Yahoo! My Web PChome Del.icio.us Digg technorati furludn bookmark 其他更多書籤

BOOKS:New and Upcoming