通过 HostScript 在 XBAP 中部分信任 JavaScript 对象访问:回调中的 SecurityException
我遇到了 WPF 4 中添加的 XBAP 脚本互操作功能的问题。它涉及以下内容的组合:
- 从 .NET 访问脚本对象的成员
- 在从 JavaScript 调用的回调中运行 .NET 代码
- 在部分信任中运行
这似乎是一个“选择任意两个”的场景...如果我尝试执行所有这三件事,我会得到一个 SecurityException
。
例如,组合 1 和 3 很容易。我可以将其放入我的托管网页的脚本中:
function ReturnSomething()
{
return { Foo: "Hello", Bar: 42 };
}
然后,在我的 WPF 代码后面的按钮单击处理程序中,我可以执行以下操作:
dynamic script = BrowserInteropHelper.HostScript;
if (script != null)
{
dynamic result = script.ReturnSomething();
string foo = result.Foo;
int bar = result.Bar;
// go on to do something useful with foo and bar...
}
即使在部分信任部署中,这也能正常工作。 (我使用的是 Visual Studio 2010 中 WPF 浏览器应用程序模板提供的默认 ClickOnce 安全设置,它调试 XBAP 就好像它在 Internet 区域中运行一样。)到目前为止,一切顺利。
我还可以将 2 和 3 结合起来。为了使我的 .NET 方法可从 JavaScript 调用,遗憾的是我们不能只传递委托,我们必须这样做:
[ComVisible(true)]
public class CallbackClass
{
public string MyMethod(int arg)
{
return "Value: " + arg;
}
}
然后我可以声明一个如下所示的 JavaScript 方法
function CallMethod(obj)
{
var result = obj.MyMethod(42);
var myElement = document.getElementById("myElement");
myElement.innerText = "Result: " + result;
}
: ,比如说,一个 WPF 按钮单击处理程序,我可以这样做:
script.CallMethod(new CallbackClass());
因此,我的 WPF 代码调用(通过 BrowserInteropHelper.HostScript
)我的 JavaScript CallMethod
函数,该函数又调用我的 . NET 代码返回 - 具体来说,它调用由我的 CallbackClass 公开的 MyMethod
方法。 (或者我可以使用 [DispId(0)]
属性将回调方法标记为默认方法,这可以让我简化 JavaScript 代码 - 脚本可以将参数本身视为一种方法。方法产生相同的结果。)
MyMethod
回调已成功调用。我可以在调试器中看到从 JavaScript (42) 传递的参数正确通过(已正确强制为 int)。当我的方法返回时,由于 CallMethod
函数的其余部分,它返回的字符串最终出现在我的 HTML UI 中。
太棒了 - 所以我们可以做 2 和 3。
但是把这三者结合起来怎么样?我想修改我的回调类,以便它可以与脚本对象一起使用,就像我的第一个代码段 ReturnSomething
函数返回的那样。我们知道完全有可能使用此类对象,因为第一个示例成功了。所以你会认为我可以这样做:
[ComVisible(true)]
public class CallbackClass
{
public string MyMethod(dynamic arg)
{
return "Foo: " + arg.Foo + ", Bar: " + arg.Bar;
}
}
然后将我的 JavaScript 修改为如下所示:
function CallMethod(obj)
{
var result = obj.MyMethod({ Foo: "Hello", Bar: 42 });
var myElement = document.getElementById("myElement");
myElement.innerText = "Result: " + result;
}
然后像以前一样从我的 WPF 按钮单击处理程序中调用该方法:
script.CallMethod(new CallbackClass());
这成功调用了 JavaScript CallMethod
函数,该函数成功回调 MyMethod
C# 方法,但是当该方法尝试检索 arg.Foo
属性时,我收到一个 SecurityException
,其中包含以下消息请求失败
。这是调用堆栈:
at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
at System.Security.CodeAccessSecurityEngine.Check(PermissionSet permSet, StackCrawlMark& stackMark)
at System.Security.PermissionSet.Demand()
at System.Dynamic.ComBinder.TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject& result, Boolean delayInvocation)
at Microsoft.CSharp.RuntimeBinder.CSharpGetMemberBinder.FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
at System.Dynamic.DynamicMetaObject.BindGetMember(GetMemberBinder binder)
at System.Dynamic.GetMemberBinder.Bind(DynamicMetaObject target, DynamicMetaObject[] args)
at System.Dynamic.DynamicMetaObjectBinder.Bind(Object[] args, ReadOnlyCollection`1 parameters, LabelTarget returnLabel)
at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at XBapDemo.CallbackClass.MyMethod(Object arg)
这是异常报告的整个跟踪。在 CallbackClass.MyMethod 上方,Visual Studio 显示了两批 [本机到托管转换] 和一个 [AppDomain 转换] - 这就是整个堆栈。 (显然我们现在处于不同的线程上。此回调发生在线程面板所描述的工作线程上 - 我可以看到主线程仍然位于我的 WPF 按钮单击处理程序中,等待对 JavaScript 的调用CallMethod
函数返回。)
显然,问题在于 DLR 最终将 JavaScript 对象包装在需要完全信任的 ComBinder
中。但在之前的情况下,我通过 HostScript 调用 JavaScript 方法并返回一个对象,HostScript 将其包装在 System.Windows.Interop.DynamicScriptObject 中对我来说。
DynamicScriptObject
类特定于 WPF XBAP 脚本互操作 - 它不是常用 DLR 类型的一部分,而是在 PresentationFramework.dll
中定义。据我所知,它所做的工作之一是使使用 C# 的 dynamic
关键字无需完全信任即可访问 JavaScript 属性成为可能,即使这些属性是通过 COM 互操作访问的(这通常需要完全信任)在幕后。
据我所知,问题是您只能获得从其他 DynamicScriptObject
实例(例如 HostScriptDynamicScriptObject
包装器>)。通过回调,似乎不会发生这种包装。在我的回调中,我得到了 C# 通常在普通的旧 COM 互操作场景中为我提供的那种动态包装器,此时,它要求我完全信任。
以完全信任的方式运行它效果很好 - 这将是上面列表中的“1 和 2”组合。但我不想得到完全的信任。 (我想要 1、2、和 3。)在回调情况之外,我可以很好地访问 JavaScript 对象成员。大多数时候我可以很好地访问 JavaScript 对象,但在回调中访问相同的对象是被禁止的,这似乎不一致。
有办法解决这个问题吗?或者,如果我想在回调中做任何有趣的事情,我是否注定要完全信任地运行我的代码?
I've encountered a problem with the XBAP Script Interop feature that was added in WPF 4. It involves a combination of the following:
- Accessing members of a script object from .NET
- Running .NET code in a callback invoked from JavaScript
- Running in Partial trust
This seems to be a "pick any two" scenario... If I try and do all three of those things, I get a SecurityException
.
For example, combining 1 and 3 is easy. I can put this into my hosting web page's script:
function ReturnSomething()
{
return { Foo: "Hello", Bar: 42 };
}
And then in, say, a button click handler in my WPF code behind, I can do this:
dynamic script = BrowserInteropHelper.HostScript;
if (script != null)
{
dynamic result = script.ReturnSomething();
string foo = result.Foo;
int bar = result.Bar;
// go on to do something useful with foo and bar...
}
That works fine, even in a partial trust deployment. (I'm using the default ClickOnce security settings offered by the WPF Browser Application template in Visual Studio 2010, which debugs the XBAP as though it were running in the Internet zone.) So far, so good.
I can also combine 2 and 3. To make my .NET method callable from JavaScript, sadly we can't just pass a delegate, we have to do this:
[ComVisible(true)]
public class CallbackClass
{
public string MyMethod(int arg)
{
return "Value: " + arg;
}
}
and then I can declare a JavaScript method that looks like this:
function CallMethod(obj)
{
var result = obj.MyMethod(42);
var myElement = document.getElementById("myElement");
myElement.innerText = "Result: " + result;
}
and now in, say, a WPF button click handler, I can do this:
script.CallMethod(new CallbackClass());
So my WPF code calls (via BrowserInteropHelper.HostScript
) my JavaScript CallMethod
function, which in turn calls my .NET code back - specifically, it calls the MyMethod
method exposed by my CallbackClass. (Or I could mark the callback method as a default method with a [DispId(0)]
attribute, which would let me simplify the JavaScript code - the script could treat the argument itself as a method. Either approach yields the same results.)
The MyMethod
callback is successfully called. I can see in the debugger that the argument passed from JavaScript (42) is getting through correctly (having been properly coerced to an int). And when my method returns, the string that it returns ends up in my HTML UI thanks to the rest of the CallMethod
function.
Great - so we can do 2 and 3.
But what about combining all three? I want to modify my callback class so that it can work with script objects just like the one returned by my first snippet, the ReturnSomething
function. We know that it's perfectly possible to work with such objects because that first example succeded. So you'd think I could do this:
[ComVisible(true)]
public class CallbackClass
{
public string MyMethod(dynamic arg)
{
return "Foo: " + arg.Foo + ", Bar: " + arg.Bar;
}
}
and then modify my JavaScript to look like this:
function CallMethod(obj)
{
var result = obj.MyMethod({ Foo: "Hello", Bar: 42 });
var myElement = document.getElementById("myElement");
myElement.innerText = "Result: " + result;
}
and then call the method from my WPF button click handler as before:
script.CallMethod(new CallbackClass());
this successfully calls the JavaScript CallMethod
function, which successfully calls back the MyMethod
C# method, but when that method attempts to retrieve the arg.Foo
property, I get a SecurityException
with a message of RequestFailed
. Here's the call stack:
at System.Security.CodeAccessSecurityEngine.Check(Object demand, StackCrawlMark& stackMark, Boolean isPermSet)
at System.Security.CodeAccessSecurityEngine.Check(PermissionSet permSet, StackCrawlMark& stackMark)
at System.Security.PermissionSet.Demand()
at System.Dynamic.ComBinder.TryBindGetMember(GetMemberBinder binder, DynamicMetaObject instance, DynamicMetaObject& result, Boolean delayInvocation)
at Microsoft.CSharp.RuntimeBinder.CSharpGetMemberBinder.FallbackGetMember(DynamicMetaObject target, DynamicMetaObject errorSuggestion)
at System.Dynamic.DynamicMetaObject.BindGetMember(GetMemberBinder binder)
at System.Dynamic.GetMemberBinder.Bind(DynamicMetaObject target, DynamicMetaObject[] args)
at System.Dynamic.DynamicMetaObjectBinder.Bind(Object[] args, ReadOnlyCollection`1 parameters, LabelTarget returnLabel)
at System.Runtime.CompilerServices.CallSiteBinder.BindCore[T](CallSite`1 site, Object[] args)
at System.Dynamic.UpdateDelegates.UpdateAndExecute1[T0,TRet](CallSite site, T0 arg0)
at XBapDemo.CallbackClass.MyMethod(Object arg)
That's the whole trace as reported by the exception. And above CallbackClass.MyMethod
, Visual Studio is showing two lots of [Native to Managed Transition] and an [AppDomain Transition] - so that's the whole of the stack. (Apparently we're on a different thread now. This callback is happening on what the Threads panel describes as a Worker Thread - I can see that the Main Thread is still sat inside my WPF button click handler, waiting for the call to the JavaScript CallMethod
function to return.)
Apparently the problem is that the DLR has ended up wrapping the JavaScript object in the ComBinder
which demands full trust. But in the earlier case where I called a JavaScript method via HostScript
and it returned me an object, the HostScript
wrapped it in a System.Windows.Interop.DynamicScriptObject
for me.
The DynamicScriptObject
class is specific to WPFs XBAP script interop - it's not part of the usual DLR types, and it's defined in PresentationFramework.dll
. As far as I can tell, one of the jobs it does is to make it possible to use C#'s dynamic
keyword to access JavaScript properties without needing full trust, even though those properties are being accessed through COM interop (which usually requires full trust) under the covers.
As far as I can tell, the problem is that you only get these DynamicScriptObject
wrappers for objects that are returned from other DynamicScriptObject
instances (such as HostScript
). With callbacks, that wrapping doesn't seem to occur. In my callback, I'm getting the sort of dynamic wrapper C# would normally give me in plain old COM interop scenarios, at which point, it demands that I have full trust.
Running it with full trust works fine - that would be the "1 and 2" combination from the list above. But I don't want to have full trust. (I want 1, 2, and 3.) And outside of callback situations, I can access JavaScript object members just fine. It seems inconsistent that I can access a JavaScript object just fine most of the time, but accessing an identical object in a callback is forbidden.
Is there a way around this? Or am I doomed to run my code in full trust if I want to do anything interesting in a callback?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我有一段时间没有做过 XBAP,但我很好奇是否是动态类型可能导致问题。尝试将动态参数更改为类型对象,看看它是否有效。
I haven't done XBAP in a while, but I am curious if it is the dynamic type that could be causing the issue. Try changing the dynamic parameter to type object and see if it will work.