Calling the Enterprise Search web service from Silverlight 3
Posted
Monday, August 3, 2009 4:37 PM
by
CoreyRoth
I needed to do a quick proof of concept of displaying search results in Silverlight recently and it actually proved easier than I thought it was going to be. I tried this once before in a beta version of Silverlight 2 and it was much more difficult at the time. I was able to easily add the reference to the search web service but the proxy was filled with classes that were not supported in the Silverlight .NET Framework. This time I was relieved to find that it was much simpler. I’ve written many applications to query using the web service, so the first thing I thought of was how is this thing going to authenticate to SharePoint. I started doing tons of unnecessary research when I should have just written some code. When you create a reference to the web service, you do not have a credentials property you can set like you could in a regular .NET application. It turns out that it just works provided you do all of the other necessary things to call a web service first in Silverlight.
For the purpose of today’s post, we are going to assume that whatever page is hosting the Silverlight application is running under an account that can access SharePoint (i.e.: Cassini or from SharePoint itself). I started by creating a reference to the web service at the usual location (/_vti_bin/search.asmx).
I thought I would need to mess with the security element in the ServiceReferences.ClientConfig file, but in fact you can just leave it alone. The next thing you have to do is create a cross-domain policy file so that Silverlight knows it has permission to call the web service. Here is what the file, ClientAccessPolicy.xml, looks like. You can customize it for your needs of course.
<?xml version="1.0" encoding="utf-8"?>
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
Creating the file is quite simple, but you need to get the file into SharePoint. This file has to be located at the root of the web application (not site or site collection). Of course since it is SharePoint, you can’t just go and drop the file into the 80 folder in wwwroot. So I found, one of the easiest ways to add the file is using SharePoint Designer. Open it up, connect to your site, and then drag the file onto the root of your site (or create a new one from the menu). When you are done, it should look something like this.
Now, we get to write code. Add whatever controls you want to in your XAML file. You can use Expression Blend to do this or just edit the file manually. You’ll need a textbox or something to contain the results of the search query. WCF calls in Silverlight have to be executed asynchronously, so you need to write an event handling method to the QueryEx call like this.
QueryService.QueryServiceSoapClient queryService = new QueryServiceSoapClient();
queryService.QueryExCompleted += new EventHandler<QueryExCompletedEventArgs>(queryService_QueryExCompleted);
To call the web service it works very much like any other .NET application. Build an input XML document and make a call to QueryExAsync. In my case I actually get the input from a textbox so that the user can provide the value to query on.
StringBuilder queryXml = new StringBuilder();
queryXml.Append("<QueryPacket xmlns=\"urn:Microsoft.Search.Query\" Revision=\"1000\">");
queryXml.Append("<Query domain=\"QDomain\">");
queryXml.Append("<SupportedFormats>");
queryXml.Append("<Format>");
queryXml.Append("urn:Microsoft.Search.Response.Document.Document");
queryXml.Append("</Format>");
queryXml.Append("</SupportedFormats>");
queryXml.Append("<Range>");
queryXml.Append("<Count>50</Count>");
queryXml.Append("</Range>");
queryXml.Append("<Context>");
queryXml.Append("<QueryText language=\"en-US\" type=\"STRING\">");
queryXml.Append(QueryTextBox.Text);
queryXml.Append("</QueryText>");
queryXml.Append("</Context>");
queryXml.Append("</Query>");
queryXml.Append("</QueryPacket>");
queryService.QueryExAsync(queryXml.ToString());
We still need to implement the queryService_QueryExCompleted event handling method to get the results and display them in the textbox.
void queryService_QueryExCompleted(object sender, QueryExCompletedEventArgs e)
{
ResultsTextBox.Text = e.Result.Nodes[1].ToString();
}
Now one thing I have noticed here is that the results XML string does not look exactly like the one that would normally come back as if it was called from a regular .NET application. I am know Silverlight or WCF expert but I am sure there is a reason for this. What ends up happening is that e.Results has a Nodes collection with 2 XElement in it. The first one contains mostly schema information and the second one contains the actual results. However, some initial data at the top of the tree (such as result count and page size) really is nowhere to be found (from what I can tell). At this point, you are good to parse and bind the XML data as you please. Here is what my ugly Silverlight application looks like.
I have attached the code to this post, so that you can use it as a starting point. If you run into issues, I warn you Silverlight won’t give you a lot of help. I recommend checking out this post on Debugging Web Services in Silverlight from the Silverlight team. This is a bit of work to do now. I can definitely say I am looking forward to the Client Object Model (publically announced a few weeks ago) in SharePoint 2010 which should make this easier..