Archivo

Archive for the ‘SharePoint Foundation’ Category

Cómo guardar contenido cuando el usuario no tiene permisos en una lista de SharePoint


Cómo guardar contenido cuando el usuario no tiene permisos en una lista

He observado con mi equipo de trabajo, colegas y sharepointeros en general que una de los problemas que tenemos cuando comenzamos a usar el modelo de objetos de SharePoint es el tema de la seguridad. De hecho a mí mismo me pasaba que tenía que crear un WebPart, el cual obtendría datos de una lista o biblioteca, los renderiza, y al final presenta alguna acción que causa una actualización en dicha fuente. Por supuesto, en ambiente de desarrollo todo jala de pelos, porque usamos la cuenta del administrador. Pero tan pronto publicamos para que el cliente pruebe, ¡boom! Un SecurityException es nuestra recompensa.

Por supuesto, el problema en estos casos suele ser que el usuario que ha ingresado no tiene permisos para acceder a una biblioteca. El caso suele ser sencillo: darle permisos a dicho usuario, pues si no los tiene, su razón ha de tener. En otras palabras, si el usuario no tiene permiso es por la lógica de seguridad implementada para la lista o biblioteca en cuestión.

Sin embargo hay ocasiones en las que el cliente quiere compartir sólo cierta información. Por ejemplo, imagina una lista donde se guarda información sobre los proveedores de una empresa y la tarifa que se les paga a cada uno por unidad de servicio. Por otro lado, el jefe de compras decide que quiere exponer una forma para que los usuarios de otras áreas busquen información de los proveedores, pero no quiere que la tarifa quede expuesta. Además, si el proveedor no buscado no existe, el usuario debería poder darlo de alta (nuevamente, sin capturar la tarifa). ¿Qué podemos hacer?

No podemos simplemente dar permisos de lectura a los usuarios generales porque entonces podrían visualizar la página AllItems.aspx de la lista y les mostraría la tarifa. Luego, lo que tenemos que hacer es un WebPart que consulte la lista y haga la búsqueda ahí, aunque el usuario autenticado no tenga permisos de lectura en la misma. Y lo mismo ocurre con el alta de un proveedor: el usuario deberá ingresar la información sin tener permisos para hacerlo.

Afortunadamente, el modelo de objetos de SharePoint nos proporciona una forma de hacer el “by-pass” de seguridad. La clase SPSecurity (ubicada en el espacio de nombres Microsoft.SharePoint) contiene un método llamado RunWithElevatedPrivileges. Éste toma un sólo parámetro: un delegado de un método sin parámetros que regresa void.

protected override void CreateChildControls()
{ 
    SPGridView myView = new SPGridView();
    ...

    SPSecurity.RunWithElevatedPrvileges( () => {

        // aunque el usuario autenticado no tenga
        // permisos para leer o modificar la lista "Proveedores",
        // al ejecutarse este delegado anónimo sí que
        // los tiene. 
        SPList list = SPContext.Current.Lists["Proveedores"];
        myView.DataSource = list.Items;
        myView.DataBind();
    });

    Controls.Add(myView);
}

El código anterior muestra la forma básica de utilizar dicho método. Como ves, le pasamos un método anónimo. Si el usuario que carga el WebPart no tiene permisos para ingresar, éste código le garantizará el pase. Lo mismo para actualizar datos.

protected void OnOK(object sender, EventArgs args)
{
    SPSecurity.RunWithElevatedPrivileges(() => {

        SPList list = SPContext.Current.Lists["Proveedor"];
        SPListItem item = list.Items[0];
        item["Título"] = "Nuevo título";
        item.Update();
    });
}

Ahora bien, hay que aclarar un asunto. SPSecurity.RunWithElevatedPrivileges no es que de un paso libre así tal cual. Lo que hace en realidad es darle los privilegios del usuario que corre el proceso. Así, si corremos el código anterior dentro de un WebPart, éste se ejecutará bajo los permisos que tenga la cuenta asociada al Application Pool (y usualmente será el administrador de la granja). Pero por otro lado, si corriésemos un código similar en, digamos, un temporizador de SharePoint o un Workflow, la cuenta que tendrá será la asociada a estos procesos (digamos, al servicio SPTimerv4).

Sin embargo, por lo anterior, hay que tener cuidado ya que no siempre funcionará. Si por ejemplo, ejecutamos desde un temporizador y la cuenta asociada a ésta no tiene permiso en el recurso que queremos consultar (i.e. la lista “Proveedor” en nuestro ejemplo) tendremos un SPException como si no tuviésemos permiso. Lo mismo ocurre si ejecutamos dicho código desde una aplicación (de consola o Windows Forms): el usuario que ejecuta la aplicación será quien deba tener permisos en la lista.

Así que ya sabes, no siempre será garantía su empleo. Sin embargo, para WebParts, páginas de aplicación y controles, siempre será una muy buena opción.

Leyendo columnas de búsqueda (Lookup) en SharePoint


Buen inicio de año a todos, feliz 2012, feliz nuevo b’ak’tun… una disculpa por mi silencio de noviembre y diciembre, pero bueno, ya saben: miles de proyectos, todo urge… yada yada yada. Pero ya vine con un post pequeño pero que es útil en tu programación para SharePoint run-of-the-mill.

Una característica padre que tiene SharePoint es que te permite crear campos de búsqueda en una lista cualquiera. Estos campos hacen referencia a campos dentro de otra lista o biblioteca, de tal suerte que permiten crear una relación padre/hijo.

Veamos un ejemplo jocoso. Supongamos que estamos creando un SharePoint para el colegio inglés Hogwarts. Pensemos que estamos armando un catálogo que contiene información sobre los cursos que daría el famoso auror Alastor Moody. Cada curso tiene asociados diversos temas, y cada tema tiene asociadas las preguntas del examen que el profesor practicará a los estudiantes.

image

Creamos nuestra lista de preguntas: una lista personalizada sencilla. Por ejemplo:

image

Posteriormente, creamos nuestra lista de temas. Pero cada tema debe tener asociada una o más preguntas, por lo que podemos sacar provecho el campo búsqueda: añadimos uno, le decimos que busque en la lista “Preguntas” y que permita valores múltiples. La siguiente imagen muestra cómo luce la lista cuando estamos creando un nuevo tema, y cómo se ve la vista de tabla.

image

image

Y algo muy similar para los cursos: creamos un campo de búsqueda y lo asociamos a los temas.

image

Ahora bien, como administradores eso está padre, y todo, pero nada nuevo bajo el sol. Lo interesante ocurre cuando tenemos que crear algún control en el cual tengamos que leer dicha información.

Afortunadamente SharePoint nos provee con una clase que nos hace la vida más fácil: SPFieldLookupValueCollection. Las columnas de búsqueda con opción múltiple (como la de Temas en la imagen anterior) pueden convertirse en esta clase, y ahí podemos iterar para obtener los objetos SPFieldLookupValue, los cuales cuentan con dos propiedades importantes: LookupId y LookupValue. De esta forma podemos buscar relaciones fácilmente.

SPWeb web = SPContext.Current.Web;

SPList cursos = web.List["Cursos"];
SPList temas = web.List["Temas"];

foreach (SPListItem curso in cursos.Items)
{
    Console.WriteLine("Curso: {0}", curso.DisplayName);

    var temasEnCursos = curso["Temas"] as SPFieldLookupValueCollection;
    foreach (var temaLookup in temasEnCursos)
    {
        var tema = temas.Items.GetItemById(temaLookup.LookupId);
        Console.WriteLine("Tema: {0}", tema.DisplayName);
    }
    Console.WriteLine("=====\n");
}

Dado que SPFieldLookupValueCollection hereda de List<SPFieldLookupValue>, tenemos disponible toda la funcionalidad de List<T>. Por ejemplo, podemos usar el método Find para obtener todos los elementos que comienzan con A:

SPWeb web = SPContext.Current.Web;

SPList cursos = web.List["Cursos"];
SPList temas = web.List["Temas"];

foreach (SPListItem curso in cursos.Items)
{
    Console.WriteLine("Curso: {0}", curso.DisplayName);

    var temasEnCursos = curso["Temas"] as SPFieldLookupValueCollection;
    var cursosFiltrados = temasEnCursos.Find(x => x.StartsWith("A"));

    foreach (var temaLookup in cursosFiltrados)
    {
        Console.WriteLine("Tema: {0}", temaLookup.LookupValue);
    }
}

Por otra parte, SPFieldLookupValueCollection tiene el método ToString sobrecargado. Al invocarlo, nos regresa una cadena de texto con el formato: ID;#Texto;ID;#Texto. Por ejemplo:

var temasEnCursos = curso["Temas"] as SPFieldLookupValueCollection;
Console.WriteLine(temasEnCursos.ToString());

Nos imprime algo como:

2;#Cruciatus;#3;#Imperio;#1;#Avada Kedavra

 

Si quisiéramos crear un nuevo objeto, podemos usar el constructor que toma un parámetro: una cadena con el formato anterior. Por ejemplo:

var texto = "2;#Cruciatus;#3;#Imperio;#1;#Avada Kedavra";
var nuevoTema = new SPFieldLookupValueCollection(texto);
curso["Temas"] = nuevoTema;
curso.Update();

Como podemos ver, es muy sencillo utilizar este tipo de campos en nuestros desarrollos para SharePoint. Y esto gana en la vida, porque usar este tipo de columnas nos permite establecer relaciones maestro-hijo de forma muy sencilla. La alternativa sería usar una tercera lista que enlace ambas entidades. Pero eso es muy de base de datos, y no tanto de SharePoint.

Tschüss!

Leer los valores de un campo de opción múltiple


SharePoint cuenta con los campos de opción múltiple, los cuales nos permiten seleccionar un valor a partir de una lista de valores predeterminados.

Cuando hacemos algún desarrollo, como –digamos- un WebPart, quizás querramos mostrar las opciones que presenta uno de estos campos. Es algo sencillo, pero pensé que sería bueno publicarlo de todas formas. Un “quickie”.

El campo de opción múltiple es representado por la clase SPFieldChoice. Y esta clase tiene una propiedad, Choices, la cual es una colección de cadena de texto. Soooo, easy peasy, sólo tenemos que conseguir una referencia a dicha clase.

SPWeb web = SPContext.Web;
SPList list = web.Lists["Mi Lista"];
SPFieldChoice field = list.Fields["Mi Campo"] as SPFieldChoice;

DropDownList list = new DropDownList();
list.DataSource = field.Choices;
list.DataBind();

El ejemplo anterior muestra cómo obtener dicha referencia. Si el campo Mi Campo es de opción múltiple, la conversión será un éxito. Luego, simplemente hacemos un enlazado de datos contra una lista desplegable de ASP.NET.

Easy peasy.

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.

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…