How To Adjust Sharepoint OOTB Webparts Relative Paths

While working on some content query webparts (CQWP), I had the need to apply my own XSLT for styling. So, after building my custom XSLT, I thought I have finished the hard part and that everything is almost ready. Unfortunately this wasn't true.

In my ".webpart" file I needed to set the path for my custom XSLT and it popped into my head that I can use the "~sitecollection" token to be replaced by the real site collection URL. So, I did it and started testing my webpart. Unfortunately, it didn't work :(

The webpart showed me an error and after tracing the error I figured out that the webpart couldn't recognize the XSLT file path because the "~sitecollection" token wasn't resolved to the real site collection URL.

After searching, I found out that this is a known issue as the "~sitecollection" token is only resolved when it is found in some webpart properties but not all of them. The XSLT path property is one of those which are not resolved.

So, to fix this issue, I kept the "~sitecollection" token in the XSLT and wrote some code in the feature activation to replace this token with the real site collection URL. This way, the XSLT path will be recognized by the webpart and the webpart will work.

Now, it is time to see some code.


The ".webpart" file (just a sample of the file)
<webParts>
 <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
  <metaData>
   <type name="Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart, Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" />
   <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
  </metaData>
  <data>
   <properties>
    <property name="Title" type="string">OOTB WebPart</property>
    <property name="ItemXslLink" type="string">~sitecollection/Style Library/XSL Style Sheets/OOTB_WebPart_ItemStyle.xsl</property>
    <property name="MainXslLink" type="string">~sitecollection/Style Library/XSL Style Sheets/ContentQueryMain_OOTB_WebPart.xsl</property>
   </properties>
  </data>
 </webPart>
</webParts>

The "Elements.xml" file
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="NewsWebParts" Url="_catalogs/wp" RootWebOnly="TRUE">
    <File Path="OOTB_WebPart.webpart" Url="OOTB_WebPart.webpart" Type="GhostableInLibrary" IgnoreIfAlreadyExists="TRUE">
      <Property Name="Group" Value="MyWebParts" />
      <Property Name="Title" Value="OOTB WebPart" />
    </File>
  </Module>
</Elements>

The feature activation code
using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;

namespace DevelopmentSimplyPut.Features.WebParts
{
    [Guid("74b0dfd2-b906-4124-801d-adbd750579c6")]
    public class WebPartsEventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            AdjustSiteRelativePaths(properties, "OOTB_WebPart.webpart");
        }
  
  public static void AdjustSiteRelativePaths(SPFeatureReceiverProperties featureProp, string webPartName)
  {
   SPSite site = null;

   if (featureProp.Feature.Parent is SPSite)
   {
    site = featureProp.Feature.Parent as SPSite;
   }
   else
   {
    site = ((SPWeb)featureProp.Feature.Parent).Site;
   }

   if (site != null)
   {
    SPList webPartsGallery = site.GetCatalog(SPListTemplateType.WebPartCatalog);
    SPListItemCollection allWebParts = webPartsGallery.Items;

    if (!webPartName.EndsWith(".webpart", StringComparison.OrdinalIgnoreCase))
    {
     webPartName += ".webpart";
    }

    SPListItem webPart =
    (
     from SPListItem wp
     in allWebParts
     where wp.File.Name == webPartName
     select wp
    ).SingleOrDefault();

    if (webPart != null)
    {
     string siteCollectionUrl = site.ServerRelativeUrl;
     if (!siteCollectionUrl.EndsWith("/", StringComparison.OrdinalIgnoreCase))
     {
      siteCollectionUrl += "/";
     }

     string fileContents = Encoding.UTF8.GetString(webPart.File.OpenBinary());
     fileContents = fileContents.Replace(@"~sitecollection/", siteCollectionUrl);
     webPart.File.SaveBinary(Encoding.UTF8.GetBytes(fileContents));
    }
   }
  }
    }
}


So, now, whenever the feature provisioning the webpart is activated, the webpart in the webparts gallery will be updated with the new ".webpart" file, then the "~sitecollection" token will be replaced with the real site collection  URL.

That's it, mission accomplished.