I am now a 50 year old manager of a small piece of a very large company that purchased us about 1 year ago. Been into computers since grade school in the late 70's, having spent years working in the field, building, fixing, installing, selling, teaching, gaming, programming and now consulting on all the aforementioned topics. Computing life is good.

Thursday, March 08, 2012

A little enlightenment


Yesterday I was working on a particularly perplexing problem where I had a need to have the Silverlight project (IntegraConnect in this case) know the exact location of the machine that was accessing it. The idea being that on a map centered on the users location could then show points of interest around their location. In this case the client would have their service coordinators in the field perhaps at a person home directing those persons to community resources, which is a prime directive for this particular client using the IntegraConnect system.


The problem is that Silverlight does not have an API to communicate with GPS hardware or even an API that will try to determine the location based on the IP address, but modern browsers do. So the problem was how to get that information from the browser. Turns out that there are copious examples of Javascript code that gets the coordinates out of the browser and place them in an alert or use them to message a Google map or some other such contrived thing… That code basically follows this simple pattern

function getCurrentLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(showMyPosition, showError);
    }
    else {
        //alert("This website does not have the API")
    }
}


function showError(error) {
    switch (error.code) {
        case error.PERMISSION_DENIED:
            //alert("This website does not have permission to use the Geolocation API")
            break;
        case error.POSITION_UNAVAILABLE:
           //alert("The current position could not be determined.")
           break;
        case error.TIMEOUT:
           //alert("The current position could not be determined within the specified timeout period.")
           break;
        case error.UNKNOWN_ERROR:
           //alert("The position could not be determined due to an unknown error.")
           break;
    }
}
function showMyPosition(position) {
    // stick the position into a hidden form variable
    var coordinates = position.coords.latitude + "," + position.coords.longitude;
    //alert("The position is “ + coordinates;
}

In the above examples I have commented out the alerts which is analogous to messagebox.show() in Silverlight and Winforms projects, but the flow is pretty clear. In it you call getCurrentLocation(), in there the if else block checks to see if the navigator.geolocation object exists. If it does the browser supports the thing, and another function is called that actually points to either an error routine or the actual get the location routine. If it does not we fall through to the browser is old and crusty and you should update it.


This is all well and good except it still does not get the information into our Silverlight application.


Now from Silverlight I can call the javascript with a simple invocation

HtmlPage.Window.Invoke("getCurrentLocation");


This is all well and good except it still does not get the information into our Silverlight application.


That’s where the fun began yesterday with all this exercise in futility where attempting to get the results passed into Silverlight from the JavaScript call had me working up quite a sweat. Each trip into Google land yielding some other wonks demonstration of creating objects that marshal the inherent differences between JavaScript types and Silverlight types. All of these one failure after another. One of the primary problems is that the JavaScript and Silverlight threads are running apart from one another. This concurrency has the Silverlight call returning (with NULL) before the JavaScript engine can get the results. If you run the thing in debug and break on the JavaScript. You can see that it’s working. Yet at full speed no workie, frustrating as all hell.


At this point I started to just give up, but then I thought about how HCSIS (The PA abomination that one of our most successful products interfaces with) In this thing they use a host of HTML form level containers that are invisible to hold values that get passed into and out of processes. To that end I changed the ASPX page that instances the Silverlight plugin as shown below…


In the body segment at the bottom I added the bolded and enlarged line to create our form level hidden container


<body>
<form id="form1" runat="server" style="height:100%">
<input id="Hidden1" type="hidden" runat="server" value="0,0" />
<div id="silverlightControlHost">
        <object data="data:application/x-silverlight-2," 
                 type="application/x-silverlight-2" width="1024" height="768" name="Xaml1">
        <param name="source" value="ClientBin/IhSIS.xap"/>
        <param name="windowless" value ="True" />
        <param name="onError" value="onSilverlightError" />
        <param name="background" value="Yellow" />
        <param name="minRuntimeVersion" value="4.0.0.0" />
        <param name="autoUpgrade" value="true" />
        <param name="Initparams" 
              value="UserAccount=<%=HttpContext.Current.User.Identity.Name%>,
              ipAddress=<%=HttpContext.Current.Request.ServerVariables["REMOTE_ADDR"]%>" />
        <a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" 
              style="text-decoration:none">
        <img src="http://go.microsoft.com/fwlink/?LinkId=161376"
             alt="Get Microsoft Silverlight" style="border-style:none"/>
       a>
       object><iframe id="_sl_historyFrame" 
             style="visibility:hidden;height:0px;width:0px;border:0px">iframe>div>
form>
body>


Then up in script region I added my Java script code to get the results and place them into the hidden container. The error routine could be made a whole lot simpler as I am not really doing anything different for each of the 4 possible errors but I left it as is in case we wanted to handle them differently in the future. The basic procedure is we populate the hidden object with the string representation of the coordinates or 0,0 if there is some problem…


function getCurrentLocation() {
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(showMyPosition, showError);
    }
    else {
        document.getElementById("Hidden1").value = "0,0";
    }
}
function showError(error) {
    switch (error.code) {
        case error.PERMISSION_DENIED:
            //alert("This website does not have permission to use the Geolocation API")
            document.getElementById("Hidden1").value = "0,0";
            break;
        case error.POSITION_UNAVAILABLE:
            //alert("The current position could not be determined.")
            document.getElementById("Hidden1").value = "0,0";
            break;
        case error.TIMEOUT:
            //alert("The current position could not be determined within the specified time    out period.")
            document.getElementById("Hidden1").value = "0,0";
            break;
        case error.UNKNOWN_ERROR:
            //alert("The position could not be determined due to an unknown error.")
            document.getElementById("Hidden1").value = "0,0";
            break;
    }
}
function showMyPosition(position) {
    // stick the position into a hidden form variable
    var coordinates = position.coords.latitude + "," + position.coords.longitude;
    document.getElementById("Hidden1").value = coordinates;
}


So now I use the simple evocation early on in IntegraConnect’s startup of

HtmlPage.Window.Invoke("getCurrentLocation");


To populate the hidden object on our page with the location.


Then when I want to use it say off the geomap screen I can get it out of that hidden object with


using System.Windows.Browser;


HtmlElement elem = HtmlPage.Document.GetElementById("Hidden1");


if (elem != null)
{
    string v = elem.GetAttribute("value");
    MessageBox.Show(v);
}


In this case I am just showing the message box of the results but from here it’s easy to split the string on the , and get the Latitude and the Longitude to work with…
And the clouds parted, and the rays of sunshine rained down upon my keyboard, and the angelic chorus rose up... and all was good…