A continuación lo que se intenta hacer es interpretar discusiones que pertenezcan a una lista de SharePoint de tipo "Panel de discusión" siguiendo la misma estructura de presentación que nos brinda la vista "Encadenada".
Investigando saqué las siguientes conclusiones:
1 – El tipo de lista "Panel de discusión" por defecto viene con dos tipos de contenido: "Discusión" y "Mensaje".
2 – El tipo de contenido "Discusión" tiene como padre al tipo de contenido "Folder".
3 – Ambos tipos de contenido comparten las columnas "Asunto" y "Cuerpo", la diferencia es que el tipo de contenido "Mensaje" tiene la columna "Asunto" oculta ya que no tiene sentido que una respuesta (o mensaje) tenga asunto.
4 – El campo "ThreadIndex" que está oculto y que pertenece a las listas de tipo "Panel de discusión" contiene el nivel al que pertenece un elemento en la jerarquía discusión/mensajes.
5 – La vista "Encadenada" da la sensación de jerarquía utilizando imágenes cuyo ancho podemos calcular así: ((string)pSPListItem[SPBuiltInFieldId.ThreadIndex]).Length – 46.
<!-- Discusión - Cuerpo de la discusión. -->
<img height="1" width="0" alt="" src="/_layouts/images/blank.gif" />
<!-- 1 -->
<img height="1" width="10" alt="" src="/_layouts/images/blank.gif" />
<!-- 1.1 -->
<img height="1" width="20" alt="" src="/_layouts/images/blank.gif" />
<!-- 1.1.1 -->
<img height="1" width="30" alt="" src="/_layouts/images/blank.gif" />
<!-- 2 -->
<img height="1" width="10" alt="" src="/_layouts/images/blank.gif" />
La estructura "ThreadIndex" de los elementos que aparecen en la primer imagen se ve así:
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855 – Discusión – Cuerpo de la discusión.
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA85 – 1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA850000016FC8 – 1.1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF855000002BA850000016FC800000142DF – 1.1.1
0×01CAA77AE5804DE8A753BCA349C89DA0D34D712CF8550000073CDB – 2
Se me ocurrió el siguiente modelo:
Como un panel de discusión por defecto lo único que puede contener son discusiones y mensajes (o respuestas) y dado que una discusión y un mensaje comparten ciertos atributos, la clase "ElementoPanelDeDiscusion" sirve como clase base para "Discusion" y "Mensaje".
Notar que la clase "Discusion" tiene la propiedad "Asunto" mientras que la clase "Mensaje" no la tiene.
La propiedad "Padre" en la clase "Discusion" devuelve null, mientras que en la clase "Mensaje" puede devolver "Discusion" o "Mensaje" (ya que un mensaje puede pertenecer a una discusión o a una respuesta dentro de otra respuesta). Por último, "PanelDeDiscusion" es la clase que hace todo el trabajo.
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
namespace NicolasFerreira.SharePoint
{
public abstract class ElementoPanelDeDiscusion
{
private string _Cuerpo;
public string Cuerpo
{
get { return _Cuerpo; }
}
internal List<Mensaje> _Mensajes;
public ReadOnlyCollection<Mensaje> Mensajes
{
get { return _Mensajes.AsReadOnly(); }
}
public abstract ElementoPanelDeDiscusion Padre { get; }
internal string _ThreadIndex;
internal ElementoPanelDeDiscusion(string pCuerpo)
{
_Mensajes = new List<Mensaje>();
_Cuerpo = pCuerpo;
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace NicolasFerreira.SharePoint
{
public class Discusion : ElementoPanelDeDiscusion
{
private string _Asunto;
public string Asunto
{
get { return _Asunto; }
}
internal Discusion(string pCuerpo, string pAsunto)
: base(pCuerpo)
{
_Asunto = pAsunto;
}
public override ElementoPanelDeDiscusion Padre
{
get { return null; }
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
namespace NicolasFerreira.SharePoint
{
public class Mensaje : ElementoPanelDeDiscusion
{
internal ElementoPanelDeDiscusion _Padre = null;
public override ElementoPanelDeDiscusion Padre
{
get { return _Padre; }
}
internal Mensaje(string pCuerpo)
: base(pCuerpo)
{
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
namespace NicolasFerreira.SharePoint.Listas
{
/// <summary>
/// Clase encargada de manipular el tipo de lista "Panel de discusión".
/// </summary>
public static class PanelDeDiscusion
{
/// <summary>
/// Método fábrica que devolverá un objeto de tipo Discusion o Mensaje dependiendo de T.
/// </summary>
/// <typeparam name="T">Tipo de objeto que se desea obtener. El tipo debe heredar de la clase ElementoPanelDeDiscusion.</typeparam>
/// <param name="pSPListItem">SPListItem que contiene la información necesaria como para crear el tipo de objeto especificado en T.</param>
private static T CrearElementoPanelDeDiscusion<T>(SPListItem pSPListItem)
where T : ElementoPanelDeDiscusion
{
ElementoPanelDeDiscusion lElementoPanelDeDiscusion = null;
if (typeof(T) == typeof(Discusion))
{
lElementoPanelDeDiscusion = new Discusion((string)pSPListItem[SPBuiltInFieldId.Body],
pSPListItem[SPBuiltInFieldId.Title].ToString());
}
else if (typeof(T) == typeof(Mensaje))
{
lElementoPanelDeDiscusion = new Mensaje((string)pSPListItem[SPBuiltInFieldId.Body]);
}
if (lElementoPanelDeDiscusion != null)
{
lElementoPanelDeDiscusion._ThreadIndex = ((string)pSPListItem[SPBuiltInFieldId.ThreadIndex]);
}
return lElementoPanelDeDiscusion as T;
}
/// <summary>
/// Método que devolverá todas las discusiones que haya en una lista.
/// </summary>
/// <param name="pSPListPanelDeDiscusion">SPList que contiene la información necesaria como para obtener todas las discusiones.</param>
public static List<SPListItem> ObtenerDiscusiones(SPList pSPListPanelDeDiscusion)
{
List<SPListItem> lDiscusiones = new List<SPListItem>();
/*Para obtener todas las discusiones de la lista,
* tenemos que utilizar la propiedad Folders de SPList.*/
foreach (SPListItem lSPListItemDiscusion in pSPListPanelDeDiscusion.Folders)
{
lDiscusiones.Add(lSPListItemDiscusion);
}
return lDiscusiones;
}
/// <summary>
/// Método que devolverá un objeto de tipo Discusion con la discusión (incluyendo mensajes y mensajes anidados).
/// </summary>
/// <param name="pSPListItemDiscusion">SPListItem que contiene la información necesaria de la discusión que queremos interpretar.</param>
public static Discusion ObtenerDiscusion(SPListItem pSPListItemDiscusion)
{
Discusion lDiscusion = null;
/*Para obtener todos los mensajes (incluidos los anidados) lo hago con un SPQuery y establezco
* la propiedad Folder al valor de pSPListItemDiscusion.Folder que es la carpeta de la discusión.*/
SPQuery lSPQuery = new SPQuery();
lSPQuery.Folder = pSPListItemDiscusion.Folder;
SPListItemCollection lSPListItemCollection = pSPListItemDiscusion.ParentList.GetItems(lSPQuery);
List<SPListItem> lSPListItemMensajes = new List<SPListItem>();
foreach (SPListItem lSPListItemMensaje in lSPListItemCollection)
{
lSPListItemMensajes.Add(lSPListItemMensaje);
}
lSPListItemMensajes.Sort(new ThreadIndexComparer()); //Ordeno los mensajes.
lDiscusion = CrearElementoPanelDeDiscusion<Discusion>(pSPListItemDiscusion); //Creo la discusión para luego agregar mensajes.
Mensaje lMensaje = null; //Lo utilizo para tener como referencia el último mensaje que agregué a una discusión o a un mensaje.
foreach (SPListItem lSPListItemMensaje in lSPListItemMensajes)
{
//ThreadIndex contiene el nivel al que pertenece el mensaje.
string lThreadIndex = ((string)lSPListItemMensaje[SPBuiltInFieldId.ThreadIndex]);
if (lMensaje != null)
/*Si anteriormente agregué un mensaje en alguna parte entonces puede que el
* nuevo mensaje tenga relación o no con el agregado.*/
{
bool lContinuar = true;
while (lContinuar)
{
if (lThreadIndex.StartsWith(lMensaje._ThreadIndex))
/*Verifico si existe una relación
* (esto es posible gracias a la estructura del campo ThreadIndex).*/
{
/*Como existe una relación con el mensaje agregado anteriormente,
* entonces el nuevo mensaje es hijo del anterior.*/
Mensaje lNuevoMensaje = CrearElementoPanelDeDiscusion<Mensaje>(lSPListItemMensaje);
lNuevoMensaje._Padre = lMensaje; //Le indico al nuevo mensaje que su padre va a ser el agregado anteriormente.
lMensaje._Mensajes.Add(lNuevoMensaje);
lMensaje = lNuevoMensaje; //¡El nuevo mensaje pasa a ser antiguo!
lContinuar = false;
}
else
{
/*Como no hay relación entre el mensaje agregado anteriormente y el que intento agregar ahora,
* me voy moviendo "hacia atrás" en busca de un mensaje que tenga relación
* con el que intento agregar.*/
if (lMensaje.Padre is Mensaje)
{
lMensaje = ((Mensaje)lMensaje.Padre);
}
else
{
//Ningún mensaje es padre del mensaje que intento agregar, entonces el mensaje es hijo de la discusión.
lMensaje = null;
lContinuar = false;
}
}
}
}
if (lMensaje == null)
{
lMensaje = CrearElementoPanelDeDiscusion<Mensaje>(lSPListItemMensaje);
lDiscusion._Mensajes.Add(lMensaje);
lMensaje._Padre = lDiscusion;
}
}
return lDiscusion;
}
}
}
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.SharePoint;
namespace NicolasFerreira.SharePoint
{
internal class ThreadIndexComparer : IComparer<SPListItem>
{
#region IComparer Members
public int Compare(SPListItem x, SPListItem y)
{
string xThreadIndex = ((string)x[SPBuiltInFieldId.ThreadIndex]);
string yThreadIndex = ((string)y[SPBuiltInFieldId.ThreadIndex]);
return string.Compare(xThreadIndex, yThreadIndex);
}
#endregion
}
}
Descargar el código.