FeatureReceiver para configurar la Global Navigation: reordenar y ocultar nodos

7.7.11 / Comments (0) / by Enric Carrión

Si alguien acude a este post buscando milagros, los encontrará (por una vez). Pero no es a mi a quien hay que besar los pies, sino al gran, enorme, incommensurable Waldek Mastykarz, que ha conseguido dar con la fórmula mágica y definitiva para hacer eso que todos hemos deseado alguna vez y nunca hemos logrado. Y no, no me estoy refiriendo a eso, mentes enfermas.

Creo mi jerarquía de sitios de publicación y deseo configurar la navegación, es decir, el caso más usual es querer reordenar y ocultar los nodos correspondientes a los sitios y las páginas que cuelgan de un determinado sitio. Ningún problema en hacerlo manualmente pero muchos problemas si intentamos hacerlo mediante la API.

new PublishingWeb(web).Navigation.GlobalNavigationNodes.Count = 0

No hay nodos. ¡Pero si yo los veo en la página de configuración de navegación! Sí, pero no estarán realmente ahí hasta que alguien haya modificado manualmente la configuración y esa colección se rellene. Esto implica un paso manual adicional en nuestros procedimientos de despliegue automatizados y no podemos permitirnos esa mancha en nuestro expediente. Hasta ahora teníamos la opción de utilizar la opción de "Ordenar automáticamente por fecha de creación", con lo cual creábamos subsitios siguiendo nuestro orden arbitrario y quedaba solucionado. Sin embargo, no había solución posible para ordenar subsitios y páginas conjuntamente. Primero aparecen los subsitios y luego las páginas. Fin del primer acto.

Waldek ha implementado una función PowerShell que es la absoluta caña y que permite especificar qué nodos queremos configurar y en qué orden. Los nodos no especificados se ocultarán. De este modo, una vez tengo mi jerarquía creada, invoco esta función y me configurará la navegación a mi gusto.

http://blog.mastykarz.nl/programmatically-configuring-menu-items-sharepoint-2010/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+WaldekMastykarz+%28Waldek+Mastykarz%29

Funciona a las mil maravillas. ¿Por qué? Porque primero hace una precarga de los nodos de navegación en la colección GlobalNavigationNodes y después, hace las reordenaciones y configuraciones especificadas.
Solo me quedaba una pequeña espinita en mi sueño, que era disponer de esa misma funcionalidad en un feature receiver para poder activarlo a nivel de sitio (SPWeb). Evidentemente, esta activación deberá lanzarse después de haber creado la jerarquía entera de sitios (si la lanzamos desde un onet.xml no tendrá efecto), pero por fin podremos definir features que personalicen completamente la navegación de un sitio.
Helo aqui para todos vosotros:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Diagnostics;
using System.Reflection;
using System.Web;
using System.Web.Configuration;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Publishing;
using Microsoft.SharePoint.Publishing.Navigation;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Navigation;

namespace SetNavigationFeatureReceiver
{
public class SetNavigationFeatureReceiver : SPFeatureReceiver
{
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
base.FeatureActivated(properties);

SetNavigation(properties);
}

private void SetNavigation(SPFeatureReceiverProperties properties)
{
var web = properties.Feature.Parent as SPWeb;

// TODO: Retrieve menu items from feature properties
var menuItems = new string[] { "/sites/test/site3", "/sites/test/site2", "/sites/test/site1" };

// Fake context
var request = new HttpRequest("", web.Url, "");
var sw = new System.IO.StringWriter();
var hr = new HttpResponse(sw);
HttpContext.Current = new HttpContext(request, hr);
SPControl.SetContextWeb(HttpContext.Current, web);

// Initalize what has to be initialized
var pweb = PublishingWeb.GetPublishingWeb(web);
var dictionary = new Dictionary();
var collection = pweb.Navigation.GlobalNavigationNodes;

// Get current nodes
var globalNavSettings = new ProviderSettings("GlobalNavSiteMapProvider",
@"Microsoft.SharePoint.Publishing.Navigation.PortalSiteMapProvider, Microsoft.SharePoint.Publishing, 
Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c");
globalNavSettings.Parameters["NavigationType"] = "Global";
globalNavSettings.Parameters["EncodeOutput"] = "true";
PortalSiteMapProvider globalNavSiteMapProvider =
ProvidersHelper.InstantiateProvider(globalNavSettings, typeof(PortalSiteMapProvider))
as PortalSiteMapProvider;
PortalSiteMapNode currentNode = globalNavSiteMapProvider.CurrentNode as PortalSiteMapNode;
var children = currentNode.GetNavigationChildren(NodeTypes.Default, NodeTypes.Default,
OrderingMethod.Manual, AutomaticSortingMethod.Title, true, -1);

// Reorder nodes
Array.Reverse(menuItems);
var menuNodes = new System.Collections.ObjectModel.Collection();
foreach (PortalSiteMapNode node in children)
{
menuNodes.Add(node);
}

foreach (var menuItem in menuItems)
{
PortalSiteMapNode node = null;
foreach (var p in menuNodes)
{
if (p.InternalUrl == menuItem)
{
node = p;
break;
}
}

if (node != null)
{
menuNodes.Remove(node);
menuNodes.Insert(0, node);
}
}

foreach (var node in menuNodes)
{
var quickId = GetQuickId(node);
if (quickId != null)
{
string typeId = null;
if ((node.Type == NodeTypes.Area || node.Type == NodeTypes.Page))
{
if (node.PortalProvider.NavigationType == PortalNavigationType.Current)
{
typeId = PortalNavigationType.Current.ToString() + "_" + node.Type.ToString();
}
else
{
typeId = PortalNavigationType.Global.ToString() + "_" + node.Type.ToString();
}
}
else
{
typeId = node.Type.ToString();
}

var id = quickId.Split(',');
var objId = new Guid(id[0]);
var nodeId = System.Int32.Parse(id[1]);

var navigationNode = GetNavigationNode(objId, nodeId, node.InternalTitle, node.InternalUrl,
node.Description, node.Type, node.Target, node.Audience, collection, dictionary);
var containsNode = false;
foreach (var mi in menuItems)
{
if (mi == node.InternalUrl)
{
containsNode = true;
break;
}
}

if (containsNode)
{
pweb.Navigation.IncludeInNavigation(true, objId);
}
else
{
pweb.Navigation.ExcludeFromNavigation(true, objId);
}
}
}

pweb.Web.Update();

HttpContext.Current = null;
}

private string GetQuickId(PortalSiteMapNode node)
{
string quickId = null;

var portalSiteMapNodeType = node.GetType();
var QuickId = portalSiteMapNodeType.GetProperty("QuickId", BindingFlags.Instance | BindingFlags.NonPublic);
quickId = (string)QuickId.GetValue(node, null);

return quickId;
}

private Microsoft.SharePoint.Navigation.SPNavigationNode GetNavigationNode(
Guid objId,
System.Int32 nodeId,
string name,
string url,
string description,
NodeTypes nodeType,
string target,
string audience,
SPNavigationNodeCollection collection,
Dictionary oldDictionary)
{
Microsoft.SharePoint.Navigation.SPNavigationNode node = null;
if ((objId != Guid.Empty) && (nodeId >= 0))
{
if (oldDictionary.TryGetValue(nodeId, out node))
{
oldDictionary.Remove(nodeId);
node = SPNavigationSiteMapNode.UpdateSPNavigationNode(node.Navigation.GetNodeById(node.Id), null,
name, url, description, target, audience, false);
node.MoveToLast(collection);
}

return node;
}

node = SPNavigationSiteMapNode.CreateSPNavigationNode(name, url, nodeType, collection);
return SPNavigationSiteMapNode.UpdateSPNavigationNode(node, null, name, node.Url, description, target,
audience, false);
}
}
}
Recordemos, este receiver solo reordena/oculta/muestra, no añade nuevos nodos. Eso ya será otra historia...