Thursday, November 20, 2008

Writing a Custom Navigation Provider for MOSS

Update: Also explained the concept and put up the code for the left navigation provider here.

Update: Added the Web.config entries at the bottom.


By popular demand here is the writeup for the Custom Navigation Provider for SharePoint 2007 I wrote last year. Be sure to check it our and send me feedback.


So here is the use case. You would like to create a consistent navigation heirarchy in your SharePoint environment. The OOB navigation is not going to work for you because your site has probably grown to many site collections and having a consistent navigation is a need. You do not want to change your navigation on every site collection when it needs to be changed. The appropriate users want to change the top navigation as needed without having full access the site.

I was faced with these challenges last year and so came up with the idea to write a custom navigation provider that can read from a list. The list can have folder heirarchies and those determine the levels and the dropdowns.


The list images and the changes that need to be made in the master page and web.config file are shown below for this to work.



We created custom site columns, custom content types and then a custom list that used these content types to allow users to easily build hierarchies that the navigation provider could read and deduce the navigation levels. Here is an example of the custom list for the top navigation content. The actual URLs below in the Url Link column have erased, but this should get the point across.




This is a view of the global navigation shared across all site collections. It includes one level of dropdowns, but those can be added by adding the list heirarchies and also tweaking the levels to show in the AspMenu in the SharePoint master page.


Here are the changes that had to be made in the master page.


<sharepoint:aspmenu id="GlobalNav" runat="server" datasourceid="topSiteMap" accesskey="">">
Orientation="Horizontal"
StaticDisplayLevels="1"
MaximumDynamicDisplayLevels="2"
StaticEnableDefaultPopOutImage="false" ItemWrap="true"
DynamicHorizontalOffset="-1" DynamicVerticalOffset="-7"
SkipLinkText=""
StaticSubMenuIndent="0" CssClass="ms-topNavContainerCustom">
<staticmenuitemstyle cssclass="topNavItemCustom" itemspacing="0">
<staticselectedstyle cssclass="topNavSelectedCustom" itemspacing="0">
<statichoverstyle cssclass="topNavHoverCustom">
<dynamicmenustyle cssclass="topNavFlyOutsCustom">
<dynamicmenuitemstyle cssclass="topNavFlyOutsItemCustom">
<dynamichoverstyle cssclass="topNavFlyOutsHoverCustom">
</dynamichoverstyle></dynamicmenuitemstyle></dynamicmenustyle></statichoverstyle></staticselectedstyle></staticmenuitemstyle></sharepoint:aspmenu>

<publishingnavigation:portalsitemapdatasource id="topSiteMap" runat="server" sitemapprovider="CustomTopNavProvider" enableviewstate="true" startfromcurrentnode="true" startingnodeoffset="0" showstartingnode="false" treatstartingnodeascurrent="false">



The code below is pre-SP1. Some things may have slightly changed since then.

#region Code Comment Header
/*******************************************************************************************
* <History>
*
* $History: CustomTopNavProvider.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 CompanyXX.ExceptionManagement;
using System.Security.Permissions;
using System.Security;

namespace CompanyXX.MOSS.Utilities.Navigation.Providers
{
//Assign the neccessary security permissions. TODO - Check the permissions required.
[AspNetHostingPermissionAttribute(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[SharePointPermissionAttribute(SecurityAction.LinkDemand, ObjectModel = true)]
[AspNetHostingPermissionAttribute(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
[SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
//This inherits from the PortalSiteMapProvider class in MOSS, just because it provides some of the functions I need.
//You could just as easily write one for WSS.
public class CustomTopNavProvider : PortalSiteMapProvider
{
//Create the in memory objects for storage and fast retreival
protected SiteMapNodeCollection siteMapNodeColl;

//
protected ArrayList childParentRelationship;

//These are only the top level nodes that will show in the top nav
protected ArrayList topLevelNodes;

private PortalSiteMapNode rootNode = null;

/// <summary>
/// Override the initialize method of the superclass. You must override the Initialize method to write
/// a custom provider.
/// </summary>
/// <param name="name"></param>
/// <param name="config"></param>
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 = "CustomTopNavProvider";

// 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", "CompanyXX Custom top navigation provider");
}

base.Initialize(name, config);

childParentRelationship = new ArrayList();
topLevelNodes = new ArrayList();

//Build the site map in memory
LoadTopNavigationFromList();
}


/// <summary>
/// Load the top navigation into memory on the first call.
/// </summary>
protected virtual void LoadTopNavigationFromList()
{
//Make sure to build the structure in memory only once
lock (this)
{
if (rootNode != null)
{
return;
}
else
{
//Initialiaze for the first time
SPSite rootSite = null;
SPWeb rootWeb = null;
SPList topnavList = null;

try
{
//Clear the top level nodes and the relationships
topLevelNodes.Clear();
childParentRelationship.Clear();

//instantiate sites and lists for now. This setting assumes that the list being
//read from for the global top navigation is in the root web of the site collection listed in web.config.
rootSite = new SPSite(ConfigurationManager.AppSettings["CompanyXXRootSite"]);
rootWeb = rootSite.RootWeb;
topnavList = rootWeb.Lists[ConfigurationManager.AppSettings["TopNavigationListName"]];

//Build the root node
//Note: Any top level site of any site collection is assigned to be the rootNode here, not neccessarily the
//top level site of the main site collection
rootNode = (PortalSiteMapNode)this.RootNode;

//We need to pass the PortalSiteMapNode constructor a PortalWebSiteMapNode object, so here it is
//Note: This is the root node of 1 site collection, but the navigation will be shown in all site collections.
PortalWebSiteMapNode pwsmn = rootNode as PortalWebSiteMapNode;

if (pwsmn != null)
{
//Get the current folder to start. The navigation heirarchy can start at that folder.
SPFolder currentFolder = topnavList.RootFolder.SubFolders[ConfigurationManager.AppSettings["NavigationListStartFolderName"]];

//Build the nodes
BuildListNodes(rootWeb, currentFolder, pwsmn, null, true);
}

}
catch (Exception ex)
{
//There was a problem opening the site or the list.
ExceptionManager.Publish(ex);
}
finally
{
//Dispose of the objects
if (rootWeb != null)
rootWeb.Dispose();

if (rootSite != null)
rootSite.Dispose();
}
}
}
}


/// <summary>
/// Go through the list and build and save the PortalSiteMapNode nodes into memory based on the list heirarchy.
/// </summary>
/// <param name="folder">this is the current folder to look for items</param>
/// <param name="prtlWebSiteMapNode">the parent PortalWeb</param>
/// <param name="parentSiteMapNode">the parent node</param>
/// <param name="rootLevel">true if this is the first level, false if its a rootnode</param>
protected virtual void BuildListNodes(SPWeb currentWeb, SPFolder folder, PortalWebSiteMapNode prtlWebSiteMapNode, PortalSiteMapNode parentSiteMapNode, bool rootLevel)
{
// Get the collection of items from this folder
SPQuery qry = new SPQuery();
qry.Folder = folder;
SortedList orderedNodes = new SortedList();
int counter = 100; //for sorting items

try
{
//Browse through the items in the folder and create PortalSiteMapNodes
SPListItemCollection ic = currentWeb.Lists[folder.ParentListId].GetItems(qry);
foreach (SPListItem subitem in ic)
{
//A SiteMapNode does not have target or audience information
//SiteMapNode smn = new SiteMapNode(this, subitem.ID.ToString(), subitem.GetFormattedValue("UrlText"), subitem.Title, subitem.GetFormattedValue("UrlText"));

//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"]));

//Error, cannot assign audience and target - read only?? This is bad!!
//psmn.Audience = subitem.GetFormattedValue("Audience");
//psmn.Target = "_blank";

//Order the nodes
try
{
int order = Convert.ToInt32(subitem.GetFormattedValue(ConfigurationManager.AppSettings["ItemOrder"]));
orderedNodes.Add(order, psmn);
}
catch (Exception ex)
{
//This will happen if 2 items are assigned the same order. Push one item to the last order.
orderedNodes.Add(counter++, psmn);
}

//if this is a folder, fetch and build the heirarchy under this folder
if (subitem.Folder != null)
BuildListNodes(currentWeb, subitem.Folder, prtlWebSiteMapNode, psmn, false);
}

//Copy nodes in the right order
foreach (object portalSiteMapNode in orderedNodes.Values)
{
//Add the node to the different collections
if (rootLevel)
topLevelNodes.Add(portalSiteMapNode);

//If the parent node is not null, add the parent and the child relationship
if (parentSiteMapNode != null)
childParentRelationship.Add(new DictionaryEntry(parentSiteMapNode.Key, portalSiteMapNode));
}
}
catch (Exception ex)
{
ExceptionManager.Publish(ex);
throw;
}
}


/// <summary>
/// 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.
/// </summary>
/// <param name="node">The node to find child nodes for</param>
/// <returns>The SiteMapNodeCollection which contains the children of the child nodes</returns>
public override SiteMapNodeCollection GetChildNodes(System.Web.SiteMapNode node)
{
return ComposeNodes(node);
}


/// <summary>
/// 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.
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
public virtual SiteMapNodeCollection ComposeNodes(System.Web.SiteMapNode node)
{
//The SiteMapNodeCollection which represents the children of this node
SiteMapNodeCollection children = new SiteMapNodeCollection();

try
{
//If an absolute 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.
//TODO: See if better way to do caching
object topNodes = HttpRuntime.Cache["TopNavRootNodes"];
if (topNodes != null && topNodes is SiteMapNodeCollection)
return ((SiteMapNodeCollection)topNodes);

lock (this)
{
//TODO: Check cache again. Threads may have been waiting at the lock.

//Two options available here.
//1. Reload from the list when cache expires in case that is needed
if (String.Compare(ConfigurationManager.AppSettings["ReloadTopNavOnCacheExpiry"], "true", true) == 1)
{
rootNode = null;
LoadTopNavigationFromList();
}

//Else generate the top level nodes from memory. This must be done regardless of option 1 above
for (int i = 0; i < topLevelNodes.Count; i++)
{
children.Add(topLevelNodes[i] as PortalSiteMapNode);
}

//Add them to the cache
HttpRuntime.Cache["TopNavRootNodes"] = children;
}
}
else
//Else this is a subnode, get only the children of that subnode
{
string nodeKey = node.Key;

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

lock (this)
{
//Two options available here.
//1. Reload from the list when cache expires in case that is needed
//Commenting out because the top node should decide if we are going to get the tree from cache, not subnodes
//if (String.Compare(ConfigurationManager.AppSettings["ReloadTopNavOnCacheExpiry"], "true", true) == 1)
//{
// rootNode = null;
// LoadTopNavigationFromList();
//}

//Else iterate through the nodes and find the children of this node
for (int i = 0; i < childParentRelationship.Count; i++)
{
string nKey = ((DictionaryEntry)childParentRelationship[i]).Key as string;

//if this is a child
if (nodeKey == nKey)
{
//Get the child from the arraylist
PortalSiteMapNode child = (PortalSiteMapNode)(((DictionaryEntry)childParentRelationship[i]).Value);

if (child != null)
{
children.Add(child as PortalSiteMapNode);
}
else
{
throw new Exception("ArrayLists not in sync.");
}
}
}
//Add the children to the cache
HttpRuntime.Cache["TopNavRootNodes" + nodeKey] = children;
}
}
}
catch (Exception ex)
{
ExceptionManager.Publish(ex);

//return empty site node collection
return new SiteMapNodeCollection();
}

return children;
}
}
}



Here are the settings in the Web.config file for the Web Application.

1. This goes in the providers section.
<add name="CustomTopNavProvider" description="Custom provider for top navigation in Portal Usage pages" type="CompanyXX.MOSS.Branding.CustomProviders.Navigation.CustomTopNavProvider, CompanyXX.MOSS.Branding.CustomProviders, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a5d522bbe3d8f31c" NavigationType="Combined" EncodeOutput="true" />

2. In the appSettings section, add the following entries.
<add key="CompanyXXRootSite" value="http://www.yoursite.com/" />
<add key="TopNavigationListName" value="TopNavList" />
<add key="CurrentLeftNavigationListName" value="CurrentLeftNavList" />
<add key="StaticLeftNavigationListName" value="StaticLeftNavList" />
<add key="NavigationListStartFolderName" value="Group1" />
<add key="UrlLink" value="Url Link" />
<add key="UrlDescription" value="Url Description" />
<add key="NewWindow" value="Open New Window" />
<add key="UrlAudience" value="Url Audience" />
<add key="ItemOrder" value="Item Order" />




I have used this TopNavProvider to build the navigation for a MOSS intranet with ~4000 users, as well as an MOSS internet facing site with ~1.5 million visitors a month. Enjoy!!


I also created another custom navigation provider that reads the current navigation for every site from a similar list on that site and displays that somewhere else on that page (left or right navigation).

47 comments:

fromonesource said...

This is almost exactly what I have been looking for. Do you have any suggestions or experience using security trimming on your navigation? For example, maybe you only want users to see navigation items that they have access to. Can this be integrated with a custom nav provider?

Faraz said...

Because the navigation is being read from a custom source (a list), MOSS doesn't track where it goes to and thus does not security trim it.

I would look at these 2 lines in my code.

//psmn.Audience = subitem.GetFormattedValue("Audience");
//psmn.Target = "_blank";

At the time of writing, those were read only. If you can assign those values, you can assign audiences to your nodes in code.

Todd said...

Can you also share the web.config settings for this provider (list name, etc)?

Anonymous said...

Is this able to be done with WSS 3?

fromonesource said...

I am having trouble getting this to work. Could you contact me by email or provide a little bit more detail on how to get this code integrated into a MOSS environment? I created a new project, compiled the dll, deployed, updated web.config and updated master page but I only get control errors and web.config errors. I am not sure what I am doing wrong. Thanks Faraz. I am at this account on gmail.com

Anonymous said...

Can you share the code for the LEFT hand navigation also
(as you have mentioned you created one for left nav and source is a list)?

thanks
Raul

Faraz said...

Hi Raul,
I will post the code for the left navigation provider in the next day or two.

Faraz

Anonymous said...

Thank you Faraz, It would be great if you can share the link on this blog so I can get to your another blog. If I may request you to do it today, it would be a great help and favor from you, as I am really struggling hard with this and want this to get working. Appreciate all your help. Thanks again Faraz, waiting desparately for your left nav code.

regards
Raul

Faraz said...

Hi Raul,
Please send me your email I will send you the code directly i am busy today and will not be able to post it up here, Ill come back and post it up here soon.
Faraz

Anonymous said...

Thank you very much Faraz, you rock and you are very very very helpful.
My email-Id is rahulbhel@live.com.
Please do send me the code and I will look it up.

I really appreciate your community effort and your willingness to help me. thanks again

Faraz said...

Rahul,
I just sent you the code, that should help you.
Faraz

Anonymous said...

Thank you Faraz, I got the code and I had question on NavigationListStartFolderName Appsettings. I am not sure what to specify in the web.config for that. I have created 3 folders in my custom list (that represent parent items) and 2 items in each folder. So the final hierarchy could look like:

Folder 1
Item1
Item2
Folder 2
Item2-1
Item2-2
Folder 3
Item3-1
Item3-2

So what do I specify in the
NavigationListStartFolderName Appsettings?

regards
Rahul

Anonymous said...

Hello Faraz,
Thanks for the great article. I am looking for something like this but in document library only
I have docuemnt library and have 3 folders on it
Shared Document
Folder 1
Folder 2
Folder 3

When i go to Quick navigation on Left I just see shared Document Link. Is it possible to get Folder 1 .... quick launch it will make automatically.
Can you please let me know in kundan{at}rogers.com

Thanks

Anonymous said...

Great Article Faraz. Can you please mail me the code for left navigation. My email address is Wango.ho@hotmail.com

Wango ho

Faraz said...

Our country is burning but we youth here are busy sending funny faraz SMS messages.

javahar said...

HI Faraz/Rahul,
can you please send me the code for Left navigation. to my ID: nveeragoni@gmail.com
Thanks much.

Ali said...

hi faraz want to think you about all the knowledge you shared here:)
can you send me the code of this solution my mail is bentalay@gmail.com
I want to know the contenttype you used to create your list and how it works excuse i still a sharepoint beginner:)
thank you for help faraz:)

Bigpatty said...

Hi Faraz, Like the article but I am a little confused about the "NavigationListStartFolderName". I have implemented all as in your article but I get nothing in my nav bar,,, do I need to put all the items and the folders inside a parent folder? or Does this StartFolderName point to the first folder in the list? Feel silly asking as I'm sure I'm missing something obvious. Cheers

Faraz said...

Hi Bigpatty,

Yes you do, the reason I did that was in case you wanted to use the same list for different navs (top or left) - then you could just point to different start folders. I create a folder called 'Group1' at the root of the list and all the nav elements (list items, sub-folders) go under that.

Cheers.

Anonymous said...

Thanks for a great post!

How did you modify your master page? Did you use SharePoint designer to save a customized version (which is the only way I know of)?

Varp said...

This is an excellent solution. We have a 2003 > MOSS upgraded farm and have several site collections. The previous navigation webpart we used in 2003 relies on the user being able to enumerate the Site Collections, which only seems to work for administrative users in MOSS.
I am curious about a couple of aspects of your solution though. You have a namespace CompanyXX.ExceptionManagement. Am I missing something obvious or is this unnecessary to the solution ?
If you could send the solution it would help me understand how it all fits together.

Faraz said...

You do not need the ExceptionManagement dll by any means - all that does it log the exception to the system event log so that a system engineer or a developer can go take a look at the event log to view the errors being thrown by the application. This also prevents the errors from bubbling up and showing up on the web page.

Bo George said...

I am seeing a strange behavior when editing any list all items with children dissappear and the GetChildNodes isn't being invoked for those nodes (which is why they aren't rendering).

Faraz said...

Hi Bo,
I haven't seen that before. Could you debug the code locally on your VPC and figure out if the provider is being invoked at all?

Faraz

Bo said...

I am able to debug it and GetChildNodes is invoked but only for the rootNode of the site collection. When debugging though the root node still shows as having the nodes from the list as child nodes and returns them from cache. It's just that GetChildNodes only gets called once.

Bo said...

I am able to debug it and GetChildNodes is invoked but only for the rootNode of the site collection. When debugging though the root node still shows as having the nodes from the list as child nodes and returns them from cache. It's just that GetChildNodes only gets called once.

Faraz said...

Bo,
I would look at this piece of code here.

NodeTypes ntypes = NodeTypes.AuthoredLink;
if (subitem.Folder != null)
ntypes = NodeTypes.Default;

I am assigning the type of node based on whether the node has children. If it does not have children then it is a AuthoredLink, which does not invoke GetChildNodes method, whereas a NodeTypes.Default does. Are you using the folder structure correctly - here was my heirarchy.

List
Group1(folder under List)
ParentLink1 (folder under Group1)
LeafLink1 (item under ParentLink1)
LeafLink2 (item under ParentLink1)
ParentLink2 (folder under Group1)
LeafLink1(item under ParentLink2)
LeafLink (item under Group1)
LeafLink (item under Group1)

Try verifying this. Also thanks for opening a support case with MS, they might be able to shed some light on this.

Bo said...

First, sorry for not responding sooner. I was actually holding to see if Microsoft would be able to figure it out.

I finally looked into the NodeTypes code and tried setting all NodeTypes to Default and it didn't make a difference. On a whim I tried this

NodeTypes ntypes = NodeTypes.AuthoredLink;
if (subitem.Folder != null)
ntypes = NodeTypes.Custom;

Apparently, setting the container nodes to custom makes it retain and call GetChildNodes for the nodes that I've created. Go figure.

Anyway, thanks for your help and advice pointing me in the right area to investigate.

Anonymous said...

Hi Faraz

Very good and informative post. However, I dont have knowledge of ASP.Net. Can you please provide me step by step procedure to successfully implement this site customization. I like your post regarding branding-intranets-with-sharepoint-moss. Can you also please give me complete code to make it possible on our intranet. My email id is guptpallavi@gmail.com.

Regards
Pallavi

Custom Paper Writing said...

Many institutions limit access to their online information. Making this information available will be an asset to all.

Anonymous said...

Hi Faraz,

Thanks for nice article. I am writing again, if you kindly able to send me the project and list structure on nnk_790@yahoo.com. I would be really grateful if you send me as earliest. Thanks and looking forward.

Anonymous said...

Hi Faraz,

Does this code works for 2010 as well?

If not, do have anything similar for Sharepoint 2010?


Thanks,
MM

Jitu said...

This is a excellent solution and I would like try in Sharepoint 2010. Can you email me code/project files?

My email address is:
jitukelkar2@gmail.com

Thanks,
Jitu.

Anonymous said...

Hi.. well this is certainly a way to go about creating a custom nav ..but there is a much easier way.. with loads more functionality and yes …security trimming..
I have said it so many times in the last two years ..in all my seminars though out the country, but it seems like we tend to forget simple basic fundamentals ?? why do we over produce and over think every thing..??
Please feel free to mail me.. jeijber@hotmail.com

Qazi Arfeen said...

Good job Faraz, really helpful.
Thanks

Ronak said...

Thanks Faraz for sharing knowledge can you please send me code if possible

Thansk
Ronak

Anonymous said...

Hi Faraz. Great information. Could you please email the code for custom navigation to adolly7@yahoo.com?

Thanks
Dolly

Alpha said...

Hi Faraz, sorry about what seems to be an off-topic comment, but which is the the licensing of this code?

Would I be able to use it for a personal project? What about a commercial project?

Anonymous said...

Faraz, Can you please provide me step by step directions on how to put the code to create the quick launch navigation. I have created the class file using your code. But how do i implement it. Please email me at forumsub@att.net

Thanks

Timothy P. Stanley said...

Writing is an excellent way in promoting learning and there are numerous writing forms that the students happen to face everyday. Essays give information about your writing skills and so is a course of every university. For you to produce a quality writings this site is the right place for you to consider.

gireesh said...

Hi Faraz, Thank you very much for this post. I am looking to integrate dynamic menu in SharePoint 2010. Can you send me code at: girish.eng@gmail.com. I m looking for left nevigation in SharePoint2010.

Thanks & Regards
Gireesh painuly

gireesh said...

Hi Faraz, Thank you very much for this post. I am looking to integrate dynamic menu in SharePoint 2010. Can you send me code at: girish.eng@gmail.com. I m looking for left nevigation in SharePoint2010.

Thanks & Regards
Gireesh painuly

Gireesh Painuly said...

Hi Faraz, Thank you very much for this post. I am looking to integrate dynamic menu in SharePoint 2010. Can you send me code at: girish.eng@gmail.com. I m looking for left nevigation in SharePoint2010.

Thanks & Regards
Gireesh painuly

Gireesh Painuly said...

Hi Faraz, Thank you very much for this post. I am looking to integrate dynamic menu in SharePoint 2010. Can you send me code at: girish.eng@gmail.com. I m looking for left nevigation in SharePoint2010.

Thanks & Regards
Gireesh painuly

Gireesh Painuly said...

Hi Faraz, Thank you very much for this post. I am looking to integrate dynamic menu in SharePoint 2010. Can you send me code at: girish.eng@gmail.com. I m looking for left nevigation in SharePoint2010.

Thanks & Regards
Gireesh painuly

Gireesh Painuly said...

Hi Faraz, Thank you very much for this post. I am looking to integrate dynamic menu in SharePoint 2010. Can you send me code at: girish.eng@gmail.com. I m looking for left nevigation in SharePoint2010.

Thanks & Regards
Gireesh painuly

rozina islam vabna said...

Excellent blog you’ve got here.It’s difficult to find high-quality writing like yours nowadays. I really appreciate individuals like you! Take care!! Please check out my site.
Email list providers