突破 Silverlight 自身限制, 做更好的动态加载导航机制(二)

上一篇文章中, 动态导航的思路已经比较完善了, 现在来实现动态导航机制。

实现按需加载 Silverlight 组件

Silverlight 客户端加载一个程序集很容易, 关键是如何分析并加载程序集引用的其它程序集, 这些程序集又会引用另外的程序集, 然后再加在这些程序集。

借助于 Mono.Cecil ,可以在客户端很容易的分析出程序集引用的其它程序集

至于如何加载, 我的实现思路是, 做一个下载队列, 每下载一个程序集, 分析其引用的程序集列表, 找出其没有加载过的程序集, 添加到下载队列, 从下载队列中删除下载过的程序集, 如果队列不为空, 则依次进行递归; 否则,触发下载完成事件。实现代如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
public class AssemblyDownloader {
 
   private static readonly IDictionary<string assembly="" ,=""> LoadedAssemblies = new Dictionary<string assembly="" ,="">(StringComparer.OrdinalIgnoreCase);
   private readonly ISet<string> _loadingSet = new HashSet<string>();
   private static readonly object LoadingSetLock = new object();
    
   private static readonly string[] SilverlightRuntimeAssemblyNames = new[] {
      "Microsoft.VisualBasic.dll",
      "mscorlib.dll",
      "System.Core.dll",
      "System.dll",
      "System.Net.dll",
      "System.Runtime.Serialization.dll",
      "System.ServiceModel.dll",
      "System.ServiceModel.Web.dll",
      "System.Windows.Browser.dll",
      "System.Windows.dll",
      "System.Windows.RuntimeHost.dll",
      "System.Xml.dll"
   };
    
   private bool _isbusy;
   private string _loadingAssemblyName;
 
   public event EventHandler<downloadassemblycommpletedeventargs> DownloadAssemblyCommpleted;
   public event EventHandler<asynccompletedeventargs> DownloadFailed;
 
   public Assembly GetAssembly(string assemblyName) {
      return LoadedAssemblies.ContainsKey(assemblyName) ? LoadedAssemblies[assemblyName] : null;
   }
 
   public void OnDownloadFailed(Exception ex) {
      var handler = this.DownloadFailed;
      if (handler != null) {
         handler(this, new AsyncCompletedEventArgs(ex, true, null));
      }
   }
 
   private void OnDownloadAssemblyCommpleted(DownloadAssemblyCommpletedEventArgs e) {
      var handler = this.DownloadAssemblyCommpleted;
      if (handler != null) {
         handler(this, e);
      }
   }
 
   public void DownloadAssemblyAsync(string assemblyName) {
      if (this._isbusy) {
         throw new InvalidOperationException(string.Format("AssemblyDownloader is loading {0}, please waite ...", this._loadingAssemblyName));
      }
      assemblyName = EnsureEndWdithDll(assemblyName);
      this._loadingAssemblyName = assemblyName;
      if (IsAssemblyLoaded(assemblyName)) {
         this.DownloadCompleted();
      }
      else {
         DownloadAssemblyAsyncCore(assemblyName);
      }
   }
 
   private void DownloadAssemblyAsyncCore(string assemblyName) {
      this._isbusy = true;
      assemblyName = EnsureEndWdithDll(assemblyName);
      var name = assemblyName;
      var webClient = new WebClient();
      webClient.OpenReadCompleted += (sender, e) => this.OnReadOneAssembly(name, e);
      try {
         webClient.OpenReadAsync(new Uri(assemblyName, UriKind.Relative));
      }
      catch (Exception ex) {
         this.OnDownloadFailed(ex);
      }
   }
 
   private static string EnsureEndWdithDll(string assemblyName) {
      if (!assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) {
         assemblyName += ".dll";
      }
      return assemblyName;
   }
 
   private void OnReadOneAssembly(string name, OpenReadCompletedEventArgs e) {
      if (e.Error != null) {
         this.OnDownloadFailed(e.Error);
         this._isbusy = false;
         return;
      }
      var assemblyStream = e.Result;
      var references = GetReferenceAssemblyNames(assemblyStream);
      AddNotLoadedReferenceAssemblyToLoadingSet(references);
 
      assemblyStream.Seek(0, SeekOrigin.Begin);
      LoadToAssemblyPart(assemblyStream, name);
 
      if (this._loadingSet.Count > 0) {
         var asm = this._loadingSet.First();
         lock (LoadingSetLock) {
            this._loadingSet.Remove(asm);
         }
         this.DownloadAssemblyAsyncCore(asm);
      }
      else {
         this.DownloadCompleted();
      }
   }
 
   private void DownloadCompleted() {
      this._isbusy = false;
      var assembly = this.GetAssembly(this._loadingAssemblyName);
      this.OnDownloadAssemblyCommpleted(new DownloadAssemblyCommpletedEventArgs(assembly));
   }
 
   private static void LoadToAssemblyPart(Stream assemblyStream, string name) {
      var part = new AssemblyPart {
         Source = name
      };
      var assembly = part.Load(assemblyStream);
      LoadedAssemblies.Add(name, assembly);
   }
 
   private void AddNotLoadedReferenceAssemblyToLoadingSet(IEnumerable<string> references) {
      var referencesNotLoaded = from reference in references
                                where !(IsAssemblyLoaded(reference))
                                select reference;
      foreach (var @ref in referencesNotLoaded) {
         lock (LoadingSetLock) {
            if (!this._loadingSet.Contains(@ref)) {
               this._loadingSet.Add(@ref);
            }
         }
      }
   }
 
   private static bool IsAssemblyLoaded(string assemblyName) {
      return SilverlightRuntimeAssemblyNames.Any(asmName => asmName.Equals(assemblyName, StringComparison.OrdinalIgnoreCase))
         || LoadedAssemblies.ContainsKey(assemblyName)
         || Deployment.Current.Parts.Any(ap => ap.Source.Equals(assemblyName, StringComparison.OrdinalIgnoreCase));
   }
 
   private static IEnumerable<string> GetReferenceAssemblyNames(Stream assemblyStream) {
      var asmDef = AssemblyDefinition.ReadAssembly(assemblyStream);
      return asmDef.MainModule.AssemblyReferences.Select(anr => anr.Name + ".dll");
   }
}</string></string></asynccompletedeventargs></downloadassemblycommpletedeventargs></string></string></string></string>

实现一个自定义的 ContentLoader

实现自定义的 ContentLoader 很容易, 只要实现 INavigationContentLoader 接口即可, 结合上面的AssemblyDownloader, 实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class MyContentLoader : INavigationContentLoader {
 
   private static AssemblyDownloader _assemblyDownloader;
 
   public IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback, object asyncState) {
      var typeFullName = targetUri.ToString();
      if (string.IsNullOrEmpty(typeFullName)) {
         return null;
      }
      var arr = typeFullName.Split(',');
      var typeName = arr[0];
      var assemblyName = arr[1];
      if (!assemblyName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) {
         assemblyName += ".dll";
      }
      var asyncResult = new MyContentLoaderAsyncResult {
         AsyncState = asyncState,
         TypeName = typeName,
         AssemblyName = assemblyName
      };
      BeginLoadCore(userCallback, asyncResult);
      return asyncResult;
   }
 
   private static void BeginLoadCore(AsyncCallback userCallback, MyContentLoaderAsyncResult result) {
      if (_assemblyDownloader == null) {
         _assemblyDownloader = new AssemblyDownloader();
      }
      var handlers = new EventHandler<downloadassemblycommpletedeventargs>[1];
      handlers[0] = (sender, e) => {
         _assemblyDownloader.DownloadAssemblyCommpleted -= handlers[0];
         result.Assembly = e.Result;
         userCallback(result);
      };
      _assemblyDownloader.DownloadAssemblyCommpleted += handlers[0];
      _assemblyDownloader.DownloadAssemblyAsync(result.AssemblyName);
   }
 
   public void CancelLoad(IAsyncResult asyncResult) {
   }
 
   public LoadResult EndLoad(IAsyncResult asyncResult) {
      var result = asyncResult as MyContentLoaderAsyncResult;
      if (result == null) {
         throw new InvalidOperationException(string.Format("Wrong kind of {0} passed in.  The {0} passed in should only come from {1}.", "IAsyncResult", "MyContentLoader.BeginLoad"));
      }
      var loadResult = new LoadResult(result.GetResultInstance());
      return loadResult;
   }
 
   public bool CanLoad(Uri targetUri, Uri currentUri) {
      return targetUri.ToString().Split(',').Length == 2;
   }
}</downloadassemblycommpletedeventargs>

使用自定义的 ContentLoader

只要设置 Frame 控件的 ContentLoader 为自定义的 ContentLoader 即可, 比如可以这样使用:

1
2
3
4
5
6
7
<hyperlinkbutton navigationuri="Widget1.HomeWidget, Widget1" navigationtarget="NavFrame">
<hyperlinkbutton navigationuri="Widget2.HomeWidget, Widget2" navigationtarget="NavFrame">
<sdk:frame name="NavFrame">
   <sdk:frame.contentloader>
      <my:mycontentloader>
   </SDK:FRAME.CONTENTLOADER />
</my:mycontentloader></sdk:frame.contentloader></sdk:frame></hyperlinkbutton></hyperlinkbutton>

当加载第一个程序集的时候, 会自动加载引用的程序集, 如下图:

image

当加载第二个程序集的时候, 重复引用的程序集将不会被加载, 如下图:

image

与 Silverlight 内置的导航机制比较

与内置的导航机制相比, 最大的优点是实现了真正的按需加载, 可以有效地减少主程序的大小, 减少用户的初次等待时间, 而且可以支持 OOB 模式。

posted @   张志敏  阅读(928)  评论(10编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示