Thursday 12 July 2007

Modifying 'system' pages in SharePoint safely - with sample code

In a presentation I gave recently I talked about modifying admin screens within SharePoint. By this, I mean areas such as the site admin area and the central admin area:

Site admin:


Central admin:



Now, if your boss (or your users) asks you to add or change functionality in these areas, how would you handle that?

Well, you'd probably start off by looking to see how SharePoint implements 'system' pages like this. You'd then find they are just standard .aspx files in the 12\TEMPLATE\ADMIN folder on the filesystem. You'd also find that if you make a change to such a file, the changes are immediately reflected on the site - sure, some of the code is in a compiled assembly which is obfuscated (so Reflector doesn't help), but otherwise there's no mysterious abstractions, no caching, it's that simple. So changing the functionality or adding a link is a matter of changing the .aspx page right?

The problem with this approach is that modifying core files in this way is unsupported. This is because when a service pack or hotfix gets applied, your system will be in an inconsistent state and at best your changes will be overwritten. So a better way to implement such changes is to:

  • take a copy of the original file
  • modify the copy to include your customization
  • create a SharePoint Feature (wrapped in a Solution package [.wsp]) to deploy your customized page to the 12 hive, and also add/modify the link in SharePoint to point to your customized page

The end result of this is that you have deployed your change without modifying any of the original files, and are therefore entirely supported. Additionally, because Features can be activated and deactivated with the click of a button, if you want to revert to the original functionality that's all it takes.

Let's look at an example - I'll link to all the code used here at the end of the article. Imagine your users are asking you to change the Recycle Bin screen so that it only displays items the currently logged on user has deleted - perhaps there's a manual document clean up exercise happening. And let's also say that after the exercise is complete, the users would like to revert back to the original functionality. (Now remember, what I'm demonstrating here is the mechanism for making customizations like this, so whilst this particular change might be unlikely in your organization, you might have other customizations which would help your users.)

So, first we need to modify the functionality of the Recycle Bin page to add the filtering. As mentioned earlier, we don't want to amend the original page, so we take a copy and modify that. In my example, I'm adding a script block to the code in front (since I don't have access to Microsoft's source code for the code-behind) to effectively perform the filtering at the UI level, which is fine for this example. Once we've created the page we want to use, we're ready to create the Feature and Solution to add our page to SharePoint.

Let's start with the manifest.xml file for the solution - this is important because this is how we actually deploy our custom .aspx page to the appropriate place within the SharePoint files. (Remember all this is available for download, the link is at the end of the article.)

<Solution xmlns="http://schemas.microsoft.com/sharepoint/" SolutionId="AA7EF934-C993-4f91-8AF4-0FB1D45927FA">

  <FeatureManifests>

    <FeatureManifest Location="COB.Demos.MyRecycleBinItems\feature.xml" />

  </FeatureManifests>

  <TemplateFiles>

    <TemplateFile Location="LAYOUTS\Custom\MyRecycleBinItems.aspx" />

  </TemplateFiles>

</Solution>


Notice what we are doing here is specifying that our custom .aspx page should be deployed to a 'Custom' subfolder within the 'LAYOUTS' directory. This is a good idea because it keeps our files separate from the core SharePoint files.

Then we have a fairly standard feature.xml file specifying the Feature identification info:

<Feature xmlns="http://schemas.microsoft.com/sharepoint/" Id="29D8B365-6024-4b1a-84D2-FF789FE56355"

        Title="COB.Demos.MyRecycleBinItems"

        Description="Adds a custom page to the Site Settings area to show items in the Recycle Bin which were deleted by the current user."

        Scope="Site" Hidden="FALSE"

        Version="1.0.0.0">

  <ElementManifests>

    <ElementManifest Location="elements.xml"/>

  </ElementManifests>

</Feature>


In this case we're specifying that our Feature is scoped at site collection-level, so we will modify all sites in the site collection.

Finally we have the our Elements file, which specifies what this Feature consists of. In this case, the key is the use of the CustomAction element - this can be used to add a link (i.e. an action) somewhere in SharePoint.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <HideCustomAction Id="HideDeletedItems"

                    HideActionId="DeletedItems"

                    GroupId="SiteCollectionAdmin"

                    Location="Microsoft.SharePoint.SiteSettings" />

  <CustomAction Id="MyDeletedItems"

                GroupId="SiteCollectionAdmin"

                Location="Microsoft.SharePoint.SiteSettings"

                Sequence="10"

                Title="My recycle bin items"

                Rights="ManageWeb,BrowseUserInfo">

    <UrlAction Url="_layouts/custom/MyRecycleBinItems.aspx" />

  </CustomAction>

</Elements>


Let's walk through this file in detail. The first thing you notice is that we're using a 'HideCustomAction' element. You guessed it, this is used to hide a link in the SharePoint UI. Since we are effectively replacing a link, we actually need to hide the original one and supply our replacement, and this is done by using these 2 elements together. We assign the ID ourselves (if we want to refer to this 'HideCustomAction' elsewhere, this is the value we'll use), but all the other attributes of this tag must correspond to those specified for this action in the Feature Microsoft created to add the link to SharePoint in the first place. So in case you've not come across this yet, much of the original functionality in SharePoint is actually implemented using Features too. The way to find these values is to search in the 12\Template\Features folder. For this particular action, the most reliable thing to search for is the URL of the page, since any 'CustomAction' tag specifies the URL. Since we can find that the URL for the original Recycle Bin page is '/_layouts/AdminRecycleBin.aspx', searching for this finds the file containing the details of the original link. These are then used for our 'HideCustomAction' tag.

Next we specify our 'CustomAction' tag, again using many of the same values since we are replacing an existing action rather than adding a new one. The values we change are for the link title and URL, to point to our replacement page. Note also you can declaratively specify what permissions levels are required to see this link.

Once these files are made into a Solution package, deploying the Solution and activating the Feature has the following effects:

The link on the site settings page has changed from the original (on the left) to our replacement (on the right):


 A 'Custom' folder has been created in the '12\Layouts' directory and our custom .aspx page (MyRecycleBinItems.aspx) has been deployed to this folder. And of course, when a user clicks the link, the modified page has the functionality to only display items they have deleted:



The key thing of course, is that we've 'customized SharePoint functionality' without modifying any original SharePoint files - hence we are are 100% supported and can be confident our customizations won't cause any issues for service packs etc.

On the same lines, the sample stuff has a 2nd example - here we modify the 'Create Site Collection' page in Central Admin to display a guidance message to the SharePoint administrators. The idea is to encourage them to stop and think and perhaps consult the document containing the organization's policy on creating site collections:


Hopefully you might find this useful if you need to customize SharePoint in this way. The full set of files, plus the slides for the presentation I gave on this, are available at:

http://sharepointchris.googlepages.com/customizingsharepointsupportedway

In the next post, I'll be supplying some reference information which will help you make this type of modification in other areas of SharePoint. An important aspect of customizing SharePoint in this way is knowing exactly where and how the 'CustomAction' tag can be used, so that's what we'll look at.

2 comments:

Anonymous said...

Hello Chris,

First of all let me say this is a gr8 article.

Need your help and suggestion regarding profile management customization-
1. How can I replace ProfileEdit.aspx page with my custom Edit Page?
2. I need to provide one interface to HR person to create/edit and delete profile from SP.

Would appreciate you help.

Chris O'Brien said...

@Anonymous,

I would create a custom application page to manage user profiles, and then provide a link to it on the Site Actions menu to HR users with appropriate permissions. You can add links to the Site Actions menu using a Custom Action.

You'll also need to take care of security context for the custom page - it will need to run under the context of an account which has appropriate 'manage user profiles' permission at the SSP.

HTH,

Chris.