Я столкнулся с проблемой с функцией взаимодействия сценариев XBAP, которая была добавлена в WPF 4. Она включает в себя комбинацию следующего:
- Доступ к членам объекта сценария из .NET
- Запуск .NET-кода в обратном вызове, вызванном из JavaScript
- Работает с частичным доверием
Кажется, это сценарий "выбери любые два" ... Если я попытаюсь сделать все три из этих вещей, я получу 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...
}
Это прекрасно работает, даже при частичном развертывании доверия. (Я использую настройки безопасности ClickOnce по умолчанию, предлагаемые шаблоном приложения браузера WPF в Visual Studio 2010, который отлаживает XBAP, как если бы он работал в зоне Интернета.) Пока все хорошо.
Я также могу комбинировать 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, в частности, вызывает метод MyMethod
, предоставляемый моим CallbackClass. (Или я мог бы пометить метод обратного вызова как метод по умолчанию с атрибутом [DispId(0)]
, что позволило бы мне упростить код JavaScript - сценарий мог обрабатывать сам аргумент как метод. Любой подход дает одинаковые результаты.)
Обратный вызов MyMethod
успешно вызван. В отладчике я вижу, что аргумент, переданный из JavaScript (42), проходит правильно (будучи правильно приведен к int). И когда мой метод возвращается, возвращаемая им строка заканчивается в моем пользовательском интерфейсе HTML благодаря остальной части функции CallMethod
.
Отлично - мы можем сделать 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
с сообщением RequestFailed
, Вот стек вызовов:
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 показывает два лота [Нативный в управляемый переход] и [Переход домена приложения] - так что это весь стек. (Очевидно, мы сейчас находимся в другом потоке. Этот обратный вызов происходит на том, что панель «Потоки» описывает как рабочий поток - я вижу, что основной поток все еще находится внутри моего обработчика нажатия кнопок WPF, ожидая вызова JavaScript CallMethod
функция для возврата.)
Очевидно, проблема в том, что DLR обернул объект JavaScript в ComBinder
, что требует полного доверия. Но в более раннем случае, когда я вызывал метод JavaScript через HostScript
и он возвращал мне объект, HostScript
обернул его в System.Windows.Interop.DynamicScriptObject
для меня.
Класс DynamicScriptObject
специфичен для взаимодействия сценариев XBAP WPF - он не является частью обычных типов DLR и определен в PresentationFramework.dll
. Насколько я могу судить, одно из заданий, которые он выполняет, - сделать возможным использование ключевого слова C # dynamic
для доступа к свойствам JavaScript без необходимости полного доверия, даже если эти свойства доступны через взаимодействие COM (которое обычно требует полного доверия). ) под одеялом.
Насколько я могу судить, проблема в том, что вы получаете эти DynamicScriptObject
оболочки только для объектов, которые возвращаются из других DynamicScriptObject
экземпляров (таких как HostScript
). С обратными вызовами такое обертывание не происходит. В моем обратном вызове я получаю динамическую оболочку, которую C # обычно дает мне в старых сценариях взаимодействия COM, и в этот момент мне требуется полное доверие.
Работа с полным доверием работает нормально - это будет комбинация «1 и 2» из списка выше. Но я не хочу иметь полное доверие. (Я хочу 1, 2, и 3). И вне ситуаций обратного вызова я могу получить доступ к элементам объекта JavaScript просто отлично. Кажется непоследовательным, что я могу получить доступ к объекту JavaScript большую часть времени, но доступ к идентичному объекту в обратном вызове запрещен.
Есть ли способ обойти это? Или я обречен на выполнение моего кода с полным доверием, если я хочу сделать что-нибудь интересное в обратном вызове?