Friday 3 May 2013

SharePoint 2013 - Delegate Controls - Beware of throwbacks to the SharePoint 2010 14 Hive

Delegate controls are particularly useful in SharePoint when you want to add functionality to all pages within your site without changing the master page. For example, if you want to add an external JavaScript library (such as JQuery) to every page, you can do this easily by registering a delegate control. An example of this approach can be seen here: http://blogs.msdn.com/b/kaevans/archive/2011/04/06/adding-jquery-to-every-page-in-sharepoint-with-delegate-controls.aspx

A delegate control is registered through an Elements.xml with a ControlSrc property pointing to your custom delegate control.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Control Id="AdditionalPageHead"
           Sequence="100"
           ControlSrc="~/_controltemplates/MyAppName.Core/MyDelegateControl.ascx" />
</Elements>
Problem is, the above code will work in SharePoint 2010 but NOT in SharePoint 2013. You will get an exception like the following:
DelegateControl: Exception thrown while building custom control 'Microsoft.SharePoint.SPControlElement': System.Web.HttpException (0x80004005): The file '/_controltemplates/MyAppName.Core/MyDelegateControl.ascx' does not exist.    
 at System.Web.UI.Util.CheckVirtualFileExists(VirtualPath virtualPath)    
 at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)    
 at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean throwIfNotFound, Boolean ensureIsUpToDate)    
 at System.Web.Compilation.BuildManager.GetVPathBuildResult(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile, Boolean ensureIsUpToDate)    
 at System.Web.UI.TemplateControl.LoadControl(VirtualPath virtualPath)    
 at Microsoft.SharePoint.Utilities.SPUtility.CreateUserControlFromVirtualPath(TemplateControl tctlPage, String sControlPath)    
 at Microsoft.SharePoint.SPControlElement.BuildCustomControl(TemplateControl tctlPage, String sControlAssembly, String sControlClass, String sControlSrc, XmlNode xnElementDefinition, SPFeatureDefinition featdefElement, String sElementId)    
 at Microsoft.SharePoint.WebControls.DelegateControl.BuildCustomControlResilient(SPControlElement ctlelemDefinition)

The control itself DOES exist - so this is a somewhat misleading exception. Looking at this issue more closely, SysInternals ProcessMon will show you the way:



Looking closely, it is actually referencing the 14 Hive rather than the 15 Hive as a throwback to SharePoint 2010. So to fix this, you simply need to update your ControlSrc to include 15 in the path:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Control Id="AdditionalPageHead"
           Sequence="100"
           ControlSrc="~/_controltemplates/15/MyAppName.Core/MyDelegateControl.ascx" />
</Elements>
As an alternative, you could also use a Module and a custom action for registering a ScriptLink to something like jQuery or knockout.js.

DDK