Contents tagged with SharePoint

  • Getting the Current User in SharePoint XSLT

    SharePoint offers a couple of ways by which we can obtain the current user for use in a XSLT web part like DataFormWebPart or XsltListViewWebPart. All involve first setting parameters through the ParameterBindings property:

       1: <ParameterBindings>
       2:     <ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
       3:     <ParameterBinding Name="LogonUser_" Location="WPVariable(_LogonUser_)"/>
       4:     <ParameterBinding Name="LogonUser" Location="ServerVariable(LOGON_USER)"/>
       5:     <ParameterBinding Name="AuthUser" Location="ServerVariable(AUTH_USER)"/>
       6:     <ParameterBinding Name="RemoteUser" Location="ServerVariable(REMOTE_USER)"/>
       7: </ParameterBindings>
       8: <Xsl>
       9:     <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl asp" xmlns:asp="System.Web.UI.WebControls" xmlns:ddwrt2="urn:frontpage:internal" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
      10:         <xsl:output method="html" />
      11:         <xsl:param name="UserID"/>
      12:         <xsl:param name="LogonUser_"/>
      13:         <xsl:param name="LogonUser"/>
      14:         <xsl:param name="AuthUser"/>
      15:         <xsl:param name="RemoteUser"/>
      16:         <xsl:template match="/">
      17:             UserID: <xsl:value-of select="$UserID"/><br/>
      18:             ID: <xsl:value-of select="ddwrt:UserLookup($UserID, 'ID')" /><br/>
      19:             EMail: <xsl:value-of select="ddwrt:UserLookup($UserID, 'EMail')" /><br/>
      20:             Login: <xsl:value-of select="ddwrt:UserLookup($UserID, 'Login')" /><br/>
      21:             LogonUser_: <xsl:value-of select="$LogonUser_"/><br/>
      22:             AuthUser: <xsl:value-of select="$AuthUser"/><br/>
      23:             RemoteUser: <xsl:value-of select="$RemoteUser"/><br/>
      24:         </xsl:template>
      25:     </xsl:stylesheet>
      26: </Xsl>

    Here we see different kinds of parameters:

    • CAMLVariable: built in variables UserID and Today;
    • WPVariable: returns one of the predefined values for _WPID_ (web part client id), _WPQ_ (web part unique id in page),  _WPR_ (web part resources folder full URL),  _WPSRR_ (web part resources folder relative URL), _LogonUser_ (server variable LOGON_USER),  _WebLocaleId_ (current site locale, as in CultureInfo.LCID);
    • ServerVariable: returns one of the HTTP or IIS server- defined variables.


    This will return something like this:

    UserID: Ricardo Peres

    ID: 10

    EMail: ricardoperes@domain.com

    Login: i:0#.w|DOMAIN\ricardoperes

    LogonUser_: 0#.w|DOMAIN\ricardoperes

    AuthUser: 0#.w|DOMAIN\ricardoperes

    RemoteUser: 0#.w|DOMAIN\ricardoperes

    The ddwrt:UserLookup extension function returns a value depending on the second parameter; valid values are Id, Email and Login, and you can easily guess what they are for.

    You can find a reference for LOGON_USER, AUTH_USER, REMOTE_USER and UNMAPPED_REMOTE_USER here. In my development server, I get the same value for all variables.

    By the way, I posted a more advanced solution, which allows access to any profile properties. You can read it here.

    Read more...

  • Access Navigation Nodes in SharePoint XSLT

    SharePoint relies on ASP.NET Site Map Providers for generating navigation links on its default pages. Specifically, the default Web.config file registers a (big!) number of providers, which control different aspects of its navigation:

       1: <siteMap defaultProvider="CurrentNavigation" enabled="true">
       2:   <providers>
       3:     <add name="SPNavigationProvider" type="Microsoft.SharePoint.Navigation.SPNavigationProvider, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
       4:     <add name="SPSiteMapProvider" type="Microsoft.SharePoint.Navigation.SPSiteMapProvider, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
       5:     <add name="SPContentMapProvider" type="Microsoft.SharePoint.Navigation.SPContentMapProvider, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
       6:     <add name="SPXmlContentMapProvider" siteMapFile="_app_bin/layouts.sitemap" type="Microsoft.SharePoint.Navigation.SPXmlContentMapProvider, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
       7:     <add name="AdministrationQuickLaunchProvider" description="QuickLaunch navigation provider for the central administration site" type="Microsoft.Office.Server.Web.AdministrationQuickLaunchProvider, Microsoft.Office.Server.UI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
       8:     <add name="SharedServicesQuickLaunchProvider" description="QuickLaunch navigation provider for shared services administration sites" type="Microsoft.Office.Server.Web.SharedServicesQuickLaunchProvider, Microsoft.Office.Server.UI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
       9:     <add name="GlobalNavSiteMapProvider" description="CMS provider for Global navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Global" EncodeOutput="true" />
      10:     <add name="CombinedNavSiteMapProvider" description="CMS provider for Combined navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" EncodeOutput="true" />
      11:     <add name="CurrentNavSiteMapProvider" description="CMS provider for Current navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="true" />
      12:     <add name="CurrentNavSiteMapProviderNoEncode" description="CMS provider for Current navigation, no encoding of output" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" EncodeOutput="false" />
      13:     <add name="GlobalNavigation" description="Provider for MOSS Global Navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Combined" Version="14" />
      14:     <add name="CurrentNavigation" description="Provider for MOSS Current Navigation" type="Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" NavigationType="Current" Version="14" />
      15:     <add name="SiteDirectoryCategoryProvider" description="Site Directory category provider" type="Microsoft.SharePoint.Portal.WebControls.SiteDirectoryCategoryProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      16:     <add name="MySitePersonalQuickLaunchProvider" description="MySite personal quick launch provider" type="Microsoft.SharePoint.Portal.MySitePersonalQuickLaunchProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      17:     <add name="MySiteHostTopNavigationProvider" description="MySite host top navigation provider" type="Microsoft.SharePoint.Portal.MySiteHostTopNavigationProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      18:     <add name="GlobalNavigationSwitchableProvider" description="Provider for MOSS Global Navigation, which maps to GlobalNavigation or GlobalNavigationTaxonomyProvider according to Navigation Settings" type="Microsoft.SharePoint.Publishing.Navigation.SwitchableSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" DefaultTargetProviderName="GlobalNavigation" Version="15" />
      19:     <add name="CurrentNavigationSwitchableProvider" description="Provider for MOSS Current Navigation, which maps to GlobalNavigation or CurrentNavigationTaxonomyProvider according to Navigation Settings" type="Microsoft.SharePoint.Publishing.Navigation.SwitchableSiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Version="15" DefaultTargetProviderName="CurrentNavigation" />
      20:     <add name="GlobalNavigationTaxonomyProvider" description="Provider for MOSS Global Navigation, driven by the Taxonomy feature" type="Microsoft.SharePoint.Publishing.Navigation.TaxonomySiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Version="15" />
      21:     <add name="CurrentNavigationTaxonomyProvider" description="Provider for MOSS Global Navigation, driven by the Taxonomy feature" type="Microsoft.SharePoint.Publishing.Navigation.TaxonomySiteMapProvider, Microsoft.SharePoint.Publishing, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Version="15" />
      22:     <add name="MySiteMapProvider" description="MySite provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteMapProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      23:     <add name="MySiteLeftNavProvider" description="MySite Left Nav provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteLeftNavProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      24:     <add name="MySiteDocumentStaticProvider" description="MySite Document library provider that returns static links to lists in the site" type="Microsoft.SharePoint.Portal.MySiteDocumentStaticProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      25:     <add name="MySiteSitesPageStaticProvider" description="MySite Document library provider that returns static links to lists on the Sites page" type="Microsoft.SharePoint.Portal.MySiteSitesPageStaticProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      26:     <add name="MySiteSubNavProvider" description="MySite Sub Nav provider that returns areas and based on the current user context" type="Microsoft.SharePoint.Portal.MySiteSubNavProvider, Microsoft.SharePoint.Portal, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      27:     <add name="EduTopNavProvider" description="Top Nav Site Map Provider for Education My Site Host" type="Microsoft.Office.Education.WebUI.EduTopNavProvider, Microsoft.Office.Education.WebUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      28:     <add name="MySiteHostQuickLaunchProvider" description="Quick Launch Site Map Provider for Education My Site Host" type="Microsoft.Office.Education.WebUI.EduQuickLaunchProvider, Microsoft.Office.Education.WebUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
      29:   </providers>
      30: </siteMap>

    It is possible to use a Site Map Data Source (SiteMapDataSource) to feed a DataFormWebPart with navigation information, so that you can play with it using XSLT.

    For example, let’s pick one of the providers, SPNavigationProvider, which is registered, appropriately, as SPNavigationProvider. This one is capable of returning the global navigation (Global Navigation) and the quick links (Current Navigation). In order to select one or the other, we supply a special key to its StartingNodeUrl property:

    • sid:1000: Home Page;
    • sid:1002: Global Navigation;
    • sid:1025: Current Navigation;
    • sid:1040: Search.

    In case you are wondering, these values are defined in the public properties of the SPNavigation class.

    This example will list all attributes of all the nodes:

       1: <WebPartPages:DataFormWebPart runat="server">
       2:     <DataSources>   
       3:         <asp:SiteMapDataSource SiteMapProvider="SPNavigationProvider" ShowStartingNode="false" StartingNodeUrl="sid:1002" runat="server" />   
       4:     </DataSources>
       5:     <Xsl>
       6:         <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       7:             <xsl:output method="html"/>
       8:  
       9:             <xsl:template match="/">
      10:                 <xsl:for-each select="dataRoot">                    
      11:                     <xsl:for-each select="NavigateUIData">
      12:                         <p>
      13:                             Name: <xsl:value-of select="@Name"/>
      14:                             Value: <xsl:value-of select="@Value"/>
      15:                             NavigateUrl: <xsl:value-of select="@NavigateUrl"/>
      16:                             Description: <xsl:value-of select="@Description"/>
      17:                             <xsl:if test="substring(@NavigateUrl, string-length(@NavigateUrl) - string-length('.aspx') + 1) = '.aspx'">
      18:                                 (ASPX)
      19:                             </xsl:if>
      20:                             <xsl:if test="substring(@NavigateUrl, string-length(@NavigateUrl) - string-length('/') + 1) = '/'">
      21:                                 (Subsite)
      22:                             </xsl:if>
      23:                         </p>                                                            
      24:                     </xsl:for-each>
      25:                 </xsl:for-each>
      26:             </xsl:template>                                
      27:         </xsl:stylesheet>
      28:     </Xsl>
      29: </WebPartPages:DataFormWebPart>

    You can see that the SiteMapDataSource returns XML in the form:

       1: <dataRoot>
       2:     <NavigateUIData Name="Some Name" Value="Some Value" NavigateUrl="Some Url" Description="Some Description" />
       3: </dataRoot>

    Attributes are mostly self-explanatory, but Name and Value have the same content and Description always appears to be empty. For subsites, the NavigateUrl attribute will end in a /. Depending on the provider, it is possible to have nested NavigateUIData nodes.

    If you set the ShowStartingNode property to true, you will also get a root node containing the name (for sid:1000 it is Home, for sid:1002 it is SharePoint Top Navigation Bar, for sid:1025 it is Quick Launch and for sid:1040 it is Search) and URL of the current site

    Unfortunately, there isn’t much documentation (read: none) about these providers, except, of course, their methods and properties, so you have to figure out yourself. One thing that can be useful is this XSLT that will give you all the nodes and their attributes:

       1: <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl asp" xmlns:asp="System.Web.UI.WebControls" xmlns:ddwrt2="urn:frontpage:internal">    <xsl:output method="html" />
       2:     <xsl:template match="*">
       3:         <xsl:param name="path" select="''" />
       4:         <xsl:variable name="previous" select="count(preceding-sibling::*[name()=name(current())])" />
       5:         <xsl:variable name="countprevious" select="count(following-sibling::*[name()=name(current())])" />
       6:         <xsl:variable name="fullpath" select="concat($path, '/', name(), substring(concat('[', $previous + 1, ']'), 1 div ($countprevious or $previous)))" />
       7:         <p><xsl:value-of select="$fullpath" /></p>
       8:         <xsl:apply-templates select="@*|*">
       9:             <xsl:with-param name="path" select="$fullpath" />
      10:         </xsl:apply-templates>
      11:     </xsl:template>
      12:     <xsl:template match="@*">
      13:         <xsl:param name="path" select="''" />        
      14:         <xsl:value-of select="concat($path, '/@', name())" />: <xsl:value-of select="." />
      15:         <br/>
      16:     </xsl:template>
      17: </xsl:stylesheet>

    Read more...

  • Retrieving SharePoint User Profile Properties in XSLT

    A common request is to be able to retrieve user information from XSLT (XsltListViewWebPart, DataFormWebPart, etc). It is possible to obtain some information through server variables (LOGON_USER), CAML variables (UserID) or ddwrt:UserLookup extension function. It works, to some extent, but sometimes it is not enough.

    It is well known that one can have server controls in XSLT stylesheets. As it happens, the ProfilePropertyValue control retrieves any value from the user profile. It just needs to have a ProfilePropertyLoader declared before it, which can be placed in the master page. For example:

       1: <SPSWC:ProfilePropertyLoader LoadFullProfileOfCurrentUser="true" runat="server"/>
       2: <SPSWC:ProfilePropertyValue PropertyName="FirstName" ApplyFormatting="false" runat="server"/>

    Just remember to register the Microsoft.SharePoint.Portal.WebControls assembly with the SPSWC tag prefix, either in the Web.config or in the page itself.

    But, what about XSLT? Well, if the SPSWC tag prefix is registered in Web.config, we can do this:

       1: <xsl:template name="GetProfilePropertyValue">
       2:     <xsl:param name="Property"/>
       3:     <SPSWC:ProfilePropertyValue PropertyName="{$Property}" ApplyFormatting="false" runat="server" />
       4: </xsl:template>
       5:  
       6: <xsl:variable name="Property">WorkEmail</xsl:variable>
       7:  
       8: <xsl:variable name="WorkEmail">
       9:     <xsl:call-template name="GetProfilePropertyValue">
      10:         <xsl:with-param name="Property" value="$Property"/>
      11:     </xsl:call-template>
      12: </xsl:variable>
      13:  
      14: Work Email: <xsl:copy-of select="$WorkEmail"/>

    With this technique, you can retrieve any of the profile properties. Ah, don’t forget to add:

       1: xmlns:spswc="Microsoft.SharePoint.Portal"

    To the <xsl:stylesheet> declaration.

    Happy SharePointing! Winking smile




    Read more...

  • Exposing SharePoint Context to XSLT

    Picking on my previous post, I am going to show how you can access SharePoint (and ASP.NET context) from XSLT.

    Create a class like this one:

       1: public class ContextExtensions
       2: {
       3:     public Object HttpContext(String expression)
       4:     {
       5:         return (DataBinder.Eval(System.Web.HttpContext.Current, expression));
       6:     }
       7:  
       8:     public Object SpContext(String expression)
       9:     {
      10:         return (DataBinder.Eval(SPContext.Current, expression));
      11:     }
      12: }

    Follow the process described in my post to register it, apply tag mappings and register the controls as safe. Then, on the XSLT, you can access any properties (and properties of properties) of both HttpContext.Current and SPContext.Current instances! The only thing you can’t do is call methods. This is a limitation of DataBinder.Eval.

    Some examples:

       1: User.Identity.Name: <xsl:value-of select="my:HttpContext('User.Identity.Name')"/>
       2: SPFormContext.FormMode: <xsl:value-of select="my:SpContext('FormContext.FormMode')"/>
       3:  
       4: <xsl:variable name="TasksItemCount" select="my:SpContext('Web.Lists[&quot;Tasks&quot;].ItemCount')"/>
       5: Web.Lists["Tasks"].ItemCount: <xsl:value-of select="$TasksItemCount"/>
       6:  
       7: <xsl:variable name="ListItemName" select="my:SpContext('ListItem[&quot;Name&quot;]')"/>
       8: ListItem[&quot;Name&quot;]: <xsl:value-of select="$TasksItemCount"/>

    Notice that when you are to evaluate expressions with “, you must do it in two steps:

    1. Define a variable that takes the expression;
    2. In that variable, replace “ for &quot.

    Hope you find it useful!

    Read more...

  • Displaying All Fields in a SharePoint Data Source

    So, you want to know which fields your data source is returning. You can use the following XSLT:

       1: <xsl:for-each select="/dsQueryResponse/Rows/Row/@*">
       2:     <p>Name: <xsl:value-of select="name()" /> Value: <xsl:value-of select="." /></p>
       3: </xsl:for-each>

    Very handy, even if I say so myself! Winking smile

    Update: this can also be useful.

    Read more...

  • Custom XSLT Functions in SharePoint

    Wouldn’t it be good if SharePoint would allow the registration of custom functions that could be used in XSLT stylesheets? Well, as it happens, it does!

    Some of the classes that allow XSLT processing – DataFormWebPart, ContentByQueryWebPart - have a virtual method called ModifyXsltArgumentList that can be used for registering custom functions. Sadly, XsltListViewWebPart, XlstListFormWebPart, among others, are sealed, and thus do not allow this.

    Let’s write a class that inherits from DataFormWebPart (you could the same for ContentByQueryWebPart) and register our own functions:

       1: public class CustomDataFormWebPart : DataFormWebPart
       2: {
       3:     protected override void ModifyXsltArgumentList(ArgumentClassWrapper argList)
       4:     {
       5:         argList.AddExtensionObject("urn:MyFunctions", new MyFunctions());
       6:         base.ModifyXsltArgumentList(argList);
       7:     }
       8: }

    Let’s forget about the MyFunctions class and pretend that it has a few public instance methods that we want to expose to SharePoint. Mind you, because this class will be instantiated in a SharePoint context, it will have access to SPContext.Current, and from here, to the whole of SharePoint!

    In a XSLT script, we need something like this in order to access our extension functions:

       1: <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:dsp="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal" xmlns:my="urn:MyFunctions">
       2: ...
       3: </xsl:stylesheet>

    Did you notice xmlns:my=”urn:MyFunctions”? This should be identical to the namespace that you used inside ModifyXsltArgumentList.

    And to call one of the functions:

       1: <xsl:value-of select="my:Function()"/>

    Again, you need to use the same prefix, my in this case. You can add parameters, as long as they are of base types (strings, number, date/time, booleans, and so on), and the same goes for the result type.

    I hear you say: “OK, but now I have to replace all declarations of DataFormWebPart for this new CustomDataFormWebPart!”. Not really, dear reader, ASP.NET offers a great solution for this: tag mapping. In a nutshell, we can ask ASP.NET to replace all instances of a control for another class that inherits from the control’s class.

    Now, we could this in several ways, but the best one is to have a feature targeting a WebApplication with a feature receiver that performs a Web.config modification:

       1: public override void FeatureActivated(SPFeatureReceiverProperties properties)
       2: {
       3:     var tagMappingDataFormWebPartModification = new SPWebConfigModification();
       4:     tagMappingDataFormWebPartModification.Path = "configuration/system.web/pages/tagMapping";
       5:     tagMappingDataFormWebPartModification.Name = "tagMappingDataFormWebPartModification";
       6:     tagMappingDataFormWebPartModification.Sequence = 0;
       7:     tagMappingDataFormWebPartModification.Owner = "WebConfigModifications";
       8:     tagMappingDataFormWebPartModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
       9:     tagMappingDataFormWebPartModification.Value = String.Format("<add tagType=\"{0}\" mappedTagType=\"{1}\"/>", typeof(DataFormWebPart).FullName, typeof(CustomDataFormWebPart).AssemblyQualifiedName);
      10:  
      11:     var webApp = properties.Feature.Parent as SPWebApplication;
      12:     webApp.WebConfigModifications.Add(tagMappingDataFormWebPartModification);
      13:     webApp.Update();
      14:     webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
      15: }

    When this feature is activated, it adds a line in the Web.config file, tagMapping section, that tells ASP.NET to replace DataFormWebPart for CustomDataFormWebPart in all markup declarations. Of course, it won’t affect code: new DataFormWebPart() will still return a DataFormWebPart!

    All is not done, yet. We still need to mark our CustomDataFormWebPart control as safe for declaration in web pages. We do that by adding an entry to the SafeControls section of the Web.config. For a change, I’m going to do this in the SharePoint Solution Manifest, Package.xml:

       1: <Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="e860c419-b399-497d-a313-545f994753c8" SharePointProductVersion="15.0">
       2:   <Assemblies>
       3:     <Assembly Location="$SharePoint.Project.AssemblyFileName$" DeploymentTarget="GlobalAssemblyCache">
       4:       <SafeControls>
       5:         <SafeControl Assembly="$SharePoint.Project.AssemblyFullName$" Namespace="MyNamespace" TypeName="*" Safe="True" />
       6:       </SafeControls>
       7:     </Assembly>
       8:   </Assemblies>
       9:   <FeatureManifests>
      10:     <FeatureManifest Location="MyFeature\Feature.xml" />
      11:   </FeatureManifests>
      12: </Solution>

    Notice the $SharePoint.Project.AssemblyFileName$ and $SharePoint.Project.AssemblyFullName$, these are two examples of tokens that will be replaced by Visual Studio when it deploys the solution.

    And that’s it! Your custom XSLT extension functions are ready to use!

    Read more...

  • A Tour of SharePoint Data Source Controls

    Introduction

    Besides the classic ASP.NET data source controls, SharePoint brings along its own. These allow us to retrieve data not just from SharePoint, but also from external sources, such as web services or SQL databases. Using SharePoint Designer you end up using these data sources without even knowing, and, specially, ignoring what other options you have. Let’s look at these controls one by one and see how the can be used. I already talked about the SPDataSource and AggregateDataSource, so I won’t cover these here. I also won’t be covering them in depth, but instead will provide an example of how to use them.

    SPHierarchyDataSourceControl

    The SPHierarchyDataSourceControl allows us to retrieve data (documents, list items) hierarchically from either a site or a list. It can only be bound to a control that can display hierarchical data, such as the SPTreeView control. It cannot be easily customized, except by using CSS and JavaScript.

    A simple example is:

       1: <SharePoint:SPHierarchyDataSourceControl runat="server" ID="hierarchy" RootContextObject="Web" ShowDocLibChildren="true" ShowListChildren="true" ShowFolderChildren="true" ShowWebChildren="true"/>
       2: <SharePoint:SPTreeView runat="server" DataSourceID="hierarchy"/>

    BdcDataSource

    A BdcDataSource let’s you retrieve data from a BCS data source (external content type). This may happen when you don’t have an external list created or you want to call a specific finder method. You need to specify the namespace, LOB instance, entity, and finder method. If the finder needs parameters, you will need to supply them. It can be customized by hosting it in a DataFormWebPart and by applying XSLT.

    Here’s an example:

       1: <WebPartPages:DataFormWebPart runat="server" Title="BCS" DisplayName="BCS" ID="bcs">
       2:     <DataSources>
       3:         <SharePoint:BdcDataSource runat="server" Mode="List" EntityName="Parent" LobSystemInstanceName="BCS" EntityNamespace="http://sp2013" FinderName="Read List"/>
       4:     </DataSources>
       5:     <Xsl>
       6:         <xsl:stylesheet version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       7:             <xsl:output method="html" indent="no"/>
       8:  
       9:                 <xsl:template match="/">
      10:                     <xsl:variable name="Rows" select="/dsQueryResponse/NewDataSet/Row"/>
      11:                     Count: <xsl:value-of select="count($Rows)"/>
      12:                 </xsl:template>
      13:             
      14:         </xsl:stylesheet>
      15:     </Xsl>
      16: </WebPartPages:DataFormWebPart>

    SoapDataSource

    SoapDataSource is the control that retrieves data from a SOAP web service. You specify the URL of the web service, the action to call and the SOAP envelope, together with any required parameters. It should be hosted in a DataFormWebPart and can thus use XSLT for the formatting of its contents.

    An example of calling the Lists.asmx web service:

       1: <WebPartPages:DataFormWebPart runat="server" DisplayName="SOAP" Title="SOAP" ID="soap">
       2:     <DataSources>
       3:         <SharePoint:SoapDataSource runat="server" WsdlPath="http://sp2013/_vti_bin/lists.asmx?WSDL" SelectUrl="http://sp2013/_vti_bin/lists.asmx" SelectAction="http://schemas.microsoft.com/sharepoint/soap/GetListCollection" SelectPort="ListsSoap" SelectServiceName="Lists">
       4:             <SelectCommand>
       5:                 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
       6:                     <soap:Body>
       7:                         <GetListCollection xmlns="http://schemas.microsoft.com/sharepoint/soap/"/>
       8:                     </soap:Body>
       9:                 </soap:Envelope>
      10:             </SelectCommand>
      11:         </SharePoint:SoapDataSource>
      12:     </DataSources>
      13:     <XSL>
      14:         <xsl:stylesheet xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ddw1="http://schemas.microsoft.com/sharepoint/soap/" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
      15:             <xsl:output method="html" indent="no"/>
      16:     
      17:             <xsl:template match="/">
      18:                 <xsl:variable name="Rows" select="/soap:Envelope/soap:Body/ddw1:GetListCollectionResponse/ddw1:GetListCollectionResult/ddw1:Lists/ddw1:List"/>
      19:                 Count: <xsl:value-of select="count($Rows)"/>
      20:             </xsl:template>
      21:         </xsl:stylesheet>
      22:     </XSL>
      23: </WebPartPages:DataFormWebPart>

    XsltListViewWebPart (External List)

    External lists can be displayed using the XsltListViewWebPart. Nothing really new here.

    A simple example (of course, do replace the list and view GUIDs):

       1: <WebPartPages:XsltListViewWebPart runat="server" Title="External List" DisplayName="External List" ListName="{C9DD692D-27C6-4B99-A197-F8892F293C04}" ID="external">
       2:     <XmlDefinition>
       3:         <View Name="{DDD613DD-BA38-4DBF-9A89-5502B0152EE1}" Type="HTML" DisplayName="Read List" Url="/Lists/ExternalList/Read List.aspx" BaseViewID="1">
       4:             <Method Name="Read List"/>
       5:         </View>
       6:     </XmlDefinition>
       7:     <Xsl>
       8:         <xsl:stylesheet version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       9:             <xsl:output method="html" indent="no"/>
      10:  
      11:                 <xsl:template match="/dsQueryResponse">
      12:                     <xsl:variable name="Rows" select="Rows/Row"/>
      13:                     Count: <xsl:value-of select="count($Rows)"/>    
      14:                 </xsl:template>
      15:         </xsl:stylesheet>
      16:     </Xsl>
      17: </WebPartPages:XsltListViewWebPart>

    XmlUrlDataSource

    The XmlUrlDataSource is used for invoking REST web services. Similar to  SoapDataSource, you need to pass it the URL, but also the HTTP method and any parameters. I is also usually hosted in a DataFormWebPart.

    Here’s an example of calling the weather service I talked about in another post:

       1: <WebPartPages:DataFormWebPart runat="server" Title="REST" DisplayName="REST" ID="rest">
       2:     <DataSources>
       3:         <SharePoint:XmlUrlDataSource runat="server" AuthType="None" HttpMethod="GET" SelectCommand="http://api.openweathermap.org/data/2.5/weather">
       4:             <SelectParameters>
       5:                 <WebPartPages:DataFormParameter Name="id" ParameterKey="id" PropertyName="ParameterValues" DefaultValue="2740637"/>
       6:                 <WebPartPages:DataFormParameter Name="mode" ParameterKey="mode" PropertyName="ParameterValues" DefaultValue="xml"/>
       7:                 <WebPartPages:DataFormParameter Name="units" ParameterKey="units" PropertyName="ParameterValues" DefaultValue="metric"/>
       8:             </SelectParameters>
       9:         </SharePoint:XmlUrlDataSource>
      10:     </DataSources>
      11:     <Xsl>
      12:         <xsl:stylesheet version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
      13:             <xsl:output method="html" indent="no"/>
      14:  
      15:                 <xsl:template match="/">
      16:                     <xsl:variable name="Rows" select="/current"/>
      17:                     Count: <xsl:value-of select="count($Rows)"/>
      18:                 </xsl:template>
      19:             
      20:         </xsl:stylesheet>
      21:     </Xsl>
      22: </WebPartPages:DataFormWebPart>

    SPSqlDataSource

    The SPSqlDataSource is what SharePoint uses to access a SQL database. If you use SharePoint Designer to add one such data source, you might be surprised to see that it places a SqlDataSource instead. The thing is, SharePoint uses ASP.NET tag mapping to replace any SqlDataSource control for a SPSqlDataSource. Again, we host it inside a DataFormWebPart for easily formatting its contents and you can pass parameters to the SQL.

    An example:

       1: <WebPartPages:DataFormWebPart runat="server" Title="SQL" DisplayName="SQL" ID="sql">
       2:     <DataSources>
       3:         <SharePoint:SPSqlDataSource runat="server" ProviderName="System.Data.SqlClient" ConnectionString="Data Source=servername;User ID=username;Password=password;Initial Catalog=database;" SelectCommand="SELECT * FROM [SomeTable]"/>
       4:     </DataSources>
       5:     <Xsl>
       6:         <xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal">
       7:             <xsl:output method="html" indent="no"/>
       8:  
       9:                 <xsl:template match="/">
      10:                     <xsl:variable name="Rows" select="/dsQueryResponse/NewDataSet/Row"/>
      11:                     Count: <xsl:value-of select="count($Rows)"/>
      12:                 </xsl:template>
      13:         </xsl:stylesheet>
      14:     </Xsl>
      15: </WebPartPages:DataFormWebPart>

    SPCalendarDataSource

    SPCalendarDataSource cannot be used exclusively on the markup, it needs a List property that can only be set through code. It is usually used to populate a SPCalendarView control.

    An example of the markup:

       1: <SharePoint:SPCalendarDataSource runat="server" ID="calendar" ViewType="Week"/>
       2: <SharePoint:SPCalendarView runat="server" DataSourceID="calendar"/>

    Conclusion

    While SPDataSource is generally the most generic and useful control, it cannot do everything, namely, access external sources. For that, we have other options; the AggregateDataSource can be used to bring together data from all of these sources, except SPHierarchyDataSourceControl and SPCalendarDataSource, but you can easily replace these by SPDataSource.

    Let me know if you have any questions!

    Read more...

  • Aggregating Lists in SharePoint

    Another post that could be subtitled “How to do Things in SharePoint By Hand”. This time, combining data sources – lists, in this case, but the same can be applied to other source types, such as SOAP (SoapDataSource) or REST (XmlUrlDataSource) web services, hierarchies of sites (SPHierarchyDataSourceControl), calendar (SPCalendarDataSource), XML files (SPXmlDataSource), BCS external lists (BdcDataSource) or even SQL queries (SPSqlDataSource).

    We will query two lists, using an SPDataSource for each, and then we’ll combine the results with an AggregateDataSource and finally we’ll display the results using DataFormWebPart and XSLT.

    Enough talk, here is the code:

       1: <WebPartPages:DataFormWebPart runat="server" ID="data">
       2:     <DataSources>
       3:         <SharePoint:AggregateDataSource runat="server" ID="aggregates" SeparateRoot="True">
       4:             <Sources>
       5:                 <SharePoint:SPDataSource runat="server" ID="tasks" DataSourceMode="List" UseInternalName="True" UseServerDataFormat="True" SelectCommand="&lt;View><Query></Query></View>">
       6:                     <SelectParameters>
       7:                         <WebPartPages:DataFormParameter Name="ListName" ParameterKey="ListName" PropertyName="ParameterValues" DefaultValue="Tasks"/>
       8:                     </SelectParameters>
       9:                     </SharePoint:SPDataSource>
      10:                 <SharePoint:SPDataSource runat="server" ID="pages" DataSourceMode="List" UseInternalName="True" UseServerDataFormat="True" SelectCommand="&lt;View><Query></Query></View>">
      11:                     <SelectParameters>
      12:                         <WebPartPages:DataFormParameter Name="ListName" ParameterKey="ListName" PropertyName="ParameterValues" DefaultValue="Pages"/>
      13:                     </SelectParameters>
      14:                 </SharePoint:SPDataSource>
      15:             </Sources>
      16:             <Aggregate>
      17:                 <concat name="data source">
      18:                     <datasource name="Tasks" id="0" type="SPList"/>
      19:                     <datasource name="Pages" id="1" type="SPList"/>
      20:                 </concat>
      21:             </Aggregate>
      22:         </SharePoint:AggregateDataSource>               
      23:     </DataSources>              
      24:     <Xsl>            
      25:         <xsl:stylesheet xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal" xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource">
      26:               <xsl:output method="html" indent="no"/>                  
      27:               <xsl:template match="/">                                
      28:                       <xsl:for-each select="/dsQueryResponse/Tasks/Rows/Row">
      29:                           <xsl:variable name="AuthorID" select="@Author.id"/>
      30:                           <p>Task Title: <xsl:value-of select="@Title"/></p>
      31:                           <p>Pages of same author: <xsl:value-of select="count(/dsQueryResponse/Pages/Rows/Row[@Author.id = $AuthorID])"/></p>
      32:                       </xsl:for-each>
      33:                   </xsl:template>                    
      34:         </xsl:stylesheet>
      35:     </Xsl>
      36: </WebPartPages:DataFormWebPart>

    Noteworthy:

    • If you specify the SeparateRoot parameter as true, in the XSL section, you will get two data sources, one for each data source specified as a concat/datasource entry, you will have to access them separately, using syntax /dsQueryResponse/Tasks/Rows/Row, of course, replacing Tasks for the right name, as specified in the concat/datasource’s name attribute;
    • If you don’t specify a value for SeparateRoot, the default is false, which means that the values for both data sources will come together in /dsQueryResponse/Rows/Row; you can sort out what records belong to one list or the other using the syntax /dsQueryResponse/Rows[@agg:source='Pages']/Row; this is made possible by the xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource" entry in the xsl:stylesheet declaration, which I’m sure you hadn’t noticed Smile;
    • I am using DataFormParameters to specify the source for the SPDataSources as a list name, instead of a list id. This makes it more portable;
    • Although I am not filtering the lists, you probably should, there are several ways to do this, which usually include using the SelectCommand together with SelectParameters;
    • In the XSLT, I am iterating through the first list, and for each item, I get one field value (Author = Creation User) and use it to filter the second list. This is the same that SharePoint Designer does when you insert a linked list data source.

    And that’s it. Enjoy!

    Read more...