WEB开发网
开发学院图形图像Flash Silverlight 2 中的浏览器互操作性 阅读

Silverlight 2 中的浏览器互操作性

 2008-12-01 11:52:34 来源:WEB开发网   
核心提示:本文示例源代码或素材下载 目录 配置 Silverlight 控件从 Silverlight 访问 DOM深入了解浏览器互操作性层将托管代码附加到 DOM 事件同步调用和 Silverlight 2XAML、托管代码和 JavaScript跨域访问和 Silverlight 插件总结利用 Silverlight

本文示例源代码或素材下载

Silverlight 2 中的浏览器互操作性目录

配置 Silverlight 控件

从 Silverlight 访问 DOM

深入了解浏览器互操作性层

将托管代码附加到 DOM 事件

同步调用和 Silverlight 2

XAML、托管代码和 JavaScript

跨域访问和 Silverlight 插件

总结

利用 Silverlight 2 可构建整页 Windows Presentation Foundation (WPF) 式的 Web 应用程序,也可借助其他一些功能(如动画、广告和特定小程序)来丰富基于 HTML 的页面。浏览器并不直接处理构成 Silverlight 应用程序的“可扩展应用程序标记语言”(XAML) 内容。实际上,在 HTML 页面中,会有一个 <object> 标记指向 Silverlight 2 插件,并且其参数中会包含用于下载所有必需 XAML 资源的 URL。

最基本的一点在于,始终都存在一个封装了 Silverlight 插件的 HTML 页面,即使通常会由于 Silverlight 应用程序被配置为全屏模式运行而看不到它。此外,在同一站点上,Silverlight 插件所承载的内容与周围页面并非隔离的。如果 Silverlight 内容来自托管 HTML 页面以外的其他域,它与页面才是真正隔离的。

Silverlight 带有一个浏览器互操作性层,它允许托管代码访问基础页面的文档对象模型 (DOM),并允许注册页面级事件的托管处理程序。同时,页面中运行的所有 JavaScript 代码均可获得对插件的 XAML 内容的访问权限,甚至可以进行修改。最后一点,只要正确公开,页面中运行的 JavaScript 代码也可以调用托管函数。本月,我将介绍 Silverlight 2 的浏览器互操作性层以及如何在应用程序中充分利用它。

配置 Silverlight 控件

如前所述,Silverlight 插件是通过 <object> 标记进行调用的。但是,此标记可通过 ASP.NET 页面中的 ASP.NET 控件来创建。如果在 Visual Studio 2008 中创建一个示例 Silverlight 应用程序,则下列用于 Silverlight 服务器控件的标记会自动插入到测试页面中:

<asp:Silverlight ID="Xaml1" runat="server"
   Source="~/ClientBin/SilverTestApp.xap"
   MinimumVersion="2.0.30523"
   Width="100%"
   Height="100%" />

MinimumVersion 属性指出运行 Source 属性中指定的应用程序时,所需的 Silverlight 运行时的最低版本。Width 和 Height 定义 Silverlight 窗口相对于宿主页面的尺寸。正如您所看到的,默认情况下,Silverlight 插件被配置为以标准尺寸模式运行。

图 1 列出了配置 Silverlight 插件时可设置的特性。图 1 中的特性对应的是 Silverlight ASP.NET 控件,此控件在服务器端执行,且可生成可能包含不同名称参数或不包含等效参数的 <object> 标记。在本专栏中,应格外注意 HtmlAccess 特性。(请注意,从 Beta 1 过渡到 Beta 2 后,Silverlight SDK 的这一部分有所变化。)

Silverlight 2 中的浏览器互操作性图 1 Silverlight ASP.NET 服务器控件的特性

特性说明
AutoUpgrade指示是否应自动升级 Silverlight 插件。默认值为 false。
DefaultScriptType用于创建 Silverlight 插件并与之建立关联的客户端 JavaScript 对象的默认类型。默认情况下,它是 Sys.UI.Silverlight.Control。
EnableFrameRateCounter指示是否在托管浏览器的状态栏中显示当前的帧速率。默认值为 true。
EnableRedrawRegions指示是否显示为每个帧重绘的 Silverlight 插件区域。默认值为 true。
HtmlAccess指示是否允许 Silverlight 应用程序访问页面 DOM。
InitParameters定义可选的一组用户定义的初始化参数。
MaxFrameRate针对 Silverlight 文档每秒呈现的最大帧数。
MinimumVersion当前应用程序所需插件的最低版本。默认值为 1.0。
PluginBackground指示插件背景色。
PluginNotInstalledTemplate未安装 Silverlight 插件时呈现的 HTML 标记。
ScaleMode指示 Silverlight 插件如何填充可用空间:none、zoom 或 stretch。默认值为 none。
ScriptType用于创建 Silverlight 插件并与之建立关联的客户端 JavaScript 对象的类型。
Source要下载的 XAML 源文件或可扩展 Ajax 平台 (XAP) 源数据包的 URL。
SplashScreenSource要在加载源文档时呈现的启动画面文档的 URL。
Windowless指示是直接在浏览器的客户端区域中还是在适当创建的窗口中呈现 Silverlight 插件。默认值为 true。

HtmlAccess 特性指出 Silverlight 应用程序对基础页面 DOM 的访问权限级别。此特性接受来自 HtmlAccess 枚举类型的值。这些值如图 2 所示。

Silverlight 2 中的浏览器互操作性图 2 用于控制对页面 DOM 的访问权限的值

说明
Disabled不允许应用程序访问基础页面的 DOM。
Enabled允许应用程序访问基础页面的 DOM。
SameDomain仅当页面来自与 Silverlight XAP 应用程序相同的域时,才允许应用程序访问基础页面的 DOM。这是默认设置。

显然,Enabled 和 Disabled 都很容易理解。默认设置为 SameDomain,它并不会在页面的标记中注入任何脚本。请注意,Silverlight 应用程序可被承载在某个页面中,而该页面又承载在其本机域以外的某个帧中。在这种情况下,Silverlight 托管代码将能够以跨域的方式访问宿主页面的 DOM。浏览器可使用自己的屏障来阻止跨域脚本,但它们对于阻止 Silverlight 插件中的托管代码也无能为力。Silverlight 页面的编写者利用 HtmlAccess 来控制跨域访问。

从 Silverlight 访问 DOM

一经授予对基础页面 DOM 的访问权限,Silverlight 应用程序即可使用静态类 HtmlPage 的成员来完成自己的任务。图 3 列出了 HtmlPage 类的特性和方法。

Silverlight 2 中的浏览器互操作性图 3 HtmlPage 类的成员

成员说明
BrowserInformation获取有关浏览器的常规信息,如名称、版本和操作系统。
Document提供对页面文档对象的访问权限。
IsEnabled指示是否允许访问页面 DOM。
Plugin获取对 Silverlight 对象的引用。
Window提供对页面窗口对象的访问权限。
RegisterCreateableType通过 JavaScript 代码将托管类型注册为可供创建时使用。
RegisterScriptableObject通过页面中的 JavaScript 代码将托管对象注册为可编写脚本。
UnregisterCreateableType注销先前使用 RegisterCreteableType 方法注册为可供创建时使用的类型。

Document 特性将返回对托管类型 System.Windows.Browser.HtmlDocument 的对象的引用,该类型代表基础页面的 DOM。Document 特性是强类型化的特性,可向 Silverlight 应用程序提供用于以弱类型化方式编写脚本的大部分功能。这些特性包括 cookie 列表、查询字符串、文档正文以及对 DOM 根的引用。该对象还具有一个布尔型的 IsReady 特性,可指示浏览器的文档是否已完全加载,这与浏览器的 DOM 中 DocumentReady 事件的变换相对应。

HtmlDocument 类还包含 Submit、AttachEvent、DetachEvent、GetElementById 和 CreateElement 等方法。此外,还可使用两个继承方法(GetProperty 和 SetProperty)在托管代码中获取和设置 HTML 元素的属性。

完整的浏览器信息可通过 BrowserInformation 特性来获取。并且在这种情况下,特性属于托管类型,可封装在浏览器级别可用的所有用户代理信息。以下代码段显示了如何访问用户代理数据:

string info = HtmlPage.BrowserInformation.UserAgent;

BrowserInformation 对象还包含一个布尔特性,可指示浏览器是否支持 cookie。

以下代码显示了如何使用托管代码来检索对 DOM 元素的引用:

HtmlElement label1 = HtmlPage.Document.GetElementById("Label1");
label1.SetProperty("innerHTML", "Dino");

GetElementById 方法利用 ID 字符串尝试在基础 DOM 中定位相应的元素。返回对象的类型为 HtmlElement,这是一种托管类型,可作为对基础浏览器对象的引用的包装。

HtmlElement 具有一组可使它看起来像是 HTML 元素的特性。它包含 AppendChild、RemoveChild、GetAttribute、RemoveAttribute 和 SetAttribute 等方法。这些特性包括 Id、Parent、TagName 和 Children。

在 Silverlight 代码中操作的所有 HTML 脚本对象均派生自基本 ScriptObject 类。此类将定义所有派生类(如 HtmlDocument)继承的两个方法——SetProperty 和 GetProperty。

获取和设置属性与获取和设置特性之间有何不同?属性始终作为字符串进行管理;而特性却作为强类型化值进行管理。

深入了解浏览器互操作性层

图 4 中的图形视图显示了 Silverlight 浏览器的互操作性层和对页面 DOM 的访问。对互操作性层中的任何类所提出的请求均通过内部浏览器宿主服务进行解析。信息被向下封送到浏览器的非托管环境,然后再封送回 Silverlight。类型差异被隐藏起来,均由互操作性层来处理。DOM 级别的对象通过新的托管接口(HtmlDocument、HtmlWindow 等类似接口)包装成托管对象并提供给 Silverlight 代码。我们将着重谈一下 GetElementById 方法。

Silverlight 2 中的浏览器互操作性

图 4 Silverlight 2 中的 HTML 桥

首先,此方法将确保代码是在 Silverlight UI 线程中被调用的。否则会抛出异常。接下来,将向基础浏览器发出一个请求,以获取对指定 DOM 元素的引用。如果请求成功,此方法将得到 DOM 对象的非托管引用,然后它将会为此对象创建并返回托管的包装。

将托管代码附加到 DOM 事件

Silverlight 与 DOM 实现交互带来的一个极大好处是能够运行托管代码来响应 DOM 事件。例如,当用户单击某个按钮时,您可执行 C# 代码而非 JavaScript 代码。其实现方式如下:

Silverlight 2 中的浏览器互操作性HtmlElement button1;
button1 = HtmlPage.Document.GetElementById("Button1");
button1.AttachEvent("click",
     new System.EventHandler(Button1_Click));

首先,检索对感兴趣按钮(或 DOM 元素)的托管引用。接下来,调用托管的 AttachEvent 方法,以便为特定事件注册一个处理程序。真正的亮点在于处理程序是托管代码,而事件在浏览器非托管级别被触发。例如,获取 GUID 在 JavaScript 中几乎是不可能实现的事情。但是,如果能够通过 Silverlight 利用托管代码的功能,则这将变得非常简单:

void Button1_Click(object sender, EventArgs e)
{
  // Get a new GUID
  Guid g = Guid.NewGuid();
  // Display the GUID in the page user interface
  HtmlElement label1 = HtmlPage.Document.GetElementById("Label1");
  label1.SetProperty("innerHTML", g.ToString());
}

毫无疑问,托管代码是 Silverlight 页面代码隐藏类的一个成员。此操作的效果可反映在宿主页面的 HTML 中(如先前示例所示),也可直接反映在 Silverlight 用户界面中——完全取决于您以及您所处的环境。

向事件附加的是一个操作,它通过浏览器互操作性层发生并以调用 DOM 对象的 AttachEvent 方法结束。当浏览器触发页面级事件时,会逆向调用 Silverlight 以执行托管代码。

此类功能在何种情况下会被用到?Silverlight 这一产品的设计目的是为了提供丰富的 Web 前端。对于浏览器托管代码完成的工作,如果您想让托管语言完成得比脚本更好更快,则需要用到 Silverlight。具体示例包括编译语言的速度明显优于脚本的那些代码密集型操作,或者在功能有限的环境中(如浏览器的环境)不可用的操作。可以肯定的是,如果您需要做的只是操作 DOM,则并非一定要使用 Silverlight。能够在托管代码中处理事件是一个非常出色的功能,但它必须要用到 Silverlight。而且您可能并不希望使用 Silverlight 只是用来处理事件。

在 Silverlight 2 中,HtmlWindow 对象将提供 JavaScript 窗口对象的托管表示。它存储着对 DOM 对象的引用并允许通过下面的一组托管方法来驱动它:Alert、Confirm、Prompt、Submit、Navigate 以及 Eval。HtmlWindow 对象的实例将通过 HtmlPage 类的 Window 特性公开给 Silverlight 开发人员。下列代码显示了如何通过 Silverlight 显示浏览器的消息框:

Silverlight 2 中的浏览器互操作性

HtmlPage.Window.Alert("Hello, world");

让我们来研究一下这一简单代码段要实现的功能。我所要研究的模型几乎在 HtmlWindow 类的所有方法中都会重复用到:

public void Alert(string message)
{
  HtmlPage.VerifyThread();
  if (message == null)
  {
    message = string.Empty;
  }
  this.Invoke("alert",
        new object[] { message });
}

对 UI 线程进行测试后,如果消息字符串为空,则此方法会修复它并继续针对基本 ScriptObject 类来调用 Invoke 方法。Invoke 方法在 Silverlight 的托管领域和浏览器之间搭建了一座桥梁。

此方法接受两个参数,如下所示:

public virtual object Invoke(string name, params object[] args)

第一个参数指出要针对存储在 ScriptObject 当前实例中的可编写脚本对象调用的方法名称。第二个参数只是要调用的方法的参数列表。

此方法负责在两种不同的运行时环境中正确封送类型。在封送到浏览器的过程中,它会将托管对象转换成与 JavaScript 兼容的类型;在返回的过程中,它将执行相反的操作。

HtmlWindow 还有一个值得关注的方法——CreateInstance 方法:

public ScriptObject CreateInstance(
  string typeName,
  params object[] args)

此方法允许创建指定 JavaScript 对象的实例。类型名称参数表示要实例化的 JavaScript 对象的名称。在内部,此方法会准备一个创建指定对象的动态 JavaScript 函数,然后再从 Silverlight 调用此函数:

ScriptObject xhr = HtmlPage.Window.CreateInstance("XMLHttpRequest");

在获取对某个 JavaScript 对象的可编写脚本引用时,方法 CreateInstance 会非常有用。接下来,使用 Invoke 方法对其编写脚本。如果的确需要从 Silverlight 2 进行同步调用,则这些功能也会非常有用。但问题是在 Silverlight 2 中不允许进行同步调用。

同步调用和 Silverlight 2

Silverlight 2 提供了各种 API 来对远程端点进行调用,但它们都必须是异步的。您可以在后台线程中启动调用,但却无法强制 UI 线程同步。如果阻截了某个同步对象的 UI 线程,则实际上相当于无限期停止了该 UI 线程,而且没有任何重置同步对象的命令可以恢复它。在社区中,有关 Silverlight 中的同步调用问题是最大的争论焦点。

尽管如此,目前仍不支持从 Silverlight API 中对远程端点进行同步调用,因为它们存在关联延迟的问题已经众所周知并经过了证实。但是,同步调用是浏览器环境中的一个重要功能。在支持 XmlHttpRequest 的所有浏览器中,也都支持它们。

XmlHttpRequest 是一个浏览器对象,使用它可以实现 AJAX 方案。此对象利用浏览器机制实现对同一域中某个端点的调用。默认情况下,XmlHttpRequest 异步执行,并且大多数 AJAX 框架在使用它时都采用这一方式。但是,可非常轻松地将 XmlHttpRequest 配置为同步执行。

通过从 Silverlight 创建和控制 XmlHttpRequest 的实例,您可以安排同步调用并修复所有采用同步调用可使编码更轻松的特殊方案。(就个人而言,我并非 Silverlight 远程调用仅提供异步方式的狂热拥趸者。在大多数情况下,异步调用即已足够;但我认为,您可能会遇到同步调用会节省许多重新设计开销的情形(尤其是要使某种现有前端适应 Silverlight 时)。我是第一个强调设计是关键的人,但是如果特意编写的同步调用可节省我数小时甚至数天的工作,我肯定会使用它。)

尽管如此,Silverlight 团队还是有充足的理由来推动仅异步方法,因为对远程端点的同步调用可能导致 Silverlight 应用程序冻结用户界面,从而损害最终用户在使用浏览器和 Web 应用程序时的体验。同步调用从技术上来说是可能的,但是出于对所有应用程序及其客户的利益的考虑,团队并不会在平台中对此提供本机支持。同样,团队并不推荐依赖手动编写且使用 XmlHttpRequest 的同步远程调用(如图 5 所示)。

Silverlight 2 中的浏览器互操作性图 5 从 Silverlight 2 进行同步调用

private void Button1_Click(object sender,
  System.Windows.RoutedEventArgs e)
{
  string url = "...";
  ScriptObject xhr = HtmlPage.Window.CreateInstance("XMLHttpRequest");
  xhr.Invoke("open", "POST", url, false);
  xhr.Invoke("setRequestHeader", "Content-Type",
    "application/x-www-form-urlencoded");
  // Prepare the body as the endpoint expects it to be
  string body = "...";
  xhr.Invoke("send", body);
  string response = (string) xhr.GetProperty("responseText");
  // Process the response and update the UI
  ProcessData(response)
}

图 5 展示了如何使用 XmlHttpRequest 从 Silverlight 2 建立对某个 URL 的同步同域调用。其中的关键是针对 XmlHttpRequest 调用 open 方法的时机。布尔型参数指示调用是否必须为异步。如果为 false,则指示对象以同步方式继续。

此技巧的缺点是它利用的工具级别较低,未提供任何功能来实现字符串与其他内容(例如 JavaScript Object Notation (JSON) 流)之间的转换。如果使用此技巧,则必须使用 DataContractJsonSerializer 类来处理所有 JSON 的序列化和反序列化操作。

XAML、托管代码和 JavaScript

JavaScript 函数可获取对 Silverlight 应用程序内容的访问权限并执行读取和写入操作。Silverlight 应用程序的内容是 XAML 元素树。

以唯一名称——x:Name 属性——为特征的 XAML 文档中的所有内容均可在 JavaScript 中进行访问和编写脚本。第一步需要获取对 Silverlight 插件的 DOM 引用。在 ASP.NET AJAX 页面中,需要使用以下代码:

var plugin = $get("SilverlightControl1");

SilverlightControl1 是 Silverlight 控件的 ID,或者是用于指向可下载内容的 <object> 标记的 ID。接下来,使用 content 特性指向实际的 XAML 内容。要定位特定的 XAML 元素,可使用 findName 方法:

// Retrieve the XAML element tagged with the name of TextBlock1
var xamlTree = $get("SilverlightControl1").content;
var textBlock1 = xamlTree.findName("TextBlock1");
// Modify the current content of the text block element
textBlock1.Text = "...";

如果使用 JavaScript 代码来驱动 XAML 文档的内容,则建议您在页面中缓存对遇到的 XAML 元素的所有引用。这样就不必重复遍历 XAML 树来一遍遍地寻找相同的元素。

请注意,对于 Silverlight 2 而言,使用 JavaScript 来访问 XAML 文档的内容略显过时。如果目标是 Silverlight 1.0,或者至少,您要提供的 Silverlight 应用程序仅由 XAML 和脚本代码组成,则它仍是一个不错的选择。如果可使用托管代码来决定要显示的内容,则很难想象使用 JavaScript 而非托管代码该如何实现要对用户界面进行的任何改动。

在 Silverlight 2 中,可使用 JavaScript 代码来调用托管代码。如果方案包含基于 HTML 的页面且该页面由一些执行关键操作的托管代码提供技术支持,则可采用此选项。

您的 JavaScript 代码可对 Silverlight 2 中承载的先前已注册为可编写脚本的任何托管对象编写脚本:

Silverlight 2 中的浏览器互操作性

guidHelper = new GuidHelper();
HtmlPage.RegisterScriptableObject("GuidTools", guidHelper);

方法 RegisterScriptableObject 将会使用被注册对象的非正式名称以及变量实例。作为第一个参数的字符串是调用方 JavaScript 代码将要用来引用已注册对象的名称。

对于之前的代码,以下代码显示了如何使用 JavaScript 来针对示例类 GuidHelper 调用某个方法:

// Invoke a method on a scriptable managed object
var guid = $get("Silverlight1").content.GuidTools.Generate();
// Update the user interface with the results
$get("Random1").innerHTML = guid;

对于可编写脚本的对象(在本例中为 GuidTools),其公共名称将被用作一个成员,可通过 JavaScript 来访问基础对象。伪特性 GuidTools 由 Silverlight 插件的 content 特性公开。让我们来看一下 GuidHelper 类:

public class GuidHelper
{
  [ScriptableMember]
  public string Generate()
  {
    Guid g = Guid.NewGuid();
    return g.ToString();
  }
}

对于可编写脚本的类的公共方法,其 ScriptableMember 属性表示可通过 JavaScript 来调用此方法。如果某个类的所有公共方法均可编写脚本,则可使用此类的 ScriptableType 属性自动将 scriptability 属性扩展到所有公共方法。

注册为可编写脚本的对象将通过托管代码进行实例化,并且现有实例会向下传递到 JavaScript。并非所有托管类型都可以在 JavaScript 中直接实例化。

要通过 JavaScript 创建托管类的实例,首先必须在 JavaScript 代码中注册可供创建时使用的类型:

HtmlPage.RegisterCreateableType("StockPicker", typeof(Samples.Order));

第一个参数表示要在 JavaScript 中使用的类型的别名。以下展示了如何创建 Samples.StockPicker 类的实例:

var plugin = $get("Silverlight1");
var type = "Samples.StockPicker"; 
var inst = plugin.content.services.createObject(type);

检索 Silverlight 插件,访问 content.services 特性,然后调用方法 createObject。createObject 的参数是一个字符串,其名称是要实例化的托管类型。

对于在方法签名中具有复杂自定义类型的可编写脚本类型而言,其工作原理与其类型的工厂一样。例如,假设托管类型 Customer 具有以下方法:

void Add(Order order);

那 Order 类型呢?是否应将其声明为可编写脚本?不一定。如果忽略对 RegisterCreateableType 的调用,则您只能使用作为工厂的可编写脚本类型,其中包含需要该类型的可编写脚本的成员。换句话说,如果 Customer 已注册为可编写脚本且方法 Add 是可编写脚本的方法,则无需将类型声明为可创建即可通过 JavaScript 实例化 Order。以下代码将可以正常运行:

var customer = $get("Silverlight1").content.Customer;
var order = customer.createManagedObject("Order");
order.ID = 1;
o.OrderDate = new Date();
customer.Add(order);

托管类型被包装成浏览器对象向下封送到 JavaScript。浏览器对象的包装包含一个可通过 JavaScript 调用的有效方法的表格。此列表包括声明为可编写脚本的所有方法以及 createManagedObject。然后,可通过在托管代码中调用相应的方法来解析对方法的调用。

跨域访问和 Silverlight 插件

对于提供公共托管 API 的 Silverlight 应用程序,会面临跨域访问的问题。如果跨域访问会带来安全隐患或只是一个多余的功能,则应采取相应的防范措施。

如果具有 Silverlight 插件的页面承载在一个帧中,则宿主页面可能会与 Silverlight 应用程序位于不同的域中。这意味着宿主页面(跨域)中的 JavaScript 代码可通过该帧获取对 Silverlight 插件的访问权限,并且能够对任何公共托管对象编写脚本。

对 Silverlight 内容的跨域访问可能会被禁用、完全启用或仅限于编写脚本。默认情况下它是被禁用的。可通过使用 Silverlight 应用程序清单文件的 Deployment 节点中的 ExternalCallersFromCrossDomain 属性来控制跨域访问,如图 6 所示。此清单文件是在编译 Silverlight 项目时由 Visual Studio 2008 生成的文件。

Silverlight 2 中的浏览器互操作性图 6 Silverlight 2 应用程序的示例清单文件

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      EntryPointAssembly="BrowserFun"
      EntryPointType="BrowserFun.App"
      RuntimeVersion="2.0.30523.6"
      ExternalCallersFromCrossDomain="FullAccess">
  <Deployment.Parts>
    <AssemblyPart x:Name="BrowserFun"
         Source="BrowserFun.dll" />
    <AssemblyPart x:Name="System.Windows.Controls.Extended"
         Source="System.Windows.Controls.Extended.dll" />
  </Deployment.Parts>
</Deployment>

如果在包含一个或多个 Silverlight 插件的浏览器中加载一个页面,则每个浏览器进程仍会得到一个 CLR 实例。但是,每个正在运行的 Silverlight 插件实例都会得到自己的 AppDomain。可以使插件和通讯之间不共享任何信息,但这必须借助代码以及一个小技巧才能实现。

此技巧实质上包括使用浏览器互操作性层来作为中介。一个插件公开托管接口,另一个插件连接到此接口并调用方法。

因此,一个插件将定义一些可编写脚本的成员,如下所示:

Silverlight 2 中的浏览器互操作性

public partial class Page : UserControl
{
 public Page()
 {
  InitializeComponent();
  HtmlPage.RegisterScriptableObject(
    "Action", new ActionPageCommand());
 }
}

ActionPageCommand 类包含插件已提供给外部调用方的所有方法。下面是一个示例:

Silverlight 2 中的浏览器互操作性

[ScriptableType]
public class ActionPageCommand
{
 public int GetRandomNumber()
 {
   Random rnd = new Random();
   return rnd.Next(0, 100);
 }
}

页面中的 JavaScript 代码可直接看到插件的可编写脚本成员。在图 7 中,您看到的是公开提供的 Silverlight 插件接口的 JavaScript 包装。对另一插件的接口感兴趣的所有插件都可创建图中 JavaScript 包装的实例:

ScriptObject so = HtmlPage.Window.CreateInstance("ActionBar");
object result = so.Invoke("invokeGetRandomNumber");

Silverlight 2 中的浏览器互操作性图 7 向另一 Silverlight 插件公开

<script type="text/javascript">
var ActionBar = function()
{}
function invokeGetRandomNumber$Impl()
{
  var plugin2Services = $get("Silverlight2").content;
  var results = plugin2Services.Action.GetRandomNumber();
  return results;  
}
ActionBar.prototype =
{
  invokeGetRandomNumber: invokeGetRandomNumber$Impl
}
</script>

有没有更直接的方法从一个 Silverlight 插件调用另一个?有,您可选择以下方法:

HtmlElement plugin = HtmlPage.Document.GetElementById("Silverlight2");
var content = (ScriptObject) plugin.GetProperty("content");
var action = (ScriptObject) content.GetProperty("Action");
action.Invoke("GetRandomNumber");

最终效果是相同的。但是,此方法需要在 Silverlight 和基础浏览器之间进行更多的往返操作。

总结

Silverlight 基类库包括用于连接到浏览器和宿主页面以及读取信息和访问 DOM 的多种功能。如果应用程序以 Silverlight 为中心,则可能无需对宿主 HTML 页面执行过多操作。但是,对于兼具 ASP.NET AJAX 和 Silverlight 的混合解决方案,则必须从 JavaScript 执行托管代码或在托管代码中调用 JavaScript 对象。

浏览器互操作性层(也称为 HTML 桥)包含多种功能,可实现 Silverlight 托管领域和 JavaScript 解释领域之间的通信。通信需要在各层之间封送类型和对象。在最高级别的抽象中,您会发现一个公共 API 和一些文档,其中解释了从 JavaScript 调用托管代码以及从托管代码调用 JavaScript 对象需要了解的一些知识。

如果有任何特殊问题,或者希望了解在具体实施时应考虑的细节,可密切关注 Wilco Bauwer 信息丰富的博客。

请将您想向 Dino 询问的问题和提出的意见发送至 cutting@microsoft.com。

Dino Esposito 是 IDesign 的架构师,也是《Microsoft .NET:Architecting Applications for the Enterprise》(Microsoft Press, 2008) 一书的合著者。Dino 定居于意大利,经常在世界各地的业内活动中发表演讲。您可加入他的博客,网址为 weblogs.asp.net/despos。

Tags:Silverlight 浏览器 操作性

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接