Archivo

Archive for the ‘Código y ejemplos’ Category

Código para buscar usuario en directorio activo por su cuenta


Ando apoyando a un colega, quien está trabajando en un programa para SharePoint que sincronice información de usuarios hacia el directorio activo. Como no sabía cómo hacerle, le ayudé creando un pequeño ejemplo en C++/CLI, el cual con gusto comparto con ustedes. Ya les tocará pasarlo a C#… Smile with tongue out


#include "stdafx.h"

using namespace System;
using namespace System::DirectoryServices;

int main(array<String^>^ args)
{
    bool exit = false;

    do 
    {
        Console::WriteLine(L"Por favor ingresa tu usuario. ");
        String^ account = Console::ReadLine();

        Console::WriteLine(L"\nBuscando información para usuario...\n\n");

        try
        {
            auto root = gcnew DirectoryEntry(L"LDAP://DC=scanda,DC=com,DC=mx");
            auto searcher = gcnew DirectorySearcher(root);
            searcher->Filter = String::Format(L"(SAMAccountName={0})", account);
            
            searcher->PropertiesToLoad->Add(L"userprincipalname");
            searcher->PropertiesToLoad->Add(L"displayName");
            searcher->PropertiesToLoad->Add(L"givenname");
            searcher->PropertiesToLoad->Add(L"sn");
            searcher->PropertiesToLoad->Add(L"company");
            searcher->PropertiesToLoad->Add(L"title");
            searcher->PropertiesToLoad->Add(L"description");
            searcher->PropertiesToLoad->Add(L"wwwhomepage");
            searcher->PropertiesToLoad->Add(L"physicaldeliveryofficename");
            searcher->PropertiesToLoad->Add(L"streetaddress");            
            searcher->PropertiesToLoad->Add(L"postalcode");
            searcher->PropertiesToLoad->Add(L"st");
            searcher->PropertiesToLoad->Add(L"ipphone");
            
            auto result = searcher->FindOne();

            if (result == nullptr)
                throw gcnew Exception(L"Usuario no encontrado.");

            for each (String^ prop in result->Properties->PropertyNames)
            {
                if(prop->Equals(L"adspath"))
                    continue;

                Console::Write(L"{0}: ", prop);
                for each (Object^ value in result->Properties[prop])
                {
                    Console::Write(L"{0}\n", value);
                }
            }
        }
        catch (Exception^ ex)
        {
            Console::WriteLine(ex->Message);
        }
        finally 
        {
            Console::WriteLine(L"\n\n¿Desea hacer otra consulta? [Y/N]");
            auto key = Console::ReadKey(true);
            exit = key.Key != ConsoleKey::Y;
            Console::Clear();
        }
    } while (!exit);

    
    return 0;
}

 

Algunos comentarios. En la línea 20 creamos un objeto DirectoryEntry que representará la raíz de donde buscaremos. En este caso, si nuestra red tiene el dominio dominio.com.mx creamos el objeto y le pasamos la dirección LDAP correspondiente. En la línea 21 creamos un DirectorySearcher y le pasamos como parámetro al constructor el DirectoryEntry creado. Nota: si no quieres buscar en un dominio específico, no crees el DirectoryEntry y utiliza el constructor por default del DirectorySearcher.

Luego, en la línea 22 establecemos el filtro. Si queremos buscar a “fernando.gomez”, entonces el filtro debe ser “(SAMAccountName=fernando.gomez)”. Nota que la cuenta va SIN EL DOMINIO y encerrado todo entre paréntesis.

A continuación, de las líneas 23 a 36, le decimos al objeto qué propiedades debe cargar. Ahí tienes algunas, como los nombres, el apellido, algunos campos de direcciones, etc. Si quieres cargar todas las propiedades, entonces deja vacía la colección PropertiesToLoad y ya.

Luego, línea 38: Ejecutamos la búsqueda. El método FindOne busca el primer elemento que concuerde. Dado que buscamos por SAMAccountName, y dado que sólo debería existir una cuenta por dominio, es seguro usar el FindOne. Si buscaras por algún otro atributo, entonces te interesaría usar el método FindAll, que te regresa la colección entera.

Luego, de las líneas 43 a 52 iteramos sobre el resultado. Nota que éste es un diccionario, con cadenas de texto como llave (el nombre de la propiedad) y como una colección de valores como valor. De ahí que tengamos que hacer una doble iteración. Para muchas propiedades, el valor es uno sólo. Pero hay algunas que tienen varios valores. Por ejemplo, “memberof” suele tener varios valores, dependiendo de a qué unidades organizacionales el usuario pertenece.

Por supuesto, el valor puede ser de diferente tipo de dato, dependiendo de la propiedad. Hay algunos que son byte[], por ejemplo. Pero bueno, ya es cuestión de que le vayas jugando.

 

Post Scriptum:

Si alguien traduce esto a C#, ¡comparta! Lo hice en C++/CLI porque ando probando el Visual Studio 11 Beta, y andaba viendo cómo el intellisense para C++/CLI anda medio atrofiado (curiosamente funciona perfecto con C++ nativo) cuando mi compañero me preguntó. En fin, no creo que sea muy difícil de entender. Nos vemos la próxima.

Anuncios
Categorías:C++/CLI, Código y ejemplos Etiquetas:

Aplicación que se ejecuta como consola e interfaz gráfica


Siguiendo una petición en la galería de código de MSDN, he publicado este ejemplo: aplicación que se ejecuta como consola e interfaz gráfica. ¡Échale un ojo, descarga y vota si te gusta! A continuación transcribo el extracto del artículo.

Introducción

En ciertas ocasiones nuestra aplicación Windows necesita ejecutarse en modo de consola, o al menos, proveer esta interfaz bajo ciertas condiciones. Es decir, una aplicación que se comporte como aplicación Windows y aplicación consola.

Construyendo el ejemplo

El código asociado es una solución de Visual Studio 2010, y se requiere tener Visual C++ instalado (aunque no debe ser difícil portar hacia versiones anteriores). El proyecto de Visual C++ es  una aplicación MFC basada en ventana de diálogos. Para compilar sólo se necesita MFC y, dado que se hacen llamadas al API de Windows, algún SDK reciente.

Descripción

Al iniciar su ejecución, la aplicación determina si ha recibido un parámetro llamado /use-console. De ser así, crea una consola y la adjunta al proceso actual, para después crear un búfer y asociarlo a la consola. Posteriormente se escribe y lee información de la misma. En caso de no recibir dicho parámetro, la aplicación simplemente muestra una ventana de diálogo modal, con algunos controles en ella.

El bloque de código interesante lo presento a continuación:

if (objCmdLine.UseConsole()) 
{ 
    ::AllocConsole(); 
    HANDLE hBuffer = ::CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, 
        CONSOLE_TEXTMODE_BUFFER, 
        NULL); 
    ::SetConsoleActiveScreenBuffer(hBuffer); 
         
    HANDLE hInputHandle = GetStdHandle(STD_INPUT_HANDLE); 
 
    CString strMsg;         
    strMsg.LoadString(IDS_INFO); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
    strMsg.LoadString(IDS_GREETINGS); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
         
    CONSOLE_READCONSOLE_CONTROL objCtrl; 
    ZeroMemory(&objCtrl, sizeof(CONSOLE_READCONSOLE_CONTROL)); 
    objCtrl.nLength = sizeof(CONSOLE_READCONSOLE_CONTROL);         
 
    const int nInputSize = 256; 
    TCHAR szInput[nInputSize]; 
    ZeroMemory(szInput, sizeof(TCHAR) * nInputSize); 
    DWORD dwCharsRead; 
    ::ReadConsole(hInputHandle, szInput, nInputSize, &dwCharsRead, &objCtrl); 
 
    CString strInput(szInput); 
    strInput = strInput.Mid(0, dwCharsRead - 2); 
     
    strMsg.Format(IDS_HELLOFELLOW, strInput); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
    strMsg.LoadString(IDS_PRESSKEYTOEXIT); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
         
    ::ReadConsole(hInputHandle, szInput, 1, NULL, NULL); 
 
    ::FreeConsole(); 
} 
else  
{ 
    CSampleDlg dlg; 
    m_pMainWnd = &dlg; 
    dlg.DoModal(); 
}

Del bloque anterior, hay que notar las siguientes llamadas a funciones del API.

  • AllocConsole se encarga de crear una consola y asociarla al proceso actual.
  • CreateConsoleScreenBuffer y SetConsoleActiveScreenBuffer crea un búfer y lo asocia a la consola previamente creada.
  • GetStdHandle nos proporciona un manejador hacia la salida de la consola (i.e. para poder leer y escribir desde y hacia ella).
  • ReadConsole y WriteConsole se encargan de leer datos desde y escribirlos hacia la consola, respectivamente.
  • Por último, FreeConsole desasocia la consola creada.

Código fuente

  • AboutDlg (h y cpp) – el cuadro de diálogo de "acerca de".
  • ConsoleCommandLineInfo (h y cpp) – implementa la clase que analiza los parámetros de la aplicación.
  • Resource.h – el encabezado donde se guardan los valores de recursos.
  • SampleApp (h y cpp) – la aplicación; en particular, contiene el método InitInstance donde se decide si mostrar la aplicación Windows o la aplicación por consola.
  • SampleDlg (h y cpp) – la ventana de diálogo principal de la aplicación.

Más información

Para mayor información, revisa esta sección: Sobre Consolas en MSDN.

Categorías:C++, Código y ejemplos, WIN-32 Etiquetas: , ,

Consulta, descarga y exportación de perfiles de usuarios en SharePoint


Puedes consultar mi último código en la galería de código de MSDN: consulta, descarga y exportación de perfiles de usuarios en SharePoint. Algunos extractos a continuación.

Introducción

El presente ejemplo muestra cómo utilizar los servicios web para construir una aplicación que permita consultar, descargar y exportar los perfiles de usuario de SharePoint Server 2010, así como información suplemental.

Construyendo el ejemplo

El código de ejemplo es una solución hecha con Visual Studio 2010 para .NET Framework 3.5. No tiene alguna otra dependencia. Hace referencia a dos servicios web, cuyas clases proxy se generaron a partir de los servicios _vti_bin/UserGroup.asmx y _vti_bin/UserProfileService.asmx. El primer servicio está disponible en SharePoint Foundation 2010 y el segundo en SharePoint Server 2010. Nota: este programa funciona también con versiones anteriores de SharePoint, aunque es posible que algunas características no estén disponibles.

Descripción

El programa cuenta con un componente llamado ProfileSource. Éste actúa como fuente de datos para el programa, y contiene los métodos que obtienen la información de los perfiles mediante consultas hechas a los servicios web. Este componente implementa gran parte de la lógica de la aplicación. En particular el método OnDoWork es quien se encarga de descargar la información de los perfiles. A continuación se muestran extractos del mismo.

_worker.ReportProgress(0, Resources.ProfileSource_StartingServices); 
userGroup = new UserGroup(); 
PrepareService(userGroup, _connectionProperties.UserGroupWebService); 
userProfile = new UserProfileService(); 
PrepareService(userProfile, _connectionProperties.UserProfileWebService); 
 
_worker.ReportProgress(0, Resources.ProfileSource_DownloadSchematics);  
Table = CreateTable(userProfile);

El método PrepareService establece algunas propiedades básicas del servicio web, como la URL de donde se encuetra y las credenciales. Nota que éstas últimas varían dependiendo de si el usuario ha seleccionado usar las credenciales por defecto o no. Por otra parte, el método CreateTable utiliza el servicio UserProfileService y su método GetUserProfileSchema para descargar todos los campos disponibles desde el servidor, y con base en ellos generar el esquema de un objeto DataTable.

XmlNode mainNode = null; 
if (ConnectionProperties.AllSiteUsers) 
{ 
    _worker.ReportProgress(0, Resources.ProfileSource_DownloadAllUserInfo); 
    mainNode = userGroup.GetAllUserCollectionFromWeb(); 
} 
else 
{ 
    _worker.ReportProgress(0, Resources.ProfileSource_DownloadUserInfo); 
    mainNode = userGroup.GetUserCollectionFromWeb(); 
} 
_worker.ReportProgress(0, Resources.ProfileSource_UserInfoDownloaded);

El extracto anterior obtiene los usuarios disponibles del sitio (todos los registrados, o bien cualquiera que haya ingresado), mediante la llamada a los métodos GetAllUserCollectionFromWeb o GetUserCollectionFromWeb del servicio web UserGroup, según sea el caso.

XmlDocument doc = new XmlDocument(); 
doc.LoadXml(mainNode.OuterXml); 
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); 
nsmgr.AddNamespace("defns", "http://schemas.microsoft.com/sharepoint/soap/directory/"); 
 
string xpath; 
if (ConnectionProperties.AllSiteUsers) 
    xpath = "defns:GetAllUserCollectionFromWeb/defns:Users/descendant::defns:User"; 
else 
    xpath = "defns:GetUserCollectionFromWeb/defns:Users/descendant::defns:User"; 
XmlNodeList userNodes = doc.SelectNodes(xpath, nsmgr); 

Una vez descargados los usuarios, necesitamos analizar el documento XML regresado. Para ello hacemos uso de XPath. El extracto anterior muestra el código que genera la consutla XPath, y que nos devuelve una lista con todos los nodos que contienen la información de los usuarios.

Posteriormente, recorremos cada elemento de la lista y llamamos al método GetUserProfile, el cual usará al método GetUserProfileByName del servicio web UserProfileService. Los resultados obtenidos los insertamos como una fila en nuestra tabla, como se muestra en el siguiente extracto.

foreach (XmlNode userNode in userNodes) 
{ 
    double stepDouble = ((double)++count / (double)maxCount) * 100.0; 
    int stepInt32 = Math.Min((int)Math.Round(stepDouble, 0), 100); 
    string loginName = userNode.Attributes["LoginName"].Value; 
    _worker.ReportProgress(stepInt32, string.Format(Resources.ProfileSource_DownloadingProfile, loginName, stepInt32)); 
    DataRow row = GetUserProfile(userProfile, loginName); 
    Table.Rows.Add(row); 
 
    if (count >= maxCount) 
        break; 
}

Esta porción de código muestra cómo el método GetUserProfile obtiene el perfil de un usuario.

PropertyData[] allData = service.GetUserProfileByName(loginName); 
foreach (PropertyData data in allData) 
{ 
    if (row.Table.Columns.Contains(data.Name)) 
    { 
        if (data.Values.Length > 0 && data.Values[0].Value != null) 
            row[data.Name] = data.Values[0].Value.ToString(); 
        else 
            row[data.Name] = "[Empty]"; 
    } 
}

Adicional a los métodos mencionados, el componente cuenta con los siguientes miembros.

  • Table. Un objeto DataTable que contiene la información descargada de los perfiles.
  • ConnectionProperties. Un componente ConnectionProperties, el cual contiene la información necesaria para establecer la conexión y descarga de elementos (i.e. usuario, contraseña, URLs, etc.).
  • GetColleagues. Consulta al servicio web UserProfileService por los colegas de un usuario determinado.
  • GetInCommon. Consulta al servicio web UserProfileService por información que el usuario actual tenga en común con otro usuario determinado.
  • GetLinks. Consulta al servicio web UserProfileService por los enlaces de un usuario determinado.
  • GetMemberships. Consulta al servicio web UserProfileService por las membrecías de un usuario determinado.
  • GetOrganizations. Consulta al servicio web UserProfileService por las organizaciones a las que pertenece un usuario determinado.
  • GetPinnedLinks. Consulta al servicio web UserProfileService por los enlaces marcados de un usuario determinado.
  • SaveAs. Exporta los perfiles de usuario hacia un archivo de valores separados por coma (CSV) o a un XML.
  • Loaded. Evento que se dispara cuando el componente ha terminado de descargar la información de perfiles.
  • LoadProgress. Evento que se dispara cuando hay un avance significativo durante la descarga de información de perfiles.
  • ProfileFailure. Evento que se dispara cuando se intenta obtener el perfil de un usuario y éste falla (i.e. por cuestiones de permisos, porque no existe, etc.), en cuyo caso se puede continuar con el siguiente perfil (comportamiento por defecto) o bien terminar con el proceso.
  • Failure. Evento que se dispara cuando la carga de información de usuarios falla y lanza una excepción.

Como última precisión, comentar que el programa está localizado y puede visualizarse en inglés y en español.

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.

    Cómo utilizar los servicios de .NET Framework para leer canales RSS y ATOM


    He añadido un código de ejemplo a la Galería de Código de MSDN, a continuación transcribo lo que publiqué. Pueden descargar el código desde aquí.

     

    Introducción

     

    Los canales RSS y ATOM proveen un mecanismo estándar para publicar contenido sobre sitios web, como texto, imágenes, vídeos, etc. Dichos formatos son documentos XML con una estructura predefinida. Durante cierto tiempo la solución para crear un lector para dichos formatos ha sido, en el mundo de .NET, crear el documento y analizarlo a mano. Las últimas versiones de .NET vienen con servicios para poder analizar ese contenido de forma automática. El código aquí presentado muestra cómo hacerlo.

    Construcción del ejemplo

    El ejemplo está construido sobre .NET Framework 4, para Visual Studio 2010. Sin embargo debe poder portarse a .NET Framework 3.5 sin mucho problema. En particular, se utiliza la biblioteca System.ServiceModel.dll, la cual contiene las clases necesarias para leer los canales RSS y ATOM.

    La compilación de este ejemplo es sencilla y no necesita consideraciones especiales.

    Descripción

    Básicamente el proceso de lectura de un canal RSS o ATOM se da siguiendo estos pasos.

    1. Descargar el contenido XML del canal, usando un XmlReader por ejemplo.
    2. Crear un formateador. Estos pueden ser, por ejemplo, las clases Rss20FeedFormatter, o bien Atom10FeedFormatter (del espacio de nombres System.ServiceModel.Syndication), para canales RSS y ATOM, respectivamente.
    3. Pasar el objeto XmlReader al método ReadFrom del Rss20FeedFormatter o del Atom10FeedFormatter.

    Una vez hecho esto, el objeto formateador habrá analizado el XML y ¡bazzinga! estamos listos. Mediante la propiedad Feed se accede al contenido del canal, la cual a su vez cuenta con la propiedad Items, que es una colección para cada uno de los elementos encontrados.

    El siguiente trozo de código es un extracto, el cual muestra cómo leemos el canal y cargamos el contenido en un control TreeView.

    // Creamos el formateador, el cual será RSS o ATOM, dependiendo de la opción
    // seleccionada por el usuario. 
    SyndicationFeedFormatter formatter = CreateFormatter();
    
    // Leemos el XML de la URL seleccionada...
    using (XmlReader reader = XmlReader.Create(_urlText.Text))
    {
        // ...y le decimos al formateador que analice el XML del feed. 
        formatter.ReadFrom(reader);
    }
    // Si la URL es inválida, recibiremos un UriFormatException. Si el XML 
    // descargado tiene algún contenido inválido, que no se ciña a la especificación
    // de RSS o de ATOM, entonces recibiremos un XmlException. 
    
    // Vaciamos el árbol y añadimos un nodo raíz. 
    _feedTree.Nodes.Clear();
    TreeNode rootNode = _feedTree.Nodes.Add(formatter.Feed.Title.Text);
    
    // Para cada elemento encontrado en el feed añadimos un nodo. Adjuntamos 
    // el objeto de dicho elemento a la propiedad Tag del nodo, para poder
    // referirnos a éste posteriormente (i.e. cuando se dispare el evento
    // NodeMouseClick). 
    foreach (SyndicationItem item in formatter.Feed.Items)
    {
        TreeNode feedNode = rootNode.Nodes.Add(item.Title.Text);
        feedNode.Tag = item;
    }
    

    El siguiente trozo muestra cómo cargar el contenido de un elemento del canal en una caja de texto. También es un extracto del código fuente.

    SyndicationItem item = args.Node.Tag as SyndicationItem; 
    if (item != null) 
    { 
        StringBuilder text = new StringBuilder() 
            .AppendFormat("Título: {0}\r\n", item.Title.Text) 
            .AppendFormat("Publicado: {0}\r\n", item.PublishDate.DateTime) 
            .AppendFormat("Actualizado: {0}\r\n", item.LastUpdatedTime.DateTime) 
            .AppendLine() 
            .AppendFormat(item.Summary.Text) 
            .AppendLine() 
            .AppendLine(); 
        if (item.Content != null) 
        { 
            text.AppendLine(item.Content.Type); 
        } 
                     
        _contentText.Text = text.ToString(); 
     
        _linkLabel.Text = item.Title.Text; 
        _linkLabel.Tag = item; 
    }
    

    A continuación, una imagen de cómo luce el programa.

    Lector sencillo de rss

    Código fuente

     

    La solución presenta estos proyectos:

    1.- SimpleRssFeed. Muestra una aplicación en Windows Forms sobre cómo utilizar los canales de comunicación RSS y ATOM. Muestra cómo emplear las clases System.ServiceModel.Syndication.Rss20FeedFormatter.

    • RssFeed.cs – el formulario principal de la aplicación.

    Más información

    Para más información, revisa estos enlaces en la documentación de MSDN:

    Atom10FeedFormatter

    Rss20FeedFormatter

    SyndicationFeed

    SyndicationItem

    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.

    Dibujar banderas en la consola


    Ayer por l tarde respondí una pregunta del foro MSDN sobre cómo cambiar el color de fondo de la consola utilizando .NET y C#. La respuesta, por supuesto, es utilizar Console.BackgroundColor y Console.ForegroundColor, y lo que se escriba a continuación saldrá en los colores especificados.

    Bueno, pues se me ocurrió hacer un pequeño programa que utilizando este principio pintara algunas banderas en el monitor. La idea es establecer el fondo e imprimir un caracter, un espacio en blanco. Y así dibujar las diferentes secciones de una bandera en la consola.

    Alemania Francia
    Inglaterra Noruega

     

    No es nada útil, realmente, pero fue entretenido y ya que lo tengo, pues a postearlo. Así que bueno, un codiguín para pasar la tarde, jejeje.

    using System;
    
    namespace Fermasmas.Wordpress.Com
    {
      abstract class Flag
      {
        public virtual int Width
        {
          get { return 79; }
        }
    
        public virtual int Height
        {
          get { return 24; }
        }
    
        protected virtual int WidthSectors
        {
          get { return 1; }
        }
    
        protected virtual int HeightSectors
        {
          get { return 1; }
        }
    
        protected bool IsWidthSector(int sector, int value)
        {
          int sectorWidth = Width / WidthSectors;
          return value >= sectorWidth * Math.Max(sector - 1, 0)
            && value < sectorWidth * sector;
        }
    
        protected bool IsHeightSector(int sector, int value)
        {
          int sectorHeight = Height / HeightSectors;
          return value >= sectorHeight * Math.Max(sector - 1, 0)
            && value < sectorHeight * sector;
        }
    
        protected void SetupEdgeSquare()
        {
          Console.BackgroundColor = ConsoleColor.Gray;
        }
    
        protected abstract void SetupSquare(int x, int y);
    
        public void Draw()
        {
          for (int y = 0; y < Height; y++) 
          {
            for (int x = 0; x < Width; x++)
            {
              Console.ResetColor();
              if (x == 0 || x == Width - 1 || y == 0 || y == Height - 1)
                SetupEdgeSquare();
              else
                SetupSquare(x, y);
              Console.Write(' ');
            }
            Console.WriteLine();
          }
        }
      }
    
      class GermanFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 3; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsHeightSector(1, y))
            Console.BackgroundColor = ConsoleColor.Black;
          else if (IsHeightSector(2, y))
            Console.BackgroundColor = ConsoleColor.Red;
          else if (IsHeightSector(3, y))
            Console.BackgroundColor = ConsoleColor.Yellow;
        }
      }
    
      class SpanishFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 3; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if(IsHeightSector(2, y))
            Console.BackgroundColor = ConsoleColor.Yellow;
          else
            Console.BackgroundColor = ConsoleColor.Red;
        }
      }
    
      class FrenchFlag : Flag
      {
        protected override int WidthSectors
        {
          get { return 3; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsWidthSector(1, x))
            Console.BackgroundColor = ConsoleColor.Blue;
          else if (IsWidthSector(2, x))
            Console.BackgroundColor = ConsoleColor.White;
          else if (IsWidthSector(3, x))
            Console.BackgroundColor = ConsoleColor.Red;
        }
      }
    
      class ItalianFlag : Flag
      {
        protected override int WidthSectors
        {
          get { return 3; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsWidthSector(1, x))
            Console.BackgroundColor = ConsoleColor.Green;
          else if (IsWidthSector(2, x))
            Console.BackgroundColor = ConsoleColor.White;
          else if (IsWidthSector(3, x))
            Console.BackgroundColor = ConsoleColor.Red;
        }
      }
    
      class SwissFlag : Flag
      {
        public override int Width
        {
          get 
          { 
            return (int)Math.Round(Math.Min(base.Width, base.Height) * 1.7, 0); 
          }
        }
    
        public override int Height
        {
          get { return Math.Min(base.Width, base.Height); }
        }
    
        protected override int HeightSectors
        {
          get { return 5; }
        }
    
        protected override int WidthSectors
        {
          get { return 6; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (((IsHeightSector(2, y) || IsHeightSector(4, y)) && 
                IsWidthSector(4, x)) || (IsHeightSector(3, y) && 
               (IsWidthSector(3, x) || IsWidthSector(4, x) || 
               IsWidthSector(5, x))))
            Console.BackgroundColor = ConsoleColor.White;
          else
            Console.BackgroundColor = ConsoleColor.Red;
        }
      }
    
      class EnglishFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 9; }
        }
    
        protected override int WidthSectors
        {
          get { return 9; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsWidthSector(5, x) || IsWidthSector(6, x) || 
              IsHeightSector(6, y) || IsHeightSector(7, y) || 
              IsHeightSector(8, y))
            Console.BackgroundColor = ConsoleColor.Red;
          else 
            Console.BackgroundColor = ConsoleColor.White;
        }
      }
    
      class SweedishFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 9; }
        }
    
        protected override int WidthSectors
        {
          get { return 15; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsWidthSector(6, x) || IsWidthSector(7, x) || 
              IsHeightSector(6, y) || IsHeightSector(7, y) || 
              IsHeightSector(8, y))
            Console.BackgroundColor = ConsoleColor.Yellow;
          else
            Console.BackgroundColor = ConsoleColor.DarkBlue;
        }
      }
    
      class NorwegianFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 15; }
        }
    
        protected override int WidthSectors
        {
          get { return 25; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsWidthSector(10, x) || IsWidthSector(11, x) || 
              IsHeightSector(12, y) || IsHeightSector(13, y) || 
              IsHeightSector(14, y))
            Console.BackgroundColor = ConsoleColor.DarkBlue;
          else if (IsWidthSector(9, x) || IsWidthSector(12, x) || 
                   IsHeightSector(11, y) || IsHeightSector(15, y))
            Console.BackgroundColor = ConsoleColor.White;
          else
            Console.BackgroundColor = ConsoleColor.Red;
        }
      }
    
      class IcelandicFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 15; }
        }
    
        protected override int WidthSectors
        {
          get { return 25; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsWidthSector(10, x) || IsWidthSector(11, x) || 
              IsHeightSector(12, y) || IsHeightSector(13, y) || 
              IsHeightSector(14, y))
            Console.BackgroundColor = ConsoleColor.DarkRed;
          else if (IsWidthSector(9, x) || IsWidthSector(12, x) || 
                   IsHeightSector(11, y) || IsHeightSector(15, y))
            Console.BackgroundColor = ConsoleColor.White;
          else
            Console.BackgroundColor = ConsoleColor.DarkBlue;
        }
      }
    
      class GreekFlag : Flag
      {
        protected override int HeightSectors
        {
          get { return 10; }
        }
    
        protected override int WidthSectors
        {
          get { return 12; }
        }
    
        protected override void SetupSquare(int x, int y)
        {
          if (IsHeightSector(3, y) || IsHeightSector(5, y) || 
              IsHeightSector(7, y) || IsHeightSector(9, y))
            Console.BackgroundColor = ConsoleColor.White;
          else
            Console.BackgroundColor = ConsoleColor.DarkBlue;
    
          if (IsWidthSector(1, x) || IsWidthSector(2, x) || 
              IsWidthSector(3, x) || IsWidthSector(4, x) || 
              IsWidthSector(5, x))
          {
            if (IsHeightSector(1, y) || IsHeightSector(2, y) || 
                IsHeightSector(3, y) || IsHeightSector(4, y) || 
                IsHeightSector(5, y) || IsHeightSector(6, y))
            {
              if (IsWidthSector(3, x) || IsHeightSector(4, y))
                Console.BackgroundColor = ConsoleColor.White;
              else
                Console.BackgroundColor = ConsoleColor.DarkBlue;
            }
          }
        }
      }
    
      class Program
      {
        static void Main(string[] args)
        {
          Flag flag;
          bool quit;
    
          do
          {
            Console.Clear();
            Console.WriteLine("Selecciona una bandera a dibujar:");
            Console.WriteLine("1. Alemania");
            Console.WriteLine("2. España");
            Console.WriteLine("3. Francia");
            Console.WriteLine("4. Italia");
            Console.WriteLine("5. Suiza");
            Console.WriteLine("6. Inglaterra");
            Console.WriteLine("7. Suecia");
            Console.WriteLine("8. Noruega");
            Console.WriteLine("9. Islandia");
            Console.WriteLine("0. Grecia");
            Console.WriteLine("Q. Quitar");
            ConsoleKeyInfo key = Console.ReadKey(true);
    
            flag = null;
            quit = false;
            switch (key.Key)
            {
              case ConsoleKey.Q:
                quit = true;
                break;
    
              case ConsoleKey.D1:
                flag = new GermanFlag();
                break;
    
              case ConsoleKey.D2:
                flag = new SpanishFlag();
                break;
    
              case ConsoleKey.D3:
                flag = new FrenchFlag();
                break;
    
              case ConsoleKey.D4:
                flag = new ItalianFlag();
                break;
    
              case ConsoleKey.D5:
                flag = new SwissFlag();
                break;
    
              case ConsoleKey.D6:
                flag = new EnglishFlag();
                break;
    
              case ConsoleKey.D7:
                flag = new SweedishFlag();
                break;
    
              case ConsoleKey.D8:
                flag = new NorwegianFlag();
                break;
    
              case ConsoleKey.D9:
                flag = new IcelandicFlag();
                break;
    
              case ConsoleKey.D0:
                flag = new GreekFlag();
                break;
    
              default:
                Console.WriteLine("Llave inválida, intenta de nuevo...");
                Console.ReadKey(true);
                break;
            }
    
            if (flag != null)
            {
              Console.Clear();
              flag.Draw();
              Console.ReadKey(true);
              Console.ResetColor();
            }
          } while (!quit);
        }
      }
    }
    
    

     

    Bueno, eso es todo. Como dije, nada extraordinario y solo una curiosidad. Hasta la próxima.

    u.u

    Categorías:.NET Framework, C#, Código y ejemplos Etiquetas: