Extend Content Query Web Part via Code in SharePoint 2010

I'm trying to understand the mechanism to extend the cqwp via code.

It may be hard to believe but I couldn't find a single article to create a web part inherited from content by query web part.

What I need to do, is to type the listname in the web part properties. Then all the groupings, sorting and the query will be implemented via code, that is in the extended web part.

I've read waldek's posts but they are a bit advanced to use as a cheat sheet.

Msdn's samples show the customization of itemstyle and setting queryoverride over the webpart properties toolbar. I need to set it via code.

Note:If that's not the way to customize cqwp, let me know. My purpose is to put the wp in the masterpage and set the listname and wait for the results to show(:

I've tried to set the listguid and queryoverride via code through OnInit and ModifyXsltArgument methods seperately. Nothing returned, and when I export the wp, the listguid and queryoverride seems not set.

Solution:

To inherit the ContentQueryWebPart just do this:

using System; using System.ComponentModel; using Microsoft.SharePoint.Publishing.WebControls; using Microsoft.SharePoint; using Microsoft.Office.Server.UserProfiles;  namespace YOURNAMESPACE {     [ToolboxItemAttribute(false)]     public class CustomContentQueryWebPart : ContentByQueryWebPart     {         protected override void OnLoad(EventArgs e)         {             try             {                 //Reemplazamos [UserContext:<field>] por su valor                 string val, field;                 UserProfile userProfile = getCurrentUserProfile();                  val = this.FilterValue1;                 if (val.StartsWith("[UserContext:") && val.EndsWith("]"))                 {                     field = val.Substring(13, val.Length - 14);                     this.FilterValue1 = userProfile[field].Value.ToString();                 }                  val = this.FilterValue2;                 if (val.StartsWith("[UserContext:") && val.EndsWith("]"))                 {                     field = val.Substring(13, val.Length - 14);                     this.FilterValue2 = userProfile[field].Value.ToString();                 }                  val = this.FilterValue3;                 if (val.StartsWith("[UserContext:") && val.EndsWith("]"))                 {                     field = val.Substring(13, val.Length - 14);                     this.FilterValue3 = userProfile[field].Value.ToString();                 }             }             catch (Exception ex) { }             finally             {                 base.OnLoad(e);             }         }          private UserProfile getCurrentUserProfile()         {             SPUser user = SPContext.Current.Web.CurrentUser;             //Create a new UserProfileManager             UserProfileManager pManager = new UserProfileManager();             //Get the User Profile for the current user             return pManager.GetUserProfile(user.LoginName);         }     } } 

In this example, I just added a filter to get a field from UserProfile like the original webpart does with querystring. What do you need exactly?



Treeview Control in Sharepoint 2010

In this post you will learn how to Programmatically use the Treeview control in Sharepoint 2010.

At First, we will create an application page in VS 2010 to contain a drop-down list bind to all the Navigation Providers in SharePoint 2010 and a TreeView Control to display the selected navigation providers in a Treeview hierarchy. Lets follow the Steps -

Steps -

1. Create a new Application Page in Vs 2010.
2. Add the below code to the aspx of the application page

<%@ Page Language="C#" AutoEventWireup="true" DynamicMasterPageFile="~masterurl/default.master"
CodeFile="NavigationProviders.aspx.cs" Inherits="NavigationProviders" CodeFileBaseClass="Microsoft.SharePoint.WebControls.LayoutsPageBase" %>

<asp:Content ContentPlaceHolderId="PlaceHolderMain" runat="server">
<asp:DropDownList id="ddlNavProviders" runat="server" AutoPostBack="True" OnSelectedIndexChanged="ddlNavProviders_SelectedIndexChanged" />
<asp:TreeView id="navTreeView" runat="server"></asp:TreeView>
< /asp:Content>

3. Add the Code behind – The code-behind class first initializes DropDownList with all the navigation providers defined in web.config. By selecting a navigation provider, the SiteMapDataSource pointing to the selected provider is bound to the TreeView.

using System;
using System.Web;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
public partial class NavigationProviders : LayoutsPageBase
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Init the DropDown element with all available navigation providers
ddlNavProviders.DataSource = SiteMap.Providers;
ddlNavProviders.DataTextField = "Name";
ddlNavProviders.DataBind();
}
}

protected void ddlNavProviders_SelectedIndexChanged(object sender,EventArgs args)
{
// Bind the selected navigation provider to the TreeView
SiteMapDataSource ds = new SiteMapDataSource();
ds.Provider = SiteMap.Providers[ddlNavProviders.SelectedItem.Text];
navTreeView.DataSource = ds;
navTreeView.DataBind();
}
}





Subclassing Content Query Web Part to override CAML query

The Content Query Web Part (CQWP) in MOSS is one of the popular out-of-the-box Publishing components which is used in content management sites, allowing you to get results from different sources.

But sometimes out of the box functionality is not enough to meet your requirements, and you need to customize CQWP. There are several resources describing how to do this - 1, 2, 3, 4

The most important features which are missed in CQWP are:

  • overriding query to use custom CAML
  • enabling web-part connections

To implement these functionality you need to subclass ContentByQueryWebPart class and override several methods.

Below I will describe what exactly need to be done to achieve desired behaviour.

Query Override

To have custom CAML query in your Web part you need to override CreateChildControls base method. You can find samples how to do this in the references above, but it's done so implicitly, that you can easily spend hours to find out why you code doesn't work and gives you different errors

The crucial part is in the way sending the query to the base class within overrided CreateChildControls

   1: protected override void CreateChildControls()
   2: {
   3:     this.QueryOverride = customQueryString;
   4:  
   5:     base.CreateChildControls();
   6:  
   7:     QueryOverride = string.Empty;
   8:  
   9:     CommonViewFields = string.Empty;
  10: }

First, you need to set your custom query to the "this.QueryOverride" to inform base class about query - line 3

Second, call base CreateChildControls() allowing your query being executed - line 5

Third and last - CLEAN query and view fields properties - line 7,9. Without this step you will get errors when open your Web Part in edit mode

Enabling Connection

The Content Query WP doesn't accept any connections by default. You need to implement this too. Everything you need to to is just add ConnectionConsumer attribute. But trick in setting the right attribute which is used in Parameter dialog box, when you connect two web-parts.

   1: [ConnectionConsumer("Another WebPart", "IFilterValues", AllowsMultipleConnections = true)]
   2: public void SetConnectionInterface(IFilterValues filterProvider)
   3: {
   4:     if (filterProvider != null)
   5:     {
   6:         // save provider with values
   7:         providerValues = filterProvider.ParameterValues;
   8:         _filterProviders.Add(filterProvider);  // variable declaration is List<IFilterValues> _filterProviders = new List<IFilterValues>();
   9:         List<ConsumerParameter> parameters = new List<ConsumerParameter>();
  10:         
  11:         // add params
  12:         parameters.Add(new ConsumerParameter("param1",
  13:                 ConsumerParameterCapabilities.SupportsSingleValue |
  14:                 ConsumerParameterCapabilities.SupportsEmptyValue));
  15:         parameters.Add(new ConsumerParameter("param2",
  16:                 ConsumerParameterCapabilities.SupportsMultipleValues |
  17:                 ConsumerParameterCapabilities.SupportsAllValue |
  18:                 ConsumerParameterCapabilities.SupportsEmptyValue));
  19:  
  20:         filterProvider.SetConsumerParameters(new ReadOnlyCollection<ConsumerParameter>(parameters));
  21:  
  22:         }
  23: }

Method name can be any, but pay attention which provider you are using . WebPart providers send data via different provider interfaces, for example when you are working with Filtering web parts (like "Current User Filter" web part) then you need to "listen" your incoming connections via IFilterValues (thx Mutaz for this findings), for other WB it could be IWebPartField or IWebPartRow.

If you are expecting to get data from WP via unsupported provider you connection link will be dimmed

Practical Sample

@d2design kindly asked to provide practical sample, for example how to use current user as a keyword :)

Actually, it's good example which shows how use both features I described.

To do this you need to have:

  1. ShareServices (SSP) with UserProfile, where current userName is stored
  2. "Current User Filter" WebPart, to get the userName from SSP. It returns you logged user name by default (not necessary actually, as well as SSP, because you could resolve you user name via standard asp.net User class when construct custom query)
  3. Enable WebPart connections as I described above. You need to use IFilterValue interface for "Current User Filter" and store your incoming user name in variable.
  4. Override query to include your userName in resulted custom query.

TIPS:

  1. When you are quering content which is not published and approved (like pages) you wont be able to see that content on the page in final view. You data will be available in page edit mode only, because they are treated as "draft" data.
  2. <OrderBy>CAML tag is not parsed by CQWP, you need to remove it from your query and set the related order field of CQWP class




poor performance on people picker search in SharePoint 2010

Consider this scenario:

You go to the people picker to search for let's say: "User42". You wait for about more than 3.5 min. until the results are displayed.
You'd now check if there is a general problem and trying it again with a simple repro on the file system as follows

- chose any folder on your hard disk, right-click and chose "properties".
- "add" the wanted User as you would like to do it on granting permissions to this folder.
- Note, that this is taking less than a second(!) to resolve and adding the named User, how this?

So on setting the ULS logging to "verbose" level and retry the peoplepicker search, we will find some interesting hints like this in our logs:

[…]
01.31.2011 15:50:13.98 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq2 Verbose SearchFromGC name = corp.lan. returned. Result count = 0 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:50:13.98 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq1 Verbose SearchFromGC name = contoso.com. start 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:50:30.12 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq3 Verbose SearchFromGC name = contoso.com. Error Message: A local error has occurred. 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:50:30.12 w3wp.exe (0x1010) 0x163C SharePoint Foundation General 7fbh Verbose Exception when search "user42" from domain "contoso.com". Exception: "A local error has occurred. ", StackTrace: " at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) at […]

01.31.2011 15:50:30.12 w3wp.exe (0x1010) 0x163C SharePoint Foundation General 72e7 Medium Error in searching user 'user42' : System.DirectoryServices.DirectoryServicesCOMException (0x8007203B): A local error has occurred. at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) at […]

01.31.2011 15:50:30.12 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq1 Verbose SearchFromGC name = de-corpx.com. start 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:12.95 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq3 Verbose SearchFromGC name = de-corpx.com. Error Message: The server is not operational. 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:12.95 w3wp.exe (0x1010) 0x163C SharePoint Foundation General 7fbh Verbose Exception when search "user42" from domain "de-corpx.com". Exception: "The server is not operational. ", StackTrace: " at […]

01.31.2011 15:51:12.95 w3wp.exe (0x1010) 0x163C SharePoint Foundation General 72e7 Medium Error in searching user 'user42' : System.Runtime.InteropServices.COMException (0x8007203A): The server is not operational. at […]

01.31.2011 15:51:13.08 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq2 Verbose SearchFromGC name = my-group.biz. returned. Result count = 0 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.08 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq1 Verbose SearchFromGC name = org-it.biz. start 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.27 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq4 Verbose GetAccountNameFromSid "0x0105000000000005150000008AA7323F23F3F66375B9755494000400" start 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.28 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq5 Verbose GetAccountNameFromSid "0x0105000000000005150000008AA7323F23F3F66375B9755494000400" returned. returnValue=True 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.28 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq2 Verbose SearchFromGC name = org-it.biz. returned. Result count = 1 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.28 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq1 Verbose SearchFromGC name = xx-ext.biz. start 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.52 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq2 Verbose SearchFromGC name = xx-ext.biz. returned. Result count = 0 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:13.52 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq1 Verbose SearchFromGC name = ap-lan.biz. start 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:51:56.33 w3wp.exe (0x1010) 0x163C SharePoint Foundation Performance ftq3 Verbose SearchFromGC name = ap-lan.biz. Error Message: The server is not operational. 21e10f56-2f45-4a29-a53c-4fda5da9f117
01.31.2011 15:53:15.22 w3wp.exe (0x1010) 0x163C SharePoint Foundation General 72e7 Medium Error in searching user 'user42' : System.Runtime.InteropServices.COMException (0x8007203A): The server is not operational. at
[…]


CAUSE:

For any given search string, i.e. "User42" (which is NOT typed in as "Domain\username" or as UPN "user42@mydomain.com") the Query fetches the account details (SearchFromGC) for the user.

The GetAccountName() function is then used to convert the SID returned by the LDAP query.
The GetAccountName() results in LSASS calling LsarLookupSids3 (when using People Picker) OR both LsaLookupNames4 + LsarLookupSids3 (when using "Check Names").

So we see that we do get the result back from LDAP with the result set and then we use that result set's SID to get the account name in the format DOMAIN\USERLOGIN. The LDAP resultset has this information in the LDAP format, but not in the expected format for SharePoint. This is why we call GetAccountName() to resolve the SID into the Account name.

This process takes a long time and impacts the performance for People Picker / CheckNames function as well as in addition waiting for each timeout on not reachable DC's.

So by using "Isolated Account Names" on peoplepicker search, performance decreases as the number of trusted domains increases…
See more on http://support.microsoft.com/kb/818024

RESOLUTION/WORKAROUND:

Use the stsadm commands for setting the properties to be limited on a particular Domain (where the user lives) and the specific Domain under your Forest on a multi trusted AD environment.

stsadm -o setproperty -url http://<WebAppName> -pn peoplepicker-distributionlistsearchdomains -pv <domainname>

stsadm –o setproperty –pn peoplepicker-searchadforests –pv domain:<domainname> -url http://<WebAppName>

Note Note:

By default, SharePoint talks to the domain controller for the domain in which SharePoint was installed and all trusted domains for two-way trusted domains.

Remarks:

The above commands will enable a limited search against a dedicated domain where the wanted user account resides.
So when having user accounts from other domains in addition, these domains must be also set according to the above command for each needed domain name.
This setting is a per web application setting as defined by the -url parameter and must be also repeated for each web application further.
So by design, SharePoint will behave as of the above description but on forcing only to use the pure ldap results and defining the requested domain explicitly,
we can significant increase the performance on people picker search results near to less than 2 seconds!

If you're having a "one-way-trust", then you need to run additionally this command first:
stsadm –o setapppassword -password <SomeKey>

see more details here: http://technet.microsoft.com/en-us/library/cc263460(office.12).aspx