Friday, December 5, 2008

Content Query Web Part (CQWP) and Audience feature – The Bug & work around

Content Query Web Part (CQWP), the much used web part in SharePoint supports feature called Audience. This isnt the audience feature supported by WebPart framework, where in a web part is shown or hidden, but CQWP can apply audience rules for content it displays. However the bug in the product is in the order these rules get applied.

CQWP & Audiencing – The Bug:
CQWP supports filtering data based on user's audience groups. E.g If you configure web part to fetch items from /mysite/pages list, it would apply audience rules and fetch you pages belonging to audience groups you are part of ( any one of it). This works in conjunction with other features of the web part

a) Query: User can select queries such as Title contains 'xyz'
b) Sort Order
c) Item Limit

However the order it applies is the cause of concern. As you would see in the image, audience rule gets set after ItemLimit rule is applied.









The impact is evident. An user can see anywhere between 0 to ItemLimit items based on his audience, though there could be lots of relevant items. Ideally the Audience rule should be set prior to Item Limit is applied.
We encountered this issue in our project and user experience was frustrating. Author configures a page to display Top 5 news items and actual news items displayed in CQWP varied anywhere between 0 to 5, though there were enough news items for all audience.
Solution:
Embarking on journey of workaround ( Sharepoint offers lot such opportunities) we considered following options
1) Can I influence this in the xslt? Answer is NO. The xslt gets called towards the end only for rendering purpose and this made sense
2) No other choice except to extend the web part to our needs. Below is the code snippet which helped to acheive this
public class AudienceContentByQueryWebPart: ContentByQueryWebPart
{
int userSelectedItemLimit ;
protected override void OnInit(EventArgs e)
{
this.ProcessDataDelegate += new ProcessData(ProcessData);
base.OnInit(e);
}
protected override void CreateChildControls()
{
//Store user selected ItemLimit and set it to -1. This is equivalent to user not setting any item limit
userSelectedItemLimit = this.ItemLimit;
this.ItemLimit = -1;

base.CreateChildControls();
}

// ProcessData is called after all rules are applied, including audience rule and before
// xslt is called.
private new DataTable ProcessData(DataTable dt)
{
// Reset the itemlimit to whatever user selected
this.ItemLimit = userSelectedItemLimit;


if (userSelectedItemLimit > 0)
{
//If number of rows greater than than the user given item limit
while (dt.Rows.Count > userSelectedItemLimit)
{
//Removing the rest of the rows
dt.Rows.RemoveAt(dt.Rows.Count - 1);
}
}
return dt;
}
}
Idea is to defer the ItemLimit rule after audiencing rule is applied. Steps were

1. Set ItemLimit = -1, forcing CQWP to avoid applying item limit. This ensures all results eligible for users, after applying audience rules are available.

2. CQWP allows you to hookup your event delegate called 'ProcessData' which gets called just before the xslt is applied. In ProcessData delegate, manually trim down the results to the user set ItemLimit.

Now all users would see the itemlimit number of items as long as enough data exists.
Ideally I would expect the webpart to apply audience rules along with filter queries. This would ensure faster performance. Hopefully Microsoft addresses this in future releases.

Thursday, December 4, 2008

Ouput Cache - Blob Cache fails when there are 10k+ items with custom permissions

Whenever you site has more that 10,000+ items which have custom permissions - meaning they dont inherit from parent site, your output caching would break. Following would be the log entry in event viewer. When blob cache is enabled images wont appear properly in the page and would exhibit random behavior.

[COMException (0x80004005): Cannot complete this action.Please try again.] Microsoft.SharePoint.Library.SPRequestInternalClass.GetAllAclsForCurrentSite(String bstrWebUrl, Int32 lMaxAcls) +0 Microsoft.SharePoint.Library.SPRequest.GetAllAclsForCurrentSite(String bstrWebUrl, Int32 lMaxAcls) +210[SPException: Cannot complete this action.Please try again.] Microsoft.SharePoint.Library.SPRequest.GetAllAclsForCurrentSite(String bstrWebUrl, Int32 lMaxAcls) +379 Microsoft.SharePoint.SPReusableAcl.GetAllReusableAclsForSite(SPSite site, Int32 maxAclsToReturn) +1185 Microsoft.SharePoint.SPSite.GetAllReusableAcls() +46 Microsoft.SharePoint.Publishing.<>c__DisplayClass3.b__0() +185 Microsoft.SharePoint.SPSecurity.CodeToRunElevatedWrapper(Object state) +73 Microsoft.SharePoint.<>c__DisplayClass4.b__2() +599 Microsoft.SharePoint.Utilities.SecurityContext.RunAsProcess(CodeToRunElevated secureCode) +309 Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(WaitCallback secureCode, Object param) +583 Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(CodeToRunElevated secureCode) +146 Microsoft.SharePoint.Publishing.BlobCache.EnsureAuthenticatedRights(Guid siteID) +593 Microsoft.SharePoint.Publishing.BlobCache.RewriteUrl(Object sender, EventArgs e) +3745 System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +177

Unfortunately no solution for this. Be watchful when working with item level permissions and avoid breaking the inheritance.