好久沒寫東西了,最近搞了下silverlight方面的東西,有一需求要求應用程序的模塊和xap包分離,實現按需加載,模塊化的更新,怎么做呢?
在沒思路的情況下,百度,google是最好的方法了,雖然能搜到得東西不多,不過也讓我有了頭緒,也就有了下面的文章。
1.用WebClient從服務端下載
這是一個用來接收和發送數據的類,這里我們用它來從服務端加載dll模塊
public void DownloadAssembly() { var webClient = new WebClient(); webClient.OpenReadCompleted += webClient_OpenReadCompleted; // //這個方法是異步的,線程池會自動分配一個線程給它 webClient.OpenReadAsync(new Uri("GridModule.dll", UriKind.Relative)); } void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e) { AssemblyPart part=new AssemblyPart(); //將讀取的流轉化為當前應用程序域中的程序集 var asm=part.Load(e.Result ); //創建一個實例 object o = asm.CreateInstance("GridModule.GridPage"); Page p = o as Page; this.MainFrame.Content = o; }
上面的方法很好的實現了客戶端按需加載,如果話題到了這里就結束,倒也是皆大歡喜,一切都很簡單,可惜問題往往不是這樣。下面有個糾結的地方
AssemblyPart這個類的load方法MSDN上給了這么一句話
You cannot load multiple versions of the same assembly into the current application domain.
當前應用程序域中,你是無法加載同一程序集的多個版本的,這真讓人抓狂啊,你不能加載,為什么就不能更新呢?
這也導致了你一旦有新的程序集更新,那么必須得關掉瀏覽器,重新打開加載。
2.WebClient與IE緩存
運行在IE上的東西,貌似總能與IE緩存扯上關系,這東西真是讓人又愛又恨。那么這里又和sliverlight有什么聯系呢?我們知道,silverlight通常是將程序集資源編譯成xap包放在服務器端,客戶端打開瀏覽器將xap包下載到本地進行解析運行的,那么既然是在瀏覽器中打開的,有請求,有響應,IE緩存也勢必會出來露個臉。
說IE緩存是好東西,那的確是好東西,第一次打開過后,以后可以很快捷的再次打開。說IE緩存是壞東西,每當我想更新,客戶端須得清理掉IE緩存,才能看到更新效果,或者說我服務端做一些URL處理,不過這些個IE內部的處理機制,誰知道哪天會來點莫名其妙的事情呢。
想知道IE是怎么更新緩存的話,那么就去查下ETAG這個東西,你就知道了。
3.1xap與dll分離,使用IE緩存做存儲介質
剛有點扯遠了,現在扯回來點,看這小標題,該如何着手做呢?
有以下幾點需要做到的
1.從服務端獲取dll的方式
2.本地存儲、加載dll的方式
3.實例化dll,或者說創建dll的對象,總之到了客戶端,你得用它
1.從服務端獲取dll的方式
說道這個,相信你肯定會想到一開始提到的webclient了吧,的確用它非常不錯,都是現成的,而且它會將下載的東西存儲在IE緩存中,連第二點要做的存儲都幫你做了,實在是非常好啊。
2.本地存儲、加載dll的方式
存儲上面已經提到,那么怎么加載呢?其實你也已經看到了,就是AssemblyPart,有個load方法,會將stream轉換成將要加載到應用程序域中程序集assembly。
3.實例化DLL
so easy 了。。。用了下小反射而已
3.2 xap與dll分離,使用獨立緩存做存儲介質
用獨立緩存的原因是什么呢?是不想受IE緩存的控制,可以完全掌控在自己的手中,這感覺還是比較踏實的,話說100M的空間一般的應用的都可以了,要做到的還是上面提到的幾點,只是怎么做變了
1.從服務端獲取dll的方式
這次直接貼代碼了,呵呵
服務端
[ServiceContract] public interface IGetAssembly { [OperationContract] Dictionary<string, string> GetAssembliesDic(); [OperationContract] RemoteFileInfo GetAssemblyStream(DownloadRequest request); } [DataContract] public class DownloadRequest { [DataMember] public string FileName; } [DataContract] public class RemoteFileInfo { [DataMember] public string FileName; [DataMember] public long Length; [DataMember] public string Version; [DataMember] public byte[] FileByte; }
實現方法
public class GetAssembly : IGetAssembly { public Dictionary<string, string> GetAssembliesDic() { Dictionary<string, string> datas = new Dictionary<string, string>(); foreach (string item in ConfigurationManager.AppSettings.AllKeys) { datas.Add(item, ConfigurationManager.AppSettings[item].ToString()); } //if (datas.Count == 0) //{ // throw new Exception("應用程序dll配置獲取失敗"); //} return datas; } public RemoteFileInfo GetAssemblyStream(DownloadRequest assembly) { RemoteFileInfo result = new RemoteFileInfo(); try { string filePath = System.IO.Path.Combine(@"E:\wwwroot\session\bin\dll", assembly.FileName); System.IO.FileInfo fileInfo = new System.IO.FileInfo(filePath); // check if exists if (!fileInfo.Exists) throw new System.IO.FileNotFoundException("File not found", assembly.FileName ); // open stream System.IO.FileStream stream = new System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read); byte[] bs = new byte[stream.Length]; stream.Read(bs, 0, bs.Length); System.Reflection.Assembly asm = System.Reflection.Assembly.Load(bs); // return result result.FileName = assembly.FileName ; result.Length = fileInfo.Length; result.FileByte = bs; result.Version = asm.GetName().Version.ToString(); } catch (Exception ex) { throw ex; } return result; } }
服務端很簡單,提供2方法,獲取版本信息,以及下載指定dll
客戶端
/// <summary> /// 從服務器端加載dll /// </summary> /// <param name="dllName"></param> /// <param name="call"></param> /// <param name="errCall"></param> public void GetDllFromServer(string dllName,Action<RemoteFileInfo > call, Action<string,Exception> errCall) { RemoteFileInfo result; System.ServiceModel.BasicHttpBinding basic = new System.ServiceModel.BasicHttpBinding(); basic.MaxReceivedMessageSize = 2147483647; basic.MaxBufferSize = 2147483647; basic.ReceiveTimeout = TimeSpan.FromMinutes(5); basic.TransferMode = System.ServiceModel.TransferMode.StreamedResponse; basic.Security.Mode = System.ServiceModel.BasicHttpSecurityMode.None; IGetAssembly service = ClientService.ClientService<IGetAssembly>.GetServiceChannel(basic, new System.ServiceModel.EndpointAddress("http://192.168.104.47/wcf2nd/GetAssembly.svc")); try { service.BeginGetAssemblyStream(new DownloadRequest { FileName = dllName }, (res) => { try { result = service.EndGetAssemblyStream(res); if (!System.Windows.Deployment.Current.Dispatcher.CheckAccess()) { System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => { if (call != null) call(result); }); } else { if (call != null) call(result); } } catch (Exception ex) { System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => { if (errCall != null) errCall(dllName,ex); }); } }, null); } catch (Exception ex) { System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => { if (errCall != null) errCall(dllName,ex); }); } }
這是主要的加載方法了,方法應該比較明了了。
2.本地存儲、加載dll的方式
存儲讀取dll的方法
/// <summary> /// 將程序集以二進制方式存入獨立緩存 /// </summary> /// <param name="path">路徑</param> /// <param name="assemblyName">程序集名</param> /// <param name="stream">要寫入的流</param> public void SaveIntoIsolatedStorage(string path, string assemblyName, Stream stream) { IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); IsolatedStorageFileStream assembly = null; try { stream.Seek(0, SeekOrigin.Begin); assembly = store.OpenFile(assemblyName, System.IO.FileMode.Create); byte[] res = StreamToBytes(stream); assembly.Write(res, 0, res.Length); } catch (Exception ex) { throw ex; } finally { assembly.Close(); } } /// <summary> /// 從獨立緩存中讀取程序集流 /// </summary> /// <param name="path">路徑</param> /// <param name="assemblyName">程序集名</param> /// <returns></returns> public Stream ReadFromIsolatedStorage(string path, string assemblyName) { IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication(); IsolatedStorageFileStream assembly = null; if (store.FileExists(assemblyName)) { assembly = store.OpenFile(assemblyName, System.IO.FileMode.Open); } return assembly; }
存儲讀取dll版本信息
/// <summary> /// 獲取獨立緩存中存儲的程序集版本集合 /// </summary> /// <returns></returns> public Dictionary<string, string> GetDllVersion() { Dictionary<string, string> dic = new Dictionary<string, string>(); IsolatedStorageSettings appSetting = IsolatedStorageSettings.ApplicationSettings; foreach (string item in appSetting.Keys) { if (item.IndexOf("dll") != -1) { dic.Add(item, appSetting[item].ToString()); } } return dic; } /// <summary> /// 將dll名稱與版本號的鍵值對存入獨立緩存,如果存在該鍵值則更新 /// </summary> public void SetDllVersion(string dllName, string version) { IsolatedStorageSettings appSetting = IsolatedStorageSettings.ApplicationSettings; try { if (!appSetting.Contains(dllName)) { appSetting.Add(dllName, version); } else { appSetting[dllName] = version; } } catch (Exception ex) { throw ex; } }
3.實例化dll
這里就和上面的一樣了,都是用assemblypart里的load方法將stream轉化為assembly
匆匆忙忙的寫完了,程序也沒有經過過多優化,不過思想已經比較清楚的表達了,歡迎討論,拍磚
