Microsoft.SharePoint.Utilities: el namespace que vale su peso en oro (1)

15.12.10 / Comments (0) / by Unknown

Resulta que un día te das cuenta que vas por tu duodécima implementación de codificación HTML (XML) de un string y decides preguntarte si existe una inteligencia suprema que ya la ha programado para todo el universo. Y obtienes la respuesta y otras muchas revelaciones que nunca habrías soñado conocer. Esas revelaciones están debidamente paquetizadas y ready-to-use en el namespace Microsoft.SharePoint.Utilities.

Quien más quien menos conoce el cajón de sastre llamado SPUtility y sus métodos miscelánicos. Ahora os propondré un viaje en distintos capítulos por algunas otras clases de indiscutible utilidad. Hoy para empezar:

SPHttpUtility o cómo olvidarse para siempre de string.Replace

La intención de esta clase estática es proveer de métodos también estáticos para la codificación y decodificación de strings durante el procesamiento de peticiones web. Lo más interesante es:

string ConvertSimpleHtmlToText(string html, int maxLength)
Convierte una cadena html (por ejemplo, de un campo de rich text o un PublishingContent) a texto plano, eliminado los tags HTML. ¿A que te estás dando de cabezazos?

string HtmlEncode(string valueToEncode)
Codifica una cadena de texto para ser incluída en markup HTML, es decir, reemplaza " / ' / < / > / & por &quot; / &apos; / &lt; / &gt; / &amp; . Opcionalmente tenemos también el HttpUtility.HtmlEncode . A ver, que el Regex que implementaste está muy bien pero...

string HtmlDecode(string valueToDecode, bool decodeNbsp)
El correspondiente método de decodificación. "decodeNbsp" a true para si deseamos decodificar también los &nbsp; a espacios.

string HtmlEncodeAllowSimpleTextFormatting(string valueToEncode)
Codifica una cadena para aparecer entre merkup HTML pero además sustituye los espacios y saltos de línea por &nbsp; y <br/> (eso sí, codificados a &amp;nbsp; y &lt;br&gt;).

string UrlKeyValueEncode(string keyOrValueToEncode)
Codifica una clave o valor de una querystring para construir una URL, es decir, escapa los caracteres no admitidos por un querystring. El HttpUtility.UrlEncode de toda la vida.

string UrlKeyValueDecode(string keyOrValueToDecode)
Decodifica una clave o valor de querystring.

string EcmaScriptStringLiteralEncode(string scriptLiteralToEncode)
Veamos, ésta no es de utilidad inmediata pero en algunas funcionalidades de SP, hay que transferir strings a traves de JavaScript (ECMAScript), para lo cual algunos caracteres necesitan ser codificados a formato Unicode (\u00XX). Este método proporciona dicha codificación.

SharePoint 2007: Reemplazar master page en páginas de aplicación

10.12.10 / Comments (0) / by Unknown

La application.master que viene de serie en las páginas del directorio  _layouts es una cruz que nos tortura constantemente a los desarrolladores de SharePoint. Es única por servidor, no está soportado el modificarla y, para colmo, las páginas de aplicación que la usan son múltiples y habituales (newsbweb.aspx, createpage.aspx, mysubs.aspx,...). Con lo cual, es frecuente el requerimiento de que estas páginas dispongan de un look&feel como mínimo parecido al del resto de páginas del sitio (ya sean páginas de publicación o páginas de formulario -DispForm.aspx, EditForm.aspx,...-, a las que no hay problema para asignarles una master page). Y con toda la razón del mundo, porque el aspecto de esas páginas canta como una almeja.

A riesgo de meter la pata en el hoyo de lo "not supported", voy a proponer un mecanismo, a mi juicio bastante limpio, para reemplazar la master page en tiempo de ejecución para esas páginas. Está comprobado con muchas de las páginas de aplicación, aunque no garantiza su efectividad en el 100% de ellas. Y no, no hay ningún HttpModule de por medio.

Si implementamos un page adapter para estas páginas de layout, podemos tener control de sus propiedades en runtime. Ahora sólo quedaría modificar la propiedad MasterPage, el pequeño inconveniente es que sólo puede modificarse antes o durante del método OnPreInit() y que ese método no existe en un control adapter. Bueno, un vistazo al ciclo de vida de los controles ASP.NET, y veremos que existe un evento DeterminePostBackMode() invocado justo antes de OnPreInit. Este evento procesa los datos enviados por postback a la petición actual, estableciendo el valor IsPostBack y devolviendo la colección de valores recibidos. Sí, sí, muy bien. Pero de lo mío, ¿qué? Bien, pues si el page adapter sobreescribe este evento, llama al base (importante no olvidarlo) y luego modifica la propiedad MasterPageFile, bingo! Tenemos una página de aplicación con la master page deseada.

/// <summary>
/// Adapter for SharePoint layout pages to replace their masterpage
/// </summary>
public class CustomLayoutPageAdapter : PageAdapter
{
public override System.Collections.Specialized.NameValueCollection
DeterminePostBackMode()
{
NameValueCollection result = base.DeterminePostBackMode();

try
{
Page page = this.Control as Page;
page.MasterPageFile = "/_catalogs/masterpage/XXXXXX.master";
}
catch { }

return result;
}
}

Ahora sólo queda utilizar este page adapter, creando un fichero my.compat.browser en App_Browsers con una definición <adapter> para cada página de aplicación que queramos reemplazar. ¿Engorroso? Depende, esto nos asegura que estaremos reemplazando la master page sólo en las páginas que queremos y no a lo loco, en todo el back-office de SharePoint. Ejemplo:



<adapter controlType="Microsoft.SharePoint.Publishing.Internal.CodeBehind.CreatePagePage,
Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral,
PublicKeyToken=71e9bce111e9429c"
adapterType="MyNamespace.CustomLayoutPageAdapter,
MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=XXXXXXXXXXXXX"
/>


¿Cómo obtengo el tipo correspondiente a una página de aplicación? Normalmente se ve fácil consultando el código fuente de la página y identificando su clase codebehind. Aquí van una cuantas páginas típicas y sus tipos:



createpage.aspx

Microsoft.SharePoint.Publishing.Internal.CodeBehind.CreatePagePage, Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



mysubs.aspx

Microsoft.SharePoint.ApplicationPages.MySubsPage, Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



sitesubs.aspx

Microsoft.SharePoint.ApplicationPages.SiteSubsPage, Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



newsbweb.aspx

Microsoft.SharePoint.ApplicationPages.SubNewPage, Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



checkin.aspx

Microsoft.SharePoint.ApplicationPages.CheckIn, Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



userdisp.aspx

Microsoft.SharePoint.ApplicationPages.UserDisplayPage, Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



regionalsetng.aspx

Microsoft.SharePoint.ApplicationPages.RegionalSettingsPage, Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



subchoos.aspx

Microsoft.SharePoint.ApplicationPages.SubChoosPage, Microsoft.SharePoint.ApplicationPages,  Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c



Pero esto no es todo. Es muy importante notar que, si reemplazamos una application.master por una master page nuestra, es probable que las páginas de aplicación a las que hemos lobotomizado empiecen a echar en falta placeholders y controles, con la consecuente eclosión de fuegos artificiales diversos. Solución: localizar cuáles son esos placeholders y añadirlos a nuestra masterpage. Esto no es ciencia exacta: debemos testear cada página a la cual estamos aplicando este fix y asegurarnos que funciona correctamente. Dejadme solo  apuntar que para la mayoría de páginas de aplicación, el PlaceHolderFormDigest es necesario.



<asp:ContentPlaceHolder id="PlaceHolderFormDigest" runat="server">  
<SharePoint:FormDigest ID="FormDigest1" runat=server/>
</asp:ContentPlaceHolder>



Y nada más, si alguien aplica este método y quiere dar feedback sobre él, soy todo oídos.

Mis blogs de cabecera

30.11.10 / Comments (2) / by Unknown

Tenía pendiente una entrada sobre mis blogs técnicos favoritos, que, fíjate tú que casualidad, son los de mis compañeros de trabajo en Spenta, a los que tengo en mucha estima y agradezco la promoción que han dado al de un servidor. Sus últimas andanzas siempre estarán disponibles en la cajita "Os estoy viendo..." de la columna derecha. Sin más, paso a enumerarlos. Starring in alphabetical order:

  • blog de Ángel Suarez, de Ángel Suárez "Guisu", el "outsider" de las tecnologías punteras, los frameworks sociales y los meollos con tramitación electrónica. Altamente recomendable para amantes de la cecina.
  • CRM a diario, de Daniel Sabater, eminencia de Microsoft Dynamics capaz de convertir CRM en todo aquello que siempre soñaste y nunca te atreviste a pedir.
  • SharendiPointy El blog de David Martos, de David Martos, el "capo" tecnológico de todo aquello que tiene que ver con Microsoft, betas, lanzamientos y retos imposibles. Cuando os parezca que pisáis territorio virgen, entrad en su blog y comprobaréis que David ya estuvo allí.
  • El blog de Jordi Ruiz, de Jordi Ruiz, el único "jedi" con midiclorianos de Resharper en su cuerpo y cuyo esqueleto está formado con patrones de diseño en lugar de huesos.
  • elblogdeluru y Amigo mío Siempre estas Programando en .NET, del omnipresente Luis Ruiz Pavón, cuya fama le precede; no hay tecnología que se resista a su virtuosismo: ASP.NET, VSTO, SharePoint, EntLib.... y el único combustible que necesita son cañas y tapas. 
  • Ignasi Tebé SharePoint Blog, de Ignasi Tebé, implacable exprimidor de posibilidades para SharePoint, producto que muy pronto ofrecerá un nuevo esquema de pricing basado en tres licencias: Standard, Enterprise y Tebé.
  • Madeinmind, de Maria Bellmunt, la diosa del "branding" y de los retos de diseño. "Imposible" y "nunca" no están en su diccionario. En su lugar, encontraréis "flor" y "nube".
  • Maximo Castagno, de Maximo Castagno, nuestro catedrático en user experience, cuyas reflexiones sobre usabilidad son firmes pasos hacia la victoria sobre el caos en las UI, la sinrazón funcional... en definitiva, todo lo que habita en el cerebro de los desarrolladores.

Y, aunque lejos, sigue cerca nuestro:

  • Developing is painful, de José Manuel Sánchez "Er Chan", talento fuera de serie que lleva el desarrollo y la integración con tecnologías Microsoft en las mismas venas, además de maestro en el uso del estilo tachado de la ironía.

Espero no dejarme ninguno y poder editar la entrada pronto para añadir más. Y yo creo que por hoy, como dijo aquél, ya está bien de... en fin, de eso.

Could not load file or assembly 'Microsoft.SharePoint.ApplicationPages PublicKeyToken=71e9bce111e9429c' o el arte de la colleja bien dada

26.11.10 / Comments (0) / by Unknown

En uno de estos iisreset con los que tan habitualmente masacramos a nuestro servidor web, podemos encontrarnos con que, de pronto, las páginas de /_layouts de SharePoint 2007 dejen de funcionar, mostrando:
Could not load file or assembly 'Microsoft.SharePoint.ApplicationPages PublicKeyToken=71e9bce111e9429c'

Este error no solo trae de cabeza a mucha gente:

http://social.technet.microsoft.com/Forums/en/sharepointgeneral/thread/e7463fba-1d31-4198-87b8-29cf107906dd

http://blog.dennus.net/2009/06/

http://www.tech-archive.net/Archive/SharePoint/microsoft.public.sharepoint.windowsservices/2008-06/msg00048.html

http://www.solomon-strutz.com/wordpress/?p=83sino

sino que además no dispone de un fix consensuado. Parece que al aparecer este mensaje, SharePoint entra en un estado de encallamiento que le impide resolver correctamente el ensamblado: reciclar el app pool igual ayuda, un iisreset o dos más pueden funcionar, quizá un reboot de la máquina... veis por donde voy, no? SharePoint necesita una colleja, un capón, un meneo; en definitiva, una acción humana que le haga ser capaz de nuevo de encontrar la DLL. Sirva esta entrada para sugerir una más a las anteriores citadas; como mínimo, ha funcionado en un caso donde no habían funcionado recycles e iisresets en solitario.

- Mover el Microsoft.SharePoint.ApplicationPages.dll de _app_bin (del directorio de la aplicación web que está fallando) a otro sitio cualquiera.

- iisreset

- Cargar una página de /_layouts . Aparecerá de un error distinto.

- Volver a poner Microsoft.SharePoint.ApplicationPages.dll en _app_bin

- iisreset

- Cargar de nuevo la página de /_layouts .

Si todo ha funcionado como debería, habréis descolocado a SharePoint con un buen capón de padre y la página debería verse correctamente de nuevo.

Emergencia: no se propagan los perfiles de usuario a la User Information List

24.11.10 / Comments (0) / by Unknown

Panic situation: necesitamos obtener inmediatamente (se requiere el uso del adverbio "inmediatamente" para
provocar cierto grado de pánico) un campo de un usuario (el e-mail, por ejemplo) en el contexto de un sitio,
es decir a través de un objeto SPUser, que obtiene la información desde la lista interna de WSS / SharePoint
Foundation "User Information List". El problema es que el origen de ese campo no está en esa lista sino en el
repositorio de perfiles de usuario (User Profiles de MOSS 2007 / SharePoint Server), ya sea importado de un origen de datos, como Directorio Activo, o bien gestionado manualmente desde los perfiles de SSP / User Profile Service Application. Y la sincronización entre perfiles de usuario y "User Information List" es, como ya todos sabemos, muy suya.

Sobre la teoría de cómo funciona la sincronización entre ambos repositorios y cómo forzar la propagación de los campos, existen unos cuantos artículos bastante clarificadores, entre ellos éste de Mirjam van Olst:

http://www.sharepointchick.com/archive/2009/06/17/user-profiles-and-the-user-information-list-or-userinfo-table.aspx

También merecen una ojeada:

http://blogs.technet.com/paulpaa/archive/2009/10/01/user-profile-information-not-updated-on-site-collection-s-people-and-group.aspx

http://www.21apps.com/sharepoint/user-profiles-why-do-my-changes-not-show-in-other-sites/

Pero vamos a ponernos en el caso de que nada de los descrito en ellos funcione. No conseguimos propagar de ninguna manera el valor de los campos a la "User Information List" y los nervios aumentan. Necesitamos una solución de emergencia; encendemos el reflector con la Power-señal, PowerShell lo ve y acude a nuestro rescate.

Primero: abrir consola de PowerShell y cargar las librerías de SharePoint. Reminder para hacerlo:
SharePoint 2007
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
Sharepoint 2010
Add-PSSnapin "Microsoft.SharePoint.PowerShell"
Segundo: manipular manualmente el elemento de la lista "User Information List" mediante object model, usando operaciones que recomiendo os acostumbréis a usar asiduamente por su sencillez e inmediatez.
$site = new-object Microsoft.SharePoint.SPSite("http:/{el_sitio}")
$web = $site.OpenWeb()
$list = $web.Lists["User Information List"]
$item = $list.GetItemByID(xx)
Donde xx será el ID del elemento (usuario), que podemos consultar en cualquiera de las URL
http://el_sitio/_layouts/people.aspx o http://el_sitio/_catalogs/users/detail.aspx . Si pasamos por encima
del enlace de cada elemento veremos una URL del estilo http://el_sitio/_layouts/userdisp.aspx?ID=xx
$item["EMail"] = "elcorreo@eldominio.com"
$item["JobTitle"] = "cargo"
$item[...] = ...
$item.Update()
Con esto, el SPUser dispondrá del campo actualizado inmediatamente. Solucionado; gracias PowerShell. Respecto a la propagación del campo desde perfiles de usuario, tranquilos, en un periodo de 24 horas, tarde o temprano se acabará produciendo automáticamente.

Anexo: entre los campos de un item de tipo usuario tenemos los siguientes:
Internal name - Display name

Title - Name
Name - Account
EMail - Work e-mail
Notes - About me
SipAddress - SIP Address
IsSiteAdmin - Is Site Admin
Deleted - Deleted
Picture - Picture
Department - Department
JobTitle - Title
IsActive - Is Active
FirstName - First name
LastName - Last name
WorkPhone - Work phone
Office - Office
UserName - User name
WebSite - Web site
SPSResponsibility - Responsibilities
ID - ID

MSDN publica "Real World Branding with SharePoint 2010 Publishing Sites" de Andrew Connell

19.11.10 / Comments (0) / by Unknown

Andrew "SPGod" Connell es la mayor referencia de los que nos dedicamos a esto y un artículo suyo en MSDN es siempre sinónimo de sabiduría, practicidad, pedagogía y, en el fondo, esperanza a cualquiera que le flaquee las fuerzas cuando bucea por la fosa abisal de SharePoint.

En este caso, un extenso artículo de Connell arremangado y en faena con el branding de sitios de publicación en SharePoint 2010, está recién publicado en MSDN y listo para vuestro deleite. Me encanta que consista en un práctico hands-on-lab que describa hasta el más mínimo detalle cómo customizar un portal para dotarlo de una "real-world interface". Algo que muchos de nosotros llevamos haciendo desde SP 2007 con sangre, sudor y lágrimas; por eso siempre te ilumina el corazoncito ver que Andrew también trastea con el PlaceHolderLeftNavBar y con el SiteMapPath. Grande.

http://msdn.microsoft.com/en-us/library/gg430141.aspx

Dos observaciones extraídas de él:

1) El uso (desconocido hasta ahora para mí) de la clase CSS s4-notdlg , que puede aplicarse a cualquier elemento HTML que deseemos que NO aparezca en los diálogos modales de SP 2010. Se acabó el tener que hacer apaños con clases CSS o plantearse incluso el uso de master pages reducidas.

2) Andrew Connell maqueta (o por lo menos nos propone con su ejemplo maquetar) prototipos HTML enteros de la página. ¿Es esta la mejor opción para afrontar el branding de un sitio? Dejadme empezar el debate y poner en tela de juicio si es necesario construir de entrada un HTML ficticio para elementos de SharePoint como el cuadro de búsqueda, el breadcrumb, la barra de navegación, el Welcome, y un largo etc., cuando luego, al integrarlo en una master page y en un layout real, ese HTML ficticio será reemplazado por el real de SharePoint y probablemente (muy probablemente) habrá que hacer ajustes de alineación, tipografías, márgenes... ¿No sería mejor construir el HTML ya directamente sobre una master page y un layout ya reales, jugar con SharePoint Designer y los controles de servidor reales? Pregunta capciosa donde las haya.

Sea como sea, artículo de cabecera. "Andrewcito de mi vida, tu eres sharepointer como yo..."

SharePoint, InfoPath y "Network problems are preventing this file from being opened"

16.11.10 / Comments (0) / by Unknown

Es turno para una entrada de las de no arremangarse demasiado sobre un tema más que manido; no todo iba a ser profundidad e innovación. En un entorno con SharePoint 2007 e InfoPath con una biblioteca de formularios asociada a una plantilla de formulario XSN, intentamos crear un nuevo formulario y al arrancar InfoPath, ta-ta-chán:




El detalle del mensaje:

InfoPath cannot create a new, blank form.
InfoPath cannot open the form. to fix this problem, contact your system administrator.
Network problems are preventing this file from being opened. If this problem persists, contact your network administrator.

Bien; existe un elevado porcentaje de probabilidad de que algo de lo siguiente ocurra:

 Si chequeáis estos tres puntos y todos están funcionando, podéis seguir circulando, éste no es vuestro post.

Error en vti_setuppath al modificar plantillas de sitio en soluciones sandboxed

8.11.10 / Comments (2) / by Unknown

Dicho queda que si alguien llega a este post con el mismo caso que voy a relatar (y aporta pruebas de ello), me comprometo a regalarle un llavero del Parque Güell.

Veamos: en un proyecto cualquiera usamos soluciones sandboxed (WSP's que viven en la Solution Gallery de nuestra colección de sitios: _catalogs/solutions) que contienen una plantilla de web. Estos WSP los obtenemos haciendo "Guardar sitio como plantilla" y constituyen el "código fuente" del sitio. El ciclo es: despliego el WSP, creo un sitio con la plantilla de sitio contenida en el WSP, hago las modificaciones manuales pertinentes y guardo de nuevo el sitio a WSP. Todo esto sin abrir Visual Studio. Maravilloso.

...hasta que llegamos a la novena iteración del ciclo. La solución sandboxed se sube, se activa, procedemos a crear el sitio y ¡crac! rotura fibrilar con el siguiente diagnóstico:

11/05/2010 12:31:38.57 powershell.exe (0x22A8) 0x20B8 SharePoint Foundation General 72lu High Cannot find doc C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\Template\global\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\v4.master 11/05/2010 12:31:38.57 powershell.exe (0x22A8) 0x20B8 SharePoint Foundation General 8yrn Medium Instantiating module "_catalogsmasterpage_global": File could not be added at URL "v4.master": Error 477317568
11/05/2010 12:31:38.57 powershell.exe (0x22A8) 0x20B8 SharePoint Foundation General 8e2s Medium Unknown SPRequest error occurred. More information: 0x80070002
11/05/2010 12:31:38.96 powershell.exe (0x22A8) 0x20B8 SharePoint Foundation Feature Infrastructure 889y High The element of type 'Module' for feature 'XXXXXXXXXModules' (id: 70a2c4f1-8cbf-4e3b-8d52-120e5be61a77) threw an exception during activation: The system cannot find the file specified. (Exception from HRESULT: 0x80070002)

Feo, ¿verdad? Error al desplegar el fichero v4.master y el origen parece ser una ruta que contiene todas las contrabarras del universo y más. ¿Alguien ha contado cuántas? 256. ¿Alguno ve la relación cabalística? Novena exportación del sitio; 2 ^ (9 - 1) = 256. ¿Será posible? Afortunadamente guardo las versiones de los WSP anteriores y puedo investigar los cambios. Abro el WSP y localizo en el Elements.xml de la susodicha feature XXXXXXXXXModules un elemento <Module> del estilo:
<Module Name="_catalogsmasterpage_global" HyperlinkBaseUrl="http://xxxxxxxxx/" Url="_catalogs/masterpage" RootWebOnly="FALSE" Path="Files\_catalogs\masterpage">
<File Url="v4.master" Type="GhostableInLibrary" Path="v4.master">
<Property Name="ID" Value="2" />
<Property Name="ContentTypeId" Value="0x01010500AA46D280A9395F49A0AD56DEF3BFAA2F" />
<Property Name="ContentType" Value="Master Page" />
<Property Name="_ModerationStatus" Value="0" />
<Property Name="FileDirRef" Value="xxxxxxxxxxx" />
<Property Name="FSObjType" Value="0" />
<Property Name="FileLeafRef" Value="v4.master" />
<Property Name="MetaInfo" Value="2;#vti_parserversion:SR|14.0.0.4762&#xD;&#xA;Order:SW|200.000000000000&#xD;
&#xA;vti_folderitemcount:IW|0&#xD;&#xA;FSObjType:SW|0&#xD;&#xA;FileDirRef:SW|
MyWork/_catalogs/masterpage&#xD;&#xA;vti_charset:SR|utf-8&#xD;&#xA;vti_author:SR|SHAREPOINT\\system&#xD;&#xA;vti_setuppath:SR|global\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\v4.master&#xD;
&#xA;UIVersion:SW|4&#xD;&#xA;MetaInfo:SW|2;#vti_parserversion:SR|14.0.0.4762\r\nvti_folderitemcount:IR|0\r\nvti_charset:SR|utf-8\r\nvti_author:SR|SHAREPOINT\\\\administrator\r\nvti_setuppath:SR|global\\\\v4.master\r\nUIVersion:SR|4\r\nvti_cachedneedsrewrite:BR|false\r\nvti_generator:SR|Microsoft SharePoint\r\nvti_foldersubfolderitemcount:IR|0\r\nvti_modifiedby:SR|SHAREPOINT\\\\administrator\r\nvti_cachedhastheme:BR|false\r\nvti_cached
customprops:VX|vti_title\r\nContentTypeId:SW|0x010105001BB704A689F80A4D8AF430B26FD34F6C
\r\nvti_cachedtitle:SR|&lt;asp:ContentPlaceHolder id=&quot;PlaceHolderPageTitle&quot; runat=&quot;server&quot;/&gt;\r\nvti_metatags:VR|HTTP-EQUIV=X-UA-Compatible IE=8 GENERATOR Microsoft\\\\ SharePoint progid SharePoint.WebPartPage.Document HTTP-EQUIV=Content-Type text/html;\\\\ charset=utf-8 HTTP-EQUIV=Expires 0\r\nvti_title:SR|&lt;asp:ContentPlaceHolder id=&quot;PlaceHolderPageTitle&quot; runat=&quot;server&quot;/&gt;\r\nvti_cachedbodystyle:SR|&lt;body scroll=&quot;no&quot; onload=&quot;if (typeof(_spBodyOnLoadWrapper) != 'undefined') _spBodyOnLoadWrapper();&quot; class=&quot;v4master&quot;&gt;\r\n&#xD;&#xA;vti_cachedneedsrewrite:BR|false&#xD;&#xA;vti_generator:SR|Microsoft SharePoint&#xD;&#xA;vti_foldersubfolderitemcount:IW|0&#xD;&#xA;vti_cachedhastheme:BR|
false&#xD;&#xA;vti_cachedcustomprops:VX|vti_title&#xD;&#xA;vti_modifiedby:SR|
SHAREPOINT\\system&#xD;&#xA;ContentType:SW|Master Page&#xD;&#xA;ContentTypeId:SW|0x01010500AA46D280A9395F49A0AD56DEF3BFAA2F
&#xD;&#xA;FileLeafRef:SW|v4.master&#xD;&#xA;vti_cachedtitle:SR|&lt;asp:ContentPlaceHolder id=&quot;PlaceHolderPageTitle&quot; runat=&quot;server&quot;/&gt;&#xD;&#xA;vti_metatags:VR|HTTP-EQUIV=X-UA-Compatible IE=8 GENERATOR Microsoft\\ SharePoint progid SharePoint.WebPartPage.Document HTTP-EQUIV=Content-Type text/html;\\ charset=utf-8 HTTP-EQUIV=Expires 0&#xD;&#xA;vti_title:SR|&lt;asp:ContentPlaceHolder id=&quot;PlaceHolderPageTitle&quot; runat=&quot;server&quot;/&gt;&#xD;&#xA;vti_cachedbodystyle:SR|&lt;body scroll=&quot;no&quot; onload=&quot;if (typeof(_spBodyOnLoadWrapper) != 'undefined') _spBodyOnLoadWrapper();&quot; class=&quot;v4master&quot;&gt;&#xD;&#xA;" />
<Property Name="Order" Value="200.000000000000" />
<Property Name="UIVersion" Value="4" />
</File>
</Module>
El metadato "MetaInfo" contiene una ristra de pares nombre/valor que se provisionarán en la colección Properties del SPListItem correspodiente al fichero desplegado. Ahí vemos como la propiedad "vti_setuppath", que indica la ruta relativa donde encontrar el fichero físico para un templated document (como parece ser nuestro caso), contiene ese path relativo con las 256 contrabarras. Y comparando con los WSP antiguos se corrobora como, a cada nueva exportación, el número de contrabarras se duplica. Hasta que por fin, irremediablemente, se supera lo que parece ser el tamaño máximo permitido. Pasmante. 

Menuda papeleta. ¿Y ahora qué? Alguien diría: "Eso te pasa por ir a lo fácil; si hubieras trabajado con una plantilla de sitio en Visual Studio tendrías absoluto control sobre ella y no dependerías del proceso de exportación de SharePoint, que salta a la vista que tiene aún algún aspecto por pulir". Desoigo esos "telodijes" e intento buscar una solución que no implique rehacer todo de arriba a abajo.

¿Modificar el WSP? Fácil decirlo pero difícil llevarlo a la práctica. Visual Studio 2010 permite importar los WSP en proyectos. Así hago; el proyecto se importa correctamente y puedo ver el immenso listado de features y artifacts exportados. Pero al compilar:

Error 3 Both "_catalogsmasterpage_global" and "_catalogsmasterpage_global1" contain a file that deploys to the same Package location: XXXXXXXXXX_Feature2\Files\_catalogs\masterpage\v4.master C:\XXXXXXXX\Package\Package.package XXXXXXXXX

Cierto, en el WSP la página maestra v4.master se está provisionando dos veces. ¿Debo extirpar una de ellas? Empezar a meter bisturí en Visual Studio no parece que vaya a ayudarme a solventar el tema sino más bien lo contrario. Empieza a cobrar fuerza la opción de editar directamente el WSP, eliminar todas las contrabarras y volver a generarlo. Abrirlo es fácil, modificar el fichero Elements.xml también pero volver a construir el WSP ya no tanto. Para no eternizar esta entrada, enlazaré al post Cómo modificar y recrear manualmente un WSP en que encontraréis detalles sobre cómo hacerlo.

Resultado: con el WSP modificado se puede volver a activar la solución y crear un sitio. Hurra. Vamos a comprobar ahora si exportando de nuevo el sitio a WSP, SharePoint sigue en sus trece. Efectivamente; volvemos a tener dos contrabarras.

vti_setuppath:SR|global\\v4.master

Es decir, que dentro de otras 9 exportaciones volveremos a estar en las mismas. ¿Existe una forma de evitar esto? ¿Se corregirá en algún Cumulative Update o hotfix? ¿Significa mientras tanto que hay que prescindir de las soluciones sandboxed para sitios que deben modificarse muy a menudo? Preguntas que quedarán sin respuesta por lo menos a día de hoy en esta entrada.

Cómo modificar y recrear manualmente un WSP

8.11.10 / Comments (0) / by Unknown

Mi primera vampirización de lo ajeno en el blog será para un tema con el que he gastado suficiente tiempo investigando y, una vez encontrada una solución, me gustaría dejarlo bien localizado, procedimentado y traducido.

Contexto: debo modificar un WSP ya creado. Es una modificación muy sencilla, no requiere añadir ni eliminar ficheros, simplemente editar uno de ellos. No tengo DDF, no tengo proyecto de Visual Studio, no puedo dedicar mucho tiempo a ello. Todo ventajas.

Mark Rackley (http://www.sharepointhillbilly.com/archive/2010/02/16/rebuilding-a-.wsp-file-in-4-easy-steps.aspx) acude al rescate y nos ofrece la guía mínima de pasos para "tunear" un WSP con éxito (que complemento con mis humildes aportaciones):

- Renombrar el fichero WSP a CAB (Cabinet file).

- Extraer todo el contenido preservando la estructura de carpetas(utilizando 7Zip, WinRar o el mismo Windows, que permite consultar y extraer de ficheros CAB).

- Modificar los ficheros necesarios del contenido (con extrema precaución si se trata de XML de definición de features, manifests, etc.)

- Obtener el fichero DDF de forma automatizada (aquí la clave de todo este post) mediante la útil herramienta DDFGenerator . Simplemente ejecutar desde línea de comandos y especificar como único parámetro la ruta local donde está descomprimido el contenido de nuestro WSP. Esto generará un "solution.ddf" dentro de esa carpeta.

- Editar el fichero solution.ddf para corregir lo siguiente:

1) En las directivas iniciales .Set incluir: 
.Set CabinetNameTemplate=NombreDeNuestraSolucion.wsp
.Set MaxDiskSize=CDROM
La primera línea para hacer que el fichero se genere con el nombre deseado; la segunda para evitar que se genere partido en distintos ficheros.

2) Repasar las rutas relativas generadas en el DDF; si alguna de ellas contiene espacios en nombres de fichero o carpeta, debe ser puesta entre comillas (")

- Ejecutar la herramienta makecab.exe (Windows)
makecab.exe /F solution.ddf
Et voilà, dentro de una subcarpeta cuyo nombre es un numérico aparecerá el fichero WSP generado con nuestras modificaciones.

Los más aventurados que deseen añadir o eliminar ficheros pueden proceder a manipular el fichero solution.ddf a placer.

Provisionar web parts dentro del contenido de una página de publicación

4.11.10 / Comments (0) / by Unknown

SharePoint 2010 permite incrustar web parts dentro del Publishing Page Content de una página. Es interesante saber que esta "incrustación" se puede exportar y provisionar en un WSP.

Primero: el webpart se define con un GUID (con el prefijo "g_" y con "_" como separador de bloques de guid), se establece webpart order = 0 y webpart zone id = "wpz". Por ejemplo:

<AllUsersWebPart WebPartZoneID="wpz" WebPartOrder="0" ID="g_e7a3c67e_a141_46cf_9640_cbd9e398a444">

Después: en el HTML del campo PublishingPageContent se define un <div> con el GUID como id. Ese GUID tiene un prefijo (en el caso que he probado es "vid_") y va dentro de otro div que le da un estilo CSS, pero lo mejor para obtener el publishing page content exacto sería incrustando el webpart a mano y consultando el HTML fuente. Saldrá algo parecido a:

<div class="ms-rtestate-read ms-rte-wpbox" contenteditable="false">
  <div class="ms-rtestate-notify ms-rtestate-read e7a3c67e-a141-46cf-9640-cbd9e398a444" id="div_e7a3c67e-a141-46cf-9640-cbd9e398a444">
  </div>
  <div id="vid_e7a3c67e-a141-46cf-9640-cbd9e398a444" style="display: none">
  </div>
</div>

TaxonomySession con propiedades vacías en SharePoint 2010

4.11.10 / Comments (0) / by Unknown

Nos hemos encontrado recientemente en un entorno de producción​ con la siguiente situación: acceder por object model al objeto TaxonomySession de un sitio (SPSite) y que dicho objeto no tenga TermSets, ni DefaultSiteCollectionTermStore; en cambio aparece un elemento string en la colección OfflineTermStoreNames (que justamente es el nombre de nuestra term store). No sabemos a partir de qué momento sucedió.

Esto es un problema porque la única manera de operar con taxonomías desde el object model es a traves de la propiedad DefaultSiteCollectionTermStore y si está null, no se puede seguir.
Hemos mirado y remirado por la interfaz de gestión de taxonomías y todo está normal. Hemos googleado y a alguien le ha pasado lo mismo pero sin solución aparente.

Solución encontrada en nuestro entorno: reiniciar máquina. Después de reiniciar, la TaxonomySession volvía a tener sus propiedades habituales.

Edición 11/05/2011: Ya tengo más pistas sobre el asunto. Hay que comprobar dos temas de conexión que tienen muy a ver con este error:
  • Que el servicio "Managed Metadata Web Service" de "Services on server" se encuentre Started.
  • Que el application pool de IIS asociado a este servicio (normalmente el "SharePoint Web Services") y el site de IIS correspondiente esten arrancados.
Otra pista: está asociado con el error de visor de sucesos "The Managed Metadata Service 'Managed Metadata Service' is inaccessible."

No está mal la ayudita, ¿eh?

Edición 15/12/2011: Ayer con mi compi Luis Ruiz Pavón descubrimos otra manera de reproducir este error: cuando se ejecutan las llamadas al object model de SharePoint con un usuario que no tiene ningún permiso sobre el Managed Metadata Service Application. Para solucionar esto, debemos ir a "Manage Service Applications", seleccionar el Managed Metadata Service y acceder a "Permissions" en la ribbon. Ahi deberemos dar de alta el usuario y asignarle Full Permissions.