Archivo

Archive for 29 julio 2011

Cómo obtener todos los usuarios de un sitio


Si estás haciendo un desarrollo para SharePoint y de pronto quisieras saber cuáles son los usuarios que hay en un sitio, ¿qué hacer? Easy peasy. Comenzamos por referenciar nuestro Microsoft.SharePoint.dll de siempre e importamos el espacio de nombres Microsoft.SharePoint. Luego obtenemos un objeto SPWeb (ya sea mediante el SPContext.Current o creando un nuevo SPSite).

Si te das cuenta, SPWeb cuenta con al menos tres propiedades interesantes, todas las cuales regresan un objeto SPUserCollection (una colección de objetos SPUser), a saber:

    ¿Cuál es la diferencia entre estas tres propiedades? Pensando un poquito, SiteUsers salta a la vista y en efecto es lo que sospechas: esa propiedad regresa a todos los usuarios existentes en la colección de sitios. ¿Cuál será la diferencia entre las otras dos? Pues, en esencia, que Users te regresa todos los usuarios registrados en el sitio, mientras que AllUsers te regresa no nada más los usuarios registrados en el sitio, sino todos aquellos que hayan entrado al sitio alguna vez, o incluso que hayan sido referenciados en alguna lista (digamos, alguna alerta o algo por el estilo).

    Puede obtenerse un usuario específico por el índice de la colección, o bien por el nombre de usuario (de la forma “dominio\\nombreUsuario”), usando el indizador. O bien alguno de los métodos expuestos, como GetByID o GetByEmail.

    SPWeb web = SPContext.Current.Web;
    
    // propiedades de SPUser
    foreach (SPUser user in web.AllUsers)
    {
        string email = user.Email;
        string name = user.Name;
        // etc
    }
    
    // Obtener un usuario específico por correo electrónico
    SPUser user = web.AllUsers.GetByEmail("fernando.gomez@dominio.com");
    
    // Buscar todos los usuarios que comiencen con "Fer"
    var query = from SPUser user in web.AllUsers
                         where user.Name.BeginsWith("Fer")
                         select user;
    SPUser[] ferUsers = query.ToArray();
    
    // Easy Peasy
    

¿Y qué pasa si queremos consultar la información del perfil, desde un lugar fuera del servidor (i.e. una aplicación externa o desde Silverlight)? Pues usamos un servicio web, en concreto: UserGroup.asmx.

Este servicio web cuenta con tres métodos que te resultarán familiares:

  • UserGroup.GetAllUserCollectionFromWeb.
  • UserGroup.GetUserCollectionFromSite.
  • UserGroup.GetUserCollectionFromWeb.

¿Te suenan? Pues claro, son los equivalentes a SPWeb.AllUsers, SPWeb.SiteUsers y SPWeb.Users. Los métodos regresan un objeto tipo XmlNode, el cual contiene un XML más o menos con este esquema:

<GetUserCollectionFromWeb xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/">
    <Users>
        <User ID="190" Sid="S-1-5-21-944133582-289434890-317593308-21562" 
            Name="Fernando Gómez" LoginName="dominio\fernando.gomez" 
            Email="fernando.gomez@dominio.com" Notes="" IsSiteAdmin="False" 
            IsDomainGroup="False" Flags="0" /> 
        <User ID="111" Sid="S-1-5-21-944133582-289434890-317593308-22617" 
            Name="Fulano De Tal" LoginName="dominio\fulano.detal" 
            Email="fulano.detal@dominio.com" Notes="" IsSiteAdmin="False" 
            IsDomainGroup="False" Flags="0" /> 
        <!-- etc -->
    </Users>
</GetUserCollectionFromWeb>

En esencia, el XML devuelto tiene un primer nodo, cuyo nombre corresponde al nombre del método (por ejemplo, de haber llamado a GetAllUserCollectionFromWeb, el nodo habría tenido este otro nombre), seguido de un nodo Users, el cuál contiene una colección de nodos User. Éstos últimos contienen atributos, cada uno representando información sobre el usuario. En el ejemplo te muesto algunos, como el ID interno, el nombre, la cuenta (LoginName) y el correo electrónico.

Cargar esta información, pues, es relativamente sencilla, dado que es el mismo esquema. Puedes cargarlo iterando sobre el nodo, o bien mediante LINQ to XML. Pero a mí me gusta usar XPath.

Es relativamente sencillo, de hecho, usar XPath. A primera instancia, puede que pienses en llamar a SelectNodes sobre el XmlNode, quizás algo así:

UserGroup service = new UserGroup();
service.Url = "http://labwf";
service.Credentials = CredentialCache.DefaultCredentials;

XmlNode rootNode =  service.GetAllUserCollectionFromWeb();
string xpath = "GetAllUserCollectionFromWeb/Users/descendant::User";
XmlNodeList nodes = rootNode.SelectNodes(xpath);
foreach (XmlNode node in nodes)
{
    string loginName = node.Attributes["LoginName"];
    string email = node.Attributes["Email"];
    // etc
}

service.Dispose();

Pero pues toma chango tu banano, lo anterior te regresará un XmlNodeList vacío. La razón es que el XML devuelto por el servicio web contiene un espacio de nombres especificado. Pero no uno cualquiera, sino el que se usa por default: xmlns.

Para solventar este problema tenemos que llamar SelectNodes con un gestionador de espacio de nombres (XmlNamespaceManager), al cual le añadiremos el espacio de nombres que viene en el XML junto con un prefijo con el que lo queramos identificar (en mi caso, he escogido “defns”). Pero el gestionador necesita de una tabla de nombres en su constructor, y la forma más sencilla de obtenerla, según pude ver, es crear un XmlDocument y usar su propiedad NameTable.

Una vez hecho esto, podremos usar el SelectNodes sober el XmlDocument, pero tendremos que cambier el XPath para que referencie el prefijo que añadimos al gestionador de espacios de nombres. Por ejemplo, en mi caso, el XPath quedaría como:

defns:GetAllUserCollectionFromWeb/defns:Users/descendant::defns:User

Así luciría nuestro código.

// preparar el servicio web
UserGroup service = new UserGroup();
service.Url = "http://labwf";
service.Credentials = CredentialCache.DefaultCredentials;

// generar el documento xml con los usuarios
XmlNode rootNode =  service.GetAllUserCollectionFromWeb();
XmlDocument doc = new XmlDocument();
doc.LoadXml(rootNode.OuterXml);

// crear el gestionador de espacios de nombres 
XmlNamespaceManager manager = new XmlNamespaceManager(doc.NameTable);
manager.AddNamespace("defns", "http://schemas.microsoft.com/sharepoint/soap/directory/");

// ejecutar nuestro xpath...
string xpath = "defns:GetAllUserCollectionFromWeb/defns:Users/descendant::defns:User";
XmlNodeList nodes = rootNode.SelectNodes(xpath);

//... y ahora sí iterar sobre los resultados
foreach (XmlNode node in nodes)
{
    string loginName = node.Attributes["LoginName"];
    string email = node.Attributes["Email"];
    // etc
}

service.Dispose();

Bien, pues eso es todo por el momento. Ya veremos más cosas de usuarios en el siguiente post…

Perfiles de usuario e información social


En esta era donde las redes sociales inundan nuestras vidas, es cada vez más común ver empresas y corporaciones que piden características de éstas en sus propios sitios corporativos.

SharePoint 2010 ha dado pasos significativos en esa dirección mediante la provisión de mecanismos para habilitar características similares a las encotnradas en redes sociales. Así pues, cuando un cliente nos pida desarrollar tal o cual característica social, sabremos que podemos apoyarnos en lo que SharePoint nos ofrece.

Quizás lo primero que viene a la mente, al respecto, sean los perfiles de usuario. Es decir, dado un usuario cualquiera en SharePoint, quisiéramos obtener su información particular: nombre, teléfonos, departamentos, móviles, jefe, direcciones, etc. En SharePoint 2010 es posible obtener esta información, si se tienen los permisos necesarios. Existen dos formas de hacerlo: mediante el modelo de objetos de SharePoint, o bien mediante los servicios web que se exponen en /_vti_bin/ de cada sitio. En esta ocasión usaremos los servicios web.

O mejor dicho, usaremos un servicio web en particular: UserProfileService.asmx. Este lo podemos referenciar desde http://misitio/_vti_bin/UserProfileService.asmx. Al hacerlo, nos generará varias clases proxy, entre las que se encuentra UserProfileService.

Vamos por partes. Lo primero que nos interesa es obtener el perfil dado un usuario determinado. Pues bien, es cuestión de llamar a UserProfileService.GetUserProfileByName. Este método toma como parámetro el nombre de la cuenta de un usuario de la forma “dominio\cuenta” (digamos, “ASGARD\thor”) y regresa una colección de objetos PropertyData (esta clase, por cierto, es una clase proxy generada al momento de referenciar el servicio web). Cada PropertyData representa una propiedad del usuario, así que para obtener todas hay que iterar sobre el array.

El siguiente ejemplo muestra cómo obtener la propiedad PreferredName.

using System;
using System.Linq;
using System.Net;
using UserProfileServiceProxy; // el espacio de nombre de tu servicio web
...

UserProfileService service = new UserprofileService();
service.Url = "http://labwf/_vti_bin/UserProfileService.asmx";
service.Credentials = CredentialCache.DefaultCredentials;

PropertyData[] properties = service.GetUserProfileByName("LABWF\\thor");
service.Dispose();

var query = from property in properties
                     where propety.Name.Equals("PreferredName")
                         && property.Values.Length > 0
                         && property.Values[0] != null
                         && property.Values[0].Value != null
                     select property.Values[0].Value.ToString();

string preferredName = query.FirstOrDefault();

                     

Algunas de las propiedades más interesantes (i.e. PropertyData.Name) son las siguientes: AccountName, ADGuid, CellPhone, Department, FirstName, HomePhone, LastName, Manager, PictureURL, PreferredName, SPSBirthday, SPSHireDate, SPSJobTitle, Title, UserName, WebSite, WorkEmail y WorkPhone. Pero hay otras, ¡muchas más!

Si estamos en una aplicación en consola, podemos iterar sobre cada elemento de forma más o menos genérica:

using System;
using System.Linq;
using System.Net;
using UserProfileServiceProxy; // el espacio de nombre de tu servicio web
...

UserProfileService service = new UserprofileService();
service.Url = "http://labwf/_vti_bin/UserProfileService.asmx";
service.Credentials = CredentialCache.DefaultCredentials;

PropertyData[] properties = service.GetUserProfileByName("LABWF\\thor");
service.Dispose();

foreach (PropertyData property in properties)
{
    string name = property.Name;
    string value;
    if (property.Values.Length > 0
        && property.Values[0] != null
        && property.Values[0].Value != null)
        value = property.Values[0].Value.ToString();
    else
        value = string.Empty;

    Console.WriteLine("Propiedad: {0} Valor: {1}", name, value);
}

Ahora bien, aunque es mucha información relacionada al usuario, y ciertamente reviste importancia, no lo es todo en una red social. En particular, nos interesará ver otros elementos que estén asociados a un usuario particular. SharePoint provee, a través del mismo WebService, información adicional. Veamos una por una.

Quizá una de las más usadas es ver qué colegas tiene un usuario determinado. Un colega es un usuario que es marcado como conocido de otro usuario. Así, un usuario determinado puede tener a varios colegas. Esa información se obtiene usando el método UserProfileService.GetUserColleagues, pasando como parámetro la cuenta del usuario en cuestión. Easy peasy:

using System;
using System.Linq;
using System.Net;
using UserProfileServiceProxy; // el espacio de nombre de tu servicio web
...

UserProfileService service = new UserprofileService();
service.Url = "http://labwf/_vti_bin/UserProfileService.asmx";
service.Credentials = CredentialCache.DefaultCredentials;

ContactData[] allData = service.GetUserColleagues("LABWF\\thor");
service.Dispose();

foreach (ContactData data in allData)
{
    Console.WriteLine("Colega: {0}", data.AccountName);
    Console.WriteLine("*** Email: {0}", data.Email);
    Console.WriteLine("*** Group: {0}", data.Group);
    Console.WriteLine("*** ID : {0}", data.ID);
    Console.WriteLine("*** IsInWorkGroup : {0}", data.IsInWorkGroup);
    Console.WriteLine("*** Name : {0}", data.Name);
    Console.WriteLine("*** Privacy : {0}", data.Privacy);
    Console.WriteLine("*** Title : {0}", data.Title);
    Console.WriteLine("*** Url : {0}", data.Url);
    Console.WriteLine("*** UserProfileID : {0}", data.UserProfileID);
    Console.WriteLine();
}

El método GetUserColleagues regresa una colección de ContactData. Ésta clase (igual, proxy generada al referenciar el servicio web) tiene varias propiedades, relacionadas con el colega. Cada elemento de la colección, obvio, representa uno.

Otro aspecto importante que SharePoint ofrece es la posibilidad de que cada usuario tenga un enlace (link), digamos, como favorito. O que otro usuario le envíe un enlace como referencia. Bueno, pues UserProfileService cuenta con dos métodos para llevar a cabo lo anterior: GetUserLink y GetUserPinnerLinks. La diferencia entre ambos es que GetUserPinnerLniks son aquellos enlaces que el usuario ha marcado como favoritos, de manera explícita. El procedimiento es muy similar: ambos métodos regresan una colección de clases proxy, QuickLinkData y PinnedLinkData, respectivamente.

using System;
using System.Linq;
using System.Net;
using UserProfileServiceProxy; // el espacio de nombre de tu servicio web
...

UserProfileService service = new UserprofileService();
service.Url = "http://labwf/_vti_bin/UserProfileService.asmx";
service.Credentials = CredentialCache.DefaultCredentials;

QuickLinkData[] quickLinks = service.GetUserLinks("LABWF\\thor");
foreach (QuickLinkData data in quickLinks)
{
    Console.WriteLine("Enlace: {0}", data.Name);
    Console.WriteLine("*** Group: {0}", data.Group);
    Console.WriteLine("*** ID : {0}", data.ID);
    Console.WriteLine("*** Privacy : {0}", data.Privacy);
    Console.WriteLine("*** Url : {0}", data.Url);
    Console.WriteLine();
}

PinnedLinkData[] pinnedLinks = service.GetUserPinnedLinks("LABWF\\thor");
foreach (PinnedLinkData data in pinnedLinks)
{
    Console.WriteLine("Enlace marcado: {0}", data.Name);
    Console.WriteLine("*** ID : {0}", data.ID);
    Console.WriteLine("*** Url : {0}", data.Url);
    Console.WriteLine();
}

service.Dispose();

Las membresías son otro aspecto interesante que nos ofrece nuestro SharePoint social. Es decir, a qué grupos pertenece tal usuario. Similar a esto, también nos interesa saber a qué organizaciones pertenece el usuario. Para hacerlo, usaremos GetUserMemberships y GetUserOrganization, los cuales regresan (como cabría esperar) una colección de objetos de tipo MembershipData y OrganizationProfileData, respectivamente (¿comienzas a ver el patrón?).

using System;
using System.Linq;
using System.Net;
using UserProfileServiceProxy; // el espacio de nombre de tu servicio web
...

UserProfileService service = new UserprofileService();
service.Url = "http://labwf/_vti_bin/UserProfileService.asmx";
service.Credentials = CredentialCache.DefaultCredentials;

MembershipData[] memberships = service.GetUserMemberships("LABWF\\thor");
foreach (MembershipData data in memberships)
{
    Console.WriteLine("Membrecía: {0}", data.DisplayName);
    Console.WriteLine("*** Group: {0}", data.Group);
    Console.WriteLine("*** ID : {0}", data.ID);
    Console.WriteLine("*** MailNickname: {0}", data.MailNickname);
    Console.WriteLine("*** MemberGroup: {0}", data.MemberGroup.SourceReference);
    Console.WriteLine("*** MemberGroupID: {0}", data.MemberGroupID);
    Console.WriteLine("*** Privacy : {0}", data.Privacy);
    Console.WriteLine("*** Source: {0}", data.Source);
    Console.WriteLine("*** Url : {0}", data.Url);
    Console.WriteLine();
}

OrganizationProfileData[] organizations = service.GetUserOrganizations("LABWF\\thor");
foreach (OrganizationProfileDatadata in organizations)
{
    Console.WriteLine("Organización: {0}", data.DisplayName);
    Console.WriteLine("*** ID : {0}", data.RecordID);
    Console.WriteLine();
}

service.Dispose();

El perfil de un usuario, más su información relacionada (en este caso: colegas, organizaciones, membrecías, enlaces y favoritos) cubre gran parte de la información que uno cabría esperar de una red social (corporativa). Pero SharePoint va un paso más allá: no nada más nos provee la posibilidad de consultar esta información, sino también permite compararla con la de otros usuarios. Por ejemplo: ¿qué usuarios tenemos en común? ¿Qué membrecías compartimos? ¿Cuál es nuestro jefe común?

El servicio web expone el método GetInCommon, el cual toma la cuenta de un usuario como parámetro. Este método compara dicho usuario contra el usuario actual (en este caso, el usuario almacenado en las credenciales del servicio web, o dicho de otra forma, el usuario que manda llamar al servicio web), y rergesa una colección (sorpresa sorpresa) de objetos tipo InCommonData. Esta clase proxy tiene tres miembros: una colección de ContactData, Colleagues, que representa los colegas en común; una colección de MembershipData, Memberships, que representa las membrecías e común; y una propiedad de tipo ContactData, Manager, que representa el jefe en común.

using System;
using System.Linq;
using System.Net;
using UserProfileServiceProxy; // el espacio de nombre de tu servicio web
...

UserProfileService service = new UserprofileService();
service.Url = "http://labwf/_vti_bin/UserProfileService.asmx";
service.Credentials = CredentialCache.DefaultCredentials;

InCommonData data = service.GetInCommon("LABWF\\thor");
service.Dispose();

Console.WriteLine("Jefe común: {0}", data.Manager.Name);

Console.WriteLine("Colegas en común:");
foreach (ContactData colleague in data.Colleagues)
    Console.WriteLine("*** {0}", colleague.Name);

Console.WriteLine("Membrecías en común:");
foreach (MembershipData membership in data.Memberships)
    Console.WriteLine("*** {0}", membership.DisplayName);

Console.WriteLine();

Alternativamente, puedes llamar a los métodos GetCommonManager, GetCommonColleagues y GetCommonMemberships, por separado, si no quieres traerte toda la información.

Pues bien, como puedes ver UserProfileService es el primer paso para crear aplicaciones sociales en SharePoint. Por supuesto, no es la única forma. Ahora hemos usado servicios web, pero también podríamos usar el modelo de objetos. Eso será asunto de otra entrada.

El método SelectNodes regresa una lista vacía tras una consulta con XPath


Ando haciendo un programa para descargar perfiles de usuarios en SharePoint (el cual espero publicar pronto en la galería de código de MSDN). Uno de los requerimientos del programa consiste en usar un WebService y descargar un XML como el siguiente:

<GetUserCollectionFromWeb xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/">
<Users>
 <User ID="190" LoginName="dominio\nombre1" Email="nombre1@dominio.com.mx" />
 <User ID="111" LoginName="dominio\nombre2" Email="nombre2@dominio.com.mx" />
 </Users>
 </GetUserCollectionFromWeb>

Así pues, parte de la lógica consiste en iterar sobre cada elemento de <Users> y mostrarlo en una cuadrícula (GridView) de Windows Forms. Easy peasy, dado que el servicio web me regresa un XML, se me ocurre hacerlo con XPath. Este es mi código inicial.

XmlNode mainNode = webService.GetUserCollectionFromWeb();

string xpath = "GetUserCollectionFromWeb/Users/descendant::User";
XmlNodeList userNodes = mainNode.SelectNodes(xpath);
foreach (XmlNode userNode in userNodes)
{
    string loginName = userNode.Attributes["LoginName"].Value;
    // hacer algo con loginName;
}

Sin embargo, al hacer la prueba, el método SelectNodes me regresaba 0 elementos. Mala cosa, digo yo. Estuve revisando un rato, hasta que me decidí a hacer una pregunta en el foro de C# de MSDN. Y como siempre, obtuve apoyo para resolver el problema.

En primer lugar, he de decir, me apoyaron con la consulta XPath, ya que yo la tenía diferente. Aún con ese cambio, SelectNodes se negaba a cooperar. Fue entonces que encontramos la respuesta: el XML en cuestión tiene un espacio de nombres por defecto, xmlns, sobrescrito. Luego, hay que añadir un XmlNamespaceManager que lo contenga. Pero para crear una instancia de dicha clase, hay que pasarle al constructor un XmlNameTable, y la forma más sencilla es mediante un XmlDocument. Una vez creado, hay que añadir el espacio de nombres en cuestión y ponerle un prefijo cualquiera (en este caso, opté por “defns”). Acto seguido, modificamos el XPath y prefijamos los elementos (en mi caso, por ejemplo, cambiamos Users por defns:Users, y así). Y por último, llamamos a SelectNodes y pasamos como segundo parámetro el XmlNamespaceManager creado con anterioridad.

Y así quedó el código:

XmlNode mainNode = webService.GetUserCollectionFromWeb();

XmlDocument doc = new XmlDocument();
doc.LoadXml(mainNode.OuterXml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(mainNode.OwnerDocument.NameTable);
nsmgr.AddNamespace("defns", "http://schemas.microsoft.com/sharepoint/soap/directory/");

XmlNodeList userNodes = doc.SelectNodes("defns:GetUserCollectionFromWeb/defns:Users/descendant::defns:User", nsmgr);
foreach (XmlNode userNode in userNodes)
{
    string loginName = userNode.Attributes["LoginName"].Value;
    // hacer algo con loginName;
}

¡Y voilá! Muchas gracias a Pedro Hurtado por el apoyo, y como siempre, a Leandro Tuttini por sus valiosas contribuciones.

Categorías:.NET Framework, C#, Resolución de problemas Etiquetas: ,

Determinar si un usuario es dueño, miembro o visitante del sitio


Hace algunas lunas estábamos platicando sobre usuarios en SharePoint y y grupos de usuarios. Por un lado vimos cómo identificar si un usuario pertenece a un grupo determinado o no. Y unos días después vimos una forma sencilla para determinar si el usuario autenticado pertenece o no a un grupo de SharePoint en particular. .

Continuando con esta línea, me gustaría ahora platicar sobre tres grupos que, por defecto, tienen todos los sitios que creamos en SharePoint. Estos son:

1.- Dueños del sitio (Site Owners)

2.- Miembros del sitio (Site Members)

3.- Visitantes (Site Visitors).

Por defecto, los dueños del sitio tendrán permisos en la totalidad del sitio, y podrán crear contenido, páginas, añadir usuarios a otros grupos, crear y eliminar bibliotecas, etc. Por su parte, los miembros del sitio pueden añadir y eliminar documentos, configurar alertas para sí mismos y, en suma, añadir y eliminar contenido de las bibliotecas y listas personalizadas. Por último, los visitantes sólo podrán leer el contenido del sitio, nunca modificarlo.

Por supuesto, uno puede crear más grupos, cambiar los roles de los existentes o eliminarlos. Sin embargo, los tres antes mencionados revisten una importancia particular para nosotros como programadores: los tres grupos están mapeados en propiedades del objeto SPWeb.

En efecto, SPWeb tiene tres propiedades: AssociatedOwnerGroup, AssociatedMemberGroup y AssociatedVisitorGroup, las tres de tipo SPGroup, y hacen referencia al grupo de dueños, miembros y visitantes del sitio, respectivamente. Por supuesto, si borras los sitios por defecto, o los cambias, las tres propiedades regresarán null.

image

Esto abre nuevas posibilidades, respecto a lo que veíamos en los puestos anteriores. Por ejemplo, ahora determinar si el usuario actualmente autenticado es administrador, dueño del sitio o visitante es tan sencillo como esto:

SPWeb web = SPContext.Current.Web;
if (web.CurrentUser.IsSiteAdmin) {
    // el usuario es el administrador del sitio
} else if (web.AssociatedOwnerGroup.ContainsCurrentUser) {
    // el usuario es dueño del sitio, tiene acceso entero a todo...
} else if (web.AssociatedMemberGroup.ContainsCurrentUser) {
    // el usuario es miembro, puede añadir contenido a listas y documentos
} else if (web.AssociatedVisitorGroup.ContainsCurrentUser) {
    // el usuario es visitante, puede ver pero no modificar el contenido
} else {
    // el usuario está en algún otro grupo
}

Easy Peasy. Obvio querrás validar primero que las propiedades no sean nulas, pero bueno, es suficiente para ejemplificar.

Más información sobre grupos y seguridad en SharePoint 2010:Determine permission levels and groups.

Cómo buscar elementos en listas sin tener que usar CAML


Cuando hacemos desarrollo de componentes para SharePoint (WebParts, Application Pages, etc.) es muy común que tengamos que leer una lista (o biblioteca de documentos, galería de imágenes, etc.) para manipular suselementos. Puede ser, sin embargo, que queramos que la lectura se haga dependiendo de ciertos criterios. En otras palabras, queremos filtrar los elementos de la lista.

Hacer eso a mano (i.e. un foreach hacia SPList.Items) no suele ser una buena práctica, especialmente si nuestra lista contendrá muchos elementos. Naturalmente, la solución consiste en crearnos una consulta con CAML (Collaborative Application Markup Language, si la memoria no me falla) y buscar los elementos llamando a SPList.GetItems, como se muestra a continuación.

SPQuery query = new SPQuery();
query.Query = "[Mi consulta CAML]";

SPList list = SPContext.Current.Web.Lists["Mi Lista"];
SPListItemCollection items = list.GetItems(query);

// y hacer algo con items. 

Hacer consultas con CAML, sin embargo, es algo que lleva tiempo. El esquema de CAML para consultas tiene muchos elementos y la curva de aprendizaje es, en mi opinión, un tanto pronunciada. Por ello, muchas veces tenemos que usar herramientas como el U2U  CAML Query Builder para que nos ayude a generar la consulta.

Un truco que me ha dado resultado, y que incluso me da mucha más flexibilidad para poder configurar mi búsqueda posteriormente, consiste en crear una vista y programáticamente leer el CAML que ésta utiliza. Ya con el CAML, repetimos el paso del código anterior y listo.

Para lograr lo anterior, debemos a) obtener la lista sobre la que haremos la consulta, b) obtener la vista, usualmente por su nombre; c) obtener la consulta CAML de la vista a través de la propiedad Query, y d) con ese CAML crear un objeto SPQuery y ejecutar SPList.GetItems. Easy peasy:

SPList list = SPContext.Current.Web.Lists["Mi Lista"];
SPView view = list.Views["Mi vista"];
// alternativamente, si quieres usar la vista por defecto:
// SPView view = list.DefaultView;

SPQuery query = new SPQuery();
query.Query = view.Query;
// si analizas view.Query con el QuickWatch de Visual Studio
// pordrás ver el CAML de la vista...

SPListItemCollection items = list.GetItems(query);
// y hacer algo con los elementos encontrados. 

Este truco te permitirá, además, que el usuario (o administrador) de la lista modifique la vista en cuestión, sin tener que recompilar. Una buena técnica que últimamente me ha salvado trabajo de codificación y de estar lidiando con CAML…

Buscar los registros creados por el usuario actual


Cuando desarrollamos una solución utilizando SharePoint, al menos en mi experiencia, es común que nuestro cliente, en algún momento, nos haga el siguiente requerimiento: dada una lista o biblioteca determinada, mostrar sólamente los elementos que hayan sido creados por dicho usuario.

Hacer esto en SharePoint, por supuesto, no es problema alguno. Basta crear una vista, o modificar la vista por defecto, irnos a la sección de filtros y seleccionar el campo “Creado por”, es igual a, y en el valor, ponemos “[Yo]” (o “[Me]” en inglés).

Sin embargo, también es común que tengamos que aplicar la misma regla de negocio al momento de desarrollar nuestros componentes (por ejemplo, un WebPart con un SPGridView). Y para ello, ni modo de iterar sobre todos los elementos de una lista… mejor utilizar una consulta con CAML.

En particular, este es el CAML que nos interesa.

<Where>
    <Eq>
        <FieldRef Name="Author" />
        <Value Type="Integer">
            <UserID Type="Integer" />
        </Value>
    </Eq>
</Where>

El Where y el Eq no debe sorprendernos, son normalitos. La etiqueta FieldRef hace referencia al campo Author, que no es más que la columna de “Creado por”. Y en la etiqueta Value, hacemos dos cosas: primero, especificar que el campo será de tipo entero (naturalmente, puesto que se filtrará por el ID del usuario), y segundo, incluímos como etiqueta hija a UserID, también de tipo entero. La etiqueta UserId, por supuesto, hace referencia al usuario actualmente autenticado.

Luego entonces, el código para hacer realidad todo esto quedaría algo así:

SPQuery query = new SPQuery();
query.Query = @"<Where>
                                   <Eq>
                                       <FieldRef Name=""Author"" />
                                       <Value Type=""Integer"">
                                           <UserID Type=""Integer"" />
                                       </Value>
                                   </Eq>
                               </Where>";

SPList list = SPContext.Current.Web.Lists["Mi Lista"];
SPListItemCollection items = list.GetItems(query);

// y hacer lo que sea que hagamos con los elementos, 
// como llamar a GetData para obtener un DataTable
// y hacer un DataBind con un SPGridView... 

 

And be done with it…

Cinco formas de usar el SPGridView


He publicado recientemente un código en la galería de MSDN. Éste contiene una solución hecha con Visual Studio 2010, la cual tiene cinco ejemplos sobre cómo utilizar el SPGridView de SharePoint.

¡Échale un ojo y descarga el código fuente!

Transcribo porciones del artículo que lo acompaña.

Introducción

La interfaz gráfica de SharePoint contiene diversos controles, los cuales están disponibles para ser usados mediante las librerías de SharePoint como Microsoft.SharePoint.dll.

En diversas ocasiones, al construir WebParts o páginas de aplicaciones, necesitamos mostrar datos de una u otra forma. Una particularmente útil es el empleo de vistas de reja (GridView). Por supuesto, podemos emplear el control GridView nativo de ASP.NET. Afortunadamente, sin embargo, SharePoint cuenta con su propia implementación de este control: SPGridView. Este control cuenta con ciertas configuraciones avanzadas, pero sobre todo, incorpora los estilos nativos de SharePoint, así como un poco de funcionalidad extra.

El código aquí contenido muestra cinco ejemplos sobre cómo utilizar este control:

1.- Cómo utilizarlo dentro de un WebPart.

2.- Cómo utilizarlo de forma declarativa en una página de aplicación.

3.- Cómo añadir un Edit Box Control a una columna del SPGridView.

4.- Cómo añadir paginadores, filtros y ordenamiento.

5.- Cómo usar el control en conjunto con un SPDataSource.

Construyendo el ejemplo

La solución de ejemplo cuenta con una solución para Visual Studio 2010. Al ser para SharePoint 2010, deberá abrirse en una máquina (virtual, por ejemplo) con SharePoint 2010 (Foundation, al menos) instalado. Compilar y desplegar la solución, sin embargo, es tan sencillo como seleccionar las opciones del Visual Studio 2010 y ya.

Ahora bien, dentro del directorio \Fermasmas.Labs.SPGridViewExample\bin\Debug está el archivo Fermasmas.Labs.SPGridViewExample.wsp. Para instalar la solución sin usar el Visual Studio 2010, basta instalar este WSP y luego hacer el deploy. Ejecutar las siguientes líneas de comando con los valores apropiados debe ser suficiente.

stsadm -o addsolution -filename "directorio-al-archivo\archivo.wsp"
stsadm" -o execadmsvcjobs
stsadm" -o deploysolution -name archivo.wsp -url http://tusitio -allowgacdeployment -immediate -force

Posteriormente, sólo será cuestión de activar el feature contenido en el WSP. Al hacerlo, se crearán las listas y páginas necesarias para mostrar los ejemplos, sobre el sitio seleccionado. También aparecerán, en el menú de Acciones del Sitio, cinco entradas, cada una direccionando a la página que contiene el ejemplo.

Descripción

Todos los ejemplos hacen uso de la lista "Asgard List", la cual contiene cierta información pre-cargada.

El primero de ellos muestra cómo crear el SPGridView dentro de un WebPart, llamado GridViewWebPart. El código dentro del método CreateChildControls muestra cómo se crea. Podrás observar que es muy similar a utilizar en GridView. Las diferencias en la interfaz gráfica, sin embargo, son notables. Así es como luce dentro de una página de WebParts:

Notarás que los campos se encuentran agrupados, y que añadimos un campo con una imagen, una flecha, la cual permite seleccionar una fila. La agrupación está habilitada y permite expander y contraer los elementos.

El segundo ejemplo muestra un SPGridView, pero éste se encuentra dentro de una página de aplicación, y como tal, está declarado solamente con marcas de ASP.NET. Para variar un poco respecto al anterior, este control permite editar los elementos al hacer clic en el botón de edición (los cambios se verán reflejados en la lista, por cierto).

El tercer ejemplo muestra el GridView, pero ahora añadimos una columna con un control EBC (Edit Box Control), que es un menú desplegable con diversas acciones. Es un control característico de SharePoint, y todas las listas personalizadas lo muestran en su campo Title. Adicionalmente, la columna de comentarios tiene un cambio. En lugar de mostrar simplemente el texto, ponemos un enlace, el cual al hacer clic abrirá una ventana desplegable (pop-up) donde se muestra el texto del comentario.

Y así es cómo luce el GridView con el EBC, y cómo luce al hacer clic sobre el enlace de comentarios.

En el cuarto ejemplo, la cosa se pone interesante: añadimos paginación, así como la capacidad de filtrar y ordenar, muy à la SharePoint. Y he aquí la imagen.

Ya por último, comentar que hasta el momento todos los ejemplos han hecho uso del ObjectDataSource para hacer el enlazado de los datos (quizás quieras revisar la clase AsgardSource, pero ahí no hay más que abrir la lista y obtener el DataTable de los elementos). En este último ejemplo mostramos cómo usar el SPGridView en conjunto con SPDataSource, un control que nos permite enlazar de manera fácil contra listas y otros elementos de SharePoint. No hay imagen, porque la vista es similar a las anteriores, pero sí hay fragmento de código a continuación.

Código fuente

    Hay bastantitos archivos, como cabría esperar en una solución para SharePoint. Veamos algunos de los importantes.

    • AsgardContentType\Elements.xml – define el tipo de contenido para la lista que usamos como fuente de datos.
    • AsgardList\Elements.xml – define la lista que usamos como fuente de datos.
    • AsgardList\Schema.xml – define la vista de la lista y su asociación con el content-type del primer punto.
    • AsgardList\ListInstance1\Elements.xml – define una instancia de la lista definida en el punto anterior y añade información pre-cargada.
    • AsgardPagesLibrary\Elements.xml – define una biblioteca de documentos donde podemos guardar páginas web. Aquí estará contenida la página de WebParts donde se muestra el ejemplo 1.
    • GridViewWebPart\Elements.xml – define un WebPart a utilizar en el ejemplo 1.
    • GridViewWebPart\GridViewWebPart.webpart – declara las propiedades iniciales del WebPart del punto anterior.
    • GridViewWebPart\GridViewWebPart.cs – el código C# del WebPart.
    • Layouts\Fermasmas.Labs.SPGridViewExample\GridPageFilterSortExample.aspx y GridPageFilterSortExample.cs  – página ASPX con su archivo de código para el ejemplo 4.
    • Layouts\Fermasmas.Labs.SPGridViewExample\GridPageSimpleExample.aspx y GridPageSimpleExample.cs  – página ASPX con su archivo de código para el ejemplo 2.
    • Layouts\Fermasmas.Labs.SPGridViewExample\GridPageWithDataSource.aspx y GridPageWithDataSource.cs  – página ASPX con su archivo de código para el ejemplo 5.
    • Layouts\Fermasmas.Labs.SPGridViewExample\GridPageWithEbcExample.aspx y GridPageWithEbcExample.cs  – página ASPX con su archivo de código para el ejemplo 3.
    • Layouts\Fermasmas.Labs.SPGridViewExample\ViewComments.aspx y ViewComments.cs – página que muestra un comentario pasado por parámetro de página.
    • Model\AsgardSource.cs – archivo C# que contiene una clase usada en los ObjectDataSource de diversos ejemplos.
    • Pages\AsgardWebPartPage.aspx – define una página de WebParts y carga de forma predeterminada el WebPart del ejemplo 1.
    • Pages\Elements.xml – define un módulo, el cual contiene la página de WebParts del punto anterior.
    • SiteActionMenu\Elements.xml – define los elementos añadidos al botón Acciones del Sitio, lo cual mejora la navegación.