Sunday, October 30, 2011

Deploy user controls in the SharePoint content database

Normally user controls (*.ascx files) in SharePoint are deployed with farm solutions and the preferred system  (hive) folder for that is the CONTROLTEMPLATES one. User controls can be used for various purposes – for form templates in SharePoint lists, with the new visual web part in SharePoint 2010 or simply to provide reusable visual bits that can be placed in more than one master page files of page layouts. User controls can be quite handy since they provide a nice separation of the presentation logic which is not directly available in the regular web controls – you can use the WebForms markup to produce your HTML output instead of having to deal with that in the code itself.

And to the subject of this posting – is it possible to have user controls (*.ascx) files directly in the content database of your SharePoint web application (site) instead of in the SharePoint hive folder (12/TEMPLATE/CONTROLTEMPLATES or 14/TEMPLATE/CONTROLTEMPLATES). The answer to this question is yes and I am going to demonstrate that shortly. But before that I would want to briefly discuss the motives and reasons that could justify the usage of user controls in the content database and also some possible advantages and disadvantages of this approach. This topic is actually a bit broader than suggested by the posting’s title and it is about the possibility of having a SharePoint custom application that doesn’t use custom assemblies and files deployed to the SharePoint hive folder. Sounds a bit like the new sandbox solutions available in SharePoint 2010 – this is to some extent so, though the code that can be executed is the normal farm version of the SharePoint object model. Unlike the sandbox solutions, there is also a big security implication that I will discuss shortly. The main motivation here is to have a way to place your code directly in your master pages or page layouts by using say the SharePoint designer. So, opposed to the usual way of deploying SharePoint farm solutions with “wsp” files, this is sorts of “SharePoint designer” development and deployment (the boundary between development and deployment with the SharePoint designer is almost non-existing). The other advantage here is that you can push all you code and code updates using the simple content deployment paths and jobs built-in functionality. The bottom line here is that if you use extensively the SharePoint designer and have a SharePoint environment with a publishing and production server using content deployment paths for content synchronization you can consider using this approach.

As for the question of how to use code directly in your master page files and page layouts (maybe you’ve done that many times already with the SharePoint designer) – the answer is simple – inline code blocks:

<script runat="server">

Two big notes here – the first one is the security issue that I mentioned above. Since it is very easy to insert a code block in a page using the SharePoint designer, there is an internal protection in SharePoint – this is the so called “safe mode” for parsing and processing of un-ghosted pages (pages that are in the content database). By default if you have a code block in a page that is un-ghosted (or was created directly in the content database) you will receive an error if you try to open the page. This can be overridden by a setting in the web.config file:

<PageParserPaths><PageParserPath VirtualPath="/_catalogs/masterpage/*" CompilationMode="Always" AllowServerSideScript="True" IncludeSubFolders="true" />
</PageParserPaths>

but keep in mind that this can be a serious security hole. Note the value of the “VirtualPath” attribute containing the location of the Master Page gallery with a wild card meaning that all your master page files and page layouts will be allowed to have code blocks. If you have a site collection under this server relative URL – “/sites/test-site” you will have to specify the full path to its Master Page gallery: “/sites/test-site/_catalogs/masterpage/*” (unless you don’t want to put something as unsecure as “/*”). For a detailed treatment of the SharePoint “safe mode” page processing, the configuration of the “PageParsePath” elements (and also of the “SafeControl” elements) check this MSDN article.

The second note is that the usage of code blocks as opposed to having the code in a code-behind assembly is not considered the very best and recommendable code practice. Apart from that the SharePoint designer is far from Visual Studio in terms of providing good IDE support for code development.

The placing of code blocks inside master pages and page layouts is maybe nothing new for most of you but the main idea of this posting is to demonstrate the using of user controls inside master pages and page layouts. And it will be the user control that will contain the inline code block in this case. Remember that the ascx file of the user control will reside in the content database (it can be uploaded to the Master Page gallery for instance), so it will be subject to the SharePoint “safe mode” processing mode too. And you will need to add some extra configuration bits to the web.config file so that your pages are allowed to use the user controls. You have two ways to enable this in the web.config – the first one is to add a “SafeControl” element like this one:

<SafeControl Src="/_catalogs/masterpage/*" IncludeSubFolders="True" Safe="True" AllowRemoteDesigner="True" SafeAgainstScript="True" />

Note that the value of the “Src” attribute should contain the full server relative URL of the target library in your site collection (e.g. for the “/sites/test-site site collection it will be /sites/test-site/_catalogs/masterpage/*”). The other way (much more unsecure) is to add an extra attribute to the “PageParsePath” element:

AllowUnsafeControls="True"

In this case you won’t need an extra “SafeControl” element.

Note also that although you can deploy your code files (pages and user controls) directly to the content database unlike the SharePoint 2010 sandbox solutions you will need to modify the web.config file of the containing web application, which has its own serious security implication, that I already pointed out.

And let me now demonstrate a sample user control that can be deployed (uploaded) to the Master Page gallery and used by page layouts (wpzone.ascx):

<%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
 
<%@ Control Language="C#"  %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.WebPartPages" %>
 
<script runat="server">
    protected string _wpZoneID;          
    public string WPZoneID { get { return _wpZoneID; } set { _wpZoneID= value; } }
    protected override void OnLoad (EventArgs args)
    {
         base.OnLoad(args);
    }
 
    protected override void Render (HtmlTextWriter writer)
    {
         if (string.IsNullOrEmpty(_wpZoneID)) return;
         Control c = this.NamingContainer.FindControl (_wpZoneID);
         WebPartZone _zone = c as WebPartZone;
         if (_zone == null) return;
         SPWebPartManager mngr = SPWebPartManager.GetCurrentWebPartManager(this.Page) as SPWebPartManager;
         if (mngr == null) return;
         if (!mngr.GetDisplayMode().AllowPageDesign)
         {
             // if we are in display mode - hide the zone control itself
             _zone.Visible = false;
             // and render the web parts directly
             foreach (WebPart part in _zone.WebParts)
             {
                 part.RenderControl(writer);
             }
         }
     }
script> 


And here is how you can use this control in a page layout (the technique is identical for master pages and regular web part pages) – first you need the control “Register” directive at the top of the page:


<%@ Register TagPrefix="Test" TagName="WPZone" Src="~SiteCollection/_catalogs/masterpage/wpzone.ascx" %>


Note the value of the “Src” attribute – you can use the handy “~SiteCollection” URL token here instead of having to hard-code the server relative URL of the containing site collection. And then the declaration of the user control’s tag inside the markup of the page:


<Test:WPZone runat="server" WPZoneID="TopZone"/>


This simple user control modifies the default rendering of the WebPartZone control whose ID is specified in its “WPZoneiD” property. When the page is in display mode the control hides the web part zone and renders only the web parts that belong to this web part zone. This effectively hides the markup that is produces by the web part zone (several nested HTML table elements) and also the chrome (or frame) headers of the web parts. You should place the control just before the declaration of the web part zone control whose rendering you want to modify.


It is also possible to use user controls just as regular classes with helper methods that can be reused in various places – in this case the user control will not have visual behavior. For example you can create a user control like this (mylib.ascx):


<%@ Assembly Name="Microsoft.SharePoint, Version=14.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint.Publishing, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
 
<%@ Control Language="C#" ClassName="MyLib" %>
<script runat="server">
 public static string SayHello(string who)
 {
  return "Hello " + who;
 }
 
 public string SayHi (string who)
 {
  return "Hi " + who;
 }
script> 


Note the “ClassName” attribute in the “Control” directive – this specifies the name of the class that will be generated from the ascx file. You will be able to use this generated class by this name in the code blocks of the pages that use the control (or in other user controls). To use the user control in this way you will only have to put the control “Register” directive at the top of the page. And then in a code block you can have something like:


<script runat="server">
protected override void OnLoad(EventArgs args)
{
 base.OnLoad(args);
 this.txtBox.Text = MyLib.SayHello("John");
}
script>