Friday, May 8, 2009

SharePoint SP2 is out

I don't know if any of you know this, but the SP2 for SharePoint was released on April 28.

The following text is from the Microsoft blog post

Benefits

Customers can be benefited from the following enhancements with Service Pack 2.

  • Performance and Availability Improvements

Service Pack 2 includes many fixes and enhancements designed to improve performance, availability, and stability in your server farms, including:

    • New Timer job automatically rebuilds content database index to improve database performance.
    • When a content database is marked as read-only, the user interface will be modified so users cannot perform tasks that require writing to the database.
    • Performance enhancement across nearly all the components. 
  • Improved Interoperability

Service Pack 2 continues to improve SharePoint interoperability with other products and platforms.

    • Broader support of browsers 
      Internet Explorer 8 is added into Level 1 browser support. 
      FireFox 3.0 is added into Level 2 browser support. (Firefox 2.0 is no longer supported by Mozilla)
    • Provide improved client integration user experience with Form Based Authentication. Now the client application can store user credentials instead of asking for them every time. For more technical details please refer to the updated articles on TechNet. 
      Configure forms-based authentication (Office SharePoint Server 
      http://technet.microsoft.com/en-us/library/cc262201.aspx 
      Configure forms-based authentication (Windows SharePoint Services) 
      http://technet.microsoft.com/en-us/library/cc288043.aspx 
  • Getting Ready for SharePoint Server 2010

A new preupgradecheck operation is added to stsadm tool. It can be used to scan your server farm to establish whether it is ready for upgrade to SharePoint Products and Technologies "14". It identifies issues that could present obstacles to the upgrade process. It checks for several SharePoint Products and Technologies "14" system requirements, including the presence of Microsoft® Windows Server® 2008 and a 64-bit hardware, and provides feedback and best practice recommendations for your current environment, together with information on how to resolve any issues that the tool discovers. 



Along with all the improvements, the preupgrade check is going to be handy for analyzing the  upgrade path to Office 14. I actually went ahead and installed the Service Pack 2 on a SharePoint server which only had SP1 installed on there and no updates after that. The upgrade process went smoothly with no hiccups. The one thing to note is that SP2 is available as a separate download for both WSS and MOSS. The SharePoint Products and Technologies Configuration wizard will pop up as soon as you finish installing the WSS SP2. It will save time to exit out of that (if you have MOSS installed), then install the SP2 for MOSS and then run the wizard. This wizard modifies database schema, so it saves time to run it once for both. 

SP2 can be installed on any build before before Fenruary 2009, which means that you could install it on a SharePoint server that has no service packs installed yet, just SP1, or the infrastructure update and builds after that but before February 2009.

This means that the newly released April "uber" package that Stefan talks about here is not included in SP2. The "uber" package will need to be installed after SP2.

Just to be safe, please try installing SP2 on a test/stage environment before unleashing it on production to make sure it will work for you :).


Update: I installed the SP2 as mentioned above, but ran into an issue right after where I could not authenticate to SharePoint using IE7 or SP Designer. I tried getting all the latest updates but that did not help either. I tried authenticating with Firefox and it authenticated just fine. So in case any of you come across this authentication with IE issue, here is what I found after some looking around.

  1. Click Start, click Run, type regedit, and then click OK.
  2. In Registry Editor, locate and then click the following registry key:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa
  3. Right-click Lsa, point to New, and then click DWORD Value.
  4. Type DisableLoopbackCheck, and then press ENTER.
  5. Right-click DisableLoopbackCheck, and then click Modify.
  6. In the Value data box, type 1, and then click OK.
  7. Quit Registry Editor, and then restart your computer.

I tried this out and it worked wonderfully.

Thursday, May 7, 2009

Script to Create Profile Properties in SharePoint Profile Store

Recently I was working on a large enterprise project that made extensive use of the user profile store in SharePoint. To meet the business requirements we needed a lot of new properties to be created in the profile store. While that may initially look easy to just open up the profile section in the SSP and start creating properties by hand - you need to take into account different developer VPCs and the dev/test/stage/prod environments (and suddenly its not so easy). There was also a need to map these properties to either AD or the BDC connection. That is where the data was being pulled from.

So here is a handy little script I wrote to create all the profile properties you need (its configurable via the application file) and also to map these properties to the appropriate connector (if you are pulling data from AD or BDC).

I wrote this in a hurry and this is not production quality code, so take it as it is :).



using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Administration;
using Microsoft.Office.Server.UserProfiles;
using Microsoft.SharePoint;
using System.Web;
using System.Configuration;
using System.Collections;
using System.Collections.Specialized;
using Microsoft.SharePoint.Portal;
using Microsoft.SharePoint.Portal.Topology;
using Microsoft.SharePoint.Administration;


namespace company.Mt.Utilities.ProfileProperties
{
class Program
{


static void Main(string[] args)
{
if (args.Length != 1)
{
Usage();
return;
}

switch (args[0])
{
case "CreateProfileAttributes":
CreateProfileAttributes();
MapExistingAttributes();
break;

case "MapExistingAttributes":
//MapExistingAttributes();
break;

case "DeleteOtherAttributes":
DeleteOtherAttributes();
break;

//case "DeleteUserProfiles":
// ProfileInstanceManagement.DeleteUserProfiles();
// break;

default:
Usage();
break;
}
}


///
/// Display user information
///

private static void Usage()
{
Console.WriteLine("Usage: Program [choice] where choice = CreateProfileAttributes or MapExistingAttributes or DeleteOtherAttributes");
Console.WriteLine("Example --> Program CreateProfileAttributes");
}


///
/// Delete other attributes
///

private static void DeleteOtherAttributes()
{
using (SPSite site = new SPSite(ConfigurationManager.AppSettings["server"])) //("http://localhost"))
{
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
UserProfileConfigManager mgr = new UserProfileConfigManager(context);


//Get the properties
PropertyCollection pc = profileManager.PropertiesWithSection;

//Property companySection = pc.GetPropertyByName(ConfigurationManager.AppSettings["CustomPropertySectionName"]);
//if (companySection == null)
//{
// companySection = pc.Create(true);
// companySection.Name = ConfigurationManager.AppSettings["CustomPropertySectionName"];
// companySection.DisplayName = ConfigurationManager.AppSettings["CustomPropertySectionValue"];
//}

int order = 1;
NameValueCollection coll = (NameValueCollection)ConfigurationManager.GetSection("company/NewProfileProperties");

string keyValue = null;

foreach (String key in coll.AllKeys)
{
try
{
pc.RemovePropertyByName(key);
}
catch (Exception ex)
{
string s = ex.Message;
}
}
}


using (SPSite site = new SPSite(ConfigurationManager.AppSettings["server"])) //("http://localhost"))
{
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
UserProfileConfigManager mgr = new UserProfileConfigManager(context);

PropertyCollection pc = profileManager.Properties;

NameValueCollection coll = (NameValueCollection)ConfigurationManager.GetSection("company/ExistingProfilePropertyUpdates");

string keyValue = null;

foreach (String key in coll.AllKeys)
{
try
{


keyValue = coll[key];
String[] details = keyValue.Split(new char[] { ',' });


Property property = pc.GetPropertyByName(key);
property.DisplayName = details[0];

DataSource ds = mgr.GetDataSource();
PropertyMapCollection pmc = ds.PropertyMapping;

string tomap = details[1].Trim();
string connnection = details[2].Trim();

try
{
pmc.Remove(key);
}
catch (Exception exe) { }

ArrayList invmap = pmc.VerifyMapping(false);

property.Commit();
}
catch (System.Exception e2)
{
Console.WriteLine(e2.Message + " - " + key);
}
}
}

}

///
/// Change mappings for existing attributes
///

private static void MapExistingAttributes()
{
using (SPSite site = new SPSite(ConfigurationManager.AppSettings["server"])) //("http://localhost"))
{
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
UserProfileConfigManager mgr = new UserProfileConfigManager(context);

PropertyCollection pc = profileManager.Properties;

NameValueCollection coll = (NameValueCollection)ConfigurationManager.GetSection("company/ExistingProfilePropertyUpdates");

string keyValue = null;

foreach (String key in coll.AllKeys)
{
try
{
keyValue = coll[key];
String[] details = keyValue.Split(new char[] { ',' });


Property property = pc.GetPropertyByName(key);
property.DisplayName = details[0];

DataSource ds = mgr.GetDataSource();
PropertyMapCollection pmc = ds.PropertyMapping;

string tomap = details[1].Trim();
string connnection = details[2].Trim();

try
{
pmc.Remove(key);
}
catch (Exception exe) { }

if ((!string.IsNullOrEmpty(tomap)) && (!(string.IsNullOrEmpty(connnection))))
pmc.Add(key, tomap, connnection);

//ArrayList invmap = pmc.VerifyMapping(false);

property.Commit();
}
catch (System.Exception e2)
{
Console.WriteLine(e2.Message + " - " + key);
}
}
}

}

///
/// Create the profile attributes
///

private static void CreateProfileAttributes()
{

//
using (SPSite site = new SPSite(ConfigurationManager.AppSettings["server"])) //("http://localhost"))
{
ServerContext context = ServerContext.GetContext(site);
UserProfileManager profileManager = new UserProfileManager(context);
UserProfileConfigManager mgr = new UserProfileConfigManager(context);


//Get the properties
PropertyCollection pc = profileManager.PropertiesWithSection;

//Property companySection = pc.GetPropertyByName(ConfigurationManager.AppSettings["CustomPropertySectionName"]);
//if (companySection == null)
//{
// companySection = pc.Create(true);
// companySection.Name = ConfigurationManager.AppSettings["CustomPropertySectionName"];
// companySection.DisplayName = ConfigurationManager.AppSettings["CustomPropertySectionValue"];
//}

int order = 1;
NameValueCollection coll = (NameValueCollection)ConfigurationManager.GetSection("company/NewProfileProperties");

string keyValue = null;

foreach (String key in coll.AllKeys)
{
try
{
keyValue = coll[key];
String[] details = keyValue.Split(new char[] { ',' });

Property p = pc.Create(false);
p.Name = key;
p.DisplayName = details[0].Trim();
//p.Type = details[1].Trim().ToLower();

//sample to get a property type "URL"
PropertyDataTypeCollection pdtc = mgr.GetPropertyDataTypes();
PropertyDataType ptype = null;
IEnumerator enumType = pdtc.GetEnumerator();
while (enumType.MoveNext())
{
ptype = (PropertyDataType)enumType.Current;
if (ptype.Name.ToLower().Equals(details[1].Trim().ToLower())) break;
}

p.Type = ptype.Name;

if (details[1].Trim().ToLower() == "string")
p.Length = Convert.ToInt32(details[2].Trim());

p.PrivacyPolicy = PrivacyPolicy.OptIn;
p.DefaultPrivacy = Privacy.Public;
if (details[3].Trim() == "true")
p.IsSearchable = true;
else
p.IsSearchable = false;

p.IsVisibleOnEditor = false;
p.IsVisibleOnViewer = true;
pc.Add(p);
pc.SetDisplayOrderByPropertyName(p.Name, order++);
pc.CommitDisplayOrder();


DataSource ds = mgr.GetDataSource();
PropertyMapCollection pmc = ds.PropertyMapping;


string tomap = details[4].Trim();
string connnection = details[5].Trim();

if ((!string.IsNullOrEmpty(tomap)) && (!(string.IsNullOrEmpty(connnection))))
pmc.Add(key, tomap, connnection);

//ArrayList invmap = pmc.VerifyMapping(false);
}
catch (DuplicateEntryException e)
{
Console.WriteLine(e.Message + " - " + key);
}
catch (System.Exception e2)
{
Console.WriteLine(e2.Message + " - " + key);
}
}


//get arraylist of invalid mappings
//ArrayList invmap = pmc.VerifyMapping(false);
//remove current mapping if invalid
//foreach (PropertyMap pm in invmap)
//{
// if (pm.PropName == p.Name)
// {
// pmc.Remove(pm.PropName);
// }
//}

}
}
}
}

Here are the corresponding entries from the web.config.


<configuration>
<configsections>
<sectiongroup name="Company">
<section name="NewProfileProperties" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<section name="ExistingProfilePropertyUpdates" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
</section></section></sectiongroup>
</configsections>
<company>
<newprofileproperties>
<!-- key=name, value="Display name, type, length, true if indexed, mapping to AD/BDC"/> -->

<add key="NamePrefix" value="Name Prefix,String,20,false,NamePrefix,BDC_Connection_Name"></add>
<add key="NameTitle" value="Name Title,String,40,false,NameTitle,BDC_Connection_Name"></add>
<add key="CompanyEmail" value="Company Email,Email,,true,userPrincipalName,ad_Connection_name"></add>

</newprofileproperties></company></configuration>
......
......

MOSS custom navigation provider for left nav

As promised, here is the code for the custom left navigation provider in MOSS. It uses a similar concept as the custom top navigation provider I detailed in this post, but there are some differences. Unlike a top or global navigation which is fairly consistent across your brand, the left navigation can change from site across site collections. It can even be different in different Webs. So based on that consideration, this solution handles those cases. Again, this navigation reads from a SharePoint list on a site. The navigation can read from a list in the root site of a site collection, the current site, or a site below the root site (this was a specific requirement) - all this is in a case statement so pretty easy to change.

Again, I have used this before successfully for a busy site. There are various settings in the Web.config that allow you to customize as per your needs.

This is demonstrated in the picture below. Only the Quick view is changeable on a site per site basis. The Quick Links section is consistent across the brand.





Again, the navigation is being read from a list in SharePoint, such as this. This doesn't have the same data, but should get the point across anyway.




#region Code Comment Header
/*******************************************************************************************
*
*
* $History: CustomLeftNavProvider.cs $
*
* *****************************************************************************************/
#endregion
using System;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Caching;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Navigation;
using Microsoft.SharePoint.Administration;
using CompanyXXX.ExceptionManagement;
using System.Security.Permissions;
using System.Security;

namespace CompanyXXX.Dept.MOSS.Utilities.Navigation.Providers
{
//Assign the neccessary security permissions
[AspNetHostingPermissionAttribute(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[SharePointPermissionAttribute(SecurityAction.LinkDemand, ObjectModel = true)]
[AspNetHostingPermissionAttribute(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
public class CustomLeftNavProvider : PortalSiteMapProvider
{
//Create the in memory objects for storage and fast retreival
protected SiteMapNodeCollection siteMapNodeColl;
protected ArrayList childParentRelationship;
protected ArrayList topLevelNodes;
//protected SPSite sourceListSite;
//protected SPWeb sourceListWeb;
//protected bool needDisposing = false;

///
/// Override the initialize method of the superclass
///

///
///
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
// Verify that config isn't null
if (config == null)
throw new ArgumentNullException("config is null");

// Assign the provider a default name if it doesn't have one
if (String.IsNullOrEmpty(name))
name = "CustomLeftNavProvider";

// Add a default "description" attribute to config if the
// attribute doesn’t exist or is empty
if (string.IsNullOrEmpty(config["description"]))
{
config.Remove("description");
config.Add("description", "CompanyXXX custom current left navigation provider");
}

base.Initialize(name, config);
}


///
/// This method will be called for all nodes and subnodes that can have children under them. For eg, NodeTypes.AuthoringLink type node
/// cannot have child nodes.
///

/// The node to find child nodes for
/// The SiteMapNodeCollection which contains the children of the child nodes
public override SiteMapNodeCollection GetChildNodes(System.Web.SiteMapNode node)
{
return ComposeNodes(node);
}

///
/// Find the web and the list from where to read navigation
///

///
public void FindWeb(ref SPWeb sourceListWeb, ref SPSite sourceListSite, ref bool needDisposing)
{
//string currentNavList = ConfigurationManager.AppSettings["CurrentLeftNavigationListName"];
//inheritNav = true;

string navListWeb = String.Empty;

try
{
//Get the web where the current list is created
navListWeb = ConfigurationManager.AppSettings["LeftNavigationListWeb"];

//if the list is at the root site of the collection, the current web or even a different
//site - this is specified in the config file
switch (navListWeb) {
case "ROOT":
sourceListWeb = SPContext.Current.Site.RootWeb;
//leftNavList = sourceListWeb.Lists[currentNavList];
break;

case ".":
sourceListWeb = SPContext.Current.Web;
//leftNavList = sourceListWeb.Lists[currentNavList];
//inheritNav = false;
break;

case "LEVEL2":
//The list will be located one level below the top level
SPWeb cmsweb = SPContext.Current.Web;
SPWeb holder = null;
SPWeb rootWeb = SPContext.Current.Site.RootWeb;

//if current web is at second level or at root, pick the respective lists from there
if (cmsweb.ID == rootWeb.ID || cmsweb.ParentWeb.ID == rootWeb.ID)
sourceListWeb = cmsweb;
else
{
//else traverse up to the second level
while (cmsweb.ID != rootWeb.ID)
{
holder = cmsweb;
cmsweb = cmsweb.ParentWeb;
}
sourceListWeb = holder;
}
break;

default:
//instantiate sites and lists.
sourceListSite = new SPSite(navListWeb);
sourceListWeb = sourceListSite.OpenWeb();
//leftNavList = sourceListWeb.Lists[currentNavList];
//inheritNav = false;
needDisposing = true;
break;
}
}
catch (Exception ex)
{
System.Collections.Specialized.NameValueCollection exc =
new System.Collections.Specialized.NameValueCollection();

exc.Add("Method", "FindWeb");

if (!String.IsNullOrEmpty(navListWeb))
exc.Add("navListWebFromAppSetting", navListWeb);
else
exc.Add("navListWebFromAppSetting", "null");
if (sourceListSite != null)
exc.Add("sourceListSite", sourceListSite.Url);
else
exc.Add("sourceListSite", "null");
if (sourceListWeb != null)
exc.Add("sourceListWeb", sourceListWeb.Url);
else
exc.Add("sourceListWeb", "null");

throw ex;
}
}

///
/// Compose nodes when the method is called. At a minimum, this method gets called with the root node of every
/// site collection. We must attach the top level nodes to the root node for this method to get called for those
/// nodes as well.
///

///
///
public virtual SiteMapNodeCollection ComposeNodes(System.Web.SiteMapNode node)
{
//Create the SiteCollection to return
SiteMapNodeCollection children = new SiteMapNodeCollection();
SortedList orderedNodes = new SortedList();

SPWeb sourceListWeb = null;
SPSite sourceListSite = null;
SPList leftNavList = null;
bool cacheLeftNav = false;
bool needDisposing = false;
string currentNavList = String.Empty;

try
{
FindWeb(ref sourceListWeb, ref sourceListSite, ref needDisposing);

string cacheNav = ConfigurationManager.AppSettings["CacheLeftNavigation"];
if (String.Compare(cacheNav, "true", true) == 1)
cacheLeftNav = true;

//Account for the PortalWebSiteMapNode
PortalWebSiteMapNode prtlWebSiteMapNode = null;
int counter = 100;

currentNavList = ConfigurationManager.AppSettings["CurrentLeftNavigationListName"];

//If a rootnode, then add the top level children which are the same for every site collection
if (node == node.RootNode)
{
//Serve it from cache if possible
if (cacheLeftNav)
{
object topNodes = HttpRuntime.Cache["LeftNavTopNodes" + sourceListWeb.ID.ToString()];
if (topNodes != null && topNodes is SiteMapNodeCollection)
return ((SiteMapNodeCollection)topNodes);
}

//else find the list
leftNavList = sourceListWeb.Lists[currentNavList];

//Create the query to browse the list
SPQuery qry = new SPQuery();

SPFolder startFolder = null;

//Get the folder to start from
startFolder = leftNavList.RootFolder.SubFolders[ConfigurationManager.AppSettings["NavigationListStartFolderName"]];

//Get the query for this folder
qry.Folder = startFolder;

//Get all the items under the start folder using the query
SPListItemCollection ic = sourceListWeb.Lists[startFolder.ParentListId].GetItems(qry);

//Get the PortalWebSiteMapNode
if (node != null && node is PortalWebSiteMapNode)
{
prtlWebSiteMapNode = node as PortalWebSiteMapNode;

//Construct the nodes
foreach (SPListItem subitem in ic)
{
//Change the nodeTypes to Authored link for leaf nodes so that the GetChildNodes method is not called for those nodes.
NodeTypes ntypes = NodeTypes.Default;
if (subitem.Folder == null)
ntypes = NodeTypes.AuthoredLink;

//Create a PortalSiteMapNode
PortalSiteMapNode psmn = new PortalSiteMapNode(prtlWebSiteMapNode, subitem.ID.ToString(), ntypes,
subitem.GetFormattedValue(ConfigurationManager.AppSettings["UrlLink"]), subitem.Title,
subitem.GetFormattedValue(ConfigurationManager.AppSettings["UrlDescription"]));

//Order the nodes
try
{
int order = Convert.ToInt32(subitem.GetFormattedValue(ConfigurationManager.AppSettings["ItemOrder"]));
orderedNodes.Add(order, psmn);
}
catch (Exception ex)
{
orderedNodes.Add(counter++, psmn);
}
}

//Copy nodes in the right order
foreach (object portalSiteMapNode in orderedNodes.Values)
{
children.Add(portalSiteMapNode as PortalSiteMapNode);
}

//Add them to the cache
if (cacheLeftNav)
HttpRuntime.Cache["LeftNavTopNodes" + sourceListWeb.ID.ToString()] = children;
}
}
else
//Else this is a subnode, get only the children of that subnode
{
string nodeKey = node.Key;

if (cacheLeftNav)
{
//Get the children for this nodeKey from cache if they exist there
object subNodes = HttpRuntime.Cache["LeftNavChildNodes" + sourceListWeb.ID.ToString() + nodeKey];
if (subNodes != null && subNodes is SiteMapNodeCollection)
return ((SiteMapNodeCollection)subNodes);
}

leftNavList = sourceListWeb.Lists[currentNavList];

//Create the query to browse the list
SPQuery qry = new SPQuery();

//Convert the nodeKey into an integer
int ID = Convert.ToInt32(nodeKey);

//Find the current item
SPListItem item = leftNavList.GetItemById(ID);

//Find the item that and get its subitems
if (item != null && item.Folder != null)
{
SPFolder curFolder = item.Folder;

qry.Folder = curFolder;

//Get all the items in the current folder using the query
SPListItemCollection ic = sourceListWeb.Lists[curFolder.ParentListId].GetItems(qry);

//To get the PortalWebSiteMapNode, ask the root node
if (node.RootNode != null && node.RootNode is PortalWebSiteMapNode)
{
//Cast node to PortalWebSiteMapNode
prtlWebSiteMapNode = node.RootNode as PortalWebSiteMapNode;

foreach (SPListItem subitem in ic)
{
//Change the nodeTypes to Authored link for leaf nodes so that the GetChildNodes method is not called
//for those nodes.
NodeTypes ntypes = NodeTypes.AuthoredLink;
if (subitem.Folder != null)
ntypes = NodeTypes.Default;

//Create a PortalSiteMapNode
PortalSiteMapNode psmn = new PortalSiteMapNode(prtlWebSiteMapNode, subitem.ID.ToString(),
ntypes, subitem.GetFormattedValue(ConfigurationManager.AppSettings["UrlLink"]), subitem.Title,
subitem.GetFormattedValue(ConfigurationManager.AppSettings["UrlDescription"]));


//Order the nodes
try
{
int order = Convert.ToInt32(subitem.GetFormattedValue(ConfigurationManager.AppSettings["ItemOrder"]));
orderedNodes.Add(order, psmn);
}
catch (Exception ex)
{
orderedNodes.Add(counter++, psmn);
}
}

//Copy nodes in the right order
foreach (object portalSiteMapNode in orderedNodes.Values)
{
children.Add(portalSiteMapNode as PortalSiteMapNode);
}

//Add them to the cache
if (cacheLeftNav)
{
HttpRuntime.Cache["LeftNavChildNodes" + sourceListWeb.ID.ToString() + nodeKey] = children;
}
}
}
}
}
catch (Exception ex)
{
System.Collections.Specialized.NameValueCollection exc =
new System.Collections.Specialized.NameValueCollection();

exc.Add("Method", "ComposeNodes");

if (!String.IsNullOrEmpty(currentNavList))
exc.Add("CurrentLeftNavigationListNameFromAppSettings", currentNavList);
else
exc.Add("CurrentLeftNavigationListNameFromAppSettings", "null");

if (sourceListSite != null)
exc.Add("sourceListSite", sourceListSite.Url);
else
exc.Add("sourceListSite", "null");

if (sourceListWeb != null)
exc.Add("sourceListWeb", sourceListWeb.Url);
else
exc.Add("sourceListWeb", "null");

ExceptionManager.Publish(ex, exc);

//Do not dispose of the web or site referenced from SPContext

//Test that finally does get called even though a return statement is executed here
//Return empty collection
return new SiteMapNodeCollection();
}
finally
{
if (needDisposing)
{
if (sourceListWeb != null)
sourceListWeb = null;
if (sourceListSite != null)
sourceListSite = null;
}
}
return children;
}
}
}


Enjoy!!