DCSIMG
March 2009 - Posts - Doron Goldberg

March 2009 - Posts

I managed to wrap both search provider into a feature so it will be easier to deploy them. The new feature includes a simple WebPart which displays link buttons for installing the providers.

Installation process can be viewed here:

 

The entire solution can be downloaded here.

In order to compile these files need to be copied into your Program Files/Netwise.

(The wsp file in located inside wsp/Debug folder inside the project)

Posted by dorong | with no comments

This post is following the previous one.

In this post I will demonstrate the second search provider for MOSS – the documents search provider for IE8.

I will be using the same solution, using the same method so this time I will only demonstrate the changes between the two. (The HTML is the same HTML from the previous post)

The service

The interface looks like this:

[ServiceContract]
public interface IDocumentSearchService
{
    [OperationContract]
    [WebGet(UriTemplate = "/Search?q={word}",
        BodyStyle = WebMessageBodyStyle.Bare)]
    [XmlSerializerFormat]
    SearchSuggestion Search(string word);
}

Same as before, a single method with a single parameter.

The Implementation:

public class DocumentSearchService : IDocumentSearchService
{
    private XmlDocument xmlIconDoc;
    #region IDocumentSearchService Members
    public SearchSuggestion Search(string word)
    {
        xmlIconDoc = new XmlDocument();
        xmlIconDoc.Load(@"C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\TEMPLATE\XML\DOCICON.XML");
        var oFrt = new FullTextSqlQuery(new SPSite(ConfigurationManager.AppSettings["server"]));
        oFrt.QueryText = string.Format(ConfigurationManager.AppSettings["DocumentsSearchQuery"].Replace('@', '"'), word);
        oFrt.ResultTypes = ResultType.RelevantResults | ResultType.SpecialTermResults;
        oFrt.StartRow = 0;
        oFrt.RowLimit = 10;
        oFrt.TrimDuplicates = true;
        ResultTableCollection lstResults = null;
        lstResults = oFrt.Execute();
        var ds = new DataSet();
        ds.Tables.Add("results");
        ds.Tables["results"].Load(lstResults[ResultType.RelevantResults]);
        var results = new SearchSuggestion();
        results.Query = word;
        foreach (DataRow row in ds.Tables["results"].Rows)
        {
            {
                results.Section.Add(new Item
                                        {
                                            Text =  row["title"] + " , " + row["Author"] ,
                                            Description =
                                                StripTagsRegex(row["hithighlightedsummary"].ToString()),
                                            Url = row["path"].ToString(),
                                            Image = new Image() 
                                            { Source = GetIcon(row["FileExtension"].ToString()),
                                                Height=16, Width=16 }
                                        });
            }
        }
        return results;
    }
    static string StripTagsRegex(string text)
    {
        return Regex.Replace(text, "<.*?>", string.Empty);
    }
   
    private string GetIcon(string p)
    {
        try
        {
            if (xmlIconDoc.SelectSingleNode("//DocIcons/ByExtension/Mapping[@Key='" + p.ToLower() + "']") != null)
                return ConfigurationManager.AppSettings["server"] +
                    "/_layouts/images/" +
                    xmlIconDoc.SelectSingleNode("//DocIcons/ByExtension/Mapping[@Key='" +
                    p.ToLower() +
                    "']").Attributes["Value"].Value;
            else
                return ConfigurationManager.AppSettings["server"] +
                    "/_layouts/images/" +
                    xmlIconDoc.SelectSingleNode("//DocIcons/Default/Mapping").Attributes["Value"].Value;
        }
        catch (Exception ex)
        {
            return ex.Message;
        }
         
    }
    #endregion
}

 

As you can see this implementation is a little bit trickier.

The query itself runs the same (different query of course), so does the building of the XML response.

Two main differences are:

1. I’m using StripTagsRegex method to remove tags from the search results because some of them are retuned with internal tagging. (the hithighlightedsummary field)

As for the Image, I’m calling the GetIcon method which uses MOSS internal xml file for mapping file extensions and icons. I’m doing it this way so the user will have the exact same experience he gets while searching inside the portal.

The file is usually located at:

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\12\TEMPLATE\XML\DOCICON.XML

Depends on how you installed the server.

This is a part of the file, just so you’ll know how the mapping goes:

image 

(This file is usually manually altered when installing new IFilters)

The XML defining the service

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
  <ShortName>Documents Search</ShortName>
  <Url 
    type="text/html" 
    template="http://doron-g/searchcenter/Pages/Results.aspx?k={searchTerms}" 
    />
  <Url 
    type="application/x-suggestions+xml" 
    template="http://localhost:52985/SearchProviders/DocumentSearchService.svc/Search?q={searchTerms}" 
    />
  <Image 
    height="16" 
    width="16" 
    type="image/icon">http://www.netwise.co.il/Images/favicon.ico
  </Image>
</OpenSearchDescription>

Same as before, two URL’s, one for redirecting when the user hits Enter and one for the service itself.

The client side script can be found in the previous post.

Adding the provider:

Clicking the second button opens the add search providers dialog:

image

(Notice the Name label - “Document Search”)

After adding the new provider it is now available in the search providers box, along with our people search provider and any other provider we have installed:

image

Selecting the documents search provider and typing a phrase will result a list of documents on the server which contains the phrase:

image

Hitting Enter will redirect me to the default search results page on my portal:

image

Clicking one of the search results in the providers search results will directly open the file without the need to actually navigate to the portal:

image

The entire solution can be downloaded from here.

Posted by dorong | 3 comment(s)

In this post, and the one following it I will present two new search providers for MOSS 2007.

Search provider is an old feature of common browsers, IE8 brings it into a new level with the extended visibility it provides for search results.

You can read more about IE8 search providers IE8 search provider.

The two search providers I will present will be:

1. People search provider for searching people in the portal.

2. Documents search provider for searching documents in the portal.

As we all know MOSS comes with a build-in search capabilities which includes a search engine and a very nice UI for search queries and search results. These two new providers are intended to extend these capabilities so users can search the portal before they actually access it.

My example is depending on Guy Burstein twitter search provider and I thank him for his direction in writing this post.

The example was developed as a regular service which has to be installed on the same machine where the portal is. It can be also deployed as a windows service exposing the service as WCF or as feature installed inside MOSS. In order to keep it simple I will only present the initial version.

Search providers are combined of 3 main parts:

1. The service

2. The XML defining the service

3. Client side script for installing the provider

 

The service

In order to search people inside the portal we must have a portal with indexed defined profiles. I won’t demonstrate how this is accomplished because it’s out of this post’s scope. In my example I simply imported the profiles from our Active Directory.

The service itself contains 2 parts:

1. Interface

2. Implementation of that interface as a service

The interface looks like this:

[ServiceContract]
public interface IPeopleSearchService
{
    [OperationContract]
    [WebGet(UriTemplate = "/Search?q={friend}",
        BodyStyle = WebMessageBodyStyle.Bare)]
    [XmlSerializerFormat]
    SearchSuggestion Search(string friend);
}

As you can see, a very simple interface with a single method the receives a single variable which is the phrase we will be searching.

The Implementations:

public class PeopleSearchService : IPeopleSearchService
{
    #region IPeopleSearchService Members
    public SearchSuggestion Search(string friend)
    {
        var oFrt = new FullTextSqlQuery(new SPSite(ConfigurationManager.AppSettings["server"]));
        oFrt.QueryText = string.Format(ConfigurationManager.AppSettings["query"].Replace('@', '"'), friend);
        oFrt.ResultTypes = ResultType.RelevantResults | ResultType.SpecialTermResults;
        oFrt.StartRow = 0;
        oFrt.RowLimit = 10;
        oFrt.TrimDuplicates = true;
        ResultTableCollection lstResults = null;
        lstResults = oFrt.Execute();
        var ds = new DataSet();
        ds.Tables.Add("results");
        ds.Tables["results"].Load(lstResults[ResultType.RelevantResults]);
        var results = new SearchSuggestion();
        results.Query = friend;
        foreach (DataRow row in ds.Tables["results"].Rows)
        {
            try
            {
                if (row["pictureurl"] != null && row["pictureurl"].ToString() != string.Empty)
                {
                    results.Section.Add(new Item
                                            {
                                                Text = row["title"] + " , " + row["department"],
                                                Description = row["workphone"] + " , " + row["workemail"],
                                                Url = row["path"] + Environment.NewLine + Environment.NewLine,
                                                Image =
                                                    new Image
                                                        {Height = 48,
                                                            Width = 48,
                                                            Source = row["pictureurl"].ToString()
                                                        }
                                            });
                }
                else
                {
                    results.Section.Add(new Item
                                            {
                                                Text = row["title"] + " , " + row["department"],
                                                Description = row["workphone"] + " , " + row["workemail"],
                                                Url = row["path"].ToString(),
                                                       Image =
                                                    new Image { Height = 48,
                                                        Width = 48,
                                                        Source = ConfigurationManager.AppSettings["server"]+"/_layouts/images/no_pic.gif" 
                                                    }
                                                
                                            });
                }
            }
            catch (Exception ex)
            {
                results.Section.Add(new Item {Text = ex.Message, Description = ex.StackTrace, Url = ""});
            }
        }
        return results;
    }
    #endregion
}

As you can see I was using Guy’s search suggestions class to generate the response using search results coming from the query search results. (The query is defined in the web.config so it can be easily altered during development) You can change the fields you ask for in the query so you can present different content in the search results.

I used title and department for the Text field and workphone + workemail for the Description field and the path as Url so clicking a search result will navigate to the personal page of the person.

Image is set by the pictureurl in case the search result contains a pictureurl, otherwise the default image is presented.

(Catch(Exception ex) returns the exception for debugging but it should be removed)

 

The XML defining the service

<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
  <ShortName>People Search</ShortName>
  <Url 
    type="text/html" 
    template="http://doron-g/SearchCenter/Pages/peopleresults.aspx?k={searchTerms}" 
    />
  <Url 
    type="application/x-suggestions+xml" 
    template="http://localhost:52985/SearchProviders/PeopleSearchService.svc/Search?q={searchTerms}" 
    />
  <Image 
    height="16" 
    width="16" 
    type="image/icon">http://www.netwise.co.il/Images/favicon.ico
  </Image>
</OpenSearchDescription>

The XML is the standard XML for search providers, which contains two URL’s – one for navigation in case the user hits Enter in the search box which navigates him to my portal’s default people’s search results page and one for the service which will be addressed while the user is typing inside the search box. (The service we created earlier)

Client side script

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script type="text/javascript">
        function Register() 
{window.external.AddSearchProvider('PeopleSuggestion.xml');}
function RegisterDocumentSearch() 
{window.external.AddSearchProvider('DocumentSuggestion.xml');}
</script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
     <button onclick='Register();'>People Search</button>
     <br />
     <button onclick='RegisterDocumentSearch();'>Document Search</button>
    </div>
    </form>
</body>
</html>

The script is the standard script for adding search providers - window.external.AddSearchProvider with the XML location as a parameter.

(This file contains both providers, the documents search provider will be presented in the next post)

The entire solution looks like this: (For both services)

image

When browsing to Default.aspx we have two buttons, one for each search provider:

image

Clicking the first button opens the add search providers dialog:

image

(Notice the Use search suggestions from this provider check box – unique for IE8)

After adding the provider it is now available for me on the search providers box:

image

Selecting it and typing my name results like this:

image

Searching other people, some with image and some without:

image

Clicking a search result leads me directly to the person’s page:

image

Hitting enter in the search box leads me to my portal’s people search results page:

image

 

I would like to thank Yuval Korin for his help in creating this example.

Posted by dorong | 9 comment(s)