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

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

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 Enric Carrión

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.