Welcome!

Microsoft Cloud Authors: Kevin Benedict, Pat Romanski, Liz McMillan, Lori MacVittie, Elizabeth White

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
DXWordEXPO New York 2018, colocated with CloudEXPO New York 2018 will be held November 11-13, 2018, in New York City and will bring together Cloud Computing, FinTech and Blockchain, Digital Transformation, Big Data, Internet of Things, DevOps, AI, Machine Learning and WebRTC to one location.
The current age of digital transformation means that IT organizations must adapt their toolset to cover all digital experiences, beyond just the end users’. Today’s businesses can no longer focus solely on the digital interactions they manage with employees or customers; they must now contend with non-traditional factors. Whether it's the power of brand to make or break a company, the need to monitor across all locations 24/7, or the ability to proactively resolve issues, companies must adapt to...
DXWorldEXPO LLC announced today that ICC-USA, a computer systems integrator and server manufacturing company focused on developing products and product appliances, will exhibit at the 22nd International CloudEXPO | DXWorldEXPO. DXWordEXPO New York 2018, colocated with CloudEXPO New York 2018 will be held November 11-13, 2018, in New York City. ICC is a computer systems integrator and server manufacturing company focused on developing products and product appliances to meet a wide range of ...
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...
@DevOpsSummit at Cloud Expo, taking place November 12-13 in New York City, NY, is co-located with 22nd international CloudEXPO | first international DXWorldEXPO and will feature technical sessions from a rock star conference faculty and the leading industry players in the world. The widespread success of cloud computing is driving the DevOps revolution in enterprise IT. Now as never before, development teams must communicate and collaborate in a dynamic, 24/7/365 environment. There is no time t...
Founded in 2000, Chetu Inc. is a global provider of customized software development solutions and IT staff augmentation services for software technology providers. By providing clients with unparalleled niche technology expertise and industry experience, Chetu has become the premiere long-term, back-end software development partner for start-ups, SMBs, and Fortune 500 companies. Chetu is headquartered in Plantation, Florida, with thirteen offices throughout the U.S. and abroad.
DXWorldEXPO | CloudEXPO are the world's most influential, independent events where Cloud Computing was coined and where technology buyers and vendors meet to experience and discuss the big picture of Digital Transformation and all of the strategies, tactics, and tools they need to realize their goals. Sponsors of DXWorldEXPO | CloudEXPO benefit from unmatched branding, profile building and lead generation opportunities.
CloudEXPO New York 2018, colocated with DXWorldEXPO New York 2018 will be held November 11-13, 2018, in New York City and will bring together Cloud Computing, FinTech and Blockchain, Digital Transformation, Big Data, Internet of Things, DevOps, AI, Machine Learning and WebRTC to one location.
Disruption, Innovation, Artificial Intelligence and Machine Learning, Leadership and Management hear these words all day every day... lofty goals but how do we make it real? Add to that, that simply put, people don't like change. But what if we could implement and utilize these enterprise tools in a fast and "Non-Disruptive" way, enabling us to glean insights about our business, identify and reduce exposure, risk and liability, and secure business continuity?
SYS-CON Events announced today that DatacenterDynamics has been named “Media Sponsor” of SYS-CON's 18th International Cloud Expo, which will take place on June 7–9, 2016, at the Javits Center in New York City, NY. DatacenterDynamics is a brand of DCD Group, a global B2B media and publishing company that develops products to help senior professionals in the world's most ICT dependent organizations make risk-based infrastructure and capacity decisions.