Archivo

Posts Tagged ‘ASP.NET’

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.

    Agrupar elementos en un SPGridView


    Hace algunas lunas escribí sobre el componente SPGridView, de SharePoint, y vimos cómo sacarle provecho para mostrar datos e información. Un pequeño comentario entonces, que en aquella ocasión no mencioné: el control nos permite agrupar su propio contenido.

    Tomemos como ejemplo el WebPart con el SPGridView que hicimos en la entrada antes mencionada. Digamos que quisiéramos agrupar nuestro control por el género al que cada Asgard pertenece, y poder ver cuáles elementos pertenecen a los Æsir y cuáles son Ásynjur.

    Para lograrlo, tenemos que seguir los siguientes pasos.

    0.- Es indispensable que desactives el ViewState

    1.- Establecemos la propiedad AllowGrouping del SPGridView a true.

    2.- Establecemos la propiedad GroupField del SPGridView con el nombre del campo que queremos agrupar (obvio necesita haber un DataSource y así).

    3.- Opcionalmente establecemos la propiedad GroupFieldDisplayName para darle un nombre amigable al campo que queremos agrupar.

    4.- Opcionalmente establecemos la propiedad AllowGroupCollapse. Si la establecemos a true, permitirá expander y contraer las filas dentro de cada agrupación (como si fuera un árbol, un TreeView).

    5.- Opcionalmente podemos establecer la propiedad GroupDescriptionField al nombre de un campo que actúe como descripción.

    6.- También de manera opcional podemos establecer la propiedad GroupImageUrlField si queremos mostrar alguna imagen que describa al grupo.

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
    
        SPList list = SPContext.Current.Web.Lists["Asgard Pantheon"];
        DataTable table = list.Items.GetDataTable();
        table.DefaultView.Sort = "Gender";
    
        _titleLiteral = new Literal();
        _titleLiteral.Text = string.Format("<br/><h2>{0}</h2>", list.Title);
        Controls.Add(_titleLiteral);
    
        _gridView = new SPGridView();
        _gridView.ID = "_gridView";
        _gridView.AutoGenerateColumns = false;
        _gridView.Width = new Unit(100, UnitType.Percentage);
        _gridView.DataSource = table.DefaultView;
        _gridView.AllowGrouping = true;
    
        // le quitamos el view state
        _gridView.EnableViewState = false;
        // agrupamos por el campo "Gender" del DataSource
        _gridView.GroupField = "Gender";
        // un nombre bonito para el campo
        _gridView.GroupFieldDisplayName = "Gender";
        // permitimos que los grupos generados colapsen
        _gridView.AllowGroupCollapse = true;
        // adicionalmente, podemos añadir algún campo descriptivo o una imagen
        // _gridView.GroupDescriptionField = "Mi Campo Descriptivo";
        // _gridView.GroupImageUrlField = "url a imagen";
    
        _gridView.Columns.Add(new CommandField {
            ButtonType = ButtonType.Image,
            ShowSelectButton = true,
            SelectImageUrl = "/_layouts/images/arrowright_light.gif"
        });
        _gridView.Columns.Add(new SPBoundField { 
            DataField = "Title", HeaderText = "Name" });
        _gridView.Columns.Add(new SPBoundField { 
            DataField = "Influence", HeaderText = "Influence" });
        _gridView.Columns.Add(new SPBoundField { 
            DataField = "Mate", HeaderText = "Mate" });
        _gridView.DataBind();
                
        Controls.Add(_gridView);
    }
    

    Si comparas este código con el del post anterior notarás una sutil diferencia respecto a la fuente de datos. En el escrito anterior simplemente obtenía el DataTable de la lista SharePoint, mientras que en esta ocasión obtengo el DataView por defecto y enlazo con éste. Esto es así para poder establecer la propiedad Sort del DataView, de tal suerte que los registros vengan ordenados, en este caso, por el género. Esto, porque el SPGridView ordena conforme va encontrando los registros. Por ejemplo, si tuvieras un conjunto de registros en donde el campo de ordenación viniera con los valores A, A, B, A, B, B, A, B, te los agruparía como A (2), B (1), A (1), B (2), A (1) y B (1) en lugar de lo esperado: A (4), B (4). Por eso, mejor que venga ordenado desde la fuente de datos y se acabó el problema.

    Así es como luciría nuestro WebPart, tras esta modificación.

    image

    Usa tipos de contenido MIME para exportar una tabla a Excel


    Este es el escenario: tienes un reporte sencillo, y se te ocurrió hacerlo con ASP.NET y alguno de los componentes que tiene para generar, digamos, una tabla HTML. Todo iba bien, el cliente feliz, tu jefe feliz, todos felices. Y de pronto, a alguien se le ocurre: hey, ¿por qué no exportarlo a Excel? Y aquí es donde te da el patatús.

    Vamos por partes. Primero creamos nuestro sitio web. El Visual Studio 2010 genera sitios iniciales bonitos, así que usemos ese. Añadimos un control Panel, y ahí dentro vendrá el HTML del reporte. Finalmente, añadamos dos botones: uno para generar el reporte y uno para exportar. El codiguín luciría algo así:

    <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
        <h2>Reporte de productos</h2>
        <p>A continuaci&oacute;n generamos un reporte sencillo y toda la cosa. </p>
        <p>
            <asp:Panel ID="_panel" runat="server">
            </asp:Panel>
            <br /><hr /><br />
            <asp:Button ID="_generateButton" runat="server" Text="Generar" 
                OnClick="OnGenerate" />&nbsp;
            <asp:Button ID="_exportButton" runat="server" Text="Exportar" 
                OnClick="OnExport" />
        </p>
    </asp:Content>
    

    Luego, es cuestión de añadir un método, el cual nos genere el reporte. En la vida real sería algo mucho más complicado, pero este es un blog, no la vida real. Entonces hagamos algo más sencillo.

    private string GetReport()
    {
        Random price = new Random();
    
        StringBuilder html = new StringBuilder()
            .Append("<table border=\"0\" width=\"100%\">")
            .Append("<tr>")
            .Append("<th>ID</th>")
            .Append("<th>Producto</th>")
            .Append("<th>Precio</th>")
            .Append("<th>Cantidad</th>");
        for (int i = 0; i < 20; i++)
        {
            string color = i % 2 == 0 ? "black" : "white";
            string bg = i % 2 == 0 ? "white" : "gray";
                
            html.AppendFormat("<tr style=\"color:{0}; background-color:{1};\">", 
                        color, bg)
                .AppendFormat("<td>{0}</td>", i)
                .AppendFormat("<td>Producto {0}</td>", i)
                .AppendFormat("<td>{0:C}</td>", price.Next(1, 10000))
                .AppendFormat("<td>{0}</td>", price.Next(1, 1000))
                .Append("</tr>");
        }
        html.Append("</table>");
    
        return html.ToString();
    }
    

    Y ya por último, en el botón Generar, pues generamos el reporte llamando a la función GetReport y metiéndola dentro de un control Literal.

    protected void OnGenerate(object sender, EventArgs args)
    {
        Literal lit = new Literal();
        lit.Text = GetReport();
        _panel.Controls.Add(lit);
    }
    

    Esto nos daría una imagen similar a la siguiente.

    image

    Bueno, entonces ¿cómo generamos el Excel? Hay varias maneras. La primera, y usualmente la más obvia, sería añadir una referencia a los componentes COM que provee Excel. Una buena medida, sin duda, máxime que el .NET Framework 4, con la introducción de tipos de datos dinámicos ha hecho que trabajar con componentes COM sea mucho más fácil.

    Aunque usualmente el problema que hay con esto es un tema de licencias. Verás, para usar esos componentes el servidor web debe tener instalado Microsoft Office (o al menos, Excel). Y alguans empresas no están dispuestas a ello.

    Una alternativa es, por supuesto, usar algún otro componente de paga como ASPOSE, o alguno OpenSource como NPOI. Pero a veces restricciones en presupuesto, o políticas, hace que esto no sea posible. En fin. Una solución que reune las tres bes: bueno, bonito y barato, consiste en escribir directamente el HTML de la tabla, y luego cambiar el tipo MIME de la misma.

    El asunto es como sigue. MIME significa Multipurpose Internet Mail Extensions. No vamos a entrar en un tema tan largo, pero la idea es que el MIME tiene una propiedad, la cual describe el tipo de contenido que el servidor HTTP está sirviendo: HTML (text/html), JavaScript (text/javascript), etc. Una extensión es la de application/ms-excel, que define un archivo de Excel.

    Cambiando el tipo de contenido a application/ms-excel hace que el navegador pase el contenido al programa que lo interpreta, en este caso, a Microsoft Office Excel. Ahora bien, Excel es lo suficientemente inteligente como para interpretar una tabla HTML e incorporar el contenido, aún con colores y toda la cosa. Incluso en algunas ocasiones he visto que incorpore imágenes.

    Entonces, el asunto es simple. Para lograrlo hay que hacer varias cosas:

    1.- Limpiar el contenido del búfer que el servidor HTTP va a enviar.

    2.- Limpiar el encabezado y el contenido previamente establecido.

    3.- Establecer el tipo de contenido a application/ms-excel.

    4.- Añadir un encabezado que diga que el contenido debe ser tratado como un documento adjunto (y por tanto, descargable).

    5.- Escribir el HTML en cuestión y descargar el búfer.

    6.- Terminar la respuesta HTTP inmediatamente.

    Para hacer estos pasos, usamos el objeto de tipo HttpResponse que toda página ASP.NET expone mediante su propiedad Response.

    string html = GetReport();
    
    Response.Clear(); // paso 1
    Response.ClearHeaders(); // paso 2
    Response.ClearContent(); // paso 2
    Response.ContentType = "application/ms-excel"; // paso 3
    Response.AppendHeader("Content-Disposition", 
        "attachment; filename=Reporte.xls"); // paso 4
    Response.Write(html); // paso 5
    Response.Flush(); // paso 5
    Response.End(); // paso 6
    

    Y lixto, tenemos un hermoso reporte de Excel, en este caso, el mismo que regresa el método GetReport.

    image

    Sólo ten en cuenta que esta técnica sirve para reportes sencillos. Para exportar reportes más complicados vale la pena la licencia de Excel o algún otro componente, o incluso pensar en Reporting Services. Ten esta técnica, sin embargo, como tu as bajo la manga, para cuando se necesite algo bueno, bonito y barato.

    Categorías:.NET Framework, Apunte, C# Etiquetas: , ,

    Ejemplo sobre el uso de metadatos de SharePoint


    Ayer puse un pequeño tip sobre cómo utilizar SPListItem.Properties y SPWeb.AllProperties para guardar información y que ésta no se muestre al usuario, de forma accesible. Pues bien, quisiera mostrarles esto que he hecho a fecha reciente para que vean cómo puede usarse.

    Primero, les comento el requerimiento. Tengo una lista que contiene información sobre productos: código del material, nombre, descripción y una columna que indica los usuarios asignados a dicho producto. Posteriormente, es responsabilidad de cada usuario asignado a dicho producto, capturar diario un valor que refleje la cantidad de producto habido en el almacén.

    Entonces, primero tengo mi lista. Ésta luce así.

    image

    Como pueden ver, es una lista normal y de hecho el sitio no tiene ninguna otra lista. Hasta aquí easy peasy. Ahora, necesitamos crear un Application Page que muestre los productos por fecha y permita capturar una cantidad. Para ello, tomamos un control calendario y un SPGridView, al cual le creamos la columna “Cantidad”. El código ASP del grid luce algo así:

    <SharePoint:SPGridView ID="_grid" runat="server" 
        AutoGenerateColumns="false" DataSourceID="_mainSource">
        <Columns>
            <asp:CommandField ButtonType="Image" ShowEditButton="true" 
                EditImageUrl="/_layouts/images/edit.gif" 
                UpdateImageUrl="/_layouts/images/save.gif" 
                CancelImageUrl="/_layouts/images/delete.gif" />
            <SharePoint:SPBoundField HeaderText="Código de material" DataField="Code" />
            <SharePoint:SPBoundField HeaderText="Nombre del producto" DataField="Name" />
            <SharePoint:SPBoundField HeaderText="Descripción" DataField="Description" />
            <asp:TemplateField HeaderText="Cantidad">
                <ItemTemplate>
                    <asp:Label runat="server" 
                        Text='<%# ((double)Eval("Quantity")).ToString("0.00") %>' />
                </ItemTemplate>
                <EditItemTemplate>
                    <asp:TextBox ID="_quantityText" runat="server" Width="100px" 
                        Text='<%# Bind("Quantity") %>' />
                    <br />
                    <asp:RangeValidator runat="server" EnableClientScript="true" Type="Double"
                        MinimumValue="0" MaximumValue="9999999999" 
                        ControlToValidate="_quantityText" 
                        Text="Sólo se admiten números positivos" />
                </EditItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Fecha">
                <ItemTemplate>
                    <asp:Label runat="server" 
                        Text='<%# ((DateTime)Eval("Date")).ToShortDateString() %>' />
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="">
                <ItemTemplate>
                    <asp:HiddenField ID="_idHiddenField" runat="server" 
                        Value='<%# Bind("ID") %>' />
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
        <EmptyDataTemplate>
            <h3>No hay información a mostrar.</h3>
        </EmptyDataTemplate>    
    </SharePoint:SPGridView>
    

    Utilizo un ObjectDataSource como proveedor de datos, el cual manda llamar un método que retorna un DataTable, filtrado por la fecha seleccionada en el calendario. Éste método realiza lo siguiente:

    1.- Obtiene una referencia a la lista (SPList) de los productos e itera sobre cada uno de ellos.

    2.- Para cada producto (SPListItem) verifica que el usuario actualmente autenticado (SPContext.Web.CurrentUser) esté dentro de los elementos de la columna “Usuarios”.

    3.- Crea una llave de texto con base en los siguientes datos: cuenta del usuario, guión bajo, la fecha del día seleccionado en el calendario.

    private string GetDatedProductKey(string user, DateTime date)
    {
        if (user == null)
            throw new ArgumentNullException("user");
    
        return string.Format("{0}_{1}{2}{3}", user, date.Year, date.Month, date.Day);
    }
    

    4.- Revisamos en el Hashtable “Properties” del producto (SPListItem) para ver si la clave existe. Si sí, obtenemos el valor y hacemos la conversión a un double. Si no, simplemente dejamos un 0.0.

    string key = GetDatedProductKey(user, date);
    if (product.Properties.Contains(key))
    {
        string value = product.Properties[key] as string;
        double quantity;
        double.TryParse(value, out quantity);
        row["Quantity"] = quantity;
    }
    else
    {
        bool allowUpdates = Web.AllowUnsafeUpdates;
        Web.AllowUnsafeUpdates = true;
        product.Properties.Add(key, "0.0");
        product.Update();
        Web.AllowUnsafeUpdates = allowUpdates;
    
        row["Quantity"] = 0.0;
    }
    

    5.- Añadimos una fila al DataTable, llenamos los datos y al final, retornamos la tabla. El SPGridView enlazará sin problemas.

    Hacer lo anterior nos garantiza que la cantidad se guarde por fecha y usuarios diferentes. Es decir, para el 9 de mayo de 2011, asgard\loki muestra una cantidad de 10 unidades mientras que asgard\heimdall muestra 20. De esta manera almacenamos la información en el mismo elemento SPListItem, pero sin comprometer la información.

    image

    El proceso de hacer la actualización de la información en el SPGridView es muy similar a cualquier otro GridView. Baste decir que dicho control está enlazado con un método Update en el ObjectDataSource. Este método termina por modificar el valor de forma relatívamente fácil, usando la misma llave generada por la fecha y la cuenta del usuario.

    public void UpdateDatedProducts(string user, DateTime date, int id, double quantity)
    {
        SPListItem product = List.Items.GetItemById(id);
        string key = GetDatedProductKey(user, date);
    
        bool allowUpdates = Web.AllowUnsafeUpdates;
        Web.AllowUnsafeUpdates = true;
                
        if (!product.Properties.ContainsKey(key))
            product.Properties.Add(key, quantity.ToString("0.00"));
        else
            product.Properties[key] = quantity.ToString("0.00");
        product.Update();
    
        Web.AllowUnsafeUpdates = allowUpdates;
    }
    

    Y listo, easy peasy.

    Cómo identificar si el usuario autenticado pertenece a un grupo de SharePoint


    ¿Recuerdan que el viernes les comentaba sobre cómo identificar si un usuario de SharePoint pertenece a un grupo cualquiera? A grandes rasgos, la única forma de hacerlo era mediante un proceso manual de obtener el usuario y buscar en sus grupos, o bien obtener el grupo y buscar sus usuarios: si no encontrábamos el elemento, no existía.

    Pues bien, si ese usuario es el usuario actualmente autenticado, hay un camino más fácil: primero, obtenemos la referencia al SPGroup que queramos; y posteriormente, invocamos la propiedad SPGroup.ContainsCurrentUser. Esta propiedad nos devuelve true si el usuario autenticado pertenece a dicho grupo.

    El codiguillo sería algo así:

    private void MatchByGroup(HtmlTextWriter writer, string groupName, string userLogin)
    {
        SPGroup spgroup = SPContext.Current.Web.SiteGroups[groupName];
        bool match = spgroup.ContainsCurrentUser;
    
        writer.Write("El grupo '{0}' contiene al usuario '{1}': {2}<br/>",
            spgroup.Name, userLogin, match);
    }
    

    Mucho más sencillo, ¿no?

    Usando el control de búsqueda de usuarios


    Cuando en una lista o biblioteca de documentos agregas un campo de tipo “Persona o Grupo”, al momento de editar el elemento verás un control como el que se muestra en la siguiente imagen, al lado de la fila “Cuenta”.

    image

    Este control cuenta con dos cosas útiles. La primera, cuando escribo algún nombre y hago clic en el primer iconito (el que muestra la palomita), el control valida si lo que escribí pertenece a algún usuario dentro de SharePoint / Directorio Activo. De ser así, lo muestra como tal, y en caso contrario lo marca como inválido.

    image

    La segunda funcionalidad es que, si no conocemos el nombre del usuario, podemos hacer clic en el segundo iconito (el del libro) y entonces un buscador aparecerá para ayudarnos a elegir alguno.

    image

    Sobra decir que este control es súmamente útil para buscar gente. Más aún, cuando hacemos algún componente para SharePoint, la gente esperará ver este control cuando tenga que ingresar gente, en lugar de un vil TextBox.

    Afortunadamente, el control que hace todas estas monerías está disponible para nosotros en la clase PeopleEditor, ubicado en el espacio de nombres Microsoft.SharePoint.WebControls.

    Utilizar esta clase es sencillo. Algunas propiedades son conocidas por nosotros, como AutoPostBack y Width. Otras son fáciles de adivinar: AllowEmpty nos permite determinar si el control debe quedar vacío o no (en caso de que sea false, si no ponemos algo el validador que lleva consigo nos lo hará saber); mientras que MultiSelect nos permite determinar si el control permitirá un solo elemento, o varios (el alto del control varía dependiendo de esto).

    He aquí el un código de ejemplo que muestra lo anterior. Es un fragmento de un WebPart creado a guisa de ejemplo, perteneciente al método CreateChildControls.

    _singleEditor = new PeopleEditor();
    _singleEditor.ID = "_singleEditor";
    _singleEditor.AutoPostBack = true;
    _singleEditor.Width = new Unit(300, UnitType.Pixel);
    _singleEditor.AllowEmpty = false;
    _singleEditor.MultiSelect = false;
    
    _multipleEditor = new PeopleEditor();
    _multipleEditor.ID = "_multipleEditor";
    _multipleEditor.AutoPostBack = true;
    _multipleEditor.Width = new Unit(300, UnitType.Pixel);
    _multipleEditor.AllowEmpty = true;
    _multipleEditor.MultiSelect = true;
    

    Todo más o menos sencillo, ¿no? Bueno, supongamos ahora que queremos pre-cargar nuestros controles con alguna información. Para ello, necesitamos el método UpdateEntities del control. Este método toma como parámetro un ArrayList (lo sé, lo sé, pero recordemos que WSS salió antes que .NET Frameowork 2.0), el cual debe contener cada uno de los usuarios a mostrar en el control. Cada entrada al arreglo debe ser de tipo PickerEntity, con su propiedad Key establecida al nombre del usuario o el de su cuenta. Por ejemplo:

    PickerEntity entity = new PickerEntity();
    entity.Key = "ASGARD\\freyja";
    ArrayList entities = new ArrayList();
    entities.Add(entity);
    _singleLoadedEditor.UpdateEntities(entities);
    

    En un control cuya propiedad MultiSelect sea true, podemos agregar varios elementos al arreglo. Por ejemplo, el siguiente código obtiene un grupo de SharePoint e itera sobre cada usuario, añadiéndolo al arreglo y por ende, al control PeopleEditor en cuestión.

    entities = new ArrayList();
    foreach (SPUser user in SPContext.Current.Web.SiteGroups["Asgard Æsir"].Users)
    {
        entity = new PickerEntity();
        entity.Key = user.Name;
        entities.Add(entity);
    }
    _multipleLoadedEditor.UpdateEntities(entities);
    

    ¿A poco no gana en la vida? Así es como luce mi WebPart.

    image

    Chidito, ¿no? ¡Y profesional! Este es el código completo del WebPart mostrado en la imagen anterior.

    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebPartPages;
    using Microsoft.SharePoint.WebControls;
    using System.Web.UI.WebControls;
    using System.Collections;
    
    namespace Fermasmas.Wordpress.Com.WebParts
    {
        public class PeopleEditorExample : WebPart
        {
            private PeopleEditor _singleEditor;
            private PeopleEditor _multipleEditor;
            private PeopleEditor _singleLoadedEditor;
            private PeopleEditor _multipleLoadedEditor;
    
            protected override void CreateChildControls()
            {
                base.CreateChildControls();
    
                _singleEditor = new PeopleEditor();
                _singleEditor.ID = "_singleEditor";
                _singleEditor.AutoPostBack = true;
                _singleEditor.Width = new Unit(300, UnitType.Pixel);
                _singleEditor.AllowEmpty = false;
                _singleEditor.MultiSelect = false;
    
                _multipleEditor = new PeopleEditor();
                _multipleEditor.ID = "_multipleEditor";
                _multipleEditor.AutoPostBack = true;
                _multipleEditor.Width = new Unit(300, UnitType.Pixel);
                _multipleEditor.AllowEmpty = true;
                _multipleEditor.MultiSelect = true;
    
                _singleLoadedEditor = new PeopleEditor();
                _singleLoadedEditor.ID = "_singleLoadedEditor";
                _singleLoadedEditor.AutoPostBack = true;
                _singleLoadedEditor.Width = new Unit(300, UnitType.Pixel);
                _singleLoadedEditor.AllowEmpty = false;
                _singleLoadedEditor.MultiSelect = false;
    
                _multipleLoadedEditor = new PeopleEditor();
                _multipleLoadedEditor.ID = "_multipleLoadedEditor";
                _multipleLoadedEditor.AutoPostBack = true;
                _multipleLoadedEditor.Width = new Unit(300, UnitType.Pixel);
                _multipleLoadedEditor.AllowEmpty = true;
                _multipleLoadedEditor.MultiSelect = true;
    
                Controls.Add(new Literal { Text = "Sencillo:<br/>" });
                Controls.Add(_singleEditor);
                Controls.Add(new Literal { Text = "<br/>Sencillo cargado:<br/>" });
                Controls.Add(_singleLoadedEditor);
                Controls.Add(new Literal { Text = "<br/>M&uacute;ltiple:<br/>" });
                Controls.Add(_multipleEditor);
                Controls.Add(new Literal { Text = "<br/>M&uacute;ltiple cargado:<br/>" });
                Controls.Add(_multipleLoadedEditor);
    
                PickerEntity entity = new PickerEntity();
                entity.Key = "ASGARD\\freyja";
                ArrayList entities = new ArrayList();
                entities.Add(entity);
                _singleLoadedEditor.UpdateEntities(entities);
    
                SPGroup group = SPContext.Current.Web.SiteGroups["Asgard Æsir"];
                entities = new ArrayList();
                foreach (SPUser user in group.Users)
                {
                    entity = new PickerEntity();
                    entity.Key = user.Name;
                    entities.Add(entity);
                }
                _multipleLoadedEditor.UpdateEntities(entities);
            }
        }
    }
    

    Para finalizar, solo comentar una cosa curiosa con la que me topé cuando escribía el ejemplo de este texto: las llamadas a UpdateEntities tienen que hacerse después de agregar los controles al WebPart (es decir, después de hacer el Controls.Add). Si no, el UpdateEntities no reconocerá a los usuarios y presentará un comportamiento extraño. No sé por qué ocurre así, pero lo hace (haz la prueba si gustas). Si alguien sabe la explicación, compártala con el mundo.

    ¡Buen fin de semana!

    Utiliza SPGridView para crear WebParts personalizados


    Dada la cantidad de clases que contiene el API de SharePoint, a veces es imposible revisar cada uno de sus componentes, y desafortunadamente dejamos de lado algunos muy valiosos. Y cuando llega el momento, nos vemos en la necesidad de reinventar la rueda.

    Un caso que me ha pasado seguido es cuando tengo que crear un WebPart que contenga una tabla donde se muestren datos. Al principio, mi insinto me decía: crea el HTML a mano. Con el paso del tiempo, mi instinto cambió su consejo: “no seas güey y mejor utiliza un GridView”. Eso está mucho mejor. Ambos enfoques, empero, presentan un problema primordial: no lucen como controles de SharePoint. Entonces hay que simularlo: al construir el HTML ó el GridView, hay que utilizar todas las clases de estilo (CSS) que el mismo SharePoint emplea.

    Pues bien, hace poco para un proyecto tuve una situación similar. Y se me topé, torpe de mí, con un control similar al GridView de ASP.NET de toda la vida. De hecho, este control deriva de GridView, y se llama, sorprendentemente, SPGridView, dentro de Microsoft.SharePoint.WebControls, en la librería Microsoft.SharePoint.dll distribuída con Windows SharePoint Services 3.0 (y, presumo, con SharePoint Foundation 2010).

    Utilizar SPGridView es exactamente igual a utilizar GridView, con algunas pequeñas diferencias. Vamos paso a paso. En primer lugar, tenemos la siguiente lista.

    SPGridView1

    Lo siguiente que debemos hacer es crearnos un miembro privado tipo SPGridView, cuyo espacio de nombres es Microsoft.SharePoint.WebControls. Posteriormente, sobreescribimos el método CreateChildControls. Nuestra clase, al momento, luce así.

    using System;
    using System.Web.UI.WebControls;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;
    using Microsoft.SharePoint.WebPartPages;
    
    namespace Fermasmas.Wordpress.Com.WebParts
    {
        public class GridViewExample : WebPart
        {
            private SPGridView _gridView;
            private Literal _titleLiteral;
    
            public GridViewExample()
                : base()
            {
            }
    
            protected override void CreateChildControls()
            {
                base.CreateChildControls();
    
                SPList list = SPContext.Current.Web.Lists["Asgard Pantheon"];
    
                _titleLiteral = new Literal();
                _titleLiteral.Text = string.Format("<br/><h2>{0}</h2>", list.Title);
                Controls.Add(_titleLiteral);
            }
        }
    }
    

    Después de instanciar SPGridView, necesitamos asignar algunas propiedades. En particular, AutoGenerateColumns debe ser falso, siempre, o si no el control lanzará una excepción.

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
    
        SPList list = SPContext.Current.Web.Lists["Asgard Pantheon"];
    
        _titleLiteral = new Literal();
        _titleLiteral.Text = string.Format("<br/><h2>{0}</h2>", list.Title);
        Controls.Add(_titleLiteral);
    
        _gridView = new SPGridView();
        _gridView.ID = "_gridView";
        _gridView.AutoGenerateColumns = false;
        _gridView.Width = new Unit(100, UnitType.Percentage);
                
        Controls.Add(_gridView);
    }
    

    Ahora, necesitamos crear las columnas a las que estarán ligados. Para ello, cada columna deberá ser de tipo SPBoundField, y debemos establecer la propiedad DataField al nombre de la columna de la lista a la que queremos enlazar.

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
    
        SPList list = SPContext.Current.Web.Lists["Asgard Pantheon"];
    
        _titleLiteral = new Literal();
        _titleLiteral.Text = string.Format("<br/><h2>{0}</h2>", list.Title);
        Controls.Add(_titleLiteral);
    
        _gridView = new SPGridView();
        _gridView.ID = "_gridView";
        _gridView.AutoGenerateColumns = false;
        _gridView.Width = new Unit(100, UnitType.Percentage);
                
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Title", HeaderText = "Name" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Gender", HeaderText = "Gender" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Influence", HeaderText = "Influence" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Mate", HeaderText = "Mate" });
                
        Controls.Add(_gridView);
    }
    

    También podemos establecer la propiedad HeaderText al nombre que queremos muestre nuestro grid.

    Ahora, solo necesitamos enlazar el control con una fuente de datos. SPList tiene una propiedad Items, de tipo SPListItemCollection, la cual tiene un método llamado GetDataTable, el cual regresa –sorpresa sorpresa- un objeto tipo DataTable. Usaremos dicho objeto para el DataSource del grid, e invocaremos al método DataBind.

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
    
        SPList list = SPContext.Current.Web.Lists["Asgard Pantheon"];
    
        _titleLiteral = new Literal();
        _titleLiteral.Text = string.Format("<br/><h2>{0}</h2>", list.Title);
        Controls.Add(_titleLiteral);
    
        _gridView = new SPGridView();
        _gridView.ID = "_gridView";
        _gridView.AutoGenerateColumns = false;
        _gridView.Width = new Unit(100, UnitType.Percentage);
        _gridView.DataSource = list.Items.GetDataTable();
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Title", HeaderText = "Name" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Gender", HeaderText = "Gender" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Influence", HeaderText = "Influence" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Mate", HeaderText = "Mate" });
                
        _gridView.DataBind();
        Controls.Add(_gridView);
    }
    

    Ya por último, comentar que puedes agregar cualquier otro tipo de columna, como por ejemplo, un CommandField para poder agregar comandos. De hecho, agregaremos uno para poder seleccionar las filas, es decir, un select command.

    protected override void CreateChildControls()
    {
        base.CreateChildControls();
    
        SPList list = SPContext.Current.Web.Lists["Asgard Pantheon"];
    
        _titleLiteral = new Literal();
        _titleLiteral.Text = string.Format("<br/><h2>{0}</h2>", list.Title);
        Controls.Add(_titleLiteral);
    
        _gridView = new SPGridView();
        _gridView.ID = "_gridView";
        _gridView.AutoGenerateColumns = false;
        _gridView.Width = new Unit(100, UnitType.Percentage);
        _gridView.DataSource = list.Items.GetDataTable();
        _gridView.Columns.Add(new CommandField {
            ButtonType = ButtonType.Image,
            ShowSelectButton = true,
            SelectImageUrl = "/_layouts/images/arrowright_light.gif"
        });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Title", HeaderText = "Name" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Gender", HeaderText = "Gender" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Influence", HeaderText = "Influence" });
        _gridView.Columns.Add(
            new SPBoundField { DataField = "Mate", HeaderText = "Mate" });
                
        _gridView.DataBind();
        Controls.Add(_gridView);
    }
    

    Así, solo resta añadir el WebPart a nuestro sitio. Así es como luce en mi máquina virtual.

    SPGridView2

    Y be done with it!