Uno de los retos que se nos ha planteado en el CIIN es como visualizar el directorio de empleados de una organización (que en este caso se encuentra en una estructura de directorio activo, DA) en un team site de MOSS utilizando las capacidades que este nos aporta. El primer punto a tener en cuenta es que MOSS a través de los Shared Services Providers (SSP), y en concreto el SSP referente a User Profiles, nos permite cargar la información de todos los usuarios de una organización de manera manual o automática definiendo un origen de importación que puede ser un DA, un recurso de DA, un directorio LDAP o bien un Business Data Catalog (BDC).

(Como ocurría con la configuración de la búsqueda, la importación automática de perfiles se puede programar con una cierta periodicidad. Mi compañero Pablo me ha prometido que contará en detalle cómo se realiza la importación de profiles de un DA en su próximo post, ya veréis que es algo realmente interesante y con algún truquito que otro.).
Una vez importados los perfiles de usuario, el listado de los mismos es visible desde la administración central de MOSS (en el SSP Perfiles de usuarios y propiedades-> Ver perfiles de usuario).

Cada user profile tiene un serie de propiedades, muchas de las cuáles coinciden con las que presenta un DA para los distintos usuarios que almacena. Pero, ¿Cómo podemos ver este listado de usuarios fuera de la administración central? ¿Se podría además ver para cada usuario la ficha de detalle del mismo? La pregunta difícil es la primera, puesto que una vez sepamos como listar los user profiles fuera de la administración central, visualizar el detalle de un profile en concreto no tiene porque ser complejo.

Alternativas para visualizar los user profiles fuera de la administración central
Para visualizar todos los user profiles creados, hay varias alternativas:
· A través de crear un sitio de búsqueda específico pare personas, de manera que una vez realizada la correspondiente indexación podremos buscar usuarios concretos en el listado importado.
· Atacando el servicio web UserProfile.asmx de nuestra máquina MOSS y mostrando el listado de usuarios en una web part o en una lista de MOSS.
· Atacando el modelo de objetos de MOSS y mostrando el listado de usuarios en una web part o en una lista.
En nuestro caso, hemos optado por la tercera opción y como no queremos duplicar la información de los profiles en una lista, construiremos una web part que permita mostrar el listado de profiles y que al seleccionar un profile concreto permita ver el detalle del mismo de forma similar al estilo preview panel de una lista de WSS 3.0 / MOSS:
Resolviendo el problema
Tras una serie de pruebas de concepto sobre como leer los user profiles y ver que objetos hay que manejar para cumplir con los requerimientos comentados, pasamos a crear la web part correspondiente. Para agilizar el proceso de creación, y despliegue posterior de la web part utilizamos las extensiones de WSS 3.0 para Visual Studio (aprovecho para comentar que es una pena que no estén oficialmente soportadas por Microsoft). Ya comentamos en un post previo, que al utilizar estas extensiones se añaden las extensiones e infrasestructura necesaria para desplegar la web part sin más que hacer el deploy de la solución.

Para poder trabajar con los user profiles, y construir una web part que dinámicamente muestra un listado de los mismos y permite visualizar el detalle del profile seleccionado necesitamos añadir e importa al proyecto los siguientes espacios de nombres:
|
using System.Web.UI.WebControls;
using System.Data;
using Microsoft.Office.Server;
using Microsoft.Office.Server.UserProfiles;
using System.Web;
using System.Drawing; |
En negrita he marcado los espacios de nombres necesarios para atacar los user profiles. El resto de espacios de nombres se utilizan para la visualización de los profiles. Antes de seguir, os voy a mostrar (esto es lo que se dice empezar por el final) que pinta tiene la web part que finalmente se despliega para enlazarlo con los pasos de creación:

Los pasos para llegar a construir la web part anterior son los siguientes:
Sobreescribir el método CreateChildControls()
Lo primero que haremos en el código de la web part es sobreescribir el método CreateChildControls(), que nos permitirá crear todos los controles que va a incluir nuestra web part (y que se construyen de manera dinámica en base a la información de los user profiles). Antes de crear los controles hijos necesarios, es en este método donde obtendremos el listado de profiles que tenemos importados / creados en el SSP de MOSS. Para el caso expuesto, el código necesario para la recuperación de los user profiles es el siguiente:
|
SPSite spsSitio = SPControl.GetContextSite(this.Context);
ServerContext scContexto = ServerContext.GetContext(spsSitio);
UserProfileManager upManager = new UserProfileManager(scContexto);
//Construimos la tabla con las columnas que nos interesan
dtData.Columns.Add(ID_PROFILE);
dtData.Columns.Add(CAMPO1_PROFILE);
dtData.Columns.Add(CAMPO2_PROFILE);
dtData.Columns.Add(CAMPO3_PROFILE);
dtData.Columns.Add(CAMPO4_PROFILE);
dtData.Columns.Add(CAMPO5_PROFILE);
dtData.Columns.Add(CAMPO6_PROFILE);
dtData.Columns.Add(CAMPO7_PROFILE);
dtData.Columns.Add(CAMPO8_PROFILE);
dtData.Columns.Add(CAMPO9_PROFILE);
//Llenamos la tabla
foreach (UserProfile cUser in upManager)
{
UserProfileValueCollection upValue;
dtRow = dtData.NewRow();
foreach (Property pPropiedad in cUser.ProfileManager.Properties)
{
//ID
if (pPropiedad.Name == ID_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo1
if (pPropiedad.Name == CAMPO1_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo2
if (pPropiedad.Name == CAMPO2_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo3
if (pPropiedad.Name == CAMPO3_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo4
if (pPropiedad.Name == CAMPO4_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo5
if (pPropiedad.Name == CAMPO5_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo6
if (pPropiedad.Name == CAMPO6_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo7
if (pPropiedad.Name == CAMPO7_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//campo8
if (pPropiedad.Name == CAMPO8_PROFILE)
{
upValue = cUser[pPropiedad.Name];
dtRow[upValue.Property.Name] = upValue.Value;
}
//Campo9
if (dtRow[CAMPO3_PROFILE].ToString() != "" || dtRow[CAMPO4_PROFILE].ToString() != "")
{
if (dtRow[CAMPO4_PROFILE].ToString() == "")
{
dtRow[CAMPO9_PROFILE] = dtRow[CAMPO3_PROFILE];
}
else
{
dtRow[CAMPO9_PROFILE] = dtRow[CAMPO3_PROFILE] + "- " + dtRow[CAMPO4_PROFILE];
}
}
}
dtData.Rows.Add(dtRow);
} |
Como vemos, una vez obtenido el sitio (a través de spsSitio que es un objeto de tipo SPSite y utilizando el método GetContext para obtener el sitio actual) en el que se va a ejecutar la web part y el contexto del servidor (objeto scContexto, de tipo ServerContext y utilizando el método GetContext para obtener el contexto actual), los perfiles de usuario almacenados en nuestro servidor de MOSS se obtienen a partir de un objeto de tipo UserProfileManager (clase utilizada para acceder a los datos de perfiles de usuario) que a partir del contexto del servidor nos devuelve una colección de objetos de tipo UserProfile. Una vez que tenemos la colección de user profiles, lo siguiente que hacemos es recorrerla y nos quedamos con las propiedades que nos interesan de cada user profile. Para ello:
· En el primer nivel de anidamiento, vamos recorriendo cada objeto de tipo UserProfile almacenado en la colección de user profiles.
|
foreach (UserProfile cUser in upManager) |
· En el segundo nivel de anidamiento, seleccionamos las propiedades que nos interesen de cada user profile.
|
foreach (UserProfile cUser in upManager)
{
UserProfileValueCollection upValue;
dtRow = dtData.NewRow();
foreach (Property pPropiedad in cUser.ProfileManager.Properties)
{ |
En este segundo nivel, para realizar el recorrido por la colección de propiedades (cUser.ProfileManager.Properties) de cada user profile estamos utilizando un objeto de UserProfiles.Property, que es la clase que representa la definición para una propiedad de un user profile. Finalmente, cada propiedad que necesitemos la estamos almacenando en el objeto upValue que es de tipo UserProfileValueCollection, que nos permite construir una colección con las propiedades que nos interese visualizar para cada user profile. A continuación, cada propiedad la asignamos a un objeto de tipo DataRow que luego añadiremos al objeto DataTable que aparece al principio del listado. Este último objeto es el que finalmente utilizaremos para construir la web part, tanto para visualizar la lista de user profiles como para luego mostrar el detalle del user profile seleccionado.
Una vez que tenemos los user proflies, ya podemos empezar a construir los controles hijos que constituirán nuestra web part.
|
////Tabla Principal
tblContenedor = new Table();
//Añadimos una fila
tblRow = new TableRow();
//Lista Empleados
tblCell = new TableCell();
tblListaEmpleados = new Table();
tblCell.Controls.Add(this.tblListaEmpleados);
tblRow.Cells.Add(tblCell);
//Detalle Empleado
tblCell = new TableCell();
this.tblDetalleEmpleado = new Table();
this.tblDetalleEmpleado.CssClass = ESTILOS_FICHA_EMPLEADO;
tblCell.Controls.Add(this.tblDetalleEmpleado);
tblRow.Cells.Add(tblCell);
//Lo añadimos todo a la tabla principal...
this.tblContenedor.Rows.Add(tblRow);
//Radiobutton
rdbListaEmpleados = new RadioButtonList();
rdbListaEmpleados.EnableViewState = true;
rdbListaEmpleados.AutoPostBack = true;
this.rdbListaEmpleados.SelectedIndex = 0;
this.rdbListaEmpleados.CssClass = ESTILOS_LISTA_EMPLEADOS;
this.rdbListaEmpleados.DataSource = dtData;
this.rdbListaEmpleados.DataValueField = ID_PROFILE;
this.rdbListaEmpleados.DataTextField = CAMPO9_PROFILE;
this.rdbListaEmpleados.DataBind();
//Evento para el radiobutton
this.rdbListaEmpleados.SelectedIndexChanged +=
new System.EventHandler(this.rdbListaEmpleados_SelectedIndexChanged);
//Añadimos el radiobutton list a la tabla
tblRow = new TableRow();
tblCell = new TableCell();
tblCell.Controls.Add(this.rdbListaEmpleados);
tblRow.Cells.Add(tblCell);
tblContenedor.EnableViewState = true;
//Añadiendo las celdas a la tabla
this.tblListaEmpleados.Rows.Add(tblRow);
if (dtData.Rows.Count > 0)
{
this.Controls.Add(this.tblContenedor);
} |
Lo más interesante del código anterior (la presentación de la información utilizando una tabla principal que contiene otras dos tablas que contendrán el listado de usuarios por un lado, y el detalle del empleado seleccionado es lo de menos) es lo siguiente:
· La fuente de datos utilizada para rdbListaEmpleados es justo el objeto DataTable construido a partir de los datos de los perfiles de usuario. En concreto, estamos utilizando los campos ID_PROFILE que contiene el ID único para cada user profile (propiedad UserProfile_GUID de cada user profile) y CAMPO9_PROFILE (que contiene la concatenación de los campos PreferredName y WorkPhone), el primero lo vinculamos a la propiedad DataValueField de rdbListaEmpleados y el segundo a la propiedad DataTextField.
|
this.rdbListaEmpleados.DataSource = dtData;
this.rdbListaEmpleados.DataValueField = ID_PROFILE;
this.rdbListaEmpleados.DataTextField = CAMPO9_PROFILE;
this.rdbListaEmpleados.DataBind();
|
· La forma en que se añade el manejador para el evento SelectedIndexChanged del control rdbListaEmpleados que es de tipo RadioButtonList():
|
this.rdbListaEmpleados.SelectedIndexChanged +=
new System.EventHandler(this.rdbListaEmpleados_SelectedIndexChanged); |
Como vemos, el manejador se añade a rdbListaEmpleados utilizando la forma ya conocida de añadir manejadores en C# para controles web o de Windows forms. Algo importante es que tenemos que configurar la propiedad AutoPostBack a True para que se pueda disparar el evento SelectedIndexChanged de rdbListaEmpleados y se ejecute el manejador.
· Como se añaden los controles hijos a la web part, y que como ya habéis visto y conoceréis, implica hacer una llamada del método Add() de la colección de controles de la web part (en este caso, lo mismo se utiliza cuando creamos user controls, páginas ASP.NET, formularios Windows Forms,…). Como vemos, para añadir todos los controles hijos nos basta con añadir tblContenedor que es la tabla maestra que contiene las tablas auxiliares utilizadas que a su vez contienen los controles necesarios para visualizar los datos de un user profile.
|
this.Controls.Add(this.tblContenedor); |
Sobreescribir el método Render()
Una vez que ya tenemos creados los controles hijos y añadidos a la web part, necesitamos que estos se visualicen. Se consigue sobreescribiendo el método Render() de la web part que nos permite justamente eso: renderizar los controles hijos de la web part de manera que al final los visualicemos de manera transparente como controles web típicos (y que nos devuelve el servidor web).
|
protected override void Render(HtmlTextWriter writer)
{
if (dtData.Rows.Count > 0)
{
if (this.rdbListaEmpleados.SelectedIndex == 0)
{
this.tblDetalleEmpleado.Rows.Clear();
this.rdbListaEmpleados_SelectedIndexChanged(this.rdbListaEmpleados, null);
}
this.tblContenedor.RenderControl(writer);
}
else
{
if (this.lblErrorProducido.Text == "")
{
writer.Write("<b> El directorio de empleados está vacio o no cargado </>");
}
else
{
this.lblErrorProducido.RenderControl(writer);
}
}
} |
Como vemos, el renderizado efectivo de los controles de la web part se realiza en la línea this.tblContenedor.RenderControl(writer). Es decir, el método RenderControl de nuestro control principal es el que se encarga de hacer dicho renderizado a través de un objeto de tipo HtmlTextWriter.
Manejador del objeto RadioButtonList()
Finalmente, sólo nos queda incluir el código del manejador para el evento SeletedIndexChangeg() del objeto rdbListaEmpleados. En este código es dónde se construirá, para cada user profile seleccionado, la ficha de detalle de un usuario. Una muestra de cómo se construye dicha ficha de detalle es el siguiente listado:
|
foreach (DataRow dr in dtData.Rows)
{
//Verificamos la opción elegida por el usuario
if (this.rdbListaEmpleados.SelectedItem.Value == dr[ID_PROFILE].ToString())
{
//Nombre
tblRow = new TableRow();
tblCell = new TableCell();
tblCell.Text = ETIQUETA1_PROFILE;
tblRow.Cells.Add(tblCell);
tblCell = new TableCell();
tblCell.Text = dr[CAMPO1_PROFILE].ToString();
//tblCell.BackColor = System.Drawing.Color.LightSkyBlue;
tblRow.Cells.Add(tblCell);
this.tblDetalleEmpleado.Rows.Add(tblRow);
…
//Fotografía
if (dr[CAMPO8_PROFILE].ToString() != "")
{
tblRow = new TableRow();
tblCell = new TableCell();
tblRow.Cells.Add(tblCell);
tblCell = new TableCell();
imgFotografia = new System.Web.UI.WebControls.Image();
imgFotografia.ImageUrl = dr[CAMPO8_PROFILE].ToString();
tblCell.Controls.Add(imgFotografia);
tblCell.Controls.Add(imgFotografia);
tblRow.Cells.Add(tblCell);
}
//Lo añadimos todo a la tabla!
this.tblDetalleEmpleado.Rows.Add(tblRow);
}
}
} |
Como vemos, construir la ficha de detalle del usuario seleccionado es una tarea sencilla. No tenemos más que comprobar cuál es el elemento seleccionado por el usuario y a partir de eta comprobación construir la ficha de detalle del empleado que estará contenida en la tabla tblDetalleempleado.
Despliegue y uso de la web part
Una vez que hemos codificado las distintas partes de la web part y tras comprobar que todo compila bien, procedemos a desplegar la web part. En este caso, y gracias a las extensiones de WSS 3.0 para Visual Studio, el despliegue es realmente sencillo puesto que se realiza de manera automática a través de la opción Implementar Solución que tenemos disponible en Generar -> Implementar Solución. El detalle de lo que se hace en este proceso de implementación ya lo comentamos en un post previo, por lo que os remito a ese post.
Ya sólo nos falta asegurar que la web part está desplegada en todos los sitios que nos interesa a través del Administrador de Soluciones de la administración central de MOSS:

Tras comprobar que la feature correspondiente a la web part está activada, ya podemos utilizar la web part en los sitios de MOSS dónde la hayamos implementado (ya sabéis, Configuración del sitio -> Características del sitio -> Características de la colección de sitios).

Sin más, esto es lo que os quería contar sobre los user profiles de MOSS. La verdad es que el tema es chulo, aunque al principio nos dio algún que otro quebradero de cabeza tanto listar los user profiles como visualizarlos del modo requerido. Por supuesto, esta web part es muy sencilla y se puede mejorar mucho ( a ver si alguien se anima). Os dejo el código de la web part aquí.
Comparte este post: