Many SharePoint projects require migrating existing functionality and content from the client's existing systems to a new SharePoint site. While content migration is more straight forward, existing functionality which is usually in the shape of standalone aspx pages can be difficult to accommodate within SharePoint. Here I will try to identify the options available to achieve this.
Note: Some parts of this post will be technically challenging so I would recommend that you read the whole post before getting started.
If you have functionality that is in the shape of user control or a stand alone aspx page, you should consider using the SmartPart Web Part . Once installed, this web part can be added to any web part zone and load user controls from a set directory. This means that you can have a different user control for each form or other types of functionality you want to provide. It makes the management of forms and UI components manageable by the end users. Since SharePoint lets you create custom page layouts, you can specify exactly how the web part zones live in relation to other parts of the page such as other content placeholders and web part zones. You can find information about the Smart Part Web Part here.
Of course you could just drop the control onto a custom page layout and this would work the same but you'd not have to have a different page layout for every flavour of functionality you aim to provide.
It is also possible that you may need to migrate a bunch of interrelated aspx pages into SharePoint. This is a bit more trickier than the solution above . You can always re-factor the code to live inside one ascx control and then use the SmartPart web part to add it to a page instance however there are sometimes scenarios where this would take too long. If his is the case then you'll find that you'll need to put in a bit of work to make SharePoint accommodate your standalone pages into its navigation as well as inheriting the look and feel from the rest of the site.
If this is your only option then you should think about placing your scattered aspx pages in the layouts folder. Maybe in a similar structure to the following:
Layouts [Folder]> CustomTools [Folder] > MyTool [Folder]
This will help your code differentiate between the different tools that you might add in the future.
Lets get started. Your first task will be to build a master page - layout page relationship between your standalone aspx pages and your SharePoint site master page. You need to do this programmatically for all aspx pages that need to be accommodated into your SharePoint site. Of course, the best way to do this would be to create a custom page class that inherits from System.Web.UI.Page, then in it's OnPreInit(EventsArgs e) event, you need to get a handle on the current SPWeb from the current Context object. The SPWeb class contains a property called CustomMasterUrl which you need to assign to your class's MasterPageFile property (inherited from System.Web.UI.Page) . Your code should look similar to the following:
using System;
using System.Web.UI;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
namespace MyProject
{
public class ExampleBasePage : System.Web.UI.Page
{
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
string myCustomMasterpageURL = SPControl.GetContextWeb(Context).CustomMasterUrl;
if(!string.IsNullOrEmpty(myCustomMasterpageURL)
{
this.MasterPageFile = myCustomMasterpageURL;
}
}
}
}
Then you can provide all standalone aspx pages with the correct masterpage and therefore the correct look and feel by making them inherit from your custom base page .
namespace MyProject
{
public class MyStandAlonePage : MyProject.ExampleBasePage
{
}
}
You'll also need to make sure you prefix all links between the pages with HttpContext.Current.Request.RawUrl. This is to retain the context of the sub site you're in. If you don't do this, the next standalone linked page will load but it will load under the root context which will confuse the navigation.
The thing to do next would be to make sure that all your aspx files implement the correct placeholders (in relation to the master page). If you don't do this, your standalone aspx page content will not render. I'll assume you know how to use placeholders referenced from a master page.
Once you've done this and deployed your solution, you should be able to browse to your standalone aspx and they should load just fine with the correct look and feel and master page selected.
To browse to the page(s) you'll need to browse directly to the layouts folder, the url should look like the following:
http://mysharepointsite/_layouts/customtools/mytool/mylandingpage.aspx
If at this stage you if don't need to fix some typical aspx errors, your page should load fine. You'll notice that even though your page loads, it does not appear in the SharePoint navigation. This is due to the site map not having any knowledge of your standalone pages. This is where you'll need to create a custom site map provider. Here is a link that will explain how to do this in more debt . In a nutshell, after adding entries into the web.config and changing properties on the navigation controls in the master page file , you'll need to write code to make your site provider accommodate your standalone aspx pages.
I am going to assume that you'll want one entry in the navigation to represent all of the pages in your 'MyTool' folder. If you have different requirements then you'll need to adapt to a different strategy to dealing with this situation.
SharePoint 2007 allows you to add a custom links to the navigation. Once you do this, the custom link you've added gets added as a node to the sitemap. Your sitemap code will need to make a judgement on which node should the current url get represented by. Since you'll be adding one navigation link to represent your tool, the link that you add should point to the landing page of the tool.
You custom site map provider class will need to inherit from Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider . You'll need to provide the CurrentNode property as this is the property used to identify the current node. Whatever node you return from this property will be selected as the current node.
In pesudo, the get section of the CurrentNode property code should follow this logic:
Get the current list item from SPContext.Current.ListItem
If current list item is not null
Then
Return node in normal way
Else
If the current url contains '/_layouts/customtools/'
Then
Loop though sitemap nodes to find exact match with current url
If match found
Then
Return found node
Else
Trim off the filename from the end of the current url and find a node that contains this url
If match found
Return partially matching node
End
End
End
End
The code for this should look like the following:
using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Navigation;
namespace MyProject
{
public class MySiteMapProvider : PortalSiteMapProvider
{
public override System.Web.SiteMapNode CurrentNode
{
get
{
SPListItem listItem = SPContext.Current.ListItem;
SiteMapNode nodeToReturn = base.CurrentNode;
try
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite spweb = new SPSite(SPContext.Current.Web.Url)
)
{
if (listItem != null)
{
SiteMapNode node = SiteMap.Providers["SPContentMapProvider"].CurrentNode;
PublishingWeb myPublishingWeb = PublishingWeb.GetPublishingWeb(spweb.OpenWeb());
if (myPublishingWeb.DefaultPage.Url.ToLower().EndsWith(listItem.Url.ToLower()))
{
nodeToReturn = node.ParentNode.ParentNode;
}
else if (node.ParentNode.Title.ToLower() == "pages")
{
nodeToReturn = node.ParentNode;
nodeToReturn.ReadOnly = false;
nodeToReturn.Title = listItem.Title;
}
}
else
{
string shortenedUrl = HttpContext.Current.Request.RawUrl.ToLower();
bool nodeFound = false;
while (!nodeFound)
{
shortenedUrl = shortenedUrl.Substring(0, shortenedUrl.LastIndexOf('/'));
// Checks to see whether the current page is a standalone aspx page
if (HttpContext.Current.Request.RawUrl.ToLower().Contains("_layouts/customtools"))
{
SiteMapNode siteMapNode = FindPartiallyMatchingNodeByUrl(shortenedUrl);
if (siteMapNode != null)
{
SiteMapNode siteMapNodetemp = siteMapNode;
while (siteMapNodetemp.ParentNode != null)
{
siteMapNodetemp.ReadOnly = false;
siteMapNodetemp.Title = HttpUtility.HtmlDecode(siteMapNodetemp.Title);
siteMapNodetemp = siteMapNodetemp.ParentNode;
}
nodeFound = true;
nodeToReturn = siteMapNode;
}
}
}
}
}
});
}
catch (Exception ex)
{
// Report the error
}
return nodeToReturn;
}
}
private SiteMapNode FindPartiallyMatchingNodeByUrl(string strUrl)
{
SiteMapProvider siteMapProvider = SiteMap.Providers["CombinedNavSiteMapProvider"];
SiteMapNode siteMapNode = FindPartiallyMatchingNode(siteMapProvider.RootNode, strUrl);
return siteMapNode;
}
private SiteMapNode FindPartiallyMatchingNode(SiteMapNode thisNode, string strUrl)
{
SiteMapNode siteMapNode = null;
/// See's if the current url partially matches the node provided
/// This is where the url upto the folder name of the tool will be
/// contained inside one of the nodes in the sitemap.
if (thisNode.Url.ToLower().Contains(strUrl.ToLower()))
{
// Found the one we want
return thisNode;
}
// If not, the same method is called for all the children nodes
foreach (SiteMapNode node in thisNode.ChildNodes)
{
siteMapNode = FindPartiallyMatchingNode(node, strUrl);
if (siteMapNode != null)
{
return siteMapNode;
}
}
return siteMapNode;
}
}
}
The above code will find a list item for 99% of the pages, the landing pages will be found with an exact match with the custom link added to SharePoint manually. Sibling pages of the landing page will be partially matched with the custom link added. The code also ensures that there will not be a performance hit for SharePoint published pages.
How to add custom links to SharePoint navigation
Log into the authoring site (if different)
Browse the subweb where the tool lives
From the site actions menu > Site Settings > Modify Navigation
Before you add the link, you need to know two important pieces of information a) Reletive path to the current subweb b) Path to the tool's landing page
You can determine this by looking at the url of the current page thought internet explorer and taking away any host name e.g. http://mysharepointsite and 'pages/page.aspx'. You should end up with something like this. /subsite1/subsite2/
Now you just need to combine the relative path of subweb to the path of the tool and you have the link path you need to add.
e.g.
/subsite1/subsite2/_layouts/customtools/mytool/mylandingpage.aspx
This will be the url you'll add.
Now you simply add a link by clicking 'Add Link'
Fill in the Title as what the name of the link should be in the navigation, add the url you put together and click ok.
Finally click ok again on the modify navigation screen. When you next visit your tool (e.g.
http://mysharepointsite/subsite1/subsite2/_layouts/customtools/mytool/mylandingpage.aspx)
, you will see that it appears in the navigation (if the 'Show Pages' setting is turned from the modify navigation page) as well as the same behaviour occurring for its sibling pages.