jueves, 27 de octubre de 2011

Anidando CDATAs, o cómo no morir personalizando el CoreResultsWebPart

Cada vez que he tenido que hacer una personalización del elemento web de resultados de búsqueda de SharePoint (CoreResultsWebPart) me he encontrado con el mismo problema: el despliegue. La teoría es muy simple, sobre un sitio creado se edita el elemento web, se obtiene el valor del campo XSL, se edita con cualquier editor de XSL, se guarda el webpart con el nuevo valor de la propiedad hasta que obtenemos el resultado deseado y finalmente exportamos el elemento web para insertarlo donde nos convenga en nuestro paquete de despliegue.

¿Pero qué pasa cuando metemos variables en juego? En ocasiones tenemos que introducir valores multilenguaje o urls relativas a la colección de sitios en la que nos encontramos. Por cierto, si habéis llegado hasta aquí y no he despertado vuestro interés, o bien es por que tenéis totalmente resuelto este problema, o bien es porque no os habéis visto en la necesidad de crear algo productizable. Si, como digo, no he despertado vuestra curiosidad a estas alturas, os recomiendo no perder más tiempo en este artículo, ya escribiré cosas mejores Winking smile.

Bien, si seguís leyendo a estas alturas, imagino que estaréis esperando la fórmula mágica para hacer esta tarea un poquito más simple de lo habitual. Primero, dediquemos unas líneas al método tradicional. Una vez obtenido el valor para la propiedad XSL anteriormente mencionada, escapamos el código y lo insertamos en el XML de aprovisionamiento. Si habéis hecho esto en alguna ocasión, estaréis conmigo en que como se te escape algún pequeño error en este punto, depurarlo va a ser poco menos que imposible. Además, la próxima vez que hagas un cambio vas a tener que repetir todo el proceso. No sé a vosotros, pero a mí eso no me gusta nada de nada. Hasta hoy esto era un trance por el que tenía que pasar, pero el frotar se va a acabar…

Una solución, como habréis deducido del título del post, consiste en anidar CDATAs. Pero, ¡si no se puede! ¡si no está permitido según el estándar! Hoy he encontrado una solución muy original de un tal Nat Dunn y que podéis encontrar aquí. La solución consiste en utilizar el siguiente patrón, teniendo en cuenta que el primer CDATA contendría el elemento web y que el CDATA anidado contendría el valor de la propiedad XSL.

<![CDATA[
 
<![CDATA[<!-- Contenido del CDATA anidado -->]]]]><![CDATA[>
      
]]>

De acuerdo pero, ¿cómo queda esto al final? El siguiente listado muestra partes del fichero de aprovisionamiento con el que he hecho las pruebas. He dejado a propósito otra propiedad (SampleData) para demostrar las diferencias entre el modo habitual de tratar este tipo de propiedades y el modo que os estoy proponiendo.

<![CDATA[<webParts>
<webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
  <metaData>
    <type name="..." />
  </metaData>
  <data>
    <properties>
      <property name="SampleData" type="string">&lt;All_Results&gt;
        &lt;Result&gt;
          &lt;workid&gt;1&lt;/workid&gt;
          &lt;rank&gt;222&lt;/rank&gt;
          &lt;title&gt;Title of document or web page&lt;/title&gt;
          &lt;author&gt;Author of document or web page&lt;/author&gt;
          &lt;size&gt;1025&lt;/size&gt;
          &lt;sitename&gt;http://www.sample.com&lt;/sitename&gt;
          &lt;url&gt;http://www.sample.com/folder/document.aspx&lt;/url&gt;
          &lt;imageurl&gt;/_layouts/images/aspx16.gif&lt;/imageurl&gt;
          &lt;description&gt;This is the summary of the document or web page. The summary is generated from the original document based on matches with query terms. In some cases, the summary is a description provided by the author.&lt;/description&gt;
          &lt;write&gt;December 26, 2004&lt;/write&gt;
        &lt;/Result&gt;
      &lt;/All_Results&gt;
      </property>
      <property name="Xsl" type="string">
        <![CDATA[
          <?xml version="1.0" encoding="iso-8859-1"?>
          <xsl:stylesheet version="1.0"
              xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
              xmlns:srwrt="http://schemas.microsoft.com/WebParts/v3/searchresults/runtime"
              xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime">
            <xsl:output method="xml" indent="no"/>
            <xsl:param name="Keyword" />
            <xsl:param name="ResultsBy" />
            <xsl:param name="ViewByUrl" />
            <xsl:param name="ShowDropDown" />
            
            <!-- Un montón de código aquí -->
            
                </xsl:otherwise>
              </xsl:choose>
            </xsl:template>
 
            <!-- End of Stylesheet -->
          </xsl:stylesheet>          
        ]]]]><![CDATA[>
      </property>
      
      <!-- Un montón de propiedades aquí -->
      
    </properties>
  </data>
</webPart>
webParts>]]>

No se vosotros, pero yo me he llevado una alegría al ver que esto funcionaba. Antes de acabar, un par de puntos:

  1. Esto está funcionando ahora, pero puede tener algún efecto secundario, como siempre. Si detecto alguno, actualizaré el artículo e indemnizaré a todos mis subscriptores con 100€ en la MartosStore
  2. He leído por ahí que nunca deberíamos anidar CDATAs jamás de los jamases. Lo único que os puedo decir es que si por cada vez que lo haces, dios mata a un gatito… lo dejaré de hacer.

lunes, 24 de octubre de 2011

¿Te funciona el intellisense pero no compila?

Hoy toca ese tipo de recordatorios que, no por ser cosas aparentemente triviales, dejan de ser importantes. En el caso que me ocupa, estuve el otro día haciendo una pequeña prueba de concepto y me pasé un buen rato con un error bastante curioso. Un código que tenía funcionando perfectamente en un proyecto no me compilaba en esta prueba de concepto. Las líneas conflictivas eran:

SPLimitedWebPartManager limitedWebPartManager;
SPUtility.GetGenericSetupPath("TEMPLATE");

Estas dos líneas, aparentemente sencillas, devolvían los siguientes dos errores:

'Microsoft.SharePoint.Utilities.SPUtility' does not contain a definition for 'GetGenericSetupPath'
The type or namespace name 'SPLimitedWebPartManager' could not be found (are you missing a using directive or an assembly reference?)

Antes de ir a la solución, es necesario entender el momento de frustración por el que se pasa. El sistema no encuentra elementos que debería encontrar y que además, son de lo más común del mundo. Eliminas y vuelves a crear la referencia a Microsoft.SharePoint.dll una y otra vez. Haces mil cambios en el código hasta dejar prácticamente aisladas estas dos llamadas. Nada, siempre el mismo error.

Como no podía ser de otra manera, el error viene causado por una tontería. Como sabréis, cuando creáis un proyecto de SharePoint se os da la opción de elegir entre Farm Solution y Sanboxed Solution. No os voy a hablar de las diferencias entre un modo y el otro, pero simplemente deciros que el modelo de objetos de SharePoint cuando estáis en modo Sanbox se reduce bastante. De esa manera, espacios de nombre, clases o miembros que funcionan perfectamente en soluciones de tipo granja, quedan deshabilitados completamente en modo Sandbox. Si váis, por ejemplo, a esta página, Veréis que la clase SPLimitedWebPartManager no está disponible para Sandbox.

La solución a este problema puede ser muy simple o muy compleja en función de la siguiente pregunta: ¿realmente necesitáis que el código funcione en modo SandBox? si la respuesta es no, os bastará con ir a las propiedades del proyecto y cambiar el valor de la propiedad Sandboxed solution. Si la  respuesta es seguramente la solución pasará por buscar una alternativa, si es que la hay, que no haga uso de los elementos que nos están dando problemas. Si tenemos la opción de desplegar un Sandbox Proxy será una buena noticia, porque siempre podremos trasladar allí el código conflictivo.