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

http://www.cnblogs.com/beginor/archive/2011/08/11/2130109.html

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

Silverlight 对反射的限制

在 Silverlight 中, 对反射做了很多的限制, 最大的两个限制是:

Silverlight 对动态加载的限制

Silverlight 对动态加载的机制支持很差, 主要表现在:

  • 没有提供内置的动态加载程序集的方案, 需要开发者自己实现, 当然, 从服务端加载一个程序集很容易, 但是没有好的办法获取到程序及的引用信息, 不能自动加载程序集引用的其它程序集;
  • 不能将动态加载到的程序集添加到当前的 Silverlight 部署程序集缓存中 (Deployment.Current.Parts) 中, 需要开发者自己对已经加载的程序及做缓存;
  • AppDomain.Current 不能添加动态加载的程序集信息。

Silverlight 内置导航机制的限制

Silverlight 内置了一种导航机制, 可以和浏览器导航栏无缝集成, 但是也有不少限制, 例如:

  • 导航的地址必须是用户控件 (UserControl) 或者页面 (Page) 对应的 xaml 文件, 不能是其它的用户控件;
  • 由于 Silverlight 对反射以及动态加载的限制, 内置的导航机制不能自动加载动态加载的程序集中的控件;

不过,内置的导航可以通过实现 INavigationContentLoader 接口来完成, Frame 控件有一个 ContentLoader 属性, 这个属性的默认值是 PageResourceContentLoader

比较理想解决方案

比较理想的解决方案是, 将导航机制和动态加载程序集结合起来, 要实现的目标如下:

  1. Silverlight 程序集都不打包, 直接复制到 clientbin 目录下, 当然, 主程序集还是要打包的, 否则客户端 Silverlight 无法加载加载;
  2. 根据用户的选择, 从服务端按需加载对应程序集, 同时自动分析引用的程序集, 也自动从服务端加载;
  3. 扩展内置的导航机制, 使其能够载入动态加载的程序集。

写到这里, 相信有很多人对这个解决方案已经了解了, 甚至已经有了清晰地思路来实现了, 在下一篇博文中, 将贴出我的实现, 希望大家继续关注。

 

突破 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 @ 2012-01-10 14:12  董雨  阅读(237)  评论(0编辑  收藏  举报