Wednesday, September 22, 2010

WebProvisioned event receiver – a practical example

The WebProvisioned web event receiver is a new receiver type introduced in SharePoint 2010. It is one very useful addition to the already extensive suite of event receiver types in SharePoint, as it allows you to add some additional logic when a sub-site in your site collection gets created. In this sense it resembles to some extent the feature stapling functionality, but it differs from it because its scope is the site collection level whereas the feature stapling affects the whole farm (I personally dislike farm wide impact like this since you generally/potentially break all other solutions that may have been installed in the same farm). [updated 2010-12-29]

Creating and deploying a WebProvisioned event receiver is also a relatively simple task as you have a dedicated project item in Visual Studio 2010 – it basically supports all available receiver types, including the WebProvisioned one. With a couple of clicks you will be able to see an elements file for your WebProvisioned receiver that will look something like this:

<?xml version="1.0" encoding="utf-8"?>

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <Receivers >

    <Receiver>

      <Name>EventReceiver1WebProvisioned</Name>

      <Type>WebProvisioned</Type>

      <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>

      <Class>Stefan.SharePoint.EventReceiver1.EventReceiver1</Class>

      <SequenceNumber>10000</SequenceNumber>

    </Receiver>

  </Receivers>

</Elements>

the event receiver element definition will come with a new feature definition if you don’t have already one in your SharePoint project. Basically this is enough to have your new event receiver provisioned, but you may further consider two more important options: the first one is the Synchronization of the event receiver – you can specify either “synchronous” or “asynchronous” here (the “asynchronous” is the default one):

<Synchronization>Synchronous</Synchronization>

The Synchronization element should be placed below the “Receiver” element. Normally, I will opt for the synchronous type – this will be justified especially in cases where you will create your sites with the SharePoint UI and will expect to see immediately the results of the receiver when the home page of the site loads. Another thing is that with asynchronous receivers you have the risk of having save conflicts and concurrency issues especially when you create many sites simultaneously which is the case when you use a portal site definition.

The second option is the Scope option – you can specify it with the “Scope” attribute of the “Receivers” element – you can set it to either “Site” or “Web”:

<Receivers Scope="Site">

The scope determines whether the event receiver will be added to the SPSite.EventReceivers or SPWeb.EventReceivers collections. And there is a substantial difference in the behavior and application of the receiver depending on whether it is added to the SPSite or SPWeb level: in the first case it will be called for every sub-site created in the site collection, while in the second case, the receiver will be called only for the sub-sites that are immediate children of the site in whose EventReceivers collection the receiver is added. One other interesting “feature” (a rather peculiar one) is that when I tested my sample “Site” scoped WebProvisioned receiver it got called twice for one and the same site (no idea yet if this is an issue with my dev environment only or it is something by design). This is not the case for “Web” scoped WebProvisioned receivers.

Another important thing with the receiver’s scope is that the “Site” scope will be applied only if the activating feature also has “Site” scope (site collection scoped). If the feature has “Web” scope, the “Scope” attribute of the “Receivers” element will be ignored and the receiver will be added to the SPWeb.EventReceivers collection.

General purpose WebProvisioned event receiver

And now let’s have a look at one practical example - the general purpose WebProvisioned event receiver that I created (download source code from here). So, first let me say several words about the real life issues that I thought could be addressed with a WebProvisioned receiver – one of these is the ability of sub-sites to inherit certain settings from their parent site – for example the site default master page or the alternate CSS URL. This functionality is available for sub-sites based on the standard publishing site templates but that’s not the case for the non-publishing site definitions (like team site, blog site, wiki site, etc). This issue can be addressed with custom feature/features but it is not handy to create new site definitions that extend the standard ones only to add this extra functionality. Feature stapling is also an alternative in this case but it affects the whole farm which is definitely not a thing that I want to have in my SP installation [updated 2010-12-29]. And apart from the web settings we also have the settings managing the site navigation that are also suitable for inheriting – apart from the option to inherit the top navigation provided in the standard SharePoint “create site” page (and you can create your sites or site hierarchies with custom tools or scripting where this option is obviously not available).

One other important thing that I wanted to have in my WebProvisioned event receiver was that it should be fully configurable. And that this configurability is achieved via feature properties – because the event receiver will be naturally provisioned by a feature which will support a set of feature properties that will determine the behavior of the event receiver.

So, let me directly start with an example of the custom configuration feature properties so that you can get an idea of the type of functionality that this custom WebProvisioned receiver provides:

<Properties>

  <Property Key="ReceiverScope" Value="Site" />

  <Property Key="Web.MasterUrl" Value="~SiteCollection/_catalogs/masterpage/v4.master" />

  <Property Key="Web.AlternateCssUrl" Value="/_layouts/styles/mydefault.css" />

  <Property Key="CustomProperty.Test" Value="Some value" />

  <Property Key="PublishingWeb.IncludeInCurrentNavigation" Value="true" />

  <Property Key="Navigation.GlobalIncludePages" Value="true" />

  <Property Key="Navigation.GlobalIncludeSubSites" Value="true" />

</Properties>

The first feature property – “ReceiverScope” – as its name suggests, determines whether the event receiver should be added to the SPSite or SPWeb EventReceivers collection respectively (you can use either “Site” or “Web” in its value, exactly the same as in the “Scope” attribute from the event receiver sample element above). And as you can probably figure this out already – the configurability of the receiver’s scope with a feature property means that you can’t use declarative CAML in a “Receiver” feature element but rather use code in a feature receiver that should create the WebProvisioned event receiver. You will see that in the sample code I simply commented out the CAML in the event receiver’s element file and added a feature receiver to the original receiver’s feature (as both were created by Visual Studio 2010) that creates everything with code. The code of the feature receiver works for both “Site” and “Web” scoped feature so you don’t have to couple the “Site” web event receiver’s scope with a site collection scoped feature as it is the case with the “Receiver” feature element.

The rest of the feature properties determine the concrete behavior of the WebProvisioned event receiver – as you see, their keys follow a specific naming convention – they consist of two parts separated by a dot. The first part can contain the following predefined values: Web, CustomProperty, PublishingWeb and Navigation. They correspond to the target instance that the receiver will modify – SPWeb, SPWeb.AllProperties, PublishingWeb and PublishingWeb.Navigation respectively (SPWeb instance in this case is the SPWeb of the newly created (provisioned) web that the WebProvisioned receiver is invoked for and the PublishingWeb is the object retrieved from this SPWeb instance using the PublishingWeb.GetPublishingWeb static method). The second part of the key specifies the name of a public instance property of the target class in the case of the SPWeb, PublishingWeb and PortalNavigation (PublishingWeb.Navigation) target objects and a key in the Hashtable instance in the case of the SPWeb.AllProperties target object. The properties of the SPWeb, PublishingWeb and PortalNavigation classes can be only of the following .NET types – System.String, primitive .NET types (boolean, integer, double, etc) or .NET enumerations, properties of other .NET types are not supported (meaning that you can modify only properties from the above mentioned types in the target instances with the general purpose WebProvisioned event receiver).

So, the feature properties from this first sample set fixed values to some of the properties of the new web that the web event receiver was invoked for, but what about inheriting these properties from the parent web. Have a look at this second sample:

  <Property Key="Web.MasterUrl" Value="${~Parent}" />

  <Property Key="Web.CustomMasterUrl" Value="${~ParentNoRoot}" />

  <Property Key="Navigation.GlobalIncludePages" Value="${~SiteCollection}" />

As you see, you can specify special tokens in the “Value” attribute of the feature property elements too, these three options are available:

  • ${~Parent} – with this token you specify that the property of the “Key” attribute will be copied from the parent web of the current web
  • ${~ParentNoRoot} – this is almost the same as the first option, the only difference being that if the parent site is the root site of the current site collection the property won’t be copied to the current web (meaning that if the current web is not a child of the root web, the property will get copied).
  • ${~SiteCollection} – this token specifies that the property will be copied from the root web of the current site collection (no matter whether it is the parent of the current web or not)

In the case of the ${~ParentNoRoot} token you saw that there will be cases when the specified web property won’t get copied to the current web (for first level children). In this case you will need to specify two feature property elements with the same “Key”:

  <Property Key="Web0.CustomMasterUrl" Value="~SiteCollection/_catalogs/masterpage/v4.master" />

  <Property Key="Web1.CustomMasterUrl" Value="${~ParentNoRoot}" />

… or almost the same “Key” – you see that there is an extra digit before the dot separator in the “Key” attribute – this is a special trick that the WebProvisioned event receiver supports because SharePoint doesn’t allow you to have feature property elements with the same “Key” attribute. Another important thing here is the order of evaluation of the feature property elements that specify one and the same web property – in this case the properties appearing later in the properties’ definition will have precedence – this means that the static “v4.master” value will be applied only for first level child webs and all other sub-webs will have their “CustomMasterUrl” property copied from their respective parent webs.

So much about web properties’ inheritance, but what about the much rarer case when you may want the opposite - to disallow the web properties’ inheritance (which is the default behavior for the SPWeb’s MasterUrl, CustomMasterUrl and AlternateCssUrl properties for publishing sites). This sample would do the trick:

  <Property Key="Web.MasterUrl" Value="~SiteCollection/_catalogs/masterpage/v4.master" />

  <Property Key="Web.CustomMasterUrl" Value="~SiteCollection/_catalogs/masterpage/v4.master" />

  <Property Key="Web.AlternateCssUrl" Value="" />

  <Property Key="CustomProperty.__InheritsMasterUrl" Value="false" />

  <Property Key="CustomProperty.__InheritsCustomMasterUrl" Value="false" />

  <Property Key="CustomProperty.__InheritsAlternateCssUrl" Value="false" />

As you see, when you create publishing sub-sites it is not enough to just set the SPWeb’s MasterUrl, CustomMasterUrl and AlternateCssUrl properties. To “break” the inheritance of the master page settings you will also need to set several custom properties in the SPWeb.AllProperties collection as well.

And one last option that the general purpose WebProvisioned event receiver supports:

<Property Key="[SPS;STS#1]Navigation.GlobalIncludeSubSites" Value="true" />

you can specify optionally the names of the target site definitions for which the property setting should be applied. You can specify one or more site definition names separated by semi-colon in the beginning of the “Key” attribute enclosed in square brackets. You can use names of site definitions with or without configuration number added (after the ‘#’ character) – when you use a site definition name without a configuration number the property will be applied for all available configurations in this site definition (actually for webs based on all configurations).

And lastly several words about the sample solution with the general purpose WebProvisioned receiver: it contains a feature named “WebProvisionedReceiver” – this is the feature which actually installs the custom WebProvisioned receiver. In order that you can use it you will need to provide it with the appropriate for your scenario feature properties (starting with the “ReceiverScope” one) – this you can do in the onet.xml file of a site definition of yours in which you will add a reference to the “WebProvisionedReceiver” feature. Optionally (handy for testing purposes) you can add the feature properties directly to the feature’s definition (the feature.xml template file).

Besides the “WebProvisionedReceiver” feature the sample solution contains one more feature which is called “WebSettings”. It can be configured with exactly the same properties as the “WebProvisionedReceiver” feature (except the “ReceiverScope” one). This feature uses the same internal logic as the general purpose WebProvisioned receiver but instead applies the specified web properties directly to the web in which it is activated.

Saturday, September 18, 2010

XsltListViewWebPart & ContentByQueryWebPart – ParameterBindings, localization and embedded server controls

In SharePoint 2010 the XsltListViewWebPart and the ContentByQueryWebPart web parts play a pivotal role for the presentation of list data. It turns out that both web parts share a lot of common functionality because they both inherit one and the same base XSL transformations using web part – the DataFormWebPart (the CQWP via the CmsDataFormWebPart and the XLV via the BaseXsltListWebPart classes respectively). One of these shared pieces of functionality is the so called “parameter bindings” available through the DataFormWebPart.ParameterBindings property. The idea of the “parameter bindings” is that using specific XML data that you assign to the ParameterBindings property you can fetch various properties or variables from the asp.net, page, web parts, etc. environments which can then become available as XSL parameters (“xsl:param” XSL items) in your custom XSL that you use in either the XsltListViewWebPart or the ContentByQueryWebPart web parts. With these additional custom XSL parameters you can apply different presentation logic and even make the presentation of the two web parts react dynamically to the changes of the variables coming from these external (from the web part’s perspective) environments/contexts. So, let’s jump directly to a sample “parameter bindings” XML:

<ParameterBindings>

  <ParameterBinding Name="CustomBinding" DefaultValue="my custom value" />

  <ParameterBinding Name="dvt_firstrow" Location="Postback" DefaultValue="1" />

  <ParameterBinding Name="ResourceBinding" Location="Resource(wss,noitemsinview_doclibrary)" />

  <ParameterBinding Name="Today" Location="CAMLVariable" />

  <ParameterBinding Name="UserID" Location="CAMLVariable" />

  <ParameterBinding Name="WPVariableBinding" Location="WPVariable(_WPID_ | _WPQ_ | _WPR_ | _WPSRR_ | _LogonUser_ | _WebLocaleId_)" />

  <ParameterBinding Name="QuerySringBinding" Location="QueryString(myqueryparam)" DefaultValue="empty" />

  <ParameterBinding Name="FormBinding" Location="Form(myhidden)" DefaultValue="empty" />

  <ParameterBinding Name="ServerVariableBinding" Location="ServerVariable(REMOTE_ADDR)" />

  <ParameterBinding Name="WPPropertyBinding" Location="WPProperty(Title)" />

  <ParameterBinding Name="ControlBinding1" Location="Control(myDDL)" />

  <ParameterBinding Name="ControlBinding2" Location="Control(myDDL,SelectedIndex)" />

  <ParameterBinding Name="CombinedBinding" Location="Form(myhidden);QueryString(myqueryparam)" DefaultValue="empty" />

</ParameterBindings>

Yes, this was a somewhat extensive sample, but it actually covers most of the options that are available for the “parameter bindings” and I will give more details for each option shortly. As you see the XML’s format is pretty simple – it contains a root “ParameterBindings” element with “ParameterBinding” child elements. The “ParameterBinding” element may have three attributes: Name, Location and DefaultValue. The “Name” attribute is mandatory and it specifies the name of the XSL parameter that will be initialized by this “parameter binding”. In most cases you are free to specify any suitable and meaningful for your scenario name for the parameter binding, but for certain values of the “Location” attribute you can use only a predefined set of possible names (see below). The “Location” attribute contains the logic for specifying the context from which you will fetch a certain variable/value that will become available as an XSL parameter – you can choose from several predefined options here. The “DefaultValue” attribute is self explanatory – when the value retrieved from the specified context is not available (is an empty string), the XSL parameter will be initialized with the value of the “DefaultValue” attribute.

So, after you know the XML format of the ParameterBinding property, the next logical step would be to assign this XML to a real CQWP or XLV web part that you have somewhere in your SharePoint farm. The standard SharePoint UI doesn’t allow to edit this property, so you will have to choose from several other alternatives: SharePoint Designer 2010 (pretty handy actually and requires no custom coding), custom code in a feature receiver or a tool, PowerShell script or a specialized tool (a good place to advertise my “web part manager” utility).

In order that you can actually use the custom parameters in your XSL you will also need to define “xsl:param” elements with corresponding names in the main (root) XSL file of your CQWP or XLV web part (your custom ContentQueryMain.xsl for the CQWP or the Main.xsl for the XLV web part). If the “xsl:param” is defined in the main XSL file it will be available (visible) in all included XSL files as well. Basically you can define the “xsl:param” elements for your “parameter bindings” in one of the included XSL files (e.g. the ItemStyle.xsl or the fldtypes.xsl) – then the parameter will be available only in this XSL file (there won’t be a problem if the parameter is defined in both the main and the included XSL file either). The definitions of the XSL parameters for the parameter bindings from the sample above can look something like:

  <xsl:param name="CustomBinding" />

  <xsl:param name="ResourceBinding" />

  <xsl:param name="dvt_firstrow" select="1" />

  <xsl:param name="Today" />

  <xsl:param name="UserID" />

  <xsl:param name="WPVariableBinding" />

  <xsl:param name="QuerySringBinding" />

  <xsl:param name="FormBinding" />

  <xsl:param name="ServerVariableBinding" />

  <xsl:param name="WPPropertyBinding" select="'no name'" />

  <xsl:param name="ControlBinding1" />

  <xsl:param name="ControlBinding2" />

  <xsl:param name="CombinedBinding" />

Note that by using the “select” attribute of the “xsl:param” attribute you can specify again a default value for the parameter if the initializing parameter binding has no “DefaultValue” attribute and returns an empty value. This default value will also be used if there is no parameter binding with that name specified in the XML of the ParameterBindings property.

And now let’s have a look at the available options that you have for the “Location” attribute of the “PropertyBinding” element:

  • missing Location attribute – this is also possible – in this case you need to provide the “DefaultValue” attribute -basically you provide the web part with a fixed/constant parameter value. This can be very handy if you have for example several CQWP parts using several different but very similar item styles (the item styles can differ in some minor elements like the target of the links or the presence of some additional small elements per item). In this scenario you can use only one item style which can check one or several parameters provided by the parameter bindings and adjust its presentation accordingly (the different web parts will provide different values in their parameter bindings or not provide some of the parameter bindings at all). It is obviously far easier to maintain only one item style instead of several, so the parameter bindings can help a lot in this scenario.
  • Location “Postback” – in this case you cannot choose arbitrary names, there is a predefined set of available names and they all have the “dvt_” prefix: dvt_sortdir, dvt_sortfield, dvt_filterfields, dvt_firstrow, dvt_nextpagedata, dvt_prevpagedata, dvt_partguid. From these only the last one is available in the CQWP (the rest will always have empty values in the CQWP). The “dvt_partguid” parameter will contain a value that seems like the “ClientID” of the web part (a sample value will look something like ctl00$ctl23$g_59a84b77_6a94_4c65_81cd_618b4c21c374) but actually you won’t find a matching HTML element with exactly this ID on the page. The other “dvt_” parameters contain specific XLV “postback” data related to the XLV’s sorting, filtering and paging. In fact these are already defined as “xsl:param” elements in the standard Main.xsl of the XLV web part and get initialized by the web part even without it being necessary to add them as parameter bindings. Depending on the current sorting, filtering and paging of your XLV you can see values like these in the “dvt_” parameters:

    dvt_firstrow: 31
    dvt_sortdir: descending
    dvt_sortfield: LinkTitle
    dvt_filterfields: ;LinkTitle;
    dvt_nextpagedata: Paged=TRUE&p_Title=item%2045&p_ID=45
    dvt_prevpagedata: Paged=TRUE&PagedPrev=TRUE&p_Title=item%2071&p_ID=71
  • Location Recource([Resource_File],[Resource_Name] – this one is very useful since it allows you to make values from resource files available in your XLV or CQWP web parts. The resource files are located under the App_GlobalResources subfolder of the physical directory of your web application and have the “resx” extension. If you have for example this value in the “Location” attribute - Resource(wss,noitemsinview_doclibrary) – it will fetch a localized string from the “wss.resx” resource file (actually it can be a resx file for a specific culture like wss.en-US.resx) with a name “noitemsinview_doclibrary”. If you check the wss.resx file you will see that the value of this resource string is “There are no items to show in this view of the "<ListProperty Select="Title" HTMLEncode="TRUE" />" document library” – you see a fancy “ListProperty” XML element inside the resource string, but in the XLV this will get replaced with the title of the actual list that the XLV web part displays. Apart from the “parameter bindings” localization support there are other alternatives for localization in both the XLV and CQWP web parts – check the paragraph titled “Localization” below.
  • Location CAMLVariable – in this case you can use only two predefined values for the “Name” attribute of the parameter binding: Today and UserID. Sample values returned by these two parameters are:

    Today: 2010-09-17T16:04:48Z
    UserID: Stefan Stanev

    Note that the UserID parameter actually returns the display name, not the account name of the current user.
  • Location WPVariable([Tokens]) – the value inside the brackets can contain one or a combination of several predefined tokens: _WPID_, _WPQ_, _WPR_, _WPSRR_, _LogonUser_, _WebLocaleId_. If you use a combination of these you can separate them with spaces or other arbitrary characters/strings. The names of these tokens are not that descriptive but you can get some idea of their meaning by these sample values that correspond to the “WPVariable” parameter binding from the sample at the top:

    g_3242c920_b37c_4cfa_a356_955bb398d47f | WPQ2 | http://myserver/sites/2/_wpresources/Microsoft.SharePoint.Publishing/14.0.0.0__71e9bce111e9429c | /sites/2/_wpresources/Microsoft.SharePoint.Publishing/14.0.0.0__71e9bce111e9429c | myserver\stefanstanev | 1033
  • Location QueryString([QueryParam]) – this allows you to get values from query parameters in the current URL in your custom XSL – it actually maps to the HttpContext.Current.Request.QueryString collection. So if you have this query parameter in the URL of the current page: default.aspx?myqueryparam=somevalue – the value “somevalue” will become available in the xsl:param corresponding to the “QueryString” parameter binding which specifies the “myqueryparam” parameter name in its Location attribute.
  • Location Form([FormParam]) – this maps to the HttpContext.Current.Request.Form collection. If you have this input element on your page:

    <input type="hidden" id="myhidden" name="myhidden" value="myhiddenvalue"/>

    this parameter binding

    <ParameterBinding Name="FormBinding" Location="Form(myhidden)" DefaultValue="empty" />

    will provide the “myhiddenvalue” in the <xsl:param name="FormBinding" /> parameter (only on page postbacks, otherwise the parameter value will be the specified in the “DefaultValue” attribute: “empty”)
  • Location ServerVariable([ServerVariable]) – this maps to the HttpContext.Current.Request.ServerVariables collection. If you have for example Location=”ServerVariable(REMOTE_ADDR)” it will initialize the corresponding XSL parameter with something like: fe80::a589:57ce:e0ec:1f1c%13
  • Location WPProperty([PropertyName]) – in the brackets you can specify the name of a public instance property of the XLV or CQWP classes – this can be the “Title” or “ID” or any other property of the web parts whose value you want to use in your XSL.
  • Location Control([ControlID]) and Control([ControlID],[PropertyName]) – here you need to provide the ID of a server control on the page containing the web part and optionally the name of a public instance property of the control’s class. If you skip the property name option the parameter will be initialized with the value of the standard Control.Text property. So if you have this drop-down list control on your page:

    <asp:DropDownList runat="server" ID="myDDL" AutoPostBack="true">

      <asp:ListItem>Item 1</asp:ListItem>

      <asp:ListItem>Item 2</asp:ListItem>

      <asp:ListItem>Item 3</asp:ListItem>

      <asp:ListItem>Item 4</asp:ListItem>

    </asp:DropDownList>


    which has its third option selected, the parameter binding with Location="Control(myDDL)" will initialize its parameter with the value of “Item 3” (the “Text” property of the DropDownList control will return the same value as its “SelectedValue” property) and the one with Location="Control(myDDL,SelectedIndex)" will initialize its parameter with the value of “2” (the third item in the zero based “SelectedIndex” property). As you see this type of parameter bindings allows you to make the presentation of your CQWP and XLV web parts interact with the server controls that you may have in the containing page.
  • Location that is a combination of several options – in this case the Location options should be separated by semi-colon (from the sample at the top - Location="Form(myhidden);QueryString(myqueryparam)"). The ordering here is important – the preceding Location options will be evaluated first – from the example – if you have both the “myhidden” from parameter and the “myqueryparam” query parameter available in the current page, the parameter binding will return the value of the “myhidden” from parameter, because the “Form” option is placed before the “QueryString” option. The idea of this combining is that if one or more of the preceding options yield no value, then the next option which returns a value will be taken by the parameter binding and passed to its corresponding parameter.

Localization

Localization can be quite a serious issue with the availability of so many multi-lingual sites around. Fortunately as you saw the “parameter bindings” support provides a way to use localized resources in the CQWP and XLV web parts. Besides the “parameter bindings” there is one other much easier alternative to get resource strings in the XLV web part:

<xsl:value-of select="/dsQueryResponse/Rows/@resource.wss.fld_yes" />

yes, it is as simple as that – you just specify with XPath an attribute of the “/dsQueryResponse/Rows” element of the source XML. And you don’t have to set additionally some web part properties or define extra XSL parameters. As you see the name of the attribute follows a specific convention – it consists of three parts separated by dots – the first one is the constant “resource” prefix, the second one specifies the name of the targeted resource file (wss.resx in this case) and the third part is the name of the resource in the resource file. In the “vwstyles.xsl” and “fldtypes.xsl” XSL files of the XLV web part there’re XSL variables like the “$Rows” and “$thisNode” which map to the “/dsQueryResponse/Rows” and “/dsQueryResponse/Rows/Row” XML elements respectively. So, these alternative variants of the special “resource” attribute’s XPath are possible (from locations where you have these XSL variables available):

<xsl:value-of select="$thisNode/../@resource.wss.Thunmbnail"/>

<xsl:value-of select="$Rows/@resource.wss.ManualRefreshText"/>

And … the interesting thing – how do these resource strings come as attributes of the “/dsQueryResponse/Rows” element, after all you can have lots of resource files with hundreds of resource strings inside them. The answer is that the “resource” attributes are actually something like pseudo-attributes of the source XML. And actually there is no source XML at least in the form of a XML DOM container class like XmlDocument or XDocument. The trick is possible because the XLV uses a custom XPathNavigator (overriding the DataFormWebPart.GetXPathNavigator virtual method). This custom XPathNavigator queries directly the SPListItem data that the XLV web part retrieves from SharePoint and as an extra provides the “resource” pseudo-attribute support. The main reason to use a custom XPathNavigator is simple – performance (this way you skip the step of transforming the list item data to a DOM XML container like XmlDocument so that you can then use its default XPathNavigator, which additionally will not perform so well as the custom one).

And how about the CQWP – I kept mentioning just the XLV web part in the previous paragraph but not the CQWP. Unfortunately the “resource” pseudo-attribute “shortcut” is not available in the CQWP (the CQWP also uses a custom XPathNavigator but it is not the same as the one used by the XLV web part and doesn’t implement the “resource” attribute logic). Still, there is yet another alternative available for the CQWP web part – the idea is that the .NET XSL transformations implementation allows you to define a .NET class whose methods (rather static methods) can become available and be used from inside the XSL (check the XsltArgumentList.AddExtensionObject method in MSDN for more details). Such “extension” class can also be used in the CQWP web part but you will need to create a new class inheriting the CQWP class. The CQWP’s ModifyXsltArgumentList virtual method (which you will need to override) is the place where you can hook the custom extension object – check this nice article which describes in detail the whole procedure. And this article describes how this technique can be used to fetch resource strings into the web part’s XSL.

Embedded server controls

In my previous posting I demonstrated how you can embed server controls inside the XSL of the XLV web part. This functionality is provided by the base DataFormWebPart class, so it would be normal to expect that this will also work in the CQWP web part. But … it doesn’t (at least in most of the cases that I tried). It turns out that in the DataFormWebPart class there is a public virtual property named CanHaveServerControls, whose obvious purpose is to enable or disable the ability of the web part to parse and instantiate embedded server controls. This property is not overridden in the CQWP but its base implementation checks several other virtual properties which in the case of the CQWP return always (or in most cases) false. So the only possible work-around in this case (if you need embedded server controls in the CQWP that badly) is to simply subclass the CWQP. The inheriting class will just need to override the CanHaveServerControls property and will look something like:

    public class MyCQWP : ContentByQueryWebPart

    {

        public override bool CanHaveServerControls

        {

            get

            {

                return true;

            }

            set

            {

                base.CanHaveServerControls = value;

            }

        }

    }