Monday, December 17, 2012

Custom Core Results Web Part in Grid Format, with Context Menus & Sorting/Filtering

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
  • Search results should enable the user to checkout/check inand perform other related actions for the document
  • User’s should be able to sort & filter on the search result columns
  • The UI for the search results should be similar to the UI of a document library / list library
 Solution
  • Custom Core Results Web Part implementing custom rendering format for the search results using the SPGridView, as this control provides the pagination, sorting & filtering feature
 Key Considerations/Challenges
  • The approach should reuse the OOTB Core Results web part to the max, thus reducing effort in re-inventing the wheel
  • Consistent UI should be provided for the user, between the search UI and the other list views in SharePoint
  • Most of all the features of OOTB Core Results web part can be reused for this approach, but the rendering of search result must be overridden for rendering the results in grid format with pagination, context menus and sorting/filtering
  • The pagination for the SPGridView control is based on the datasource being bound to that. Hence, the OOTB core results web part’s output should be set to the desired size, which is really not possible, as the PageSize property of the OOTB core results web part restricts the maximum size to 50
  • he context menus for the search results should be generated by using the same mechanism how SharePoint does it for the document library / list items
 Approach
  • To implement the custom rendering format in grid, the SPGridView control is used, as it implicitly provides the following features:
    • Pagination
    • Sorting
    • Filtering
  • For the SPGridView to have its own pagination, the results being returned from the OOTB Core Results web part should be overridden with the custom value. The number of records being returned by the OOTB core results web part can be overridden by manipulating the SRHO (Search Result Hidden Object) being used by the OOTB core results web part
  • The javascript function that is responsible for generating the context menu for list items, expects few set of parameters for each item to determine the items to be generated for the context menus. These set of parameters can be explicitly rendered along with the search results
 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 });
 srhoInContext.GetType().GetMethod
(“SetQueryProperties”,
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic
).Invoke(srhoInContext, new object[] { customQuery });
 //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”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
).SetValue(srhoInContext, customResults);
 srhoInContext.GetType().GetField(
“m_totalRows”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
).SetValue(srhoInContext, relevantResultTable.TotalRows);
 srhoInContext.GetType().GetField(
“m_RowCount”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
).SetValue(srhoInContext, relevantResultTable.RowCount);
 srhoInContext.GetType().GetField(
“m_BestBetRowCount”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
).SetValue(srhoInContext, bestBetResultTable.RowCount);
 srhoInContext.GetType().GetField(
“m_HighConfidenceRowCount”,
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance
).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
listBaseType               
The base type id for the item list 
   Numeric value      
listTemplate               
The list template id for the list  
   Numeric value      
listName                   
The list name in GUID format       
   GUID               
view                       
The list’s view ID                 
   GUID               
listUrlDir                 
Relative URL for the list          
   string             
HttpPath                   
Docsf_vti_binfowssvr.dll?65001  
   string             
HttpRoot                   
Fully qualified URL for the site   
   string             
imagesPath                 
Relative URL for the images        
   string             
PortalUrl                  
                                      
                      
SendToLocationName         
                                       
                      
SendToLocationUrl          
                                      
                      
RecycleBinEnabled          
                                      
                      
OfficialFileName           
                                      
                      
WriteSecurity              
                                      
                      
SiteTitle                  
Title of the site                  
   string              
ListTitle                  
Title of the list                  
   string             
displayFormUrl             
Server relative Url for the display form.
   string             
editFormUrl                
Server relative Url for the edit form.
   string             
ctxId                      
The Id for the context object      
   string             
g_ViewIdToViewCounterMap[x]
x – replace it with list view GUID 
   string             
CurrentUserId              
Numeric notation for current user  
   Numeric            
isForceCheckout            
Is force checkout enabled          
   true/false         
EnableMinorVersions        
Is minor versions enabled          
   true/false         
verEnabled                  
Is versioning enabled              
   0 – true / 1 – false
WorkflowAssociated         
Is workflow associated             
   true/false         
ContentTypeEnabled         
Is content type enabled            
   true/false         
ctx<id>=ctx                
Above properties are associated to the contextinfo object ctx & assign it to the ctx<id> – id replace with 1, if only one context object in place, else replace <id> with appropriate number                 
                      


INPUT TYPE ATTRIBUTES
Attribute Name
Description
Value Type
CTXName                    
Name of the attribute specifies the context info object reference, with the above-mentioned properties set.
ContextInfo object
id                         
Id of the item in it’s list        
Numeric          
url                        
Relative Url of the item           
Url as string    
dref                       
Item’s file directory reference    
Url as string    
perm                       
Permission Mask of the list item   
Octal as string  
type                       
Type of the item                   
string           
ext                        
Item’s file extension              
string           
icon                       
<icon>|<client app. ProgID>|<action name>                      

    
<icon> – icon name
string
                           
<client app. ProgID> – ProgID of the item’s associated application
string
                           
<action name> – Action to be done  
string
otype
Object Type                        
string           
couid
Checked out user’s numeric id      
Numeric          
sred                       
Server redirect Url
Url as string    
cout                       
Checked Out Status
Numeric          
hcd                        
Controls if a menu item to navigate to the “/_layouts/updatecopies.aspx” will be added.  I am guessing this   has to do with records management (update copies of the document when it was changed).
                    
csrc                       
Copy source link                   
string           
ms                         
Moderation Status                  
Numeric          
ctype                      
Content Type name                   
string           
cid                        
Content Type Id                    
Octal as string  
uis                        
UI version Id                      
Numeric as string
surl                       
Source Url                          
Url as string    



// 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


Monday, September 3, 2012

SharePoint 2010 Backup and Recovery – Configuring Backup

Even before installing SharePoint 2010 it is good practice to deal with business continuity management – in other words: backup and recovery of SharePoint 2010 farms. Many things became easier in the newest release of Microsoft's composite collaboration platform, but certain manual configuration steps cannot be avoided.
This mini-series of articles attempts to give you the nuts and bolts of backup configuration and performing it, as well as dealing with how to recover the backup. Obviously, the better backup and recovery is prepared, the less time you will spend restoring a SharePoint 2010 environment. And after all, the less time you business loses, the better it is.
Initial Backup Configuration
As mentioned before, there is no way around configuring manually (or at least verifying) some initial settings. First of all, you need to distinguish on what level you want to perform backups. Depending on this level, you must have different permissions to perform backup and recovery from the Central Administration:
  • Farm, Service Applications, Content Databases: Local Administrator Group Member
  • Site collections, sites, lists, document libraries: Farm Administrator Group Member
In addition, the Timer and SQL Service account must be granted "Full Control" permissions on any local folder used for backing up. Once you have configured these settings, you may proceed to the backing up SharePoint in a continuum.
Backing Up the Farm
SharePoint 2010 offers a high level of granularity: You can separately back up any object starting from the entire farm down to single lists. It is straightforward to choose the backup components you want to save for a later recovery. On the "Backup and Restore" section of the Central Administration (CA), you will find the same granularity. If you go for a farm backup, the before mentioned requirements hold. Moreover, the following services must be running at the time you are issuing the backup command from CA:
  • Timer Service
  • SharePoint Foundation Administration Service
For backing up from farm level downwards, "Perform farm backup" is the right starting point. It offers insight on whether there are running backups or restores, and it will let you choose the different backup components, such as the entire farm, single web applications, as well as shared service applications, such as the Managed Metadata Store or the Secure Store Service.
More options can be chosen on the next page of the backup wizard: You can specify whether you want to run a full or differential backup and whether you want to back up only the configuration settings or also add the content databases. And, of course, you can also specify the backup folder.
It is good practice to perform any type of farm backup into a local folder and only afterwards transfer the backup files to a network share or remote location.  

While you can back up both content and configuration data for your farm using CA and PowerShell (PS), you can only back up the farm's content databases using the SQL Server tools. The preferred tool for this operation is the SQL Server Management Studio (SSMS).
In order to be able to back up the content databases, you must have the db_backupoperator role assigned to the user you are trying to perform the backup with. 
Backing Up Content Databases
Content databases are the heart of your SharePoint web applications. Using CA or PowerShell, you can backup entire web applications, in other words configuration settings plus content databases. Although these two methods allow for the full backup of single web applications, you can only back up the content databases using the before mentioned SSMS backup method.
If you opt to choose the CA approach, please note also that there is no way to generate a separate backup for configuration data and content databases. The generated backup files for a web application are self-contained. Again, choosing "Perform a backup" from CA is the way to go. The only option that should be changed is the selection of the desired web application and its corresponding backup folder.
The PowerShell cmdlet to perform the same task is straightforward to use: No matter on which granularity you would like to back up your farm, the cmdlet of choice is always
1Backup-SPFarm -Directory "path" -BackupMethod "Full|Differential" -Item
Backup-SPFarm comes with a set of parameters, of which the most important are:
  • Directory: Set the backup folder
  • BackupMethod: Specify whether to run a full or differential backup
  • Item: Specify the farm, web application or (shared) service application you want to backup
The most interesting parameter is Item. It lets you specify any kind of component, from the entire farm to a single web application to a shared service application. Consider Backup-SPFarm as your swiss army knife when it comes to backing up instantily using PowerShell.
You can review the status of running or finished jobs in CA, in the section "Backup and Restore" | "Check backup and restore status".
The report gives you an insight of the current jobs, including the time, result, the person who started the job and a finish date for each backup component (in case it didn't fail). This is a great way to see at a glance where the backup failed. If one component cannot be backed up, the overview will show a corresponding error message, along with the details. This helps to easily identify backup or restore problems and track them to their root.

There is another way in order to get detailed insight into the backup status and check for detailed error messages and exceptions. In the directory you specify during the CA or PS setup of the backup configuration, at the end of the backup/restore operation you will find either spbackup.log or sprestore.log.
Backing Up Site Collection, Lists and Document Libraries
SharePoint offers also the possibility to back up entire site collections, lists and documents. These options can be found in CA, specifically in the section "Granular Backup". There are only a couple of mouse clicks that need to be performed in order to export an entire site collection: You simply select the site collection you want to export and specify the backup folder.
Requirements for this step include a running SharePoint Timer Service as well as there must not any backup be running already. The site collection will then be available on the file system as a single backup file, in the directory you specified for the backup. The preferred file extension is .bak.
While backing up on farm level, for different granularity you had to use always the Backup-SPFarm PowerShell cmdlet, for the granular backup it is a bit different. There are different cmdlets for backing up site collections and lists or document libraries. Backing up a site collection using PowerShell works using the following cmdlet and corresponding parameters:
Backup-SPSite -Identity "Site Collection Name"-Path "path" [-UseSqlSnapshot] [-NoSiteLock]
The Identity and Path parameters are straight forward. UseSqlSnapshot will create a read-only view of the current content database and use this one for restoring the content database. NoSiteLock causes the standard behaviour of setting the site collection to read only not to take effect. In other words: Your site collection will still be writable, i.e. modifiable by users. As you can imagine, this could possibly cause the currently backed up site collection not to be up-to-date at the end of the backup procedure.
Similarly, you can back up single sites, lists or document libraries:
Export-SPWeb -Identity "Site/List/Library name" -Path "path"
[-IncludeUserSecurity] [-GradualDelete] [-IncludeVersions]
The Export-SPWeb cmdlet is the backup command with the most granularity. Using the IncludeUserSecurity parameter you can include full permissions of the list/library. GradualDelete is especially recommended for larger sites or lists. It will cause the site or list to be gradually deleted and prevents further access to it.
It is a very good strategy to use it in order to minimize the impact of content deletion on SharePoint and the underlying SQL server. If you want to remove a site or list and archive it, then Export-SPWeb -GradualDelete -IncludeUserSecurity -IncludeVersions is the command of choice.
Backing Up Log Files
Log files are good and important to have. But sometimes they are just loitering on different drives and growing very large. Hence, it makes sense to merge them for backup purposes and save them away as a single file. This can explicitly be done using PowerShell:
Merge-SPLogFile -Path"path" -Overwrite
This command will merge all diagnostic log files into a single log file (in the location you specify). "All" in this case means all diagnostic log files from each server in the farm – hence this operation usually takes a notable amount of time.
In addition, using PowerShell it is also possible to copy single servers' diagnostic log files into a specified archive folder, using the standard Copy-Item cmdlet.
The default location for the Collect Usage Data Files is on the same partition where SharePoint is installed. Since they can grow very large, it is recommended to store them to another driver in order to achieve better performance.
Conclusion
SharePoint Server 2010 offers a lot of functionality for backing up using different granularity levels. Whether you want to use Central Administration (preferrable for manual backups) or PowerShell, depens completely on your preferences: Most of the times all the operations are available using either approach.
In this first part of the Backup and Restore Introduction we introduced the initial configuration and setup of SharePoint 2010 backups. Moreover, we had a look at different backup methods using Central Administration, PowerShell or SQL Server tools and concluded this first part with backing up diagnostic and usage logs.