We will now create a custom Web Part that can provide dynamic content rollups across the different DocVault site collections. There are two views available in the initial Web Part, but additional ones can be identified as needed by extending the code and defined QueryMode
property.
The DocVault Listings Web Part will be added to the previously created SPBlueprints.WebParts
project created in Chapter 2, Building an Out of Office Delegation Solution.
To add the additional Web Part:
SPBlueprints.WebParts
project in Visual Studio 2010. DocVaultListings
and click on the Add button. DocVaultListings.webpart
file with the following definition:<properties> <Property Name="Group" type="string">SPBlueprints</Property> <property name="Title" type="string">DocVault Listings</property> <property name="Description" type="string">SPBlueprints - The DocVault Rollup web part displays content rollups for DocVault documents.</property> <property name="SearchProxyName" type="string">Search Service Application</property> <property name="SearchScopeName" type="string">DocVault</property> <property name="DisplayLimit" type="int">15</property> </properties>
Start by editing the DocVaultListings.cs
file and add in the following references:
using System.Collections; using System.Data; using System.Text; using Microsoft.SharePoint.Administration; using Microsoft.Office.Server.Search; using Microsoft.Office.Server.Search.Query; using Microsoft.Office.Server.Search.Administration;
Next we will need to define the Web Part's properties starting with the Search Proxy Name property. This property will be used to manage the connection to the Search service application.
private string searchProxyName; [WebBrowsable(true), Category("Custom Properties"), WebDisplayName("Search Proxy Name"), WebDescription("Please provide the name of your Search Service Application."), Personalizable(PersonalizationScope.Shared)] public string SearchProxyName { get { return searchProxyName; } set { searchProxyName = value; } }
Next we will define the Search Scope Name property which can be used to target the desirable content for display.
private string searchScopeName; [WebBrowsable(true), Category("Custom Properties"), WebDisplayName("Search Proxy Name"), WebDescription("Please provide the name of your Search Service Application."), Personalizable(PersonalizationScope.Shared)] public string SearchProxyName { get { return searchProxyName; } set { searchProxyName = value; } }
Next we will define the Display Limit property used to determine how many records to display.
private int displayLimit; [WebBrowsable(true), Category("Custom Properties"), WebDisplayName("Result limit"), WebDescription("The number of items to display."), Personalizable(PersonalizationScope.Shared)] public int DisplayLimit { get { return displayLimit; } set { displayLimit = value; } }
In order to provide multiple views, we will add an enum
property that will display as a drop-down list from within the Web Part properties page. This requires that we define and set a value so that it can be used within the Web Part.
private queryMode _queryMode; public enum queryMode { ByUser, Recent } [WebBrowsable(true), Category("Custom Properties"), WebDisplayName("Query Mode"), WebDescription("Please select the query mode."), Personalizable(PersonalizationScope.Shared)] public queryMode QueryMode { get { return _queryMode; } set { _queryMode = value; } } public DocVaultListings() { _queryMode = queryMode.ByUser; }
The output will be built within a literal control defined within the class, and instantiated within the CreateChildControls()
method shown as follows:
protected Literal _output; protected override void CreateChildControls() { this._output = new Literal(); this._output.ID = "output"; this.Controls.Add(this._output); }
With all of the setup work complete, we can now define the Display()
method that can be called from the OnLoad()
method. The method starts by defining the StringBuilder
class, which we will use to build the output of the Web Part, and then attempts to connect to the Search Proxy specified in the Web Part properties.
protected void Display() { StringBuilder messages = new StringBuilder(); try { SearchQueryAndSiteSettingsServiceProxy settingsProxy = SPFarm.Local.ServiceProxies.GetValue<SearchQueryAndSiteSettingsSer viceProxy>(); SearchServiceApplicationProxy searchProxy = settingsProxy.ApplicationProxies.GetValue<SearchServiceApplication Proxy>(this.searchProxyName); FullTextSqlQuery mQuery = new FullTextSqlQuery(searchProxy);
Next we will do some preparation work that is common to each of the views.
try { ResultTableCollection resultsTableCollection; DataTable results = new DataTable(); bool bAltRow = true;
The multiple views of the Web Part are handled by a central switch
statement that will check the _queryMode
value and display the appropriate view.
switch (_queryMode){ case queryMode.ByUser: break; case queryMode.Recent: break; }
Within the ByUser
view, we will construct and execute a query and then format the returned results. The FullTextSQLQuery
will grab the desired properties from the specified search scope and filter them based on content generated by the current user. The Display Limit Web Part property will be used to limit the number of results returned. The included output will present the returned data similar to a normal list view, though the complex view functions have not been included.
string user = SPContext.Current.Web.CurrentUser.Name; mQuery.QueryText = "SELECT Title, Customer, CustomerAccount, LastModifiedTime, DocID, SiteTitle, ContentTypeSearch, CreatedBy, Filename, FileExtension, Path FROM SCOPE() WHERE ("scope" = '" + searchScopeName + "') AND Author = '" + user + "' ORDER BY LastModifiedTime Desc"; mQuery.ResultTypes = ResultType.RelevantResults; mQuery.TrimDuplicates = false; mQuery.RowLimit = DisplayLimit; resultsTableCollection = mQuery.Execute(); if (resultsTableCollection.Count > 0) { ResultTable relevantResults = resultsTableCollection[ResultType.RelevantResults]; results.Load(relevantResults, LoadOption.OverwriteChanges); messages.AppendFormat(@"<table width='100%' border='0' cellpadding='1' cellspacing='0' class='ms-listviewtable'> <tr class='ms-viewheadertr ms-vhltr'> <td>Type</td><td>Site</td><td>Customer</td> <td>Account</td><td>DocumentID</td> <td>Name</td><td>Modified</td></tr>"); foreach (DataRow row in results.Rows) { messages.AppendFormat(@"<tr "); if (bAltRow) { messages.AppendFormat(@"class='ms-alternatingstrong'"); } messages.AppendFormat(@"><td><a href='{6}'><img src='{7}' border='0'></a></td><td>{0}</td><td>{1}</td><td>{2}</td> <td>{3}</td><td><a href='{6}'>{4}</a></td><td>{5}</td></tr>", row[5].ToString(), row[1].ToString(), row[2].ToString(), row[4].ToString(), row[0].ToString(), row[3].ToString(), row[10].ToString(), getImageRef(row[9].ToString())); bAltRow = !bAltRow; } messages.AppendFormat(@"</table>"); }
Within the Recent
view, we will construct and execute a similar query as the ByUser
, but this query will look for documents recently edited by any user. It will also search against the specified search scope and limit the results to the Display Limit specified in the Web Part property.
mQuery.QueryText = "SELECT Title, Customer, CustomerAccount, LastModifiedTime, DocID, SiteTitle, ContentTypeSearch, CreatedBy, Filename, FileExtension, Path FROM SCOPE() WHERE ("scope" = '" + searchScopeName + "') AND LastModifiedTime >= DATEADD (DAY, -30, GETGMTDATE()) ORDER BY LastModifiedTime Desc"; mQuery.ResultTypes = ResultType.RelevantResults; mQuery.TrimDuplicates = false; mQuery.RowLimit = DisplayLimit; resultsTableCollection = mQuery.Execute(); if (resultsTableCollection.Count > 0) { ResultTable relevantResults = resultsTableCollection[ResultType.RelevantResults]; results.Load(relevantResults, LoadOption.OverwriteChanges); messages.AppendFormat(@"<table width='100%' border='0' cellpadding='1' cellspacing='0' class='ms-listviewtable'> <tr class='ms-viewheadertr msvhltr'> <td>Type</td><td>Site</td><td>Customer</td><td>Account</td> <td>Document ID</td><td>Name</td><td>Modified</td></tr>"); foreach (DataRow row in results.Rows) { messages.AppendFormat(@"<tr "); if (bAltRow) { messages.AppendFormat(@"class='ms-alternatingstrong'"); } messages.AppendFormat(@"><td><a href='{6}'><img src='{7}' border='0'></a></td><td>{0}</td><td>{1}</td><td>{2}</td> <td>{3}</td><td><a href='{6}'>{4}</a></td><td>{5}</td></tr>", row[5].ToString(), row[1].ToString(), row[2].ToString(), row[4].ToString(), row[0].ToString(), row[3].ToString(), row[10].ToString(), getImageRef(row[9].ToString())); bAltRow = !bAltRow; } messages.AppendFormat(@"</table>");
To complete the Display()
method, we will set the Text
property of the literal control named _output
to the StringBuilder
object message that we have been building. There is some additional error handling and object disposal included as part of the overall code flow.
this.EnsureChildControls(); this._output.Text = messages.ToString(); } catch (Exception ex) { this.EnsureChildControls(); this._output.Text = "Error: " + ex.Message.ToString(); } finally { mQuery.Dispose(); } } catch { this.EnsureChildControls(); this._output.Text = "Error: Please specify a Search Service Application."; } }
The views also reference a simple method that will display an appropriate document icon for the specified file extension.
private string getImageRef(string extension) { string image = ""; switch (extension){ case "DOCX": image = "/_layouts/images/icdocx.gif"; break; case "XLSX": image = "/_layouts/images/icdocx.gif"; break; case "PDF": image = "/_layouts/images/ICLOG.GIF"; break; default: image = "/_layouts/images/ICLOG.GIF"; break; } return image; }
3.144.154.208