Introduction

OK, another post in response to a forum question. This time on SharePoint Overflow. I was looking for a way to display pushpins on a Bing Map Silverlight control using data from a SharePoint list. I’ve split this post into two parts mainly because there’s a fair amount of code involved to just to render the Silverlight control in the web part, never mind the building the required JavaScript in managed code, so we’ll leave that for another day (or night).

The answer to the question in this case is to use the HTML Bridge that enables interaction between HTML and Managed Code, specifically it allows us expose methods in managed code to JavaScript, but we’ll get that part later. First up let’s create the Silverlight control.

The Silverlight Control

This part is fairly straightforward so I won’t go into the details here. This article will give you all you need to get started. One thing to note is that to test the Silverlight control I’ve made use of the web project that Visual Studio can create for you when you create a new Silverlight project so if you want to follow along with that particular section be sure to let Visual Studio create the web project for you. You can, however, skip the “Change the Map Mode using C#” step if you want. Before we move on to the code we need to add a layer to the map so that we have somewhere to put the pushpins so add the following fragment to the <m:Map> element in the MainPage.xaml file: –

<m:Map.Children>
	<m:MapLayer x:Name="Pushpins">
	</m:MapLayer>
</m:Map.Children>

Now we have somewhere to put them we’ll write the code that places the pushpins on the map, so open up the MainPage.xaml.cs file. To be able to use the HTML Bridge and Bing Map Control we need to add some references so add the following lines to the top of the file: –

using System.Windows.Browser;
using Microsoft.Maps;
using Microsoft.Maps.MapControl;
using Microsoft.Maps.MapControl.Core;

Now that we have the reference we can add a custom location class that will contain the latitude and longitude coordinates enabling us to position the pin on the map. Add the following class to the MainPage.xaml.cs file within your namespace: –

[ScriptableType]
public class MapLocation
{
    [ScriptableMember]
    public string Title { get; set; }
    [ScriptableMember]
    public double Latitude { get; set; }
    [ScriptableMember]
    public double Longitude { get; set; }
}

The ScriptableType attribute allows us to make use of the public properties, methods and events of a managed code object in JavaScript. ScriptableMember indicates that the specific property is available to JavaScript callers. Technically this isn’t required in this scenario as we’ve decorated the entire class with the ScriptableType attribute but it won’t break it either.

Next we can add our method to add a pushpin to the map that will be called by JavaScript. Add the following lines to the MainPage class in your MainPage.xaml.cs file: –

[ScriptableMember]
public void AddLocation(MapLocation location)
{
    Pushpin pin = new Pushpin();
    pin.Location = new Location(location.Latitude, location.Longitude);
    pin.Name = location.Title;
    pin.Tag = location.Title;
    Pushpins.Children.Add(pin);
}

The ScriptableMember attribute indicates that this method is available to us from JavaScript. To complete the MainPage class changes add the following lines to the constructor after the InitializeComponent(); call: –

HtmlPage.RegisterCreateableType("MapLocation", typeof(MapLocation));
HtmlPage.RegisterScriptableObject("Communicator", this);

This first line registers our MapLocation class for use in JavaScript with the key of “MapLocation” (why complicate things?). The second line registers the MainPage class for the same purpose. This is all we need to do to the Silverlight control but before we dive into creating the SharePoint web part lets test that we can indeed call the method from JavaScript by using the test page in the web project that Visual Studio has created for us.

The JavaScript

I’ve called my solution BingMapWebPart so I’m opening the BingMapWebPartTestPage.aspx file (yours might be named more succinctly). There should already be an onSilverlightError function in the head of the page. Below that we can add our JavaScript function: –

function onSilverlightLoad(sender, args) {
    var bingMapsControl = sender.getHost();
    var l = bingMapsControl.content.services.createObject("MapLocation");
    l.Title = "API Software - Glasgow";
    l.Latitude = 55.864438;
    l.Longitude = -4.262776;
    bingMapsControl.content.Communicator.AddLocation(l);
}

This function should add a pushpin to the map at the location of API Software’s offices in Bath Street, Glasgow. Note the use of the keys we defined in the Silverlight control. To create a new MapLocation object on the client we’ve called the content.services.createObject method of the control, passing in the “MapLocation” key, then to call the AddLocation() method of the MainPage class we’ve called the AddLocation() method of the content.Communicator object. The last thing to do is ensure that this function is called once the Silverlight control has loaded, so add the following line to the existing list of params in the object tag for the control: –

<param name="onLoad" value="onSilverlightLoad" />

So if this all works, when you press F5 you’ll see a pushpin pointing to Glasgow on a map of the world. In Part 2 we’ll turn this into a SharePoint web part.

References

Working for a Microsoft shop, you get used to only using Microsoft products, it’s only when one of doesn’t work you go and look at the alternatives. Getting ready for the imminent public beta release of SharePoint 2010 I decided to set up a new virtual machine with Windows Server 2008 and SQL Server 2008 on my 64-bit Windows 7 machine. I downloaded Windows Virtual PC, created a new virtual machine and pointed the DVD Drive to the Windows Server 2008 ISO. I started the VM and…

Windows Boot Manager Fail

Windows Boot Manager Fail

“CPU not compatible with 64-bit mode.” Yes it is. I even flashed the BIOS to make sure that I could enable AMD-V for the chip. Hmmm, what now? Google!

Amongst the Virtual PC and Hyper-V results I stumbled across VirtualBox. Seemed to do what I needed it to and while it didn’t specifically mention Windows Server 2008 in the list of guest operating systems I took the chance and downloaded it. It’s a neat little program, nice interface and on creating a new virtual machine I discovered that it does indeed support Windows Server 2008 (64-bit). Result!

Create New Virtual Machine

Create New Virtual Machine

I ran through the rest of the setup process without a hitch and then adjusted the settings to mount the Windows Server 2008 ISO and set the Network Adapter.

Settings - CD/DVD-ROM

Settings - CD/DVD-ROM

Next, I started the VM and ran through the normal Windows setup procedure, again without a hitch, and voila!

Running Virtual Machine

Running Virtual Machine

I decided to do a bit of tinkering while waiting for the SharePoint beta release, so after installing IIS and SQL Server 2008 and all the requisite updates I wondered if I could host a website on the VM and access it externally. Turns out that’s quite easy to achieve. A quick browse through the user manual and I found Configuring port forwarding with NAT. I’ve configured the guest to use the Intel PRO/1000 NIC rather than the PCNet one so have changed pcnet to e1000, so the following script will forward TCP connections on port 4321 on the host to port 1234 on the guest.


cd "C:\Program Files\Sun\VirtualBox"
VBoxManage setextradata "SP2010" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/Protocol" TCP
VBoxManage setextradata "SP2010" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/GuestPort" 1234
VBoxManage setextradata "SP2010" "VBoxInternal/Devices/e1000/0/LUN#0/Config/guestssh/HostPort" 4321

All that remained was to enable port 4321 on my firewall and forward connections to the host machine, the host machine then took care of forwarding to the VM, and I could access the website residing at port 1234 on the virtual machine!

Note: These were not the actual port numbers I used, and even if you did guess correctly all you’d get is a one word HTML test page!

In an attempt to solve the issue of the never-ending workflow I kicked off the installation of Service Pack 2 for WSS and MOSS this morning. Both updates installed successfully but both failed during the Products and Technologies Configuration Wizard.

Investigating the PSCDiagnostics log I found the following line: –

09/01/2009 08:32:26  8  ERR            The B2B upgrader timer job failed.

and in the Upgrade log: –

[SPIisWebSiteWssSequence] [ERROR] [9/1/2009 7:57:04 AM]: Access to the path 'C:\Inetpub\wwwroot\wss\VirtualDirectories\portal.stuartwhiteford.org80\App_GlobalResources\cms.en-us.resx' is denied.

Q. Why would access be denied?

A. Because the root web directory is a web project in Visual Studio, is under source control and is checked in.

So, the resolution is either to check the project out from Visual Studio or change the attributes using Windows Explorer.

I’ve been working with a serial device that captures a signature recently and one of the methods of its associated ActiveX control lets me have a VBScript array of bytes that represents the captured image. It’s a reasonably simple method to convert this into a JavaScript array of bytes and I thought that once I had this it would be easy enough to call the SharePoint web services using jQuery’s built-in AJAX methods. Calling the web service is a piece of cake but it won’t just accept an array of byes, it needs to be a Base64 encoded string, so what follows is how to get yourself one of those from the aforementioned array of bytes and pass it to the Copy web service.

For a good description on how to achieve Base64 encoding have a look at the example on this Wikipedia page.

Firstly, the table variable contains the allowable character in a Base64 encoded string. The OctetPad() function pads a given string parameter to 8 characters in length with zeroes. The Decimal() function accepts a string parameter in binary format and converts it to its decimal equivalent.

The Base64String() method accepts an array of bytes and deals with them in chunks of 3 at a time. It converts each byte to a bit pattern and then concatenates them into a single bit pattern having a length of 24. This bit pattern is split into 4 chunks of 6, converted to a decimal number and the character from the table variable at the index equal to the decimal number is appended to the encoded string.

If the length of the array is not a multiple of 3 then we deal with the remaining 1 or 2 bytes in a slightly different manner. If there are 2 bytes remaining construct the bit pattern with these 2 bytes (giving us 16 bits) and add 2 zeroes to the end to give us 18 bits (divisible by 6). In this case we manually set the fourth Base64 encoded chracter to “=”. If there is 1 byte remaining construct the bit pattern with this single byte (giving us 8 bits) and add 4 zeroes to the end to give us 12 bits (again, divisible by 6). In this case we manually set the third and fourth Base64 encoded chracters to “=”.

var table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
 
function OctetPad(str) {
    return Array(8 + 1 - str.length).join("0") + str;
}
 
function Decimal(bin) {
    var len = bin.length;
    var ans = 0;
    var plc = len - 1;
    for (i = 0; i < len; i++) {
        if (bin.substr(i, 1) == 1) {
            ans = ans + Math.pow(2, plc);
        }
        else if (bin.substr(i, 1) != 0) {
            return;
        }
        plc--;
    }
    return ans;
}
 
function Base64String(bytes) {
    var base64 = "";
    while (bytes.length > 0) {
        var byte1 = null;
        var byte2 = null;
        var byte3 = null;
        var enc1 = null;
        var enc2 = null;
        var enc3 = null;
        var enc4 = null;
        byte1 = bytes.shift().toString(2);
        if (bytes.length > 0) {
            byte2 = bytes.shift().toString(2);
            if (bytes.length > 0) {
                byte3 = bytes.shift().toString(2);
            }
            else {
                enc4 = "=";
            }
        }
        else {
            enc3 = "=";
            enc4 = "=";
        }
        var bitPattern = OctetPad(byte1) + (byte2 != null ? OctetPad(byte2) : "00") + (byte3 != null ? OctetPad(byte3) : "00");
        enc1 = table.charAt(Decimal(bitPattern.substr(0, 6)));
        enc2 = table.charAt(Decimal(bitPattern.substr(6, 6)));
        if (enc3 == null) { enc3 = table.charAt(Decimal(bitPattern.substr(12, 6))); }
        if (enc4 == null) { enc4 = table.charAt(Decimal(bitPattern.substr(18, 6))); }
        base64 = base64 + enc1 + enc2 + enc3 + enc4;
    }
    return base64;
}

Our Upload() function makes use of the jQuery ajax method and looks like the following: –

function Upload(jsArray, sourceUrl, destinationUrl) {
    var jsStream = Base64String(jsArray);
    var soap12Env =
        "<soap12:Envelope xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:soap12='http://www.w3.org/2003/05/soap-envelope'> 
            <soap12:Body> 
                <CopyIntoItems xmlns='http://schemas.microsoft.com/sharepoint/soap/'> 
                    <SourceUrl>" + sourceUrl + "</SourceUrl> 
                    <DestinationUrls> 
                        <string>" + destinationUrl + "</string> 
                    </DestinationUrls> 
                    <Fields> 
                        <FieldInformation Type='File' /> 
                    </Fields> 
                    <Stream>" + jsStream + "</Stream> 
                </CopyIntoItems> 
            </soap12:Body> 
        </soap12:Envelope>";
    $.ajax({
        url: "http://sharepoint/_vti_bin/copy.asmx",
        type: "POST",
        dataType: "xml",
        data: soap12Env,
        contentType: "text/xml; charset="utf-8""
    });
}

I’ve fixed the value of the SharePoint web service URL in this function but it’s probably a better idea to have it passed in as a parameter to the function. All we’re doing here is creating a SOAP envelope for the Base64 stream of characters and the method would be called as follows: –

var jsArray = new Array(71,73,70,56,57,97,2,0,2,0,241,0,0,255,255,0,255,0,0,51,204,0,0,0,255,44,0,0,0,0,2,0,2,0,0,2,3,12,52,5,0,59);
var sourceUrl = "test.gif";
var destinationUrl = "http://sharepoint/ImageLibrary/test.gif";
Upload(jsArray, sourceUrl, destinationUrl);

In this example I’ve set the sourceUrl parameter to the file name we want to give the image when uploaded, this works around the “This item is a copy of…” issue when using the Copy web service.

Recently, my Lacie Big Disk (500GB) died on me. It just stopped appearing as a drive on my PC and I tried it on other machines without success. Looking at some of the forum posts it appeared as if it was going to be one of two problems. Either one (or both) of the drives had failed or the internal controller had failed.

Hoping that it was the controller I decided to get myself another one (eBay to the rescue). Taking both of them apart I swapped the drives from the bust one into the bought one, plugged it in and voila, it worked! So, I copied off all the stuff I wanted to keep then took it apart again.

Now I had four 250GB drives sitting and I can only put two back into the Lacie enclosure. That wouldn’t do at all. I acquired myself a 3Ware RAID card (eBay again), albeit an older model as it had to support IDE drives not SATA. On arrival, off came the PC cover and in went the card and the four drives. I fired up the machine and opted for a RAID5 configuration giving me 750GB-ish, left it to format the drives and build the array for a few hours.

Once in Windows I installed the drivers downloaded from the 3Ware support site. I had to force Vista to allow the unsigned drivers but other than that all went smoothly, and now I have 700GB+ fault tolerant drive in my PC. So if you’ve got an external hard drive, Lacie or otherwise, that’s broke and there’s a chance it’s not the disk itself then there just might be some of getting your data back and re-using the disk.