Welcome!

Microsoft Cloud Authors: Nick Basinger, Kevin Benedict, Pat Romanski, Liz McMillan, Lori MacVittie

Related Topics: Microsoft Cloud, Silverlight

Microsoft Cloud: Blog Feed Post

Building a SharePoint Advanced Search Application with Silverlight 4

A couple of years ago, I demonstrated how to build an advanced search application with Silverlight 3

At SharePoint Conference 2011, I showed off a great looking advanced search application using Silverlight 4.  This application queried the Search web service at /_vti_bin/search.asmx to retrieve results and display them directly inside the application.  A couple of years ago, I demonstrated how to build an advanced search application with Silverlight 3.  This application is very similar to that one except that I take it a step further and show you more of the possibilities of what the user interface could look like.

The code you will see today was intended for Office 365 / SharePoint Online but will work quite well with SharePoint 2010 (and to a degree SharePoint 2007).  Everything from the pervious article pretty much applies.  We create a reference to search.asmx, we build an XML input document, and then we make an asynchronous call to the web service.  One thing I will point out is that I have been unsuccessful in getting the ClientAccessPolicy.xml file to work with SharePoint Online.  This means that the application cannot run locally to allow us to debug it.  I’ve posted to the Office 365 forums but have had no luck.  If anyone figures this out, please let me know.

The way we build the input XML document and call the web service is exactly the same as the pervious post.  However, what is different is the actual keyword query we construct.  Let’s take a look at what the interface looks like first.

image

There is a number of things going on here in this interface.  We first provide the user to do a simple keyword query search.  However, we also give the user the ability to query by File Size, Modified Date, Author, and by Document Only.  To do this, we use the following built-in managed properties respectively FileSize, Write, Author, and IsDocument.  The user can select any combination of the above to get a more specific query.  When the user clicks the SearchButton, our code builds a custom keyword query and sends it to the search web service.  The QueryTextBox displays the query that was constructed by the code.  However, it can also be modified by the user to test out a query manually.  This serves as a great search query testing tool.  After the user searches, the returned XML document is displayed in the large multiline textbox.  Beneath the textbox, I have added a Telerik GridView control.  I had this available to me so I decided to use it.  I think you could just as well have used a DataGrid control to bind the data too.

The code for the Silverlight application is surprisingly simple.  When the user clicks the SearchButton, we begin to construct the keyword query we want to pass to the web service.  To do this, we need a StringBuilder class so be sure and add a reference to System.Text.  We then check each control to see if it has a value.  For example, for SearchTextBox, if it has a value we simply append it to the StringBuilder named searchQuery.

if (!string.IsNullOrEmpty(SearchTextBox.Text))
searchQuery.AppendFormat("{0} ", SearchTextBox.Text);

The FileSize managed property has an operator with values such as >, >=, < and, <=.  These are contained in the ModifiedDateOperatorComboBox.  If there is a value then we append it to searchQuery.

if (FileSizeOperatorComboBox.SelectedItem != null)
searchQuery.AppendFormat("Size{0}\"{1}\" ",
((ComboBoxItem)FileSizeOperatorComboBox.SelectedItem).Content.ToString(),
FileSizeTextBox.Text);

We continue to this for the rest of the controls on the page in the SearchButton click event handling method.  Here is the entire method.

private void SearchButton_Click(object sender, RoutedEventArgs e)
{
StringBuilder searchQuery = new StringBuilder();

if (!string.IsNullOrEmpty(SearchTextBox.Text))
searchQuery.AppendFormat("{0} ", SearchTextBox.Text);

if (FileSizeOperatorComboBox.SelectedItem != null)
searchQuery.AppendFormat("Size{0}\"{1}\" ",
((ComboBoxItem)FileSizeOperatorComboBox.SelectedItem).Content.ToString(),
FileSizeTextBox.Text);

if (ModifiedDateOperatorComboBox.SelectedItem != null)
searchQuery.AppendFormat("Write{0}\"{1}\" ",
((ComboBoxItem)ModifiedDateOperatorComboBox.SelectedItem).Content.ToString(),
ModifiedDatePicker.SelectedDate.ToString());

if (!string.IsNullOrEmpty(AuthorTextBox.Text))
searchQuery.AppendFormat("Author:\"{0}\" ", AuthorTextBox.Text);

if (DocumentsOnlyCheckBox.IsChecked.Value)
searchQuery.Append("IsDocument:1 ");

// pass the search query to the method to actually call the search service
QuerySearchService(searchQuery.ToString());
}

The QuerySearchService method makes the actual call to the web service.  Since we’re dealing with Silvelright, we have to call the web service method asynchronously.  We do this by binding an event handling method to the QueryExCompleted event.  Again for more details on how the XML is constructed see my information from the Silverlight 3 post.

private void QuerySearchService(string searchQuery)
{
QueryServiceSoapClient queryService = new QueryServiceSoapClient();
queryService.QueryExCompleted += new EventHandler<QueryExCompletedEventArgs>(QueryService_QueryExCompleted);

QueryTextBox.Text = searchQuery;
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(searchQuery);
queryXml.Append("</QueryText>");
queryXml.Append("</Context>");
queryXml.Append("</Query>");
queryXml.Append("</QueryPacket>");

BusyIndicator.IsBusy = true;
queryService.QueryExAsync(queryXml.ToString());
}

The last line passed the XML input document to the web service method.  Now, it’s just a matter of handling the return results in the event handling method.  The first thing we need to do is get the XML document with the results.  We can always find this in the Result.Nodes[1] object available in QueryExCompletedEventArgs. For convenience, I write this value to a TextBox so that I can view it.

ResultsTextBox.Text = e.Result.Nodes[1].ToString();

However, I want to bind this XML to our nice looking RadGridView.  To do this I must extract the data from the XDocument and expose it in a custom type. Here is where the LINQ to XML comes in.  Normally, I would just use an anonymous type for this, but that doesn’t work in Silverlight.  This means I have to create a new class to hold our search results.  I call this class SearchResult.

public class SearchResult
{
public string Title { get; set; }
public string Path { get; set; }
public string Author { get; set; }
public string Size { get; set; }
public DateTime? Write { get; set; }
public string SiteName { get; set; }
public string HitHighlightedSummary { get; set; }
public string ContentClass { get; set; }
public bool IsDocument { get; set; }
}

I then use LINQ to XML to write the value of each property in.  Since nulls are a real possibility, I use .Any() before assigning each value to ensure we don’t get an exception.  To understand the LINQ we use, let’s take a quick look at the result XML document.

<diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
<Results xmlns="">
<RelevantResults diffgr:id="RelevantResults1" msdata:rowOrder="0">
<WorkId>2799582</WorkId>
<Rank>78969610</Rank>
<Title>Sales</Title>
<Size>65211</Size>
<Path>https://dotnetmafia.sharepoint.com/sites/fabrikam/teamsites/sales</Path>
<Write>2011-08-11T07:11:59-07:00</Write>
<SiteName>https://dotnetmafia.sharepoint.com/sites/fabrikam/teamsites</SiteName>
<CollapsingStatus>0</CollapsingStatus>
<HitHighlightedSummary>Site Actions &lt;ddd/&gt; This page location is: &lt;ddd/&gt; Home &lt;ddd/&gt; Team Sites &lt;ddd/&gt; Pages &lt;ddd/&gt; default &lt;ddd/&gt; Employee &lt;ddd/&gt; Resources &lt;ddd/&gt; Facilities &lt;ddd/&gt; News &lt;ddd/&gt; I Like It &lt;ddd/&gt; Tags &amp;amp; Notes &lt;ddd/&gt; Libraries &lt;ddd/&gt; Shared Documents &lt;ddd/&gt; Flyers &lt;ddd/&gt; Presentations &lt;ddd/&gt; Proposals &lt;ddd/&gt; &lt;c0&gt;Sales&lt;/c0&gt; Forecasts &lt;ddd/&gt; Lists &lt;ddd/&gt; Calendar &lt;ddd/&gt; Tasks &lt;ddd/&gt; </HitHighlightedSummary>
<HitHighlightedProperties>&lt;HHTitle&gt;&lt;c0&gt;Sales&lt;/c0&gt;&lt;/HHTitle&gt;&lt;HHUrl&gt;https://dotnetmafia.sharepoint.com/sites/fabrikam/teamsites/&lt;c0&gt;sales&lt;/c0&gt;&lt;/HHUrl&gt;</HitHighlightedProperties>
<ContentClass>STS_Web</ContentClass>
<IsDocument>false</IsDocument>
</RelevantResults>

Each search result is contained inside a ReleventResults node inside of the Results element.  So we look inside there to create our query.

var results = from result in
e.Result.Nodes[1].Descendants("RelevantResults")
select new SearchResult
{
Title = (result.Elements("Title").Any())
? result.Element("Title").Value : string.Empty,
Path = (result.Elements("Path").Any())
? result.Element("Path").Value : string.Empty,
Author = (result.Elements("Author").Any())
? result.Element("Author").Value : string.Empty,
Size = (result.Elements("Size").Any())
? result.Element("Size").Value : string.Empty,
Write = (result.Elements("Write").Any())
? DateTime.Parse(result.Element("Write").Value) : DateTime.MinValue,
SiteName = (result.Elements("SiteName").Any())
? result.Element("SiteName").Value : string.Empty,
HitHighlightedSummary = (result.Elements("HitHighlightedSummary").Any())
? result.Element("HitHighlightedSummary").Value : string.Empty,
ContentClass = (result.Elements("ContentClass").Any())
? result.Element("ContentClass").Value : string.Empty,
IsDocument = (result.Elements("IsDocument").Any())
? bool.Parse(result.Element("IsDocument").Value) : false
};

We simply assign each property after verifying that it’s not null.  Most values are strings but we did do some casting for DateTime and Boolean values.  The last thing we do is bind to the RadGridView.

ResultsRadGridView.ItemsSource = results;

The next section applies to the Telerik specific content.  If you don’t have those controls available to you, you can skip this section and you can configure the built-in grid in a similar manner.  RadGridView has some column types that allow us to format links and checkboxes in a nice manner.  Telerik has free trials available if you are interested.  Here is what that code looks like.

<telerik:RadGridView HorizontalAlignment="Left" Margin="13,227,0,0" Name="ResultsRadGridView" VerticalAlignment="Top" Width="776" AutoGenerateColumns="False">
<telerik:RadGridView.Columns>
<telerik:GridViewDynamicHyperlinkColumn Header="Title" DataMemberBinding="{Binding Title}" NavigateUrlFormatString="{} {0}" NavigateUrlMemberPaths="Path" TargetName="_blank" />
<telerik:GridViewDataColumn Header="Author" DataMemberBinding="{Binding Author}" />
<telerik:GridViewDataColumn Header="Write" DataMemberBinding="{Binding Write}" DataFormatString="{} {0:MMM, dd, yyyy}" />
<telerik:GridViewDataColumn Header="Size" DataMemberBinding="{Binding Size}" />
<telerik:GridViewDataColumn Header="HitHighlightedSummary" DataMemberBinding="{Binding HitHighlightedSummary}" />
<telerik:GridViewDataColumn Header="ContentClass" DataMemberBinding="{Binding ContentClass}" />
<telerik:GridViewHyperlinkColumn Header="SiteName" DataMemberBinding="{Binding SiteName}" TargetName="_blank" />
<telerik:GridViewCheckBoxColumn Header="IsDocument" DataMemberBinding="{Binding IsDocument}" />
</telerik:RadGridView.Columns>
</telerik:RadGridView>

That’s all the code that is involved.  I’ve attached the code to this post (minus the Telerik controls).  This code will work on-premises or in the cloud with SharePoint Online.  I just used built-in managed properties in my example, but if you create custom properties of your own you can add those as well.  Try it out and see what you think.  Here is a screenshot of it in action.

image

Read the original blog entry...

More Stories By Corey Roth

Corey Roth, a SharePoint Server MVP, is an independent consultant specializing in Cloud technologies such as Azure and Office 365. He also specializes in mobile development. Corey serves as the product manager for two cloud-first mobile app platforms: BrewZap and HappenZap.

IoT & Smart Cities Stories
Bill Schmarzo, author of "Big Data: Understanding How Data Powers Big Business" and "Big Data MBA: Driving Business Strategies with Data Science," is responsible for setting the strategy and defining the Big Data service offerings and capabilities for EMC Global Services Big Data Practice. As the CTO for the Big Data Practice, he is responsible for working with organizations to help them identify where and how to start their big data journeys. He's written several white papers, is an avid blogge...
Nicolas Fierro is CEO of MIMIR Blockchain Solutions. He is a programmer, technologist, and operations dev who has worked with Ethereum and blockchain since 2014. His knowledge in blockchain dates to when he performed dev ops services to the Ethereum Foundation as one the privileged few developers to work with the original core team in Switzerland.
René Bostic is the Technical VP of the IBM Cloud Unit in North America. Enjoying her career with IBM during the modern millennial technological era, she is an expert in cloud computing, DevOps and emerging cloud technologies such as Blockchain. Her strengths and core competencies include a proven record of accomplishments in consensus building at all levels to assess, plan, and implement enterprise and cloud computing solutions. René is a member of the Society of Women Engineers (SWE) and a m...
Andrew Keys is Co-Founder of ConsenSys Enterprise. He comes to ConsenSys Enterprise with capital markets, technology and entrepreneurial experience. Previously, he worked for UBS investment bank in equities analysis. Later, he was responsible for the creation and distribution of life settlement products to hedge funds and investment banks. After, he co-founded a revenue cycle management company where he learned about Bitcoin and eventually Ethereal. Andrew's role at ConsenSys Enterprise is a mul...
In his general session at 19th Cloud Expo, Manish Dixit, VP of Product and Engineering at Dice, discussed how Dice leverages data insights and tools to help both tech professionals and recruiters better understand how skills relate to each other and which skills are in high demand using interactive visualizations and salary indicator tools to maximize earning potential. Manish Dixit is VP of Product and Engineering at Dice. As the leader of the Product, Engineering and Data Sciences team at D...
Dynatrace is an application performance management software company with products for the information technology departments and digital business owners of medium and large businesses. Building the Future of Monitoring with Artificial Intelligence. Today we can collect lots and lots of performance data. We build beautiful dashboards and even have fancy query languages to access and transform the data. Still performance data is a secret language only a couple of people understand. The more busine...
Whenever a new technology hits the high points of hype, everyone starts talking about it like it will solve all their business problems. Blockchain is one of those technologies. According to Gartner's latest report on the hype cycle of emerging technologies, blockchain has just passed the peak of their hype cycle curve. If you read the news articles about it, one would think it has taken over the technology world. No disruptive technology is without its challenges and potential impediments t...
If a machine can invent, does this mean the end of the patent system as we know it? The patent system, both in the US and Europe, allows companies to protect their inventions and helps foster innovation. However, Artificial Intelligence (AI) could be set to disrupt the patent system as we know it. This talk will examine how AI may change the patent landscape in the years to come. Furthermore, ways in which companies can best protect their AI related inventions will be examined from both a US and...
Bill Schmarzo, Tech Chair of "Big Data | Analytics" of upcoming CloudEXPO | DXWorldEXPO New York (November 12-13, 2018, New York City) today announced the outline and schedule of the track. "The track has been designed in experience/degree order," said Schmarzo. "So, that folks who attend the entire track can leave the conference with some of the skills necessary to get their work done when they get back to their offices. It actually ties back to some work that I'm doing at the University of San...
When talking IoT we often focus on the devices, the sensors, the hardware itself. The new smart appliances, the new smart or self-driving cars (which are amalgamations of many ‘things'). When we are looking at the world of IoT, we should take a step back, look at the big picture. What value are these devices providing. IoT is not about the devices, its about the data consumed and generated. The devices are tools, mechanisms, conduits. This paper discusses the considerations when dealing with the...