Previous Page Page 2 of 10 in the SharePoint-es category Next Page
El martes 23 de Octubre, se celebra en Madrid la tercera conferencia de usuarios de SharePoint.
Si queréis asistir, debéis registraros con anterioridad, es imprescindible.
Por la mañana habrá diversas ponencias comentando casos prácticos de implantación y por la tarde sesiones técnicas. La agenda completa se puede consultar en este ENLACE.
10:00 Bienvenida y Situación General de SharePoint 10:20 Experiencia Criteria Caixa Corp 10:50 Experiencia Ancert - Agencia Notarial de Certificación 11:20 Experiencia Ministerio de la Presidencia 11:50 Descanso y café 12:20 Experiencia Caja Mediterráneo 12:50 Microsoft Office Sharepoint en Intenet - Web 2.0 - Silver Light 13:20 Búsqueda Empresarial - Search 13:40 Futuro de SharePoint 14:00 Almuerzo

Para más información llamar al 902322344 o un email a eventos.microsoft@sitel.es
Y podéis registraros AQUÍ.
Y el séptimo día, descansó
¿Pero? ¿Y si hubiera tenido que seguir un día tras otro y una semana tras otra? Más o menos lo que nos pasa a los desarrolladores, el trabajo nunca termina, con una línea completas un método, con un método una clase, con una clase un caso de uso un espacio de nombres, una biblioteca… siempre habrá detrás otra nueva clase, otra nueva biblioteca y así sin parar.
Un día tras otro… codificando, depurando, compilando, probando, no pongo meando porque a veces creo que me lo puedo hacer encima por no despegar los dedos del teclado …
Y a todo esto, cientos y cientos de tecnologías a nuestro alrededor que cambian constantemente, ¿y si a Dios le hubiesen cambiado las especificaciones una y otra vez?, y ¿si cada día hubiera tenido una nueva tecnología a su alcance?
Sinceramente, el mundo estaría por hacer. Sólo con el tiempo que lleva aprender, probar, crear un manual de buenas prácticas y realizar un framework adaptable y extensible y bla bla bla, para poder empezar a trabajar… estaría todavía diseñando a Adán.
Y eso gracias a que no dejo que Adán diera las especificaciones de Eva, porque todavía estaríamos esperando… (Lo digo sin ánimo de ser machista), porque estaríamos todavía en alguna iteración de la metodología divina, comprobando si el modelo cumple con las especificaciones del cliente.
En fin, si las cosas fueron así de rápidas supongo que serian por que no había, “cliente” y ¿si no había cliente?, ¿si no había especificaciones?, siempre me quedaré con la duda si se pudo hacer en 5 ó incluso en 4 días y se demoró el asunto. Digo yo, que siendo Dios, podía haberlo hecho todo en UNO, Mundo y Ploffff, toma MUNDO.
De modo que no me queda más que pensar que uso algún tipo de metodología para acometer el trabajo. ¿Cuál?, ¿Cómo hacer un mundo en 6 días y descansar el 7º? ó tal vez usó ¿Cómo hacer el mundo en 4 días y descansar 3?, desde luego que si uso esta no consiguió su objetivo, a pesar de la metodología.
Creo que nos quedaremos sin saberlo…
Volviendo al asunto, que en absoluto es la metodología, si no la gran cantidad de tecnologías de las que disponemos, y la gran cantidad de las que dispondremos en breve. Haciendo un pequeño resumen de las que necesitamos para desarrollar, pongamos por ejemplo SharePoint… un lenguaje .Net(C# ó VB.Net), Html, ASP.Net, XML, XSLT, JavaScript, Windows Workflow Foundation, InfoPath, TSQL, un SDK gordito y sin ejemplos, con más de 120Mb, 98 NameSpaces y 1.256 clases…
¿Sabéis una cosa? Yo quiero, que llegue el 7º Día.
Jhon Holliday (tambien MVP de SharePoint) lidera un proyecto llamado CAML.Net, que es una manera muy inteligente de integrar las consultas CAML en cualquier lenguaje .Net, con CAML.Net podemos mantener la estructura de las consultas de una manera intuitiva y natural, permitiéndonos construir componentes reusables. Además podemos realizar data-bindings con los resultados devueltos por las consultas, de manera brillante. Además está trabajando en un editor visual que tiene un aspecto excelente.
Podéis leer más sobre CAML.Net en:
Working with CAML.Net - Part 1 Working with CAML.Net - Part 2 (Introducing the CAML)
y el proyecto está en CodePlex http://codeplex.com/camldotnet
Yo he seguido jugando con YACAMLQT mi programita y he decidido adaptarlo para generar también CAML.Net.

YACAMLQT.zip (15,07 KB)
Update: Thanks to Cameron to report the Gq/Gt bug.
Como comentaba hace unos días, realizar pruebas unitarias usando mocks, no se siempre la mejor solución, personalmente, creo que no se debe abusar de ellos y usarlos para todo.
A continuación os cuento, como realizo mis pruebas unitarias cuando programo para SharePoint, seguramente no sean lo más idóneo del mundo, pero como dije en su día, no considero esto una ciencia sino más bien, un arte en donde cada uno utiliza aquello con lo que obtiene los mejores resultados, en cuanto a calidad. Siempre mantengo en mente el principio de que no deben llevar más trabajo que la realización del código.
El SDK de SharePoint, como comenté con anterioridad, es un framework con lo cual debemos dar por hecho algunas cosas, tenemos que tener claro que Microsoft, tomo ciertas decisiones en su diseño que por ende, nosotros no podemos cambiar.
SharePoint consta de un complejo modelo de objetos, en tres capas. De modo que cada vez que manipulamos estos, de forma transparente vamos cambiando la base de datos donde estos son persistidos. Esta cuestión es importante ya que es como si trabajásemos contra una base de datos y todos los que han realizado pruebas unitarias con bases de datos, saben la complejidad añadida que esto conlleva. Al margen de la discusión ya conocida sobre si las pruebas unitarias deben o no deben usar la base de datos como apunto Rodrigo.
Yo soy de la opinión de que mientras se pueda (por que se conoce) que base de datos se va a usar y podemos recrear un modelo de los datos sin un gran esfuerzo, es la mejor opción.
En SharePoint, aplico el mismo principio. Por diseño, la base de datos esta de manera subyacente así que la mejor manera de probar las cosas es usándola.
Mis pruebas con las Webparts
Las Webparts son un elemento dentro de SharePoint que nos permite interactuar con el usuario, a través del interface de usuario. En esta nueva versión como sabéis son los nativos de ASP.Net.
Como tales elementos, debemos confiar en que el comportamiento que tienen es el esperado, y me centro en realizar pruebas de la lógica subyacente, aquello que se encargará de producir los resultados en función de las entradas que reciba.
Veamos un simple ejemplo con un webpart que muestra el número de elementos que cumplen una condición en una lista. private string _list = string.Empty;
private string _message = string.Empty;
private string _query = string.Empty;
#region WEBPART PROPERTIES
[Personalizable(PersonalizationScope.Shared),
WebBrowsable,
Category("Settings"),
WebDisplayName("List Name")]
public string List
{
...
}
[Personalizable(PersonalizationScope.Shared),
WebBrowsable,
Category("Settings"),
WebDisplayName("CAML Query")]
public string Query
{
...
}
[Personalizable(PersonalizationScope.Shared),
WebBrowsable,
Category("Settings"),
WebDisplayName("Mesaage")]
public string Message
{
...
}
#endregion
protected override void Render(HtmlTextWriter output)
{
try
{
SPWeb web = SPControl.GetContextWeb(Context);
SPList list = web.Lists[List];
SPQuery query = new SPQuery();
query.Query = Query;
SPListItemCollection items = list.GetItems(query);
output.Write("<div>{0}<b>{1}</b></div>", Message, CountListItems(web, List, Query));
}
catch (Exception ex)
{
output.Write(String.Format("<div>{0}: {1}</div>", ex.GetType(), ex.Message));
}
}
Bien, aquí algunas buenas prácticas que suelo recomendar. Siempre que podamos debemos independizar nuestra lógica del componente, esto lo podemos hacer bien separando nuestro código en nuevos métodos: protected override void Render(HtmlTextWriter output)
{
SPWeb web = SPControl.GetContextWeb(Context);
try
{
output.Write("<div>{0}<b>{1}</b></div>", Message, CountListItems(web, List, Query));
}
catch (Exception ex)
{
output.Write(String.Format("<div>{0}: {1}</div>", ex.GetType(), ex.Message));
}
}
private int CountListItems(SPWeb web, string listName, string queryFiler)
{
SPList list = web.Lists[listName];
SPQuery query = new SPQuery();
query.Query = queryFiler;
SPListItemCollection items = list.GetItems(query);
return items.Count;
}
O lo que es mejor, aplicando los principios de delegación o composición para extraer las responsabilidades y la lógica a una nueva clase. protected override void Render(HtmlTextWriter output)
{
SPWeb web = SPControl.GetContextWeb(Context);
ListItemCounter listCounter = new ListItemCounter(web);
try
{
output.Write("<div>{0}<b>{1}</b></div>", Message, listCounter.CountListItems(List,Query));
}
catch (Exception ex)
{
output.Write(String.Format("<div>{0}: {1}</div>", ex.GetType(), ex.Message));
}
}
...
internal class ListItemCounter
{
private readonly SPWeb _web;
public ListItemCounter(SPWeb web)
{
_web = web;
}
public int CountListItems(string listName, string queryFiler)
{
SPList list = _web.Lists[listName];
SPQuery query = new SPQuery();
query.Query = queryFiler;
SPListItemCollection items = list.GetItems(query);
return items.Count;
}
}
Esto es importante ya que de este modo nuestro código quedará aislado de algunos de los elementos que el componente Webpart lleva implícitos, como el contexto en el cual la aplicación ASP.Net se está ejecutando; que entre otras cosas da bastante guerra ya que es complicado de reproducir. Y como consecuencia será más sencillo poder realizar las pruebas como veremos.
Estos días mi querido amigo Gustavo, ha estado pasando un infierno tratando de mejorar el proceso de realizar pruebas unitarias con SharePoint.
Desde mi punto de vista las pruebas con objetos Mock funcionan (luego veremos cómo), pero creo que no siempre son lo más adecuado y esto hay que tenerlo muy en cuenta.
Cuando desarrollamos pruebas unitarias tratamos de ver si existen diferencias entre el comportamiento esperado y el comportamiento observado. Cuando la realización de estas pruebas se convirtió en algo complejo, por la dependencia de nuestro código con sistemas externos surgieron distintas técnicas que nos permiten substituir estos sistemas e independizar nuestras pruebas.
Con estas técnicas, entre las cuales están los objetos Mock, el código que escribimos para realizar la prueba, interactúa con impostores, falsificaciones, espías, objetos simulados etc... Es decir con fantasmas.
Fantasmas, “ectoplasmas” que aparentan ser lo que no son; objetos de verdad. Pero que nos permiten entre otras cosas que nuestra prueba no se detenga por depender de un subsistema externo y muchas veces ajeno a nosotros.
De modo que en la mayoría de los casos, estamos dando por sentado como se comportará ese subsistema, comportamiento esperado. Al predeterminar esto estamos dando por hecho que dicho subsistema funcionará correctamente ó incorrectamente si queremos comprobar que nuestro código sabe responder a las fallas del subsistema, con lo que el subsistema real queda fuera del ámbito de nuestra prueba. Resumiendo, independizamos nuestras pruebas.
Veamos ahora un ejemplo conocido: public static class Tools
{
public static string GetCurrentUserName(HttpContext myContext)
{
SPWeb myWeb = SPControl.GetContextWeb(myContext);
SPUser myUser = myWeb.CurrentUser;
return myUser.LoginName;
}
}
Si, es enrevesado ya que estamos usando el contexto de la aplicación web (cosa que deberíamos evitar siempre que podamos, pero eso es otra historia)
Volviendo al código si lo que estamos haciendo es una aplicación ASP.Net que debe interactuar con SharePoint (el subsistema) y queremos realizar una prueba unitaria de este código podemos hacerlo usando objetos Mock.
De este modo nuestra prueba funcionará correctamente sin la necesidad de disponer de una instalación de SharePoint.
Yo personalmente uso TypeMock, que me parece sin lugar a dudas el más completo y con el que menos cuesta (en términos de líneas) realizar los test.
Ejemplo [Test]
public void GetCurrentUserNameTest()
{
string expectedUserName = "Sample User";
MockManager.Init();
// Hacemos un Mock de SPUser
// Usando MockManager.MockObject nos permitira crear un objeto sin
// llamar a su constructor para recuperar la instancia inmediatamente
MockObject mockSPUser = MockManager.MockObject(typeof (SPUser));
// Fijamos el valor que será devuelto cuando se solicite el valor de la
// propiedad LoginName
mockSPUser.ExpectGet("LoginName", expectedUserName);
// Devolvemos la instancia para poder usarla
SPUser spUser = mockSPUser.Object as SPUser;
// Hacemos un Mock de SPWeb
MockObject mockSPWeb = MockManager.MockObject(typeof (SPWeb));
// Fijamos el valor que será devuelto cuando se solicite el valor de la
// propiedad CurrentUser que es el objeto SPUser que creamos anteriormente
mockSPWeb.ExpectGet("CurrentUser", spUser);
// Devolvemos la instancia para poder usarla
SPWeb spWeb = mockSPWeb.Object as SPWeb;
// Hacemos un Mock de SPControl
// En esta ocasion no vamos a instanciarlo ahora, se instanciará automaticamente
// cuando se solicite
Mock mockSPControl = MockManager.Mock(typeof (SPControl));
// En cualquier caso el método GetContextWeb, devolverá el objeto SPWeb
mockSPControl.AlwaysReturn("GetContextWeb", spWeb);
// Tomamos el nombre del usuario
// Incluso podemos pasarle null (este no es el test de parametros/excepciones)
string userName = Tools.GetCurrentUserName(null);
// Comprobamos que el valor esperado es el valor que hemos obtenido
Assert.AreEqual(expectedUserName, userName, "Correct!!!");
// Verificamos la prueba
MockManager.Verify();
}
El código de la prueba nos permite verificar que el método GetCurrentUserName funciona correctamente. Es más no necesitamos depender de SharePoint para probar nuestro código. Ya somos independientes del Subsistema.
Pero si analizamos la prueba, lo único que hemos hecho es prefijar el comportamiento de cada uno de los objetos que usa nuestro método, que como dice un gran amigo, es como decirle al ordenador: Cuándo te pregunte como me llamo, di que me llamo PEPE, y preguntarle ¿Cómo me llamo? a lo cual responderá PEPE, y diremos BIEN, aunque yo me llame JUAN.
Pero eso sí, el maremágnum de pruebas correrá feliz por los mundos de yupi.
Otro tema a tener en cuenta, es, si es necesaria la prueba y cuando debemos realizar este tipo de pruebas ¿Qué estamos probando aquí?, no estamos haciendo un test de excepciones, ni un test de carga, ni un test de delegación etc., ni siquiera una prueba de integración, lo único que estamos haciendo es comprobar que el subsistema de SharePoint, responderá como nosotros esperamos que lo haga. (Cosa que los programadores de SharePoint hacemos habitualmente) De modo que garantizar que SharePoint funciona correctamente no es nuestra tarea. Para nosotros lo importante es el hecho de que interactuamos con un sistema externo y el ámbito de nuestras pruebas debería ser nuestro propio código.
Puede parecer complicado pero yo creo que hay que poner límites a las pruebas, de modo que probaría a ver si estoy controlando todas las excepciones que debería, a la hora de tratar con el subsistema y a asegurarme que los datos que le estoy pasando o que estoy recibiendo de él son los correctos.
Por otro lado cuando desarrollamos en el interior del subsistema (WebParts, Eventos, Workflows, etc.) las cosas pueden cambiar...
En Desarrolla con MSDN han publicado el Curso de Programación para SharePoint 2007 que a realizado Gustavo, sin duda un buen punto para comenzar. El curso es muy completo y además incluye videos.
1.1 Introducción a WSS y MOSS 1.2 Instalación de MOSS 1.3 Construcción de un sitio web basico 2.1 Integracion con ASP.NET 2.0 2.2 Modelo de Objetos 2.3 Paginas Maestras 2.4 WebParts 3.1 Que son Tipos de Contenido de Sitios 3.2 Desarrollo de Tipos de Contenido 3.3 Que son Columnas de Sitio 4.1 Introduccion a Caracteristicas 4.2 Uso de Caracteristicas 4.3 Estructura XML 4.4 Desarrollo de Caracteristicas 5.1 Introduccion a Flujos de Trabajo 5.2 Uso de Flujos de Trabajo 5.3 Creacion de Flujos de Trabajo 5.4 Flujos de Trabajo con SharePoint Designer 6.1 Introduccion al Catalogo de Datos Profesionales 6.2 Uso y Creacion del Catalogo de Datos Profesionales 6.3 Programacion del Catalogo de Datos Profesionales 7.1 Introduccion al Servicio de Excel 7.2 Uso del Servicio de Excel 7.3 Programacion con el Servicio de Excel
También os recuerdo que Gustavo, mantiene un PORTAL donde podéis encontrar cantidad de información sobre programación en SharePoint.
No pierdas el tiempo corre a verlo ...
En está última parte de Destripando el Wiki, veremos como crear un campo personalizado para cambiar el comportamiento por defecto del SPFieldMultiLineText el usado por WikiField, de modo que podremos cambiar el comportamiento a la hora de visualizar los enlaces a entradas que no están definidas para que estas sean redirigidas a nuestra página CustomCreatePage.aspx.
Como comentaba en anteriores posts, hemos visto que el wiki es una biblioteca de documentos que almacena páginas basadas en un tipo de contenido llamado Página Wiki que hereda de documento. Cuando se crea una nueva página la plantilla llamada wkpstd.aspx se copia en la biblioteca de documentos. Vimos que podemos crear nuestra propia página CreateCustomPage.aspx, para recrear el mismo comportamiento, utilizando nuestras propias plantillas y que esto funcionaba correctamente. Pero el problema llega cuando el campo WikiField se renderiza, ya que mantiene un enlace a CreateWebPage.aspx.
Antes de continuar, creo que es evidente que los errores de diseño en la solución Wiki de Sharepoint son más que notables. Para empezar, han limitado la plantilla incrustando el nombre de la misma en el código de CreateWebPage.aspx, limitando los usos del Wiki. Por otro lado, el campo WikiField, es el campo SPFieldMultiLine, al cual han añadido el método ProcessWikiLinks para realizar la conversión de los enlaces, cuando este debía ser una clase heredada del mismo. Por último, el control a través del cual se renderiza WikiField, es RichTextField, el cual hereda de NoteField, y que debía haber sido también una clase heredada con el fin de que cada una se dedicara a lo suyo y no mezclar distintas funcionalidades aunque sean parecidas en la misma clase, lo cual nos permitiría a los desarrolladores una comprensión mejor del funcionamiento sin tener que buscar donde han ido haciendo los trucos, y nos facilitaría la tarea a la hora de extender SharePoint. Como si no fuera lo suficientemente grande el SDK, como para añadir unas cuantas clases más.
En primer lugar tendremos que crear nuestro propio campo personalizado, yo lo he llamado Wiki que a su vez hereda de SPFieldMultiLineText, y he sobreescrito GetFieldValueAsHtml, para reemplazar la página por defecto por la nuestra.
1: public class Wiki : SPFieldMultiLineText 2: { 3: #region SPField Constructors 4: 5: public Wiki(SPFieldCollection fields, string fieldName) 6: : base(fields, fieldName) 7: { 8: } 9: 10: public Wiki(SPFieldCollection fields, string typeName, string displayName) 11: : base(fields, typeName, displayName) 12: { 13: } 14: 15: #endregion 16: 17: public new string GetFieldValueAsHtml(object value, SPListItem item) 18: { 19: string html = base.GetFieldValueAsHtml(value, item); 20: return html.Replace("CreateWebPage", "CreateCustomPage"); 21: } 22: 23: public override BaseFieldControl FieldRenderingControl 24: { 25: get 26: { 27: BaseFieldControl fieldControl = new WikiField(); 28: fieldControl.FieldName = InternalName; 29: return fieldControl; 30: } 31: } 32: }
Para subsanar el segundo problema, necesitamos crear también un control que llame a nuestro GetFieldValueAsHtml, para lo cual he heredado de NoteField, y sobrescrito el método de RenderFieldForDisplay
1: public class WikiField : NoteField 2: { 3: 4: protected override void RenderFieldForDisplay(HtmlTextWriter output) 5: { 6: Wiki field = (Wiki) Field; 7: if (field != null) 8: { 9: if (field.WikiLinking) 10: { 11: output.Write("<div class=\"ms-wikicontent\">"); 12: output.Write(field.GetFieldValueAsHtml(ItemFieldValue, ListItem)); 13: output.Write("<p></p></div>"); 14: return; 15: } 16: } 17: else 18: { 19: return; 20: } 21: base.RenderFieldForDisplay(output); 22: } 23: 24: }
Y la definición del nuevo campo Wiki
1: <FieldType> 2: <Field Name="TypeName">Wiki</Field> 3: <Field Name="ParentType">Note</Field> 4: <Field Name="TypeDisplayName"> 5: WikiField 6: </Field> 7: <Field Name="TypeShortDescription"> 8: WikiField supports custom page 9: </Field> 10: <Field Name="UserCreatable">FALSE</Field> 11: <Field Name="ShowInListCreate">FALSE</Field> 12: <Field Name="ShowInSurveyCreate">FALSE</Field> 13: <Field Name="ShowInDocumentLibraryCreate">FALSE</Field> 14: <Field Name="ShowInColumnTemplateCreate">FALSE</Field> 15: <Field Name="Sortable">FALSE</Field> 16: <Field Name="Filterable">FALSE</Field> 17: <Field Name="FieldTypeClass">IdeSeg.SharePoint.CustomFields.Wiki.Wiki,
IdeSeg.SharePoint.CustomFields,Version=1.0.0.0,
Culture=neutral, PublicKeyToken=...</Field> 18: </FieldType>
Bien, por último solo nos quedaría crear nuestro propio tipo de contenido para poder adjuntarlo a la biblioteca de documentos, como es lógico podríamos crear una definición de lista con el tipo de contenido, pero por abreviar, yo solo he creado el tipo de contenido que después podemos añadir a una biblioteca de documentos.
feature.xml
1: <Feature Id="1d9a8ea3-0b7d-42cf-8c64-78df773b7e41" 2: Title="IdeSeg Wiki" 3: Description="Wiki with custom templates" 4: Version="1.0.0.0" 5: Scope="Site" 6: xmlns="http://schemas.microsoft.com/sharepoint/"> 7: <ElementManifests> 8: <ElementManifest Location="fields.xml" /> 9: <ElementManifest Location="ctypes.xml" /> 10: </ElementManifests> 11: </Feature>
fields.xml
1: <?xml version="1.0" encoding="utf-8"?> 2: <!-- 3: --> 4: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 5: <Field ID="{f1d10620-1f85-460b-8132-7c57cd6eABCD}" 6: Name="WikiFld" 7: DisplayName="Contenido" 8: StaticName="WikiField" 9: Group="_IdeSeg" 10: Hidden="TRUE" 11: Type="Wiki" 12: RichText="TRUE" 13: RichTextMode="FullHtml" 14: IsolateStyles="TRUE" 15: RestrictedMode="FALSE" 16: NumLines="30" 17: DisplaySize="110" 18: UnlimitedLengthInDocumentLibrary="TRUE" 19: WikiLinking="TRUE" 20: Sortable="FALSE" 21: Sealed="TRUE" 22: AllowDeletion="TRUE"> 23: </Field> 24: </Elements>
ctypes.xml
1: <?xml version="1.0" encoding="utf-8"?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ContentType ID="0x0101005ADDEAA7312EE967BFC3076B9699C3D4" 4: Name="IdeSeg Wiki" 5: Group="_IdeSeg" 6: Description="IdeSeg Wiki with custom templates" 7: Version="1"> 8: <FieldRefs> 9: <RemoveFieldRef ID="{fa564e0f-0c70-4ab9-b863-0177e6ddd247}" Name="Title" /> 10: <FieldRef ID="{f1d10620-1f85-460b-8132-7c57cd6eABCD}" Name="WikiField" /> 11: </FieldRefs> 12: <XmlDocuments> 13: <XmlDocument NamespaceURI="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms"> 14: <FormTemplates xmlns="http://schemas.microsoft.com/sharepoint/v3/contenttype/forms"> 15: <Display>DocumentLibraryForm</Display> 16: <Edit>WikiEditForm</Edit> 17: <New>WikiEditForm</New> 18: </FormTemplates> 19: </XmlDocument> 20: </XmlDocuments> 21: <DocumentTemplate TargetName="/_layouts/CreateCustomPage.aspx" /> 22: </ContentType> 23: </Elements>

He recibido algunos comentarios acerca de el nombre del post "Destripando", puede que se deba al carácter latino, pero no. En un principio pensé en llamarle Anatomía del Wiki, que sonaba más científico, al igual que Disección del Wiki; Funcionamiento interno del Wiki, sonaba muy clásico y Autopsia del Wiki es para los cadáveres y moss todavía esta calentito. Destripando el wiki, tiene un sabor más de carnicero, por que lo que estamos haciendo con el wiki, ya que su diseño es de casquería, Destripando el Wiki, era el título correcto.
En esta parte veremos cómo trabaja el campo especial WikiField, antes de nada recordaros un webpart, que escribí el año pasado y pasó sin pena ni gloria, pero resolvía el problema de los Wikis en Windows Sharepoint Services 2.0.
csegMiniWiki

Bueno, resulta que el WikiField realiza (CASI) las mismas tareas que hacía yo en el webpart que es traducir las etiquetas marcadas entre corchetes por los vínculos correspondientes.
El problema de las búsquedas
Cuando escribí el webpart csegMiniWiki, el único problema que tenía eran las búsquedas. Esto es debido a que al albergar el contenido del Wiki en elementos de la lista, cuando se buscaba cierto texto, el enlace de los resultados era al elemento correspondiente de la lista, en vez de la página en donde aparecía el texto renderizado y con la traducción de vínculos correspondiente; con lo que veíamos la fuente de la página wiki.
Este problema, lo han resuelto en el Wiki de MOSS dando a cada entrada una página, la plantilla que vimos anteriormente y que es copiada para cada entrada. De este modo MOSS indexa la página con el contenido correspondiente. Y la lista que mantiene los datos a su vez no es indexada.
Sin embargo, (y no es, porque yo lo hiciera) me encontraba más cómodo con mi Wiki.
WikiField
Ya lo hemos visto con anterioridad, y está definido en fieldswss.xml
1: <!-- Wiki Fields --> 2: <Field ID="{C33527B4-D920-4587-B791-45024D00068A}" 3: Name="WikiField" 4: DisplayName="$Resources:core,WikiField;" 5: StaticName="WikiField" 6: SourceID="http://schemas.microsoft.com/sharepoint/v3" 7: Group="_Hidden" 8: Type="Note" 9: RichText="TRUE" 10: RichTextMode="FullHtml" 11: IsolateStyles="TRUE" 12: RestrictedMode="FALSE" 13: NumLines="30" 14: DisplaySize="110" 15: UnlimitedLengthInDocumentLibrary="TRUE" 16: WikiLinking="TRUE" 17: Sortable="FALSE" 18: Sealed="TRUE" 19: AllowDeletion="TRUE" /> 20: <!-- End Wiki Fields -->
WikiField, es un campo de tipo "Note", control NoteField, e internamente usa SPFieldMultiLineText, como sabéis los campos tienen un tipo interno que alberga la lógica del campo, en este caso SPFieldMultiLineText y un control que sirve de fachada para manipularlo, NoteField en este caso.

De modo que el trabajo se realiza en la clase SPFieldMultiLineText, que hereda de SPField

Todos los campos de sharepoint heredan de SPField, dentro de SPField, hay un conjunto de métodos virtuales que son sobrescritos para cada implementación, los que nos interesan ahora son :
// Devuelve el valor del campo public virtual object GetFieldValue(string value) { return value; } // Devuelve el valor del campo en formato Html public virtual string GetFieldValueAsHtml(object value) { return SPHttpUtility.HtmlEncode(this.GetFieldValueAsText(value)); } // Devuelve el valor del campo como texto public virtual string GetFieldValueAsText(object value) { if (value != null) { return value.ToString(); } return string.Empty; } // Devuelve el valor del campo en formato para usarse en los formularios nuevo y de edición public virtual string GetFieldValueForEdit(object value) { return this.GetFieldValueAsText(value); }
Si vemos el diagrama de la clase SPFieldMultiLineText, veremos como estos son sobrescritos para el campo mistilínea, en concreto GetFieldValueAsHtml, es el encargado de procesar los enlaces para los campos WikiField (aquellos que establecen la propiedad WikiLinking en True, ver el XML de la definición del campo), este tratamiento se realiza en el método ProcessWikiLinks, que gracias a dios es público de modo que podremos sobrescribirlo .
Y digo esto, por que cuando se procesan los enlaces del Wiki, aquellos que no se encuentran definidos automáticamente son enlazados a _layouts/CreateWebPage.aspx :-(
Continuando con el Wiki, hemos visto como esta construido básicamente y donde oculta el secreto de las páginas que se generan, ahora bien
¿Podemos cambiar el diseño?
Una vez que hemos visto como esta construido, podemos de tratar de realizar una solución que implemente un comportamiento similar. Para ello deberemos hacer algunas cosas, como crear nuestra propia página de de entrada, y configurar una lista (biblioteca de documentos) para que realice el mismo comportamiento que el Wiki, eso es que llame a nuestra página personalizada para crear nuevas páginas.
Para crear la página de entrada podemos partir de la que trae SharePoint, CreateWebPage.aspx e implementar nuestra propia solución para que en vez de usar la plantilla del wiki por defecto Wkpstd.aspx, podamos utilizar la nuestra. A modo de ejemplo yo he creado una solución un poco más genérica que la que viene por defecto. En este caso, he llamado a la solución CustomTemplate, y lo único que hace es añadir al layouts una nueva página llamada CreateCustomPage.aspx que será la encargada de crear nuevas páginas para nuestro Wiki.
A diferencia de la CreateWebPage.aspx que copia la plantilla Wkpstd.apsx para cada una de las entradas, esta nueva página CreateCustomPage.aspx buscará una página llamada template.aspx que deberá existir en la biblioteca de documentos, para usarla como plantilla. Esto nos va a permitir tener en cada uno de nuestros wikis, una plantilla personalizada, como es lógico la solución podría extenderse incluso para tener más de una plantilla e incluso, decidir que plantilla es la que se quiere aplicar a las páginas que creemos.
Como he comentado anteriormente, podemos partir del código suministrado por CreateWebPage.aspx y lo único que debemos hacer es modificar el Submit, para que copie nuestra plantilla
1: protected void SubmitBtn_Click(object sender, EventArgs e) 2: { 3: string fileName; 4: bool flag; 5: 6: Page.Validate(); 7: 8: if (Page.IsValid) 9: { 10: fileName = Name.Text.Trim(); 11: 12: if (fileName == null || fileName.Length == 0) 13: { 14: Error.Text = SPHttpUtility.HtmlEncode(SPResource.GetString("CreateWebPageInvalidTitle", new object[0])); 15: } 16: else 17: { 18: fileName = fileName + ".aspx"; 19: string listGuid = Request.QueryString.Get("List"); 20: try 21: { 22: Guid guid; 23: 24: guid = new Guid(listGuid); 25: 26: SPList list = spWeb.Lists[guid]; 27: 28: if (list != null) 29: { 30: // Check if already exist the file 31: string newFileUrl = list.RootFolder.Url + "/" + fileName; 32: if (spWeb.GetFile(newFileUrl).Exists) 33: { 34: Error.Text = SPHttpUtility.HtmlEncode(SPResource.GetString("CreateWebPageDuplicateTitle", new object[0])); 35: } 36: else 37: { 38: 39: // Get the template file 40: SPFile srcFile = list.RootFolder.Files["template.aspx"]; 41: byte[] binFile = srcFile.OpenBinary(); 42: 43: // Create the new file based in the template 44: SPListItem item = list.RootFolder.Files.Add(fileName, binFile, false).Item; 45: 46: // Save field values 47: foreach (BaseFieldControl baseField in SPContext.Current.FormContext.FieldControlCollection) 48: { 49: item[baseField.Field.InternalName] = baseField.Value; 50: } 51: item.UpdateOverwriteVersion(); 52: SPUtility.Redirect(item.Url, SPRedirectFlags.UseSource | SPRedirectFlags.Static, Context); 53: } 54: } 55: else 56: { 57: Error.Text = SPHttpUtility.HtmlEncode(SPResource.GetString("CreateWebPageInvalidList", new object[0])); 58: } 59: } 60: catch (Exception ex) 61: { 62: // There is a template file ? 63: Error.Text = SPHttpUtility.HtmlEncode(ex.ToString()); 64: } 65: } 66: } 67: }
Una vez que tenemos nuestra solución para crear páginas, lo que debemos hacer es crear nuestra propia biblioteca de documentos para almacenar las páginas de nuestro wiki. Para que esta biblioteca de documentos pueda usar nuestra página personalizada para crear contenido CreateCustomPage.aspx, tendremos que crear un tipo de contenido y asignarle nuestra página.

Podemos aprovechar este tipo de contenido para albergar más columnas que luego formen parte de la plantilla, imágenes, y otros cosas. Por último el campo WikiField, El campo WikiField, es básicamente el contenido de nuestra página, y es un campo especial de SharePoint, que debemos respetar ya que tiene un comportamiento particular, tanto para la entrada de datos como a la hora de renderizarse, ya que trata realiza un tratamiento especial para los los vínculos. Aquí tenemos otro problema ya que tanto la definición del tipo de contenido para los Wikis así como la definición del campo WikiField esta oculta.
De modo que no podemos implementarla directamente y deberemos crear nuestro propio tipo de contenido, para ello, podemos copiar la definición que vimos anteriormente e implementarlo como una característica de modo que podamos asignarlo a cualquier biblioteca de documentos.
feature.xml
1: <?xml version="1.0" encoding="utf-8" ?> 2: <Feature Id="6D145000-D35F-43af-83A2-797A263A3ABC" 3: Title="Custom Wiki Content Type" 4: Description="Wiki content type to use templates" 5: Version="12.0.0.0" 6: Scope="Site" 7: xmlns="http://schemas.microsoft.com/sharepoint/"> 8: <ElementManifests> 9: <ElementManifest Location="elements.xml" /> 10: </ElementManifests> 11: </Feature>
elements.xml
1: <?xml version="1.0" encoding="utf-8" ?> 2: <Elements xmlns="http://schemas.microsoft.com/sharepoint/"> 3: <ContentType ID="0x010100f7c73f52e54d0bbdf1e83273d6db6800" 4: Name="Template based Wiki"
| |