See the attachment to this post for the full source code.

When I first started developing with SharePoint, I wanted to learn how to do the things that I did in ASP.NET.  Many of those things easily transfer, thanks to Visual Studio 2010.  For instance, it is very easy to use a WYSIWYG designer to create a web part using the new Visual Web Part template.  It is also very easy to create an Application Page.  What is not immediately evident is how to create a site page.  In this post, we’ll see how to create and deploy a page template, instances of the template, and how to enable code-behind for a site page.

Application Pages versus Site Pages

Using the most basic definition, a site page is customizable by an end user while an application page is not.  That means that a user can pop open SharePoint Designer 2010 and make changes to a site page, but they cannot do this with an application page.  So, what do we mean by “customizable”?  When we open SharePoint Designer 2010 and make changes to a site page, those changes are stored in the database.  The next time we request the page, the page is loaded from the database.  There are more differences than this, but the key difference is really the ability to customize a page.

Coming from an ASP.NET background, we are much more used to coding application pages.  We create a page, drag and drop some controls, write some backend code, hit F5, and see our page do something.  One of the benefits of SharePoint is that we can create page templates that allow an end user to make changes to the page without requiring a developer.  This means they can add web parts, add JavaScript, do neat things with jQuery and XSLT… all the kind of stuff you see on http://endusersharepoint.com

As a developer, you can do some pretty cool stuff to enable the end user with site page templates.

Creating the Site Page

To start with, create an empty SharePoint 2010 project.  I called mine “SampleToDeployAPage”.  When prompted, choose to create this as a farm solution rather than a sandboxed solution.  Once you have the project created, right-click and add a new Module called “CustomPages”.  A folder is created called “CustomPages” with a file called “Sample.txt”.  Rename Sample.txt to “MyPageTemplate.aspx”.  Add the following markup to it:

   1:  <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
   2:  <%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
   3:  <%@ Register Tagprefix="SharePoint" 
   4:          Namespace="Microsoft.SharePoint.WebControls" 
   5:          Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
   6:  <%@ Register Tagprefix="Utilities" 
   7:          Namespace="Microsoft.SharePoint.Utilities" 
   8:          Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
   9:  <%@ Register Tagprefix="asp" 
  10:          Namespace="System.Web.UI" 
  11:          Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" %>
  12:  <%@ Import Namespace="Microsoft.SharePoint" %>
  13:  <%@ Assembly 
  14:          Name="Microsoft.Web.CommandUI, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
  15:   
  16:  <%@ Page 
  17:      Language="C#"     
  18:      CodeBehind="MyPageTemplate.aspx.cs" 
  19:      Inherits="SampleToDeployAPage.MyPageTemplate, $SharePoint.Project.AssemblyFullName$" 
  20:      masterpagefile="~masterurl/default.master" 
  21:      title="Testing This Page" 
  22:      meta:progid="SharePoint.WebPartPage.Document" %>
  23:   
  24:  <asp:Content id="Content1" runat="server" contentplaceholderid="PlaceHolderMain">
  25:          <asp:Button runat="server" ID="button1" OnClick="Button_Click" />
  26:          <asp:Label runat="server" ID="label1"/>
  27:          <div></div>
  28:          <div></div>
  29:          For more information, visit 
  30:          <a href="http://msdn.microsoft.com/en-us/library/bb964680(v=office.12).aspx">
  31:          Chapter 3: Pages and Design (Part 1 of 2)</a>
  32:  </asp:Content>

It looks like a lot of code, but there’s really very little there.  Line 1 defines the assembly, and Visual Studio 2010 will replace the token placeholder with the full 5-part name of our assembly.  Lines 2 and 12 import a specific namespace (like a using statement in C#), and lines 3-11 register namespace prefixes.  Line 19 references our assembly from the Global Assembly Cache so that SharePoint knows where to find the the class called SampleToDeployAPage.MyPageTemplate.

Now that you’ve created the markup, let’s create the code-behind.  Right click the CustomPages folder and add a new Class called MyPageTemplate.aspx.cs.  We’ll keep this one short:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI.WebControls;

namespace SampleToDeployAPage
{
    public class MyPageTemplate : WebPartPage
    {
        protected Button button1;
        protected Label label1;

        protected void Page_Load(object sender, EventArgs e)
        {
        }

        protected void Button_Click(object sender, EventArgs e)
        {
            label1.Text = System.DateTime.Now.ToLongTimeString();
        }
    }
}

Looking at the two samples together, we are simply creating markup that references an assembly in the GAC.  When we click a button, our Button_Click handler is called, and we can set the Text property of a label control in the markup.  This is basic stuff to an ASP.NET developer.  The result in our Solution Explorer pane should look kind of like the following:

image

See the attachment to this post for full source code.

Provisioning a Page Using Visual Studio 2010

Now that we’ve created our page, we need to deploy it to Visual Studio 2010.  When we created our module called “CustomPages”, a file called “elements.xml” was created for us.  That file tells SharePoint how to deploy our page to all of the web front end servers.  We can even provision multiple instances of our template.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="CustomPages"
          Url="SitePages"
          Path="CustomPages">
    <File Url="MyPageTemplate.aspx"
          Name="SamplePage1.aspx"
          Type="Ghostable"/>
    <File Url="MyPageTemplate.aspx"
          Name="SamplePage2.aspx"
          Type="Ghostable"/>
    <File Url="MyPageTemplate.aspx"
          Name="SamplePage3.aspx"
          Type="Ghostable"/>
  </Module>
</Elements>

In this example, we are provisioning three instances of our page based on the single page template.  We are provisioning this to the SitePages library for our site.  If we deploy the solution as-is, we should see the following in SharePoint Designer 2010.

image

I highly recommend reading Understanding and Creating Customized and Uncustomized Files in Windows SharePoint Services 3.0 for deeper explanation of what is happening here.

Adding Links to the UI for Our Pages

At this point, we can right click the page and choose “Preview in Browser” to see the page.  The URL on my box is http://kirke1/sites/team/SitePages/SamplePage3.aspx, but this may be different for your environment.  I wanted to add a few links to the Site Actions menu, and the easiest way to do this is to use the freely available Community Kit for SharePoint: Development Tools Edition.  It includes a Custom Action project item that makes creating the following XML a snap, but you can simply cut and paste and add the following to the Elements.xml file (as a child of the Elements node) that we created previously.

  <CustomAction Description="Custom action for page 1"
                GroupId="SiteActions"
                Id="MySiteAction1"
                Location="Microsoft.SharePoint.StandardMenu"
                Sequence="1000"
                Title="MyCustomAction">
    <UrlAction Url="{SiteUrl}/SitePages/SamplePage1.aspx" />
  </CustomAction>
  <CustomAction Description="Custom action for page 2"
                GroupId="SiteActions"
                Id="MySiteAction2"
                Location="Microsoft.SharePoint.StandardMenu"
                Sequence="1001"
                Title="MyCustomAction">
    <UrlAction Url="{SiteUrl}/SitePages/SamplePage2.aspx" />
  </CustomAction>
  <CustomAction Description="Custom action for page 3"
                GroupId="SiteActions"
                Id="MySiteAction3"
                Location="Microsoft.SharePoint.StandardMenu"
                Sequence="1002"
                Title="MyCustomAction">
    <UrlAction Url="{SiteUrl}/SitePages/SamplePage3.aspx" />
  </CustomAction>

Notice the “SiteUrl” token in the Url attribute of the UrlAction elements that we defined.  There are several token placeholders that you can use to avoid hardcoding paths in your solutions.

See the attachment to this post for full source code.

Marking Our Page Template as Safe

If we deployed everything right now, it would work.  By default, a feature was created to deploy our module, and that feature is scoped to Web, meaning it is scoped to an individual site.  And when we deploy our code and feature definitions, we will see links in the Site Actions menu as advertised, and the pages will render fine.

When we try to customize one of our new pages with SharePoint Designer 2010, we will get a series of errors.

For example, in SharePoint Designer 2010, go to the Site Pages node, right-click SamplePage3.aspx, and choose “Edit File in Advanced Mode”, you can edit the file, but when you save it you will get a series of errors.  Save it as a new file called SamplePage4.aspx and try to preview it in the browser, you are met with the following error:

The base type 'SampleToDeployAPage.MyPageTemplate' is not allowed for this page. The type is not registered as safe.

Remember that SharePoint is built upon ASP.NET.  SharePoint is like ASP.NET with a healthy dose of Code Access Security layered on top.  So, we need to do some security work to tell SharePoint that it’s OK for our page to use code behind.

Right-click the Package node in Visual Studio 2010’s Solution Explorer pane and choose View Template.

image

This lets us add a new configuration entry that marks our code as safe by adding a SafeControls entry to web.config.

  <Assemblies>
    <Assembly Location="SampleToDeployAPage.dll"
              DeploymentTarget="GlobalAssemblyCache">
      <SafeControls>
        <SafeControl 
               Assembly="$SharePoint.Project.AssemblyFullName$" 
               Namespace="SampleToDeployAPage" 
               TypeName="MyPageTemplate" Safe="True"/>
      </SafeControls>
    </Assembly>
  </Assemblies>

Yeah, that one’s a freebie :)  This is a pretty cool trick on how to mark types in the current assembly as SafeControls. 

Update:  Waldek Mastykarz pointed out that there’s even an EASIER way to do add SafeControls.  Click the CustomPages module and look at the properties window.  There is a collection called “SafeControls”.  Click that, and add a new SafeControl entry.  I didn’t know this was there, thanks Waldek!

image

Adding web.config Modifications Using SPWebConfigModification

If we deployed everything right now, we’d still get another error if we tried to customize the page and preview it in a browser.  This time, the error has to do with a security setting that enables certain pages to have code behind.

image

I blogged on this some time ago (see Code-blocks are not allowed within this file: Using Server-Side Code with SharePoint).  The problem is that SharePoint has a setting that disallows server-side code with pages.  This is a security feature that is good (you really don’t want end users to arbitrarily inject server-side code), but there may be cases where you are OK with some of your users having this capability.  For instance, you can have a page that is only visible to a small team within your enterprise, and one of the team members is very technical and wants to provide some custom code for SharePoint.  Party on, have fun with it, it saves my team from having to write that code.

To enable this scenario (and enable the Button_Click event handler in our code), we need to add an entry to web.config.  Knowing that we can’t just go to every front-end web server and make the modifications (any admin worth his salt should slap you silly for even thinking about hand-modifying multiple web.config files in a production farm), we should provide this as part of our solution. 

In the Solution Explorer, you will see a node called Features.  Right-click this node and choose “Add Feature”.  That will create a new feature called Feature2.  Double-click this node to bring up the designer for the feature and change its scope to WebApplication.

image

After changing the scope to WebApplication, right-click the feature and choose “Add Event Receiver”.  This will create a code file where you can handle events related to your feature.  We will add code that will make modifications to web.config, adding a new entry to PageParserPaths when the feature is activated, and removing it when the feature is deactivated.

What we want to add is the following:

<SafeMode MaxControls="200" CallStack="false" DirectFileDependencies="10" TotalFileDependencies="50" AllowPageLevelTrace="false">
    <PageParserPaths>
       <PageParserPath VirtualPath="/SitePages/SamplePage3.aspx*" CompilationMode="Always" AllowServerSideScript="true" IncludeSubFolders="true" />
    </PageParserPaths>
</SafeMode>

We want to add an entry into web.config that allows the path SitePages/SamplePage3.aspx as one of the pages that allows server-side scripting.  Additionally, we don’t want to add this entry into web.config multiple times, and we want to remove this entry when our feature is deactivated.  Below is the code that enables this.

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Administration;
using System.Collections.ObjectModel;

namespace SampleToDeployAPage.Features.Feature2
{
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>

    [Guid("122ec36d-8fbf-454b-a514-b0d9ef30af43")]
    public class Feature2EventReceiver : SPFeatureReceiver
    {        
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApplication = properties.Feature.Parent as SPWebApplication;


            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                SPWebConfigModification mod = new SPWebConfigModification();

                mod.Path = "configuration/SharePoint/SafeMode/PageParserPaths";
                mod.Name = "PageParserPath[@VirtualPath='/SitePages/SamplePage3.aspx']";
                mod.Owner = "SampleToDeployAPage";
                mod.Sequence = 0;
                mod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
                mod.Value = "<PageParserPath VirtualPath='/SitePages/SamplePage3.aspx' CompilationMode='Always' AllowServerSideScript='true' />";

                webApplication.WebConfigModifications.Add(mod);
                webApplication.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
                webApplication.Update();
            });

        }
        

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApplication = properties.Feature.Parent as SPWebApplication;

            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                Collection<SPWebConfigModification> mods = webApplication.WebConfigModifications;
                int initialModificationsCount = mods.Count;

                for (int i = initialModificationsCount - 1; i >= 0; i--)
                {
                    if (mods[i].Owner == "SampleToDeployAPage")
                    {
                        SPWebConfigModification modToRemove = mods[i];
                        mods.Remove(modToRemove);
                    }
                }

                if (initialModificationsCount > mods.Count)
                {
                    webApplication.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
                    webApplication.Update();
                }

            });
        }

    }
}

There is a bit of code to digest here, I am not going to explain it all (maybe in a future blog post).  See the resources section below, I did a bit of research to make it finally work.  Many thanks to Kirk Liehmon for his insight here.

See the attachment to this post for full source code.

Conclusion

That’s it… we’ve defined our page template, our elements.xml file to deploy the instances of the page template, a custom action so that the page shows up in the UI somewhere, we altered the solution manifest to include a SafeControls entry in web.config, and we event modified web.config to include a PageParserPaths entry for our page so that we could use code-behind.  We now have a page that enables customization from within SharePoint Designer 2010. 

For those wondering why this was so much work than ASP.NET, think about it: We create a page template that an end user can load into a tool and provide new widgets on the screen.  In ASP.NET, we could have altered the master page or the page theme, but providing this level of end-user customization is just plain unheard of.  This is a huge plus for using SharePoint as a foundation for ASP.NET application development.  Even further, think about the security implications… we enabled a user to open a tool on a specific resource (thank goodness, not all resources) to provide server-side script for a page.  This is simply awesome to think about how end users can gain a new level of productivity while freeing the developers from the mundane to focus on more strategic initiatives.

For More Information

Pages and Design (Part 1 of 2) – An excerpt from Inside Microsoft Windows SharePoint Services 3.0 by Ted Pattison and Dan Larson.  This is an excellent book, it is very highly recommended.  All of the content still applies to learning SharePoint 2010. 

Understanding and Creating Customized and Uncustomized Files in Windows SharePoint Services 3.0 – an excellent article by Andrew Connell that goes into depth on how page customization works with examples.

Community Kit for SharePoint: Development Tools Edition – a set of free add-ons to Visual Studio 2010 that make SharePoint development even easier. 

Replaceable Parameters – list of replaceable token parameters in Visual Studio 2010 for SharePoint 2010 development.

 

UrlTokens of the CustomAction Feature – tokens that can be used with UrlAction for CustomActions

Force Visual Studio 2010 to Add a SafeControl Entry – This is a real gem, you have to bookmark this one.  Given the current project in Visual Studio 2010, this shows how to add a SafeControl entry for a type in your project.

SharePoint CustomAction Identifiers – John Holliday did an awesome job listing the various CustomAction identifiers for SharePoint.  This list is for SharePoint 2007, but highly applicable to SharePoint 2010.

 

 

Web.Config and SPWebConfigModifications Resources

I can’t thank Kirk Liehmon from ThreeWill enough for pointing out that the feature needed to be scoped to the application level instead of the Web level to deploy web.config modifications.  Below is a chronological list of links that I followed while learning how to create a web.config modification in SharePoint.

http://geekswithblogs.net/juanlarios/archive/2009/07/14/web-config-changes-security-issue.aspx

http://micktechblog.blogspot.com/2009/10/sharepoint-feature-that-adds-webconfig.html

http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spsecurity.runwithelevatedprivileges.aspx

http://weblogs.asp.net/wesleybakker/archive/2009/01/21/web.config-modifications-with-a-sharepoint-feature.aspx – this is where the lightbulb went off, I IM’d Kirk Liehmon, and it finally made sense.