This post is about creating a custom core results web part which will render the search results similar to list view UI i.e. using grid view with pagination, context menus, & column sorting / filtering. Most of the business users demand this kind of UI, as they expect to have a consistent user interface while working with documents. Moreover, it’s much usable when the document actions are available in the search results itself. The user can search for some documents and can do the same set of actions what they can do with the document’s library list view.
Problem
Solution
Key Considerations/Challenges
Approach
1) Manipulating the SRHO to return the desired no. of results (using Reflection)
//Get the SRHO from the context
object srhoInContext = System.Web.HttpContext.Current.Items["OSSSRHDC_0"]
//Create a Query instance
Microsoft.Office.Server.Search.Query.Query customQuery = new Microsoft.Office.Server.Search.Query.Query(Microsoft.Office.Server.ServerContext.Current)
//Invoke the methods “SetKeywordQueryProperties” & “SetQueryProperties” to populate the properties for the customQuery object
srhoInContext.GetType().GetMethod
(“SetKeywordQueryProperties”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
).Invoke(srhoInContext, new object[] { customQuery });
(“SetQueryProperties”,
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
//Set the desired page size to the custom query object
customQuery.RowLimit = 1000
//Execute the query and obtain results
Microsoft.Office.Server.Search.Query.ResultTableCollection customResults = customQuery.Execute();
//Set the property variables of the SRHO using Reflection
srhoInContext.GetType().GetField(
“m_Result”,
).SetValue(srhoInContext, customResults);
“m_totalRows”,
).SetValue(srhoInContext, relevantResultTable.TotalRows);
“m_RowCount”,
).SetValue(srhoInContext, relevantResultTable.RowCount);
“m_BestBetRowCount”,
).SetValue(srhoInContext, bestBetResultTable.RowCount);
“m_HighConfidenceRowCount”,
).SetValue(srhoInContext, highConfidenceResultTable.RowCount);
//Put the manipulated SRHO object back into context
System.Web.HttpContext.Current.Items["OSSSRHDC_0"] = srhoInContext
//Get the results data table
DataTable tbl = new DataTable();
tbl.Load(customResults[Microsoft.Office.Server.Search.Query.ResultType.RelevantResults],LoadOption.OverwriteChanges);
The above manipulation, just updates only the “m_Result” variable of ResultTableCollection type in SRHO object, which of ResultTableCollection. But, the xmlResponseDoc, representing the results in xml document, is not updated.
2) Using SPGridView for enabling Pagination, Sorting & Filtering
//INITIALIZE THE SPGRIDVIEW WITH THE PROPERTIES ENABLED FOR PAGINATION, SORTING AND FILTERING
SPGridView resultsView = new SPGridView();
// Initialize SPGridView
resultsView = new SPGridView();
// setting the basic properties of SPGridView
resultsView.ID = “<SPGridViewID>”";
resultsView.AutoGenerateColumns = false;
// setting the sorting properties
resultsView.AllowSorting = true;
// setting the paging properties
resultsView.PageSize = <desiredPageSize>;
resultsView.AllowPaging = true;
resultsView.PagerStyle.HorizontalAlign = HorizontalAlign.Right;
// setting the event handlers
resultsView.RowDataBound += new wRowEventHandler(resultsView_RowDataBound);
// setting the filter properties
resultsView.AllowFiltering = true;
resultsView.FilterDataFields = “<list of column names to be filtered>”;
resultsView.FilteredDataSourcePropertyName = “FilterExpression”;
resultsView.FilteredDataSourcePropertyFormat = “{1} LIKE ‘{0}’”;
// INITIALIZE THE DATASOURCE
ObjectDataSource ds = new ObjectDataSource();
ds.TypeName = “<TypeName>,”;
ds.TypeName += System.Reflection.Assembly.GetExecutingAssembly().FullName;
ds.SelectMethod = “FillDataTable”;
ds.ID = “<ID>”;
// setting the data source for the grid view
resultsView.DataSourceID = ds.ID
// add both the datasource and the grid to the control collection
this.Controls.Add(ds);
this.Controls.Add(resultsView);
// Set the PagerTemplate property to null for enabling the default pagination controls
// this line must be after adding the grid to the control collection
resultsView.PagerTemplate = null;
// Override the OnPreRender & Render method to set the filter expression and perform data //bind
protected override void OnPreRender(EventArgs e)
{
ViewState["FilterExpression"] = ds.FilterExpression;
base.OnPreRender(e);
}
protected override void Render(HtmlTextWriter writer)
resultsView.DataBind();
base.Render(writer);
// Setting the column headers with filter icon
// include the following code block in the RowDataBound Eventhandler method for
// SPGridView
if ((sender != null) && (e.Row.RowType == DataControlRowType.Header))
string strFilteredColumn = ((SPGridView)sender).FilterFieldName;
SetGridViewFilterIcon(resultsView, strFilteredColumn, e.Row);
public void SetGridViewFilter(SPGridView gridView, string strFilteredColumn, GridViewRow gridViewRow)
if ((string.IsNullOrEmpty(strFilteredColumn) == false) && (gridViewRow != null))
// Show icon on filtered column
for (int iIndex = 0; iIndex < gridView.Columns.Count; iIndex++)
DataControlField currentField = gridView.Columns[iIndex];
if (currentField.HeaderText.Equals(strFilteredColumn))
Image filterIcon = new Image();
filterIcon.ImageUrl = “/_layouts/images/ewr093.gif”;
filterIcon.ImageAlign = ImageAlign.Left;
filterIcon.Style[System.Web.UI.HtmlTextWriterStyle.MarginTop] = “2px”;
filterIcon.ID = “FilterIcon”;
Panel panel = new Panel();
panel.Controls.Add(filterIcon);
gridViewRow.Cells[iIndex].Controls.Add(panel);
break;
3) Populating the context menus for search result items, using the OOTB javascript function which automatically picks up the required attributes and generates the context menus
Reference Table for the hidden attributes being used in this custom search results webpart, for enabling the javascript to populate the context mneus for search results. The attributes needs to be passed in two places
1. ContextInfo object attributes
2. Input type attributes
Attribute Name
Description
Value Type
INPUT TYPE ATTRIBUTES
// FORMAT FOR THE CONTEXT INFO ATTRIBUTES SCRIPT
<SCRIPT>
ctx = new ContextInfo();
ctx.listBaseType = <listBaseTypeID>;
ctx.listTemplate = <ListTempateID>;
ctx.listName = “{<GUID of List>}”;
ctx.view = “{GUID of List Default View}”;
ctx.listUrlDir = “<list url>”;
ctx.HttpPath = “u002f<site>u002f_vti_binu002fowssvr.dll?CS=65001″;
ctx.HttpRoot = “http:u002fu002f<SERVER>:<PORT>u002f<SITE>”;
ctx.imagesPath = “u002f_layoutsu002fimagesu002f”;
ctx.PortalUrl = “”;
ctx.SendToLocationName = “”;
ctx.SendToLocationUrl = “”;
ctx.RecycleBinEnabled = -1;
ctx.OfficialFileName = “”;
ctx.WriteSecurity = “1″;
ctx.SiteTitle = “<SITE TITLE>”;
ctx.ListTitle = “<LIST TITLE>”;
if (ctx.PortalUrl == “”) ctx.PortalUrl = null;
ctx.displayFormUrl = “<URL OF DISPLAY FORM>”;
ctx.editFormUrl = “<URL OF EDIT FORM>”;
ctx.isWebEditorPreview = 0;
ctx.ctxId = 1;
g_ViewIdToViewCounterMap[ "{<GUID of List's default view>}" ]= 1;
ctx.CurrentUserId = <CURRENT USER ID (numeric)>;
ctx.isForceCheckout = <true/false>;
ctx.EnableMinorVersions = <true/false>;
ctx.verEnabled = 1;
ctx.WorkflowsAssociated = <true/false>;
ctx.ContentTypesEnabled = <true/false>;
ctx1 = ctx;
</SCRIPT>
// FORMAT FOR THE INPUT TYPE ATTRIBUTES
<table height=”100%” cellspacing=0 class=”ms-unselectedtitle” onmouseover=”OnItem(this)”
CTXName=”ctx1″
Id=”<ID of the item in it’s list>”
Url=”<Relative Url of the item>”
DRef=”<File Directory Reference>”
Perm=”<Permission Mask>”
Type=”"
Ext=”<file extension>”
Icon=”<ImageIcon filename>|<ProgID of client application>|<action>”
OType=”<FileSystemObjectTypeID>”
COUId=”<CheckedOutUserID>”
SRed=”"
COut=”<IsCheckedOut><0/1>”
HCD=”"
CSrc=”"
MS=”<ModerationStatus>”
CType=”<ContentType>”
CId=”<ContentTypeID>”
UIS=”<UI String for version>”
SUrl=”">
<tr>
<td width=”100%” Class=”ms-vb”>
<A onfocus=”OnLink(this)”
HREF=”<itemURL>”
onclick=”return DispEx(this,event,’TRUE’,'FALSE’,'TRUE’,'SharePoint.OpenDocuments.3′,
’0′,’SharePoint.OpenDocuments’,”,”,”,’1073741823′,’1′,’0′,’0x7fffffffffffffff’)”>
The above code implemented still have some nitty bitty issues, which I’m still working on. I thought I’d just give a headsup on the overall approach, so that it will help someone firefighting the same kind of issue.
Screenshots
1. Custom Core Results Web Part
2. Custom Core Results Web Part – with Filter Menu
3. Custom Core Results Web Part – with Context Menu