How to access the Sharepoint SPNavigationNode.QuickLaunch Property? - sharepoint

I have a site as follows:
--SiteA
----Subsite1
----Subsite2
Now whenever i try to access the QuickLaunch Property its always empty e.g
SPNavigation nav = spWeb.Navigation;
if (nav.QuickLaunch.Count == 0)
{
// ALWAYS TRUE
}
However if i go into the Naviation Settings (Through the UI) of SiteA and reorder any site in the list, only then will the QuickLanuch become available. (Other settings are left as default)
Can anyone explain this behaviour? I really need access to the QuickLaunch items.
Thanks

This error occurs if you access quicklaunch while site is being created.Below code causes the feature activated code to wait until the site collection has been created before executing.
using System.Threading;
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//Queues changes until after site exists. For use in provisioning.
SPWeb web = properties.Feature.Parent as SPWeb;
ThreadPool.QueueUserWorkItem(ApplyYourChanges, web.Url);
}
private void ApplyYourChanges(object state)
{
string webUrl = state as string;
Uri uri = new Uri(webUrl);
// additional conditions here -- perhaps check if a feature was activated
while (!SPSite.Exists(uri))
{
Thread.Sleep(5000);
}
using (SPSite site = new SPSite(webUrl))
{
using (SPWeb web = site.OpenWeb())
{
//configure the quicklaunch menu
configureQuickLaunch(web);
}
}
}
public static void configureQuickLaunch(SPWeb spWeb)
{
SPNavigationNodeCollection nodeCollection = spWeb.Navigation.QuickLaunch;
SPNavigationNode heading = nodeCollection.Cast<SPNavigationNode>().FirstOrDefault(n => n.Title == headingNode);
SPNavigationNode item = heading.Children.Cast<SPNavigationNode>().FirstOrDefault(n => n.Url == url);
if(item == null)
{
item = new SPNavigationNode(nodeName, url);
item = heading.Children.AddAsLast(item);
}
}

I think, by default, the QuickLaunch uses shared navigation. In other words, the QuickLaunch for a subsite doesn't have its own collection of nodes until you do something with it. If you reorder a site, that gives it its own unique set of nodes.
If you wanted to programmatically set your QuickLaunch to have its own set of nodes programmatically, you should be able to do so this way:
SPNavigation nav = spWeb.Navigation;
nav.UseShared = false;
spWeb.Update();
I think your count should be something other than zero at that point.

I seem to remember reading somewhere that the QuickLaunch collection only stored customisations to the default ordering. Looking around, I can't find that documentation to show you, but it would explain the behavior you see if true.
So your QuickLaunch.Count == 0 is just confirming that default ordering of items is in place.
You can still add nodes, if that's at all helpful;
SPNavigationNodeCollection nodes = web.Navigation.QuickLaunch;
SPNavigationNode node = new SPNavigationNode("Node Name", "Node URL", true);
nodes.AddAsFirst(node);

Related

SiteMapNode Attributes - values being lost (SharePoint 2010)

OK - I've created a custom navigation provider that inherits PortalSiteMapProvider and I'm trying to extend my class via the GetChildNodes method (Simple example here).
Essentially what I'm trying to do is add the current SPWeb's SiteLogoUrl string as an attribute (called imgurl) of each SPWeb's respective SiteMapNode, with a view to retrieve this value later in my custom Menu control (which inherits SharePoint:AspMenu).
I'm setting the attributes like so in my provider under GetChildNodes:
public override SiteMapNodeCollection GetChildNodes(SiteMapNode node)
{
PortalSiteMapNode portalNode = (PortalSiteMapNode)node;
if (portalNode != null)
{
if (portalNode.Type == NodeTypes.Area)
{
SiteMapNodeCollection nodeColl = base.GetChildNodes(portalNode);
using (SPSite currentSite = new SPSite(portalNode.PortalProvider.CurrentSite.Url))
{
foreach (SiteMapNode topLevelNode in nodeColl)
{
foreach(SiteMapNode currentNode in topLevelNode.ChildNodes)
{
string currentWebUrl = currentNode.Url.Substring(0, currentNode.Url.ToLower().IndexOf("/pages/"));
using (SPWeb currentWeb = currentSite.OpenWeb(currentWebUrl))
{
if (!string.IsNullOrEmpty(currentWeb.SiteLogoUrl))
{
currentNode["imgurl"] = currentWeb.SiteLogoUrl;
}
}
}
}
}
return nodeColl;
}
Now I can debug this and retrieve the value once it has been inserted like so (x being the index of node, and me doing this in the Command Window):
? topLevelNode.ChildNodes[x]["imgurl"]
and that returns the url of the image fine : "/_layouts/images/myimage.jpg"
Now.. when I try to do this in my custom Menu control I first get a handle on my custom nav provider like so:
CustomNavProvider customProvider = (CustomNavProvider)SiteMap.Providers["CustomNavProvider"];
SiteMapNode currentNode = customProvider.FindSiteMapNode(childitem.NavigateUrl);
and the currentNode variable now contains my SiteMapNode...great, however instead of the attribute I added previously, there is now only a single attribute called 'AlternateUrl' - the imgurl attribute I added in the provider has now disappeared. All other properties like title, desc etc are there so it's definitely returning the correct node.
What's my issue here? Is the attribute not being persisted when I added it to the node in the provider?
Any help or suggestions appreciated.

Finding control on SharePoint page

Im trying to locate an SPDataSource control located on my SharePoint page. I found the following code which probably works fine, I just don't know what to pass into it.
public static Control FindControlRecursive(Control Root, string Id)
{
if (Root.ID == Id)
return Root;
foreach (Control Ctl in Root.Controls)
{
Control FoundCtl = FindControlRecursive(Ctl, Id);
if (FoundCtl != null)
return FoundCtl;
}
return null;
}
I don't know how to have it search the whole page or at the very least the ContentPlaceHolder that the control is in.
edit
Looks like I have a more rudimentary issue here. Not sure how to explain but I'm not opening up the page before running my code. I'm opening the site via the following:
using (SPWeb web = thisSite.Site.OpenWeb("/siteurl/,true))
So when I try to find the page below I'm getting Object reference not set to instance of object.
var page = HttpContext.Current.Handler as Page;
Perhaps I'm going about this the wrong way, I'm in my infancy here so I'm just kind of stumbling along figuring stuff out!
What you got is actually not SharePoint specific, it's c# asp.net.
Anyway, you could call it like this
var page = HttpContext.Current.Handler as Page;
var control = page; // or put the element you know exist that omit (is a parent) of the element you want to find
var myElement = FindControlRecursive(control, "yourelement");
Most likely you'll need to cast the return as well
var myElement = (TextBox)FindControlRecursive(control, "yourelement");
// or
var myElement = FindControlRecursive(control, "yourelement") as TextBox;
There are however more efficient ways to write such a method, here is one simple example
public static Control FindControlRecursive(string id)
{
var page = HttpContext.Current.Handler as Page;
return FindControlRecursive(page, id);
}
public static Control FindControlRecursive(Control root, string id)
{
return root.ID == id ? root : (from Control c in root.Controls select FindControlRecursive(c, id)).FirstOrDefault(t => t != null);
}
Call it the same way as I suggested earlier.
If you are handling larger pages the methods above might be a bit slow, what you should do is aim for a method using generics instead. They are way faster than traditional methods.
Try this one
public static T FindControlRecursive<T>(Control control, string controlID) where T : Control
{
// Find the control.
if (control != null)
{
Control foundControl = control.FindControl(controlID);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
// Continue the search
foreach (Control c in control.Controls)
{
foundControl = FindControlRecursive<T>(c, controlID);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
}
}
return null;
}
You call it like this
var mytextBox = FindControlRecursive<TextBox>(Page, "mytextBox");

Additional code required when developing custom visual web part for anonymous access?

I have a simple custom web part with three drop downs that reads from three different list. When the user tries to access this page they get prompted for password, if they don't enter any credentials they get a 401 error.
I have enabled anonymous access both in central admin and on the site itself, users can browse to the site and view it without getting prompted for password. I have made sure that anonymous user have "view" access to the lists in questions but they still can't view any page with a custom web part.
So is it a SharePoint setting or do I have to add something in my web part projects?
Thanks in advance.
Edit:
I call this method in page load and still get the same error
private void LoadImageGallery()
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
SPSite oSiteCollection = SPContext.Current.Site;
SPWebCollection collWebsites = oSiteCollection.AllWebs;
DataTable dt = new DataTable();
for (int i = 0; i < collWebsites.Count; i++)
{
using (SPWeb oWebsite = collWebsites[i])
{
if (oWebsite.Title == "People")
{
SPList peopleList = oWebsite.Lists["Pages"];
if (peopleList != null)
{
SPListItemCollection collListItems = peopleList.Items;
dt = collListItems.GetDataTable();
// Include Surname to omit default/search page
dt = collListItems.GetDataTable();
rptImageGallery.DataSource = dt;
rptImageGallery.DataBind();
}
}
}
}
});
}
I also tried with
SPSite oSiteCollection = SPContext.Current.Site;
SPWebCollection collWebsites = oSiteCollection.AllWebs;
above runwithelevated..
I set system\sharepoint to have full control in the entire site
The custom page needs to meet two requirements so that the anonymous users can access it:
it needs to inherit from the class UnsecuredLayoutsPageBase,
the property AllowAnonymousAccess needs to be overridden to return true.
For anonymous access, you have to modified your code for access List to bind drop down list as below. Add you code in SPSecurity.RunWithElevatedPrivileges Delegate method.
SPSecurity.RunWithElevatedPrivileges(delegate() {
using (SPSite site = new SPSite(web.Site.ID))
{
//ADD YOUR WEB PART Code HERE
}
});
Important: You must create SharePoint Objects in this Delegate otherwise code will not run with Admin access.

Consolidating data in SharePoint from different sites based on variable site naming

am working on a project where I want to pull data from different lists in SharePoint and have these data imported into a single list. The list has the same attribute everywhere; it is located in different sites.
I have a list which contains all the site names and URL to those sites. The idea is to read from this list all the site names and then go to each one of those sites and try and pull the information from the list under that particular site, in synchronies matter. Data that are pulled from last week’s process do not need to be pulled again.
Can someone guide me in explaining what would be the best way to doing this solution?
Am using SharePoint 2007
You may be better off looking at the Data View Web Part (DVWP) and rollups
A common topic that is asked about in
SharePoint, is how to roll up
information from sub-sites to a top
level site, and just generally how to
show data from one site on another
site.
I have this scenario. I made a webpart that sends the information for my consolidation list, through the sharepoint web services.
I installed the webpart in each sharepoint site, this webpart get the data (when it's outdated) and make a insert in the consolidation list through the lists.asmx webservice. See http://msdn.microsoft.com/en-us/library/lists(v=office.12).aspx. Then a I create a user with permission to write in the consolidation list in my consolidation site, and used this to authenticate my webpart when it will do the insert on the list.
I did something like this, in Visual Studio 2005, with Sharepoint dll referenced.
public void InsertToPainel(string strID, string user, string password)
{
SPSecurity.RunWithElevatedPrivileges(delegate()
{
try
{
//WSPainel is the WebReference to http://<site>/_vti_bin/Lists.asmx.
using (WSPainel.Lists lstPainel = new webPartSender.WSPainel.Lists())
{
lstPainel.UseDefaultCredentials = true;
lstPainel.Credentials = new System.Net.NetworkCredential(user, password);
#region Make the fields in consolidation list
Dictionary<string, string> fieldsNamesAndValues = new Dictionary<string, string>();
fieldsNamesAndValues.Add("ID", strID);//Recuperar quando for atualizar ou incluir New
fieldsNamesAndValues.Add("URL", SPContext.Current.Web.Url + ", " + splInfGerItems[0]["Title"].ToString()); //The link of the actual site that is sending the information
fieldsNamesAndValues.Add("Comments", splStatusItems[0]["Title"].ToString());//In my case, I just need the last result.
#endregion
//It will make the register in CAML format and updates the lists.
lstPainel.UpdateListItems(_listaPainel, NewCAMLRegister(fieldsNamesAndValues, strID));
}
}
catch (Exception e)
{
//Exception
}
});
}
private XmlNode NewCAMLRegister(Dictionary<string, string> FieldsNamesAndValues, string strID)
{
try
{
XmlDocument xdMensagem = new XmlDocument();
XmlElement xeBatch = xdMensagem.CreateElement("Batch");
XmlNode xnMethod = xdMensagem.CreateElement("Method");
xdMensagem.AppendChild(xeBatch);
xeBatch.AppendChild(xnMethod);
XmlAttribute xaID = xdMensagem.CreateAttribute("ID");
XmlAttribute xaCmd = xdMensagem.CreateAttribute("Cmd");
xaID.Value = "1"; //Id do comando no Batch.
if (strID == "New")
{
xaCmd.Value = "New";
}
else
{
xaCmd.Value = "Update";
}
xnMethod.Attributes.Append(xaID);
xnMethod.Attributes.Append(xaCmd);
foreach (KeyValuePair<string, string> strfieldname in FieldsNamesAndValues)
{
XmlNode xnField = xdMensagem.CreateElement("Field");
XmlAttribute xaName = xdMensagem.CreateAttribute("Name");
xaName.Value = strfieldname.Key;//Nome do Campo
xnField.Attributes.Append(xaName);
xnField.InnerText = strfieldname.Value;//Valor do Campo
xnMethod.AppendChild(xnField);
}
//"<Method ID=\"1\" Cmd=\"New\">" + "<Field Name=\"ID\">New</Field>" + "<Field Name=\"Title\">This is a test</Field>" + "</Method>";
return xdMensagem;
}
catch (Exception e)
{
//Exception
return null;
}
}
I hope it helps.

Updating the url of a navigation node in moss programatically

Can anyone see why this should not work:
SPSite topNavigationSite = new SPSite("http://moss");
SPWeb topNavigationWeb = topNavigationSite.OpenWeb();
SPNavigationNodeCollection topNavigationBarNodes = topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode updateNode = topNavigationBarNodes.Navigation.GetNodeByUrl("/about");
updateNode.Url = "";
topNavigationWeb.Update();
I can see debugging that the url get's set to "" but when the page renders, the navigation still shows the url as /about/default.aspx
I'm running this in page_load and expected it to update the moss database with the new url value.
I know this is 3years old! but as there is no where online about updating a current url!
I had to do some debugging and this is what iv come up with! By the way I got the hint from
topNavigationWeb.Update();
indicating that its updating a list! hint hint!
a bit of background! I wanted to update the quick links from a list when they add, update and delete an item from the list! On my list I have two columns Title and URL!
I then created a project in VS 2010, its an event receiver that is connected only to that list (done in the elements.xml file)
within the .cs file I added item added, item deleting(not deleted ;) ) and item updated
public override void ItemAdded(SPItemEventProperties properties)
public override void ItemUpdated(SPItemEventProperties properties)
public override void ItemDeleting(SPItemEventProperties properties)
now within each method you can simply call this method!
add/update
public static void AddQuickLaunchItem(string header, string url, SPWeb web)
{
SPNavigationNodeCollection quickLaunch = web.Navigation.QuickLaunch;
// try to get quick launch header
SPNavigationNode nodeHeader =
quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).FirstOrDefault();
//if header not found create it
if (nodeHeader == null)
{
nodeHeader = quickLaunch.AddAsFirst(new SPNavigationNode(header, url,true));
}
else
{
web.AllowUnsafeUpdates = true;
nodeHeader = quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).First() ;
nodeHeader.Url = url;
web.AllowUnsafeUpdates = false;
}
nodeHeader.Update();
web.Update();
}
the first part is checking if the node exists using the title (header)! I'm comparing between what headers there are and from the list item:
SPNavigationNode nodeHeader =
quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).FirstOrDefault();
this part is the comparison:
n.Title == header
I'm getting these values (header and url / spweb) like so:
public static void AddQuickLaunchItem(string header, string url, SPWeb web)
the method that is calling the above would look like this:
private void addloopweblinks(SPSite siteCollection, SPItemEventProperties properties)
{
// Enumerate through each site and apply branding.
foreach (SPWeb web in siteCollection.AllWebs)
{
AddQuickLaunchItem(properties.ListItem["Title"].ToString(), properties.ListItem["URL"].ToString(), web);
}
}
and the above method is called within the itemadded and itemupdated ;) passing the values like so:
public override void ItemAdded(SPItemEventProperties properties)
{
using (SPSite siteCollection = new SPSite(properties.WebUrl))
{
if (siteCollection != null)
{
addloopweblinks(siteCollection, properties);
}
}
}
similar things can be done for the delete ;)
SPNavigationNode nodeHeader =
quickLaunch.Cast().Where(n => n.Title == header).FirstOrDefault();
that will get the node!
nodeHeader.delete();
nodeHeader.update();
that will delete the item!
so in your case what you need, I don't know if you noticed it was this part:
web.AllowUnsafeUpdates = true;
nodeHeader = quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).First() ;
nodeHeader.Url = url;
web.AllowUnsafeUpdates = false;
nodeHeader.Update();
web.Update();
as you can see what i mean by the hint! nodeHeader.Update(); didnt update the url, when debugging it does change but when i go to the update part it doesnt work :( and it clicked in i need web.AllowUnsafeUpdates = true; loool only becuse I experienced a similar situation before!
for you it would be:
using(SPSite topNavigationSite = new SPSite("http://moss"))
{
using(SPWeb topNavigationWeb = topNavigationSite.OpenWeb())
{
web.AllowUnsafeUpdates = true;
SPNavigationNodeCollection topNavigationBarNodes =topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode updateNode = topNavigationBarNodes.Navigation.GetNodeByUrl("/about");
updateNode.Url = "";
updateNode.Update();
web.Update();
web.AllowUnsafeUpdates = false;
}
}
If the user doesnt have the sufficent rights than you need to encapsulate the above with runwithelevatedprivalages :) hope this helps :)
Have you tried:
updateNode.Update();
topNavigationWeb.Update();
It doesn't look like you are updating the SPNavigationNode object. (Note: you may not need the second Update call.)

Resources