Thursday, August 11, 2011

Filtered Search Sample (part 3 - service layer)

In Part 1 we talked about interacting and consuming the search service. In Part 2we discussed a Tagging strategy to allow for a Filtered Search to be possible. Today we are going to look at the service layer that is called by the presentation layer.

SearchService.cs

Screen1
Here we have the method signature that is called by the client. So as we went over in Part 1, if the user searches for "cost savings" and selects the Industries of "Construction" and "Energy" the following url is used in the Ajax call on the client end.

htttp://www.domainname.com/Services/SearchService.svc/SearchInsightsJson?&Category2={017E7503-83A1-4A45-9053-4C6755715162}|{66310DCB-3BD1-4C4C-9AFA-FBBC8D4BA967}&page=1&perPage=8&searchString=cost%20savings

This url ends up calling the SearchInsightsJson method within the SearchService web service class and it will pass in the GUIDs associated with Construction and Energy, the current page the user is on, the results the client wants per page the searchString is set to "cost savings".

You will notice the method has two attributes that mark the method as an OperationContract (available via the web service) and the WebGet attribute is stating that the response should be in a JSON format.

Prevent Client-side Caching

Screen2
The three lines of code above help prevent any caching that may occur by the client. Technically because the url would be unique for each search request we could remove this block of code all together with little impact but this ensures if a new item has been published since the client ran the query that it would get picked up in the search results.

Building-up the Criteria

Screen3
The code shot above shows the approach to calling the Search service. We are populating a SearchParam object with the information needed by the Search Service to run the query.

  • LocationIds = the top folder or folders within the CMS we want to search.
  • TemplateIds = the page types we want included in our search.
  • FullTextQuery = the key word or phrase the user entered in the search form.
  • ShowAllVersions = this specifies if we want to search all versions of a page or just the latest
  • Language = sets the language context of our search (for multi-lingual sites)

We then check each Category parameter and if it is not null we will add the pipe delimited GUIDs to the Tags collection.

Search Call and Setup Paging

Screen4
The first piece is calling our Search Service and passing in our Criteria. We can then set some parameter values that we will include in our JSON result so our presentation can provide paging functionality without having to do the logic on their end.

We set the TotalResults is set to the hits.Count and the CurrentPage is the page that was passed in originally so the client doesn't have maintain state.

The results we get back from the Search Service is actually just a List of what we are calling a SkinnyItem, that is to say, it is just a pointer to the actual Item along with some other meta data. SkinnyItem has a GetItem() method that will retrieve the full Item from Sitecore but if we have 1000+ items that match our search and we truly only need to fetch the ones that are being displayed on the current page (8 of them), it would be a lot of unnecessary overhead to do this.

We end up creating an s and an e variable that we will use in the next code snippet. e is either going to be (perPage * page) or the total results from the search whichever  is less. s is either ((perPage * page) - perPage) or the hits.Count - perPage whichever  is less (as long as it is >= 0).

Screen5

Above we are iterating through the SkinnyItems between our s and or e count and grabbing the full Item out of Sitecore in order to populate a SearchResultItem and add it to our resultItems collection. This seems like an extra step because we are going from a SkinnyItem to get a Sitecore Item to get a SearchResultItem but there is a logical reason. The SkinnyItem is essentially a representation of the raw search result from Lucene. The Sitecore Item is acting as full data record or Domain Object and the SearchResultItem is a simple Data Transfer Object containing only the fields needed by the view.

We set the JSON resultData.Results equal to the collection of SearchResultItems we just created and we set the TotalPages equal to the total records divided by items per page and round up.

We set the SearchUrl with the value that was passed in and provide it back to the client so it doesn't have to worry about state.

The PageStart is what the starting page in the page navigation should be. This is hard coded right now with a paging of 5 but this could come from a config file or even the CMS. This allows our client to setup paging of 6 through 10 when a current page of 7 is passed in (see below).

Screen6
Next Up

So that is what our web service looks like. While writing this blog post and taking screenshots I see some areas of improvement and refactoring but you should get the idea of what the service layer should be doing. Part 4 will focus on our actual Search service. This will go into detail about how we customized our Crawler, setup our Indexes and leveraged the advanced searcher / crawler from the Sitecore Shared Source library - if you want to get a heads start on that I recommend this video.

No comments:

Post a Comment