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。

沒有留言:

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

BOOKS:New and Upcoming