<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://www.dotnetmafia.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>Corey Roth [MVP] : LINQ to XML, SharePoint, Wildcard Search</title><link>http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/tags/LINQ+to+XML/SharePoint/Wildcard+Search/default.aspx</link><description>Tags: LINQ to XML, SharePoint, Wildcard Search</description><dc:language>en</dc:language><generator>CommunityServer 2007.1 (Build: 20917.1142)</generator><item><title>NEW! Web Part for Wildcard Search in Enterprise Search</title><link>http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2008/06/09/new-web-part-for-wildcard-search-in-enterprise-search.aspx</link><pubDate>Mon, 09 Jun 2008 13:28:15 GMT</pubDate><guid isPermaLink="false">ceb7fe2a-c56b-4d85-99e6-8dd548580538:603</guid><dc:creator>CoreyRoth</dc:creator><slash:comments>79</slash:comments><wfw:commentRss xmlns:wfw="http://wellformedweb.org/CommentAPI/">http://www.dotnetmafia.com/blogs/dotnettipoftheday/rsscomments.aspx?PostID=603</wfw:commentRss><comments>http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2008/06/09/new-web-part-for-wildcard-search-in-enterprise-search.aspx#comments</comments><description>&lt;p&gt;I have had &lt;a href="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2008/02/01/things-users-complain-about-with-enteprise-search.aspx"&gt;countless people&lt;/a&gt; tell me that they want to be able to do wildcard search in MOSS Enterprise Search using the existing Search Center site templates.&amp;nbsp; The Search Center site template uses keyword query syntax which does not support wildcards.&amp;nbsp; To get wildcards you need to use a full text SQL query.&amp;nbsp; Until now the only solutions to getting wildcard search was either use &lt;a href="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/2007/08/29/product-review-ontolica-wildcard.aspx"&gt;Ontolica&amp;#39;s Wildcard Search&lt;/a&gt; or write your own search page.&amp;nbsp; Neither option has ever been appealing to me.&amp;nbsp; Ontolica replaces the entire search center and provides an extra layer of abstraction to your managed properties.&amp;nbsp; I didn&amp;#39;t want to write my own search page because the search center already produces great looking results and it would be a lot of effort to reinvent everything on the search page.&amp;nbsp; The reason why you would have had to write your own search page is that you could not get access via conventional means to the objects to change the query.&lt;/p&gt; &lt;p&gt;This is why I finally decided to take matters into my own hand.&amp;nbsp; I really just wanted to just inherit from CoreResultsWebPart, change out the keyword query with a FullTextSqlQuery and call it good.&amp;nbsp; Anyone who may have looked at this before knows it is not that simple.&amp;nbsp; The class that does all the work &lt;em&gt;SearchResultsHiddenObject&lt;/em&gt; is marked internal.&amp;nbsp;&amp;nbsp; Right now this seems an impossible task unless you bend the rules a little.&amp;nbsp; That&amp;#39;s right.&amp;nbsp; I decided I am going to break OO rules and use reflection to get to the properties I needed.&amp;nbsp; Some people say you should never do this and that its a hack.&amp;nbsp; I agree to some extent, but when you are programming against an API and the provider of said API doesn&amp;#39;t give you the tools you need to do your job, sometimes you have to bend the rules.&amp;nbsp; Let&amp;#39;s face it.&amp;nbsp; Microsoft should have given us this support out of the box and at the minimum should have allowed us to change the query that the CoreResultsWebPart executes through the API.&amp;nbsp; They did neither so here we are.&lt;/p&gt; &lt;p&gt;So how does the code work?&amp;nbsp; Well, CoreResultsWebPart happens to be the one class in all of the Enterprise Search controls that isn&amp;#39;t marked sealed.&amp;nbsp; That is good news.&amp;nbsp; Through the use of Reflector, I discovered the method I need to inherit from is &lt;em&gt;SetPropertiesOnHiddenObject&lt;/em&gt;.&amp;nbsp; Microsoft was even nice enough to mark this method as virtual for me.&amp;nbsp; I then got access to the type and then used the base type to get a FieldInfo object for the private field srho (which is the SearchResultsHiddenObject).&amp;nbsp; &lt;/p&gt; &lt;div style="font-size:10pt;background:white;color:black;font-family:courier new;"&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// get the type of the current object&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;Type&lt;/span&gt; coreResultsWebPartType = &lt;span style="color:blue;"&gt;this&lt;/span&gt;.GetType();&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// get the private field containing the searchResultsHiddenObject&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;FieldInfo&lt;/span&gt; searchResultsHiddenObjectField = coreResultsWebPartType.BaseType.GetField(&lt;span style="color:#a31515;"&gt;&amp;quot;srho&amp;quot;&lt;/span&gt;, &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.NonPublic | &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Instance);&lt;/p&gt;&lt;/div&gt; &lt;p&gt;Once, I got access to the hidden object, I read the value of the KeywordQuery property to get what the user searched for.&amp;nbsp; I then had to set this value to null, because I was replacing the keyword query with a FullTextSqlQuery.&amp;nbsp; &lt;/p&gt; &lt;div style="font-size:10pt;background:white;color:black;font-family:courier new;"&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// get the actual internal srho object attached to CoreResultsWebPart&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:blue;"&gt;object&lt;/span&gt; searchResultsHiddenObject = searchResultsHiddenObjectField.GetValue(&lt;span style="color:blue;"&gt;this&lt;/span&gt;);&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// get the type of the srho&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;Type&lt;/span&gt; searchResultsHiddenObjecType = searchResultsHiddenObject.GetType();&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// get the keyword query property&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;PropertyInfo&lt;/span&gt; keywordQueryProperty = searchResultsHiddenObjecType.GetProperty(&lt;span style="color:#a31515;"&gt;&amp;quot;KeywordQuery&amp;quot;&lt;/span&gt;, &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Instance | &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Public);&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// read what the user searched for&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:blue;"&gt;string&lt;/span&gt; keywordQuery = (&lt;span style="color:blue;"&gt;string&lt;/span&gt;)keywordQueryProperty.GetValue(searchResultsHiddenObject, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// set the keywordProperty to null so we can change it to a fullTextQuery&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;keywordQueryProperty.SetValue(searchResultsHiddenObject, &lt;span style="color:blue;"&gt;null&lt;/span&gt;, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;/p&gt;&lt;/div&gt; &lt;p&gt;It was then just a matter of forming a new SQL query string (check the code on how I did that), and setting some additional fields &lt;em&gt;_IsFullTextQuerySetFromForm&lt;/em&gt; and &lt;em&gt;m_bIsKeywordQuery&lt;/em&gt;.&amp;nbsp; &lt;/p&gt; &lt;div style="font-size:10pt;background:white;color:black;font-family:courier new;"&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// get the fullTextQuery field&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;PropertyInfo&lt;/span&gt; fullTextQueryProperty = searchResultsHiddenObjecType.GetProperty(&lt;span style="color:#a31515;"&gt;&amp;quot;FullTextQuery&amp;quot;&lt;/span&gt;, &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Instance | &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Public);&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// create a new query and set it&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:blue;"&gt;string&lt;/span&gt; fullTextQueryString = GetFullTextQuery(keywordQuery, keywordsAsQuery);&lt;/p&gt; &lt;p style="margin:0px;"&gt;fullTextQueryProperty.SetValue(searchResultsHiddenObject, fullTextQueryString, &lt;span style="color:blue;"&gt;null&lt;/span&gt;);&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// this field needs to be set to true to use a full text query&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;FieldInfo&lt;/span&gt; fullTextQuerySetField = searchResultsHiddenObjecType.GetField(&lt;span style="color:#a31515;"&gt;&amp;quot;_IsFullTextQuerySetFromForm&amp;quot;&lt;/span&gt;, &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.NonPublic | &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Instance);&lt;/p&gt; &lt;p style="margin:0px;"&gt;fullTextQuerySetField.SetValue(searchResultsHiddenObject, &lt;span style="color:blue;"&gt;true&lt;/span&gt;);&lt;/p&gt; &lt;p style="margin:0px;"&gt;&amp;nbsp;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:green;"&gt;// tell the srho that it is not a keyword query any more&lt;/span&gt;&lt;/p&gt; &lt;p style="margin:0px;"&gt;&lt;span style="color:#2b91af;"&gt;FieldInfo&lt;/span&gt; isKeywordQueryField = searchResultsHiddenObjecType.GetField(&lt;span style="color:#a31515;"&gt;&amp;quot;m_bIsKeywordQuery&amp;quot;&lt;/span&gt;, &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.NonPublic | &lt;span style="color:#2b91af;"&gt;BindingFlags&lt;/span&gt;.Instance);&lt;/p&gt; &lt;p style="margin:0px;"&gt;isKeywordQueryField.SetValue(searchResultsHiddenObject, &lt;span style="color:blue;"&gt;false&lt;/span&gt;);&lt;/p&gt;&lt;/div&gt; &lt;p&gt;The code for this is really pretty simple (aside from the reflection).&amp;nbsp; Had the SearchResultsHiddenObject been marked public, we could have had this functionality over a year ago, but oh well.&amp;nbsp; &lt;/p&gt; &lt;h3&gt;Installation&lt;/h3&gt; &lt;p&gt;Installation is relatively simple and instructions are included in a readme file in the document.&amp;nbsp; A solution package has been provided for ease of installation.&amp;nbsp; Once the package has been installed activate the Wildcard Search Web Part feature on your site collection.&amp;nbsp; I went with a site collection feature because the search center site does not have a web part gallery in it.&amp;nbsp; Now that the feature is activated, go to your results page in your Search Center, edit the page, and add the Wildcard Search Core Results Web Part to the Bottom Zone.&amp;nbsp; You can then remove the old CoreResultsWebPart.&amp;nbsp; Also note that this web part requires .NET Framework 3.5 because I used LINQ to XML to parse through the &lt;em&gt;SelectColumns&lt;/em&gt; property.&lt;/p&gt; &lt;h3&gt;Usage&lt;/h3&gt; &lt;p&gt;Once you have the web part installed, you can perform a wildcard search by just adding an asterisk to whatever you type in the search box.&amp;nbsp; For example &lt;em&gt;app*&lt;/em&gt; would return mataches on app, apple, and application.&amp;nbsp; You can also set the &lt;em&gt;Always Use Wildcard &lt;/em&gt;property in the Miscellaneous property settings to always perform a wildcard search.&lt;/p&gt; &lt;p&gt;One thing to note.&amp;nbsp; Wildcard searches reek havoc on your search relevance.&amp;nbsp; Where you might be used to having nice clean looking results with your keyword searches, your wildcard searches will look different.&amp;nbsp; It might not always be obvious why a particular item was returned in the search results.&amp;nbsp; The best thing to do is try and see if it works for your particular situation.&lt;/p&gt; &lt;p&gt;This type of web part probably could have been sold, but I thought it was more important to give it to the community.&amp;nbsp; This feature gets asked for by MOSS customers all the time.&amp;nbsp; Finally there is an easy solution to implementing it.&amp;nbsp; Since this web part is new, I am sure there are going to be issues with it.&amp;nbsp; Please, log any issues you run into in installation or in us on the issue tracker of the CodePlex site.&amp;nbsp; Currently, it only supports simple keyword queries.&amp;nbsp; I still need to implement support for passing scopes and managed properties, so look for that in an update soon.&lt;/p&gt; &lt;p&gt;You can find the release files at &lt;a href="http://www.codeplex.com/WildcardSearch"&gt;CodePlex&lt;/a&gt;.&lt;/p&gt; &lt;p&gt;&lt;em&gt;Corey Roth is a MOSS consultant for &lt;a href="http://www.sbti.com/"&gt;Stonebridge&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;&lt;img src="http://www.dotnetmafia.com/aggbug.aspx?PostID=603" width="1" height="1"&gt;</description><category domain="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/tags/SharePoint/default.aspx">SharePoint</category><category domain="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/tags/LINQ+to+XML/default.aspx">LINQ to XML</category><category domain="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/tags/MOSS/default.aspx">MOSS</category><category domain="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/tags/Enterprise+Search/default.aspx">Enterprise Search</category><category domain="http://www.dotnetmafia.com/blogs/dotnettipoftheday/archive/tags/Wildcard+Search/default.aspx">Wildcard Search</category></item></channel></rss>