Presentación

Hoy, en mi post invitado del mes, les presento a Andrés Ortiz. Lo he invitado porque desarrolló una interesante manera de generar clientes WindowsRT para aplicaciones de SharePoint. En este interesante artículo, nos cuenta su experiencia su experiencia:

Contenido

Desde hace ya unos meses que Windows 8 viene cobrando mucha fuerza en diferentes campos: usuarios finales (portátiles, tablets, teléfonos), desarrolladores, diseñadores y más. Esto ha sido definitivamente un cambio de paradigma total de Microsoft de cara a su ya famoso sistema operativo, que básicamente no había tenido una evolución trascendental hasta que apareció Windows 7. Pero con Windows 8, se cambió definitivamente de página.

Por esta razón, hemos decido explorar lo que ofrece Windows 8 en el campo de los desarrolladores, específicamente para los que desarrollamos sobre la plataforma SharePoint, y definitivamente ha sido una experiencia muy gratificante. Nuestro resultado actual y el motivo de esta entrada, es una aplicación que corre en Windows 8, despliega Tiles en vivo con ítems de una lista de noticias (Thumbnail + Título de la noticia), y cuando se ingresa a la aplicación, se conecta a MySite de una granja de SharePoint y despliega la información de las personas que tengan su sitio personal creado. A continuación vamos a comentar los aspectos más importantes del desarrollo de esta aplicación.

Capa de Servicios: Cuando comenzamos a implementar la solución, lo primero que se nos vino a la mente es cómo íbamos a conectar la aplicación Windows 8 con la información almacenada en el servidor SharePoint. Como ya sabemos, SharePoint ofrece servicios Web, a través de los cuales se puede consumir la información almacenada en sus listas, y también la información de los perfiles, lo cual es necesario para obtener información de MySite. Por tal razón lo primero que debíamos implementar era la capa de servicios, a través de un proyecto .NET de tipo Windows Communication Foundation. En este sentido, tendríamos toda nuestra lógica de negocio, y temas de consumo de datos bien separados y aislados en un proyecto WCF totalmente dedicado a esa parte.
La Interfaz del servicio WCF es similar a la siguiente y resume los métodos utilizados que se deberán implementar para consumir la información:

[ServiceContract]
public interface IService1
{
    [OperationContract]
    List<TGNews> GetNewsTile();
    [OperationContract]
    List<TGUsers> GetUserInfo();
    [OperationContract]
    TGUsers GetUserInfoDetails(string loginName);
    [OperationContract]
    CompositeType GetDataUsingDataContract(CompositeType composite);
}  

Los servicios Web que estamos consumiendo en nuestra solución cuando se implementan los métodos descritos en la interfaz, son los siguientes: El consumo de la información de la lista de Noticias se realiza a través del consumo del servicio REST que ofrece SharePoint 2010, similar al siguiente ejemplo:
public List<TGNews> GetNewsTile()  
{        
   TGNews item = null;  
   List<TGNews> items = new List<TGNews>();  
   try  
   {  
        HomeDataContext dataContext = new HomeDataContext(new Uri("http:
        //server/_vti_bin/listdata.svc"));  
        dataContext.Credentials = new System.Net.NetworkCredential("user"
        , "password", "dominio");  
        var news = from temp in dataContext.Noticias  
                      select temp;  
        foreach (NoticiasItem temp in news)  
        {  
             item = new TGNews();  
             item.Title = temp.Título;  
             item.Thumbnail = temp.Thumbnail;  
             if (!string.IsNullOrEmpty(item.Thumbnail))  
             {  
                  GetImageNews("http://server" + item.Thumbnail, 
                  item.Title);  
                  item.Thumbnail = "ms-appx:///Assets/" + item.Title +
                  ".png";  
             }  
             items.Add(item);  
        }       
   }  
   catch (Exception ex)  
   {  
        //Log de la excepción  
        throw;  
   }  
   return items;  
}
El método anterior muestra claramente que se obtiene una referencia al servicio REST, luego a través de LINQ se consulta la información de la lista específica llamada Noticias y finalmente se itera por los resultados obtenidos, dando como resultado el listado genérico de tipo TGNews. Esta clase TGNews es la clase entidad encargada de transportar los datos entre las capas, específicamente título y thumbnail de la noticia. Posteriormente esa es la información que sirve de fuente para el Tile en la aplicación Windows 8.
Seguidamente el método encargado de consumir la información de los perfiles es similar a lo siguiente.
public List<TGUsers> GetUserInfo()  
{  
   UserGroup userGroup = new UserGroup();  
   userGroup.Credentials = new System.Net.NetworkCredential("user",
   "password", "dominio");  
   XmlNode allUsers = userGroup.GetAllUserCollectionFromWeb();  
   XNode xNode = XDocument.Parse(allUsers.OuterXml);  
   return (from root in xNode.Document.Elements()  
    from users in root.Elements()  
    from user in users.Elements()  
    let loginName = (string)user.Attribute("LoginName")  
    let userInfo = GetUserInfoDetails(loginName)  
    //Filter out admin accounts.  
    //Strangely enough I couldn’t find a consistent  
    //correlating property.  
    where loginName != "SHAREPOINT\\system" &&  
      loginName != "NT AUTHORITY\\LOCAL SERVICE" &&  
      loginName != "NT AUTHORITY\\SERVICIO LOCAL" &&  
      loginName != "NT AUTHORITY\\usuarios autentificados"  
    select new TGUsers()  
    {  
      Title = userInfo.Title,  
      Image = userInfo.Image,  
      Id = loginName,  
      Phonenumber = userInfo.Phonenumber,  
      Manager = userInfo.Manager,  
      Cellphone = userInfo.Cellphone,  
      Birthay = userInfo.Birthay,  
      Department = userInfo.Department,  
      HireDate = userInfo.HireDate,  
      JobTitle = userInfo.JobTitle,  
      Localitationoffice = userInfo.Localitationoffice,  
      Titulo = userInfo.Titulo,  
      Workemail = userInfo.Workemail  
    }).ToList();  
 }  

 public TGUsers GetUserInfoDetails(string loginName)  
 {  
    TGUsers userInfo = new TGUsers();  
    if (loginName != "SHAREPOINT\\system" && loginName != 
     "NT AUTHORITY\\LOCAL SERVICE")  
    {  
      //Set the default picture URL. Silverlight can't use the
      //default GIF  
      //provided by SharePoint.  
      userInfo.Image = "ms-appx:///Assets/usuario.jpg";  
      try  
      {  
        UserProfileService userProfileService = 
         new UserProfileService();  
        userProfileService.Credentials =
         new System.Net.NetworkCredential("user", "password", "dominio");  
        PropertyData[] data = userProfileService.
         GetUserProfileByName(loginName);  
        string pictureUrl = GetPropertyData(data, "PictureURL");  
        if (!string.IsNullOrEmpty(pictureUrl))  
        {   
          GetImage(pictureUrl, loginName.Split('\\')[1]);  
          userInfo.Image = "ms-appx:///Assets/"+ 
         loginName.Split('\\')[1] + ".png";  
        }  
        userInfo.Title = GetPropertyData(data, "FirstName") + 
         " " + GetPropertyData(data, "LastName");  
        if (string.IsNullOrWhiteSpace(userInfo.Title))  
        {  
          userInfo.Title = GetPropertyData(data, "AccountName");  
        }  
          userInfo.Phonenumber = GetPropertyData(data, "WorkPhone");  
          userInfo.Cellphone = GetPropertyData(data, "CellPhone");  
          userInfo.Manager = GetPropertyData(data, "Manager");  
          userInfo.Department = GetPropertyData(data, "Department");  
          userInfo.Birthay = GetPropertyData(data, "SPS-Birthday");  
          userInfo.HireDate = GetPropertyData(data, "SPS-HireDate");  
          userInfo.JobTitle = GetPropertyData(data, "SPS-JobTitle");  
          userInfo.Localitationoffice = 
         GetPropertyData(data, "SPS-Location");  
          userInfo.Titulo = GetPropertyData(data, "Title");  
          userInfo.Workemail = GetPropertyData(data, "WorkEmail");  
      }  
      catch (System.Web.Services.Protocols.SoapException ex)  
      {  
        if (!ex.Message.Contains("A user with the account name"))  
        {  
          //throw;  
        }  
      }  
      catch (Exception ex)  
      {  
           throw;  
      }  
    }  
    return userInfo;  
  }      

Los métodos anteriores se encargan principalmente de obtener diferentes propiedades del perfil de un usuario específico, a través de su Login,  y retornar esa información como una lista genérica de tipo TGUser. De ese modo la primera pantalla de la aplicación Windows 8 se encarga de listar a todas las personas, y luego podremos navegar al detalle de la información de cada uno de esos contactos. Vale la pena destacar del método anterior y también del método que trae la información de la noticia, la manera de manipular las imágenes. Aunque para el caso del Tile se pueden consumir imágenes a través de una URL, en este caso específico, dichas imágenes se encuentran en una biblioteca de SharePoint. Al momento de querer consumirlas a través de su URL obteníamos un error de permisos y no fue posible. Por tal razón la solución fue, descargarlas primero a la carpeta Assets de la aplicación Windows 8, y desde ahí, si darle la ruta a la aplicación para que las cargue. Esa fue la solución que le dimos a esa parte para poder que las imágenes se desplegaran, tanto las fotos de las noticias, como las fotos del perfil de cada usuario.
Hasta este punto ya teníamos listas genéricas, con información de la lista de Noticias y de los Perfiles. Era el momento de sentarnos a escribir la capa Cliente, donde básicamente lo que se hace, es en Visual Studio 2012 crear un proyecto de tipo Windows 8.
Con el proyecto de Windows 8 creado, lo primero era referenciar el proyecto WCF a través de una referencia de servicio. Con esto tendríamos acceso a los métodos expuestos por el servicio.

Tile en Vivo: Los tiles son una de las características interesantes de Windows 8, y quizá lo que le da vida a la interfaz del usuario final. Es muy llamativo ingresar al escritorio del Sistema Operativo en la interfaz estilo “Metro” y ver toda esa información en vivo, por cada una de las aplicaciones instaladas: lectores RSS, correo, mensajería, juegos, entre otros. En nuestro caso la idea era desplegar el thumbnail y título de las Noticias almacenadas en una lista de SharePoint, y dicha información ya estaba lista en el método respectivo ofrecido por el servicio WCF. El tile luce similar a la siguiente imagen enmarcada en rojo, cuando la aplicación es desplegada en Windows 8:

Tile

El código encargado de obtener la información de las noticias y la creación del tile es el siguiente:

public void Actualizartile()  
 {  
   var tileUpdateManager = TileUpdateManager.
    CreateTileUpdaterForApplication();  
   tileUpdateManager.EnableNotificationQueue(true);  
   ServiceReference.Service1Client client = 
    new ServiceReference.Service1Client();  
   Task<List<TGNews>> news = client.GetNewsTileAsync();  
   List<TGNews> items = news.Result;  
   foreach (TGNews temp in items)  
   {  
     if (!string.IsNullOrEmpty(temp.Thumbnail))  
     {  
       tileUpdateManager.Update(new TileNotification(  
        CrearIconoCuadradoYLargo(temp.Title, 
        "ms-appx:///Assets/" + temp.Title + ".png"))  
       {  
         ExpirationTime = DateTime.UtcNow.AddMinutes(60) 
       });  
     }  
     else  
     {  
       tileUpdateManager.Update(new TileNotification(  
        CrearIconoCuadradoYLargo(temp.Title, 
        "ms-appx:///Assets/default.png"))  
       {  
         ExpirationTime = DateTime.UtcNow.AddMinutes(60) 
       });  
     }  
   }  
 }  

Del método anterior lo primero que debemos observar es el uso de la clase TileUpdateManager, que será la encargada de permitirnos manipular la funcionalidad de Tiles de Windows 8. Luego de definirla, encontramos que debido que en nuestro caso debemos cargar más de un Tile, y que se repita durante una cantidad de tiempo, es indispensable activar la Cola de Notificaciones del Tile:
var tileUpdateManager = TileUpdateManager.CreateTileUpdaterForApplication();
tileUpdateManager.EnableNotificationQueue(true);
Luego de eso, se puede observar cómo se instancia la referencia del servicio y con eso poder consumir los métodos que ya hemos explicado, en este caso el método GetNewsTileAsync().
Nuevamente volviendo al objeto de tipo TileUpdateManager, podemos observar que se crea una plantilla para desplegar los Tiles amplios, y otra para desplegar los Tiles cuadrados. Esto es muy importante porque es una característica de Windows 8, que el usuario puede activar cuando desee, y si esto no se implementa, la experiencia de usuario se desmejora. Esto se hace en la llamada al método CrearIconoCuadradoYLargo() cuya implementación es similar a lo siguiente:  
private XmlDocument CrearIconoCuadradoYLargo(string texto,
        string rutaimagen)  
     {  
       //obtenemos el xml del del icono largo  
       var larga = CrearIconoLargoTextoImagen(texto, 
        rutaimagen);  
       //obtenemos el xml del icono cuadrado y extraemos su
        etiqueta binding  
       var cuadrada = larga.ImportNode(CrearIconoCuadradoTexto(texto).
        GetElementsByTagName("binding")[0], true);  
       // insertamos en el xml del icono largo el binding del icono 
        //cuadrado y hacemos que sean hermanos ambos tienen a visual como padre  
       larga.GetElementsByTagName("visual")[0].AppendChild(cuadrada);  
       return larga;  
     }  

     private XmlDocument CrearIconoCuadradoTexto(string texto)  
     {  
       var tilexml = TileUpdateManager.GetTemplateContent(
        TileTemplateType.TileSquareText04);  
       tilexml.GetElementsByTagName("text")[0].AppendChild(
        tilexml.CreateTextNode(texto));  
       return tilexml;  
     }  

     private XmlDocument CrearIconoLargoTextoImagen(string texto,
         string rutaimagen)  
     {  
       var tilexml = TileUpdateManager.GetTemplateContent(
        TileTemplateType.TileWideImageAndText02);  
       //añadimos nuestro texto  
       tilexml.GetElementsByTagName("text")[0].InnerText = texto;  
       //obtenemos la etiqueta image  
       dynamic imagen = tilexml.GetElementsByTagName("image");  
       //añadimos la ruta de la imagen dentro del atributo "src"  
       imagen[0].SetAttribute("src", rutaimagen);  
       return tilexml;  
    }  

El método CrearIconoCuadradoTexto() se encarga de retornar la plantilla de tipo TileTemplateType.TileSquareText04, la cual básicamente nos permite desplegar un mensaje de texto cuando se despliega el Tile en formato cuadrado o pequeño. El método CrearIconoLargoTextoImagen() se encargará a través de la plantilla TileTemplateType.TileWideImageAndText02 de desplegar la imagen y el título de la noticia cuando el Tile se despliega más grande.

Página de Items Agrupados: En el instante que el usuario hace clic en la aplicación lo primero que verá será el grupo de perfiles que se consultan a través del servicio Web de SharePoint 2010 ya  explicado. Básicamente el grupo de perfiles mostrará por cada persona su foto y su nombre, luciendo similar a lo siguiente:

Principal

La imagen anterior es el consumo de los perfiles de un sitio Demo de SharePoint 2010, por lo cual se ven algunas fotos repetidas, pero esto no sucederá consumiendo los servicios de un sitio SharePoint donde ya se tengan datos de perfiles de usuarios reales. Lo importante es que observemos entonces lo interesante de la solución, y es que desde una interfaz de Windows 8 muy amigable, podamos consultar la información de nuestros compañeros en la organización, sin tener que navegar a la interfaz Web del sitio en SharePoint. Esto significa un acceso rápido a información de compañeros de trabajo que podamos requerir consultar rápidamente.

Luego que se han cargado todos los perfiles, se puede hacer clic en alguno de ellos, y posteriormente se desplegará una ventana donde se muestra el detalle de las propiedades de dicho perfil, similar a la siguiente imagen:

informacion

Bien, hasta este punto hemos mostrado lo interesante que es implementar soluciones en Windows 8, porque definitivamente las plantillas que ofrece para carga de información y su consecuente navegación, el modelo de objetos, y todas las facilidades para implementar interfaces WOW para el usuario final, ya están listas, es solo comenzar a utilizarlo. Y por otro lado toda la riqueza que SharePoint 2010 ofrece para consumir su información, esta vez desde una aplicación cliente como Windows 8, son definitivamente una reunión de tecnologías muy interesantes para crear soluciones mucho más avanzadas y con mayor funcionalidad. Todo esto ha sido construido en una máquina con Windows 8 instalado y Visual Studio .NET 2012 Ultimate. La granja SharePoint 2010 está totalmente aislada de todo esto y gracias a los servicios Web y REST se puede consumir su información sin requerir mucho más de eso.

 Andres Ortiz Es Ingeniero de Sistemas de la Universidad del Cauca, con más de 10 años de experiencia en el desarrollo de soluciones de Software y actualmente apasionado y especializado en la plataforma Microsoft SharePoint. Con más de 4 años de experiencia en SharePoint, trabajo en la compañía TopGroup, como Líder de Proyectos del área SharePoint, diseñando la arquitectura e implementando soluciones sobre esta plataforma para una gran variedad de clientes. Puede contactarlo en aortiz@topgroup.com.ar si está interesado en crear soluciones SharePoint de alto impacto en su compañía.