Return object from javascript to silverlight - c#

Here are the steps I'd like to take:
1) User enters search term into box in silverlight, then presses enter
2) Search term is passed into javascript via C# code: HtmlPage.Window.Invoke("CallAPI", SearchText);
3) CallAPI function hits the API via $.getJSON & returns a value into the JS callback function [this is already done]
4) Resultant object is returned to the Silverlight/C# page for display in silverlight UI
I can do everything except step 4. How do I get a JSON object from Javascript into C#? I've been working on this for the last few hours, and this is what I thought would do it:
ScriptObject myScriptObject = (ScriptObject)HtmlPage.Window.Invoke("CallWordStreamAPI", SearchText);
I set a breakpoint in my JS & validated that the object in my return statement is definitely populated with the 20 rows of data, as expected.
I set a breakpoint in my C# (ScriptObject myScriptObject = ....), and myScriptObject is null after the call.
If I set a breakpoint in firebug/chrome dev at the line "return r" (my object), I can see there are 20 items listed in r.data. If I set a breakpoint after the myScriptObject line listed above, myScriptObject is null.
Your help is appreciated.
Scott

I was calling this from a ViewModel on the server side. I ended up using MVVM Messaging to send the keyword over to my code behind on the client side. I then called my JS function, returned the result, and shot a message back into my view model.
Aside from that, the syntactic issues were resolved here:
How can I pass a JavaScript function to Silverlight?

My Code:
<!-- language: JavaScript -->
function sendText() {
return "Hi from Javascript!";
}
<!-- language: C# -->
string obj = HtmlPage.Window.Invoke("sendText", null) as string;
txtReturnData.Text = obj;
<!-- language: VB.Net -->
Dim obj As String = TryCast(HtmlPage.Window.Invoke("sendText", Nothing), String)
txtReturnData.Text = obj

Related

How to get JavaScript stack trace with WebBrowser control?

Knowing how to get notified about script errors when hosting a WebBrowser control using OLECMDID_SHOWSCRIPTERROR inside my WinForms C# application, I currently do it successfully this way:
private void handleError(mshtml.IHTMLDocument2 htmlDocument)
{
var htmlWindow = htmlDocument.parentWindow;
var htmlEventObject = htmlWindow.#event as mshtml.IHTMLEventObj2;
_lineNumber = (int)htmlEventObject.getAttribute(#"errorLine");
_characterNumber = (int)htmlEventObject.getAttribute(#"errorCharacter");
_errorCode = (int)htmlEventObject.getAttribute(#"errorCode");
_errorMessage = htmlEventObject.getAttribute(#"errorMessage") as string;
_url = htmlEventObject.getAttribute(#"errorUrl") as string;
}
This works as expected.
What I currently cannot solve is to get the JavaScript call stack.
I've tried several things in the example above:
_callStack = htmlEventObject.getAttribute(#"stack") as string;
_callStack = htmlEventObject.getAttribute(#"errorStack") as string;
_callStack = htmlEventObject.getAttribute(#"stackTrace") as string;
...
All those do return an empty/NULL string.
Whether I'm unsure if this information can be retrieved at all, still my question is:
How to get the call stack of a JavaScript error from within the application hosting an Internet Explorer web browser control?
I'm not entirely sure if that's possible at all either, but I might have some useful information related to your question. Back in IE7 days I worked on a custom host for WebBrowser control in C++, and I still keep the list of service GUIDs the control was requesting from my OLE site object through IServiceProvider. One of those interfaces was IDebugApplication, which might open a door to access JavaScript stack frame via IDebugApplication::AddStackFrameSniffer. I had not tried it back then. If you're ready to do further research, you could use this project as a starting point to implement a custom WebBrowser host in C#.

Silverlight passing an array to a web page's dialog arguments

I have the following line of code to open a web page modal dialog in C# (Silverlight):
var so = (ScriptObject)HtmlPage.Window.Invoke(
"showModalDialog",
modalWindowUrl,
dialogArgs,
"dialogWidth:600px;dialogHeight:600px;");
Now, code similar to the following is being called on the page I am displaying, and I need to make sure it gets the values I'm trying to pass in (this is a MSCRM web page I don't have control over):
dialogArgs.items <-- will be an array I pass in
dialogArgs.items[i].getAttribute("oid") <-- will return something
dialogArgs.items[i].getAttribute("otype") <-- will return something
dialogArgs.items[i].values <-- will return something
What I have tried to send in (from my C# code) is this:
dialogArgs = #"{items:[{oid:" + id + ",otype:" + type + "}]}";
which will result in a JSON string... but I'm guessing this just ends up as a string within the JavaScript and not a JSON object.
Any ideas how I get this to work?
A few side notes:
I can't get IE to debug the modal dialog that results from this call. I can get the debugging tools displaying, but it won't attach to the page because it cannot refresh it.
I don't have control over this modal dialog. It's a page that is displayed using MS Dynamics CRM. For that reason I cannot mess with the JavaScript or anything to test stuff.
Looks like I won the tumbleweed award for this one! Can't believe how uncommon this scenario seems to be. The solution ended up being quite simple, but not very documented so took me a while to track down. Thought I would share here.
Firstly, a quick search across the internet reveals that we can set this up using the following:
var dialogArgs = HtmlPage.Window.CreateInstance("Object");
Which gives you a ScriptObject back. For properties:
dialogArgs.SetProperty("items", items);
Some code for setting up an array and an item should look something like this (I have just created a new GUID for the purpose of this example):
var item = HtmlPage.Window.CreateInstance("Object");
item.SetProperty("oid", Guid.NewGuid());
item.SetProperty("otype", "account");
var items = HtmlPage.Window.CreateInstance("Object");
items.SetProperty(0, item);
And finally, just pass that object straight into your dialog window like this:
var so = (ScriptObject)HtmlPage.Window.Invoke("showModalDialog", lookUpWindow, dialogArgs, "dialogWidth:600px;dialogHeight:600px;");

WCF RIA service return data not matching on the 2 sides of the service

I originally thought this was a INotifyPropertyChanged/Binding issue because I'm not sure how to debug the silverlight part. So I had to put a messagebox in a foreach loop and view the values after the data returned that way. It turns out that I'm having trouble getting the updated data from the service. I use the service to do some updates to data on the server and when it gets back call to reload the data. This part of the service is returning the correct data (I verified using a breakpoint so I could look at the data result was holding). But the silverlight side is not getting the right data. Here is the relevant code.
public IQueryable<OurClass> GetItems(string condition)
{
var result = from items in context.OurClass
where item.value == condition
select item;
return result; //had my breakpoint here and the values were the correct updated values
}
/
Context.Load<OurClass>(Context.GetItemsQuery(condition)).Completed += new EventHandler(Context_LoadCompleted);
/
private void Context_LoadCompleted(object sender, EventArgs e)
{
IEnumerable<OurClass> result = ((LoadOperation<OurClass>)sender).Entities;
//This is where I put a MessageBox to view the returned results and the data was different
//than what was contained in the other result
}
Any ideas what could cause this? What should I look at next?
EDIT:
Some example data is OurClass.OurProperty will equal "Test" on the server side but once it is received on the client it will equal "Development" which was the old value. The IEnumerable will hold the newly added records and not have the deleted ones. Any that previously existed will contain the old property values and not the new values.
The solution was that I needed to add the parameter LoadBehavior.RefreshCurrent to the query call. So this:
Context.Load<OurClass>(Context.GetItemsQuery(condition)).Completed += new EventHandler(Context_LoadCompleted);
Needed to be this:
Context.Load<OurClass>(Context.GetItemsQuery(condition), LoadBehavior.RefreshCurrent, true).Completed += new EventHandler(Context_LoadCompleted);
How is the data different on each side of the service? can you show us some example data. You can have a look at data using something like wireshark (will need to be on the server, or on your client where you are running your silverlight applet from.)
Have you tried debugging your silverlight properly, ie attach to the process as shown here: http://www.michaelsnow.com/2010/04/22/silverlight-tip-of-the-day-2-attach-to-process-debugging/
I would also recommend turning on WCF tracing as detailed here: http://msdn.microsoft.com/en-us/library/ms733025.aspx

How to send JavaScript code to IE using C# (.Net 3.5), run it, then get a string return value from the JS code?

We are developing an application which needs to interact with the active document in IE.
Context: The app is a C#, .Net 3.5 desktop app. The goal is to highlight specific text elements in the web page on user request. For this we need to retrieve and interpret web page elements (the need for the return value) then act on them through another JS call. The operations that must be made in the web page are not all done at the same time so we must get some kind of "snapshot" of the interesting text elements (we do this on the Mac version of our app by returning a string containing an XML representation of those elements).
In .Net we used IHTMLDocument2's execScript method successfully to run some JavaScript inside the active IE document, but we can't seem to find a way to get a return value from the call. Based on the doc execScript returns an execution success/failure constant which is not what we need.
In essence what we need to do is to load some JavaScript from a text file into a string, then send it to IE for execution. Then we need to get a string back from the called script.
Any hints on what objects to use? How to proceed to get this functionality?
Thanks in advance!
My colleague found the solution, based on what Alun Harford said:
string jsToRun = "function myTest() { return document.title; } myTest();";
mshtml.IHTMLDocument2 myIHTMLDocument2 = GetSelectedIEWindow();
IE ie = IE.AttachToIE(Find.ByUrl(myIHTMLDocument2.url));
string jsReturn = ie.Eval(jsToRun);
jsReturn then contains the string value returned from myTest() in JavaScript. Note that there is no return before the myTest() function call in the script!
Have a look at the WatiN codebase. In there, IE.Eval does exactly what you're looking for.
If you are providing the html and script yourself you can do the following:
execute the javascript function
let the js function place the result in an html element
wait till the function is done running
retrieve the html element using document.getElementById
and retrieve the value
I'm not sure if there's a easier way to do this.
Well it is nasty but it can be done.
Try this:
[Guid("626FC520-A41E-11CF-A731-00A0C9082637"), InterfaceType(ComInterfaceType.InterfaceIsDual)]
interface IHTMLDocument
{
void Script([Out, MarshalAs(UnmanagedType.Interface)] out object ppScript);
}
public object RunScript(InternetExplorer ie, string scriptText)
{
IHTMLDocument doc = (IHTMLDocument)ie.Document;
object scriptObj;
doc.Script(out scriptObj);
Type t = scriptObj.GetType();
return t.InvokeMember("eval", System.Reflection.BindingFlags.InvokeMethod, null, scriptObj, new object[] { scriptText });
}
That will return your value in the object (just cast to what ever type you expected). Of course .NET 4 makes this even easier ;)

Why doesn't this JavaScript display a confirm box?

I am trying to fix some bugs in a product I inherited, and I have this snippet of javascript that is supposed to hilight a couple of boxes and pop up a confirm box. What currently happens is I see the boxes change color and there is a 5 or so second delay, then it's as if the missing confirm just accepts itself. Does anyone smarter than I see anything amiss in this code?
function lastCheckInv() {
document.getElementById("ctl00$ContentPlaceHolderMain$INDet$txtInvNumber").style.background = "yellow";
document.getElementById("ctl00$ContentPlaceHolderMain$INDet$txtInvNumber").focus();
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_AddCharges").style.background = "yellow";
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight").style.background = "yellow";
bRetVal = confirm("Are you sure the information associated with this invoice is correct?");
return bRetVal;
}
The only thing I can think of is if one of the lines before the confirm is throwing an exception and you're never actually getting to the confirm.
If you're using IE, make sure script debugging is enabled. If you're using Firefox, install the Firebug add-on and enable it for your website.
Or, for very primitive debugging, just put alerts after each statement and figure out where it's bombing.
You should use the following method to reference your controls from JavaScript:
document.getElementById(<%= txtInvNumber.ClientID %>).style.background = "yellow"
If that doesn't help, try setting a breakpoint in your JavaScript and stepping through it to see where it's failing.
This test script ran fine for me in IE, Firefox, and Opera. So there doesn't seem to be anything wrong with your basic syntax. The problem is either in the ID's (which doesn't fit with the fact that it acts as if confirmed after 5 seconds) or in some other conflicting JavaScript on the page. It will be difficult to help you without seeing more of the page.
<script language="javascript">
function lastCheckInv() {
document.getElementById("test1").style.background = "yellow";
document.getElementById("test1").focus();
document.getElementById("test2").style.background = "yellow";
document.getElementById("test3").style.background = "yellow";
bRetVal = confirm("Are you sure?");
return bRetVal;
}
</script>
<form method="get" onsubmit="return lastCheckInv();">
<input id="test1" name="test1" value="Hello">
<input id="test2" name="test2" value="Hi">
<input id="test3" name="test3" value="Bye">
<input type="submit" name="Save" value="Save">
</form>
A few thoughts: Could be the .focus() call is hiding your confirm behind the page? Or could it be that one of your control id's is not correct causing, the .style.background references to fail?
You should set the focus after showing the confirm box, otherwise the confirm box will grab focus away, making that line meaningless.
Don't hard-code ASP.Net id's like that. While they typically are consistent, a future version of ASP.net won't guarantee the same scheme, meaning your setting yourself up for pain when it comes time to update this code at some point in the future. Use the server-side .ClientID property for the controls to write those values into the javascript somewhere as variables that you can reference.
Update:
Based on your comment to another response, this code will result in a postback if the function returns true. In that case, there's not point in running the .focus() line at all unless you are going to return false.
I do not like accesing objects directly by
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight").style.background = "yellow";
If the object is not returned JavaScript will error out. I prefer the method of
var obj = document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight");
if(obj)
{
obj.style.background = "yellow";
}
My guess is you are trying to access an object that is not on the DOM, so it never gets to the confirm call.
It might be worth checking that the elements actually exist. Also,try delaying the focus until after the confirm():
function lastCheckInv() {
var myObjs,bRetVal;
myObjs=[
"ctl00_ContentPlaceHolderMain_INDet_AddCharges",
"ctl00_ContentPlaceHolderMain_INDet_lblFreight",
"ctl00$ContentPlaceHolderMain$INDet$txtInvNumber"
];
bRetVal = confirm("Are you sure the information associated with this invoice is correct?");
for (var i=0, j=myObjs.length, myElement; i<j; i++){
myElement=document.getElementById(myObjs[i]);
if (!myElement){
// this element is missing
continue;
}
else {
// apply colour
myElement.style.background = "yellow";
// focus the last object in the array
if (i == (j-1) ){
myObj.focus();
}
}
}
return bRetVal;
}
function lastCheckInv()
{
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_txtInvNumber").style.background = "yellow";
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_txtInvNumber").focus();
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_AddCharges").style.background = "yellow";
document.getElementById("ctl00_ContentPlaceHolderMain_INDet_lblFreight").style.background = "yellow";
return confirm("Are you sure the information associated with this invoice is correct?");
}
The first two lines in your function are wrong, $ are in the UniqueID of an ASP.Net Control.
On Clientside you have to use the ClientID, replace $ with _ .
If you are sure that the Controls exists, the
code above might work.
Is bRetVal actually declared anywhere?
If not, "var bRetVal = confirm...." is a friendly way of telling jscript that it is a variable.

Categories