Wednesday, July 17, 2013

Developing a Custom Content Search Web Part, Part 2: Search and Building a Query

In the previous post we defined our requirements and architecture and created content to display in our custom content search web part (CSWP). In this post we'll dive into search and building our query in the web part.

Search

Before we get too far ahead of ourselves, it's worthwhile to check the search service application in Central Administration to make sure that a) it's running and b) incremental crawls are running.

New to search? Let me help you out. In Central Administration in the Application Management section click Manager Service Applications. Then click Search Service Application. From the home page check out that search is up and running and see that there are items in the Searchable Items.

From the left-hand nav click Content Sources. Click Local SharePoint sites. Check out the details.

If a crawl ran recently, skip to the next section. If nothing is in the details section, scroll down to the Crawl Schedules section.

Click Enable Incremental Crawls. Then click Create Schedule. 

Since we're developing, I'm going to set my incremental to run every 5 minutes. See the screen below for how to do that. 

Now create a full crawl schedule. This can (and should) be set to run just once a day.

When done, click OK and you'll be taken back to the Manage Content Sources screen. Kick off a full crawl by clicking the dropdown for Local SharePoint sites and select Start Full Crawl. Give Search a few minutes to work its magic. Depending on the size of your environment this could take at least 5 minutes. Go stretch your legs for a few, come back and refresh this page. We're just trying to bide some time while our full crawl completes.

Once a full crawl completes we can start working on our query in the web part.

Building a Query

Content Search is pretty awesome in that you can pretty much search and refine on anything you want in SharePoint. When you think about it though, that can be a little dangerous. I'm not interesting in overwhelming me or my web part at the current moment which is why in these series of posts we're developing with content types on a single site collection.

On a blank web part page, add a Content Search Web Part (this can be found in the Content Rollup category)

Edit the web part's properties

Click Change Query

As you can see, we already have a bunch of items showing up in our Search Result Preview pane. These are all the recently changed items.

Obviously we don't want this. Let's roll up our sleeves and jump into the query builder's advanced mode.

Click Switch to Advanced Mode

We're taken to our unadulterated query text. We need the path to point to our site, but I could care less about displaying items and documents; I want to filter by content types. Go ahead and delete (IsDocument:"True" OR contentclass:"STS_ListItem") from the query.

In the property filter section, select "--Show all managed properties--" then click the property filter again and select Content Type. Where the dropdown currently says "Contains" leave that be. In the dropdown that displays "Select value" select "--Show all content types--" then click "News Article." When done, click Add property filter.

All right, let's test our query by clicking Test query. Big money, big money, no whammy! STOP! What? Zero results? I know those articles are there. I saw them a minute ago.

What's happening is your search query is looking for a content type's ID, but yet the query builder is building based on the content type's name. Why? I dunno. If you update the query to be ContentTypeID instead of ContentType, you'll get results. Is this intuitive? Not at all. If you like manually entering queries, you can do that as well. For example ContentType:"News Article." Why did I not do this? Because I thought the query builder would do that for me, but it likes giving me GUIDs over names so I'll play by its rules this time.

Here's another reason I'm using GUIDs. When I try using ContentType = "News Article" in my query, I have zero results - which makes no sense. If I try ContentType:"News Article" it brings back all the content type names containing news article. The OOB wonkiness has to do with the ContentType managed property in search and whether or not it's set to searchable. In the environments I've tried this in it works great when using the contains operator ":", but when using the equal operator it fails like my beloved Chicago Cubs bullpen in a save situation (aka it bombs). What I'm trying to get at is be cautious with how you're building your queries as you may get unnecessary results or omit results. There's a lot involved with Search when using the CSWP and I'm trying to minimize that overhead as there's already plenty of other moving pieces involved. For more about query syntax and how it's supposed to work, be sure to check out this article.

Sorry for digressing. Let's finish up our query. We have news articles, but we need the Redirect News Article content types too. Repeat the previous step of adding a content type only select Redirect News Article. Be sure to update the query text to be ContentTypeID.

Now come the Start Dates and End Dates. I documented my woes with this in an earlier blog. To summarize, if we add StartDate<={Today} EndDate>={Today} to our query we get zero results. Here's how to fix that.

In my previous blog I reset the search index. We're going to do something a little different this time since you may not have the luxury of resetting an index. In the Search service application, we're going to manually set the crawled properties.

Open up Search in Central Administration. Click Search Schema from the left-hand nav. Click Managed Properties at the top of the screen. Search "Start." Click Start Date.

Scroll down to the Mappings to crawled properties section. One by one click each property and click Remove Mapping. Then click Add a Mapping. Search for "Start", select ows_PublishingStartDate, and click OK.

Scroll down and click OK. Repeat these steps for End Date and use ows_PublishingExpirationDate as the crawled property.

Kick off a full crawl. Once that completes come back to your web part. After the content type IDs add the text StartDate<={Today} EndDate>={Today}. The complete query should be something like this:

path:"http://sp2012devvm:90/sites/cswp" ContentTypeId:0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00B84D4A8B26EF1949BFC283791C82BAFB01* ContentTypeId:0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00B84D4A8B26EF1949BFC283791C82BAFB02* StartDate<={Today} EndDate>={Today}

Sweet! Our query is beautiful and returning all the results!

Now let's kick it up a notch and refine by the sort order column.

Click the sorting tab in the web part query.

Click the Sort by dropdown and find Sort Order.

What? It's not there?!

If you go back to the search service application, go to Search Schema. Search the managed properties for "Sort". Looks like we can't refine and sort by this property…yet.

Click SortOrderOWSNMBR. In the Main Characteristics section click the box for Searchable. In the Refinable and Sortable sections set both properties to Yes - active. Scroll to the bottom and click OK. We just enabled 1) the searchability of the column and 2) that we can refine on the column.

Wait for an incremental crawl to run. Return to the CSWP and the Sorting tab. Add SortOrderOWSNMBR and set the order to Ascending.

Did it work? Click the Test tab and compare the result preview to the results before applying the sort.

Here's a side-by-side comparison:

Satisfied with the query we can click OK and close out of the query builder.

We've now built our query and have the results returning the data we want how we want. In the next post we'll go window shopping with display templates and start developing our own.