SharePoint 2010 BCS en Visual Studio

En el anterior artículo, SharePoint 2010 BCS en el Designer, veíamos las posibilidades de BCS con SharePoint Designer, en este artículo hablaremos sobre el potencial enorme que encontramos en el BCS pero desarrollándolo con Visual Studio 2010.

Veamos un ejemplo para entender mejor el desarrollo de un tipo de contenido externo desde la visión del desarrollador. Para esto vamos a crear una entidad BCS que obtenga los datos de los clientes de un ERP y el estado de los contactos en LinkedIn, utilizando la API REST de este servicio. Todo esto será presentado en SharePoint sin que el usuario tenga que intervenir en las consultas.

Utilizaremos la nueva plantilla de proyecto Business Data Connectivity Model de la sección SharePoint 2010 de Visual Studio. Esta plantilla nos crea, por defecto, una clase con la Entidad, una clase con el servicio de la entidad y una definición del modelo BCS.

El archivo de Entity1.cs describe el esquema de la entidad y se modela como una clase típica con propiedades públicas. 

   1: namespace CustomerBDC.CustomerBdcModel

   2: {

   3:     /// <summary>

   4:     /// This class contains the properties for Entity1. The properties keep the data for Entity1.

   5:     /// If you want to rename the class, don't forget to rename the entity in the model xml as well.

   6:     /// </summary>

   7:     public partial class Customer

   8:     {

   9:         //TODO: Implement additional properties here. The property Message is just a sample how a property could look like.

  10:         public int CustomerId { get; set; }

  11:         public string Name { get; set; }

  12:         public string LinkedInName { get; set; }

  13:         public string LinkedInStatus { get; set; }

  14:     }

  15: }

El archivo de Entity1Service.cs proporciona la implementación para los métodos CRUD (Create, Read, Update, Delete). El servicio necesita la implementación, como mínimo de dos métodos. El primer método, ReadItem (en el ejemplo GetCustomer), permite recuperar un registro específico desde el almacén externo basado en un identificador. Esto se asigna a los metadatos XML como un método de instancia del tipo SpecificFinder. El segundo método, ReadList (en el ejemplo GetList), recupera todos los registros desde el almacén externo. Esto está asignado en los metadatos XML como una instancia de método de Finder.

 

 

   1: namespace CustomerBDC.CustomerBdcModel

   2: {

   3:     /// <summary>

   4:     /// All the methods for retrieving, updating and deleting data are implemented in this class file.

   5:     /// The samples below show the finder and specific finder method for Entity1.

   6:     /// </summary>

   7:     public class CustomerService

   8:     {

   9:         public static Customer GetCustomer(int customerId)

  10:         

  11:         public static IEnumerable<Customer> GetList()

  12:  

  13:         public static void Update(Customer customer)

  14:     }

  15: }

Ahora toca implementar la lógica necesaria para la ejecución de los métodos que queremos proporcionar en nuestra entidad BCS. Desde la clase del servicio tenemos un abanico enorme de posibilidades de conexión: LINQ TO SQL, Entity Framework, SOAP, WCF, etc.

Para la entidad del ejemplo, vamos a generar un DBML de LINQ TO SQL para obtener la información de nuestra base de datos de clientes, e implementaremos los métodos de nuestro servicio, inclúida la llamada al API REST de LinkedIn para obtener el estado de un usuario concreto de este servicio.

   1: public static Customer GetCustomer(int customerId)

   2: {

   3:     using (var context = new AdventureWorksDataClassesDataContext(connString))

   4:     {

   5:         Customer customer = (from c in context.Contacts

   6:                              where c.ContactID == customerId

   7:                              select new Customer 

   8:                              {

   9:                                  CustomerId = c.ContactID,

  10:                                  Name = c.FirstName + " " + c.LastName,

  11:                                  LinkedInName = c.EmailAddress

  12:                              }).FirstOrDefault();

  13:  

  14:         //TODO: Obtener los datos de LinkedIn utilizando la API REST

  15:  

  16:         return customer;

  17:     }

  18: }

Una vez creada nuestra entidad y el servicio, nos toca modelar la entidad externa, asociarla con la entidad y crear y asociar los métodos que estarán disponibles.

BCSvs_1

Para crear los métodos SpecificFinde y Finder utilizaremos la ventana BDC Method Details y le asociaremos la entidad y el método correspondiente del servicio.

Ya sólo nos quedaría hacer el deploy del BCS y crearnos una External List en SharePoint para hacer uso de él.

BCS nos presenta un gran potencial que nos permite desarrollar múltiples aplicaciones en SharePoint, integradas con nuestras aplicaciones actuales de negocio y mejorar el trabajo diario de nuestros usuarios. Tendremos que empezar a pensar en ellas y en todas las posibilidades que nos ofrece para extraerle todo el jugo y ofrecer mejores soluciones sobre SharePoint.

 

Saludos a todos…

SharePoint 2010 BCS en el Designer

En una implantación común de un portal de colaboración, como SharePoint, siempre llegamos al punto de necesitar conectar con algunos datos externos. Los usuarios progresan y se van adaptando a la nueva filosofía de trabajo, cargan documentos, utilizan los flujos de trabajo para mejorar la gestión de la información, emplean formularios para obtener información y al final necesitan poder conectarse a otros sistemas para tener toda la información accesible desde el portal.

SharePoint 2007 facilitaba esta situación mediante el uso de los Business Data Catalog (BDC). Estos catálogos eran ficheros XML que definían la relación entre SharePoint 2007 y  los datos externos, y se podían representar con Web Parts, en columnas dentro de una lista o mediante la búsqueda.

BDC nos creaba grandes dolores de cabeza. es muy difícil crear soluciones sin un buen diseñador. Otro problema era hacer que esos datos se pudieran actualizar, ya que la facilidad de BDC permite hacer conexiones de sólo lectura y parece que no se pensó para conexiones de actualización de los datos.

Business Connectivity Services (BCS), nuevo en SharePoint 2010, se centra en todas las conexiones posibles a nuestros datos. Mejora las capacidades de la plataforma de SharePoint con características out-of-box, servicios y herramientas que optimizan el desarrollo de soluciones con profunda integración de datos externos y servicios. Por ejemplo, en SharePoint 2010 es fácil crear un tipo de contenido externo con SharePoint Designer, crear una lista de externa en la interfaz de usuario y tener la lista sin conexión en Outlook como un conjunto de contactos y poder realizar actualizaciones en Outlook y que esos cambios se hagan efectivos en el sistema externo.

Para crear los BCS disponemos de dos herramientas de diseño, SharePoint Designer 2010 y Visual Studio 2010. SharePoint Designer permite a los usuarios avanzados crear soluciones BCS, incluyendo la capacidad para definir tipos de contenido externos y listas externas para crear soluciones simples. Visual Studio proporciona al desarrollador profesional la capacidad de crear soluciones avanzadas aprovechando este marco.

Tipos de Contenido Externo

Lo primero que tenemos que crear para enlazar a datos externos es un Tipo de Contenido Externo. Este lo podemos crear desde nuestro sitio de SharePoint o directamente desde SharePoint Designer.

BCSDesigner_1

Por ejemplo, vamos a crear un BCS para conectar con los datos de los empleados de una aplicación de Recursos Humanos. Para ello creamos el External Content Type, le ponemos un nombre y seleccionamos el tipo de elemento que va a representar (en nuestro caso va a ser Contact, pero podría ser Generic List, Appoinment, Task o Post).

Ahora tenemos que enlazar este tipo de contenido externo con nuestro sistema externo, la aplicación de Recursos Humanos. Para conectarnos a estos sistemas tenemos tres opciones, .NET Type, SQL Server o WCF Service. Y creamos las operaciones que van a estar accesibles desde SharePoint.

BCSDesigner_2

Para crear las operaciones tenemos un asistente que nos permitirá elegir que campos van a devolverse en las consultas, que campos se van a actualizar e incluso nos permite mapear los campos de la entidad externa a los campos del tipo de contenido que hemos elegido, en este caso es Contact.

Una vez creado el Tipo de Contenido Externo podemos crear una nueva lista externa y utilizarla para visualizar el contenido o actualizarlo desde nuestro SharePoint o incluso desde nuestro Outlook.

BCSDesigner_3

Aunque BCS es una muy buena funcionalidad de SharePoint 2010, tendremos que ser cautos y probar bien la carga de trabajo con las conexiones externas para no encontrarnos con alguna sorpresa en nuestros servicios.

 

Saludos a todos…

Curso de Evolución del Acceso a Datos en Tenerife

La semana del 24 de Mayo se ha organizado un curso en Tenerife sobre la Evolución de la plataforma de Acceso a Datos en .NET y tendré el placer de impartirlo.

Este curso pretende dar a conocer los modelos actuales de acceso a datos en .NET Framework y como aplicarlos para acceder a bases de datos desde los distintos tipos de aplicaciones que permite la plataforma. Nos centraremos en la versión .NET Framework 4.0 y en Visual Studio 2010.

La duración es de 20 horas y el contenido será:

  1. Introducción a ADO.NET
  2. LINQ
  3. LINQ to SQL
  4. Entity Framework
  5. WCF Data Services

Esperamos que sea el primero de una larga serie de cursos en Tenerife orientados a .NET y Visual Studio 2010.

Si estáis interesados y necesitáis más información la tenemos en el Centro de Formación de General de Software de Canarias y en el documento del curso.

 

Saludos a todos…

SharePoint 2010. Web Application Text Message Service o Alertas por SMS

Dentro de las múltiples novedades que vamos a encontrar en SharePoint 2010, se encuentra la posibilidad de Enviar SMS desde SharePoint a un dispositivo móvil. Las ventajas de estos envíos, en lugar de los mensajes de correo electrónico son obvias: las alertas SMS o recordatorios que se reciben en los teléfonos móviles son más populares que los mensajes de correo electrónico que pueden perderse en el spam.

Si miramos en la configuración de nuestra Aplicación Web, en las opciones generales tenemos el menú Mobile Account que nos permite configurar el servicio SMS.

SP2010SMS_1

Este servicio tiene que cumplir con las especificaciones Office Mobile Service Protocol. Un protocolo definido por Microsoft para la transmisión de mensajes a móviles entre un cliente (SharePoint) y el servidor con conexión a la red de telefonía móvil. Vamos que han definido los métodos de un Servicio Web para el envío de SMS desde un cliente, que puede ser una aplicación web o, en este caso, SharePoint 2010.

Para configurarlo vamos a necesitar la url del servicio web, el usuario y la contraseña de acceso.

SP2010SMS_2

En Microsoft Office Online disponemos de los proveedores de servicio que permiten, previa suscripción de pago, el envío de SMS.

Una vez que tenemos todo configurado, SharePoint 2010 nos permite configurar las alertas para que sean enviadas por SMS.

SP2010SMS_3

Nota: Para que un usuario pueda configurar sus alertas, es necesario que se configure el Outgoing E-mail de la aplicación web.

Esta nueva característica nos permite integrar la plataforma con los dispositivos móviles, sin necesidad de que estos estén continuamente conectados a internet, con el consecuente ahorro de energía en los mismos.

 

Saludos a todos…

Ya tenemos RTM de SharePoint 2010

Microsoft acaba de publicar en la MSDN, sólo para suscriptores, la versión RTM de SharePoint 2010 y Office 2010.

Ya tenemos juguete nuevo y podemos empezar a prepararnos para el lanzamiento oficial el día 12 de mayo.

Os deja el enlace del post oficinal con el esperado anuncio http://blogs.msdn.com/sharepoint/archive/2010/04/16/sharepoint-2010-reaches-rtm.aspx

Actualizado. Si queremos actualizar los servicios OData de SharePoint, tenemos que instalar este update para ADO.NET Data Services http://www.microsoft.com/downloads/details.aspx?familyid=79d7f6f8-d6e9-4b8c-8640-17f89452148e&displaylang=en

Saludos a todos…

EF4. Consultas ordenadas aleatoriamente

Hace unos días @fisica3 intentaba migrar un proyecto en LINQ to SQL a Entity Framework 4. El problema surgía cuando intentaba agregar al modelo una función SQL Server del tipo Composable.

Este tipo de funciones no se pueden mapear en un modelo de entidades, para hacer uso de estas hay que construir el Query con Entity SQL y utilizar las funciones, por ejemplo:

   1: using (var context = new ObjectContext("Name=Entities"))

   2: {

   3:     ObjectQuery<EntidadResult> query = context.CreateQuery<EntidadResult>

   4:         (@"select value c.Nombre, Model.Store.GetApellidos(c.Apellido1, c.Apellidos2) from Entities.Clientes as c");

   5:  

   6:     ObjectResult<EntidadResult> result = query.Execute(MergeOption.NoTracking);

   7:  

   8:     foreach (var item in result)

   9:     {

  10:         Console.WriteLine("{0} {1}", item.Nombre, item.Apellidos);

  11:     }

  12: }

El problema se produce si la función que queremos utilizar es parte de la condición de ordenación, la idea es utilizar una función que devuelve un GUID aleatorio para que la consulta se ordene aleatoriamente.

   1: CREATE FUNCTION [dbo].[GetNewId]()

   2: RETURNS uniqueidentifier

   3: AS

   4: BEGIN

   5:   RETURN (SELECT ID FROM RandomView)

   6: END

   7:  

   8: CREATE VIEW [dbo].[RandomView]

   9: AS

  10: SELECT NEWID() As ID

Si creamos un DataContext con LINQ to SQL y mapeamos la función GetNewId en nuestro modelo, tenemos la posibilidad de realizar nuestra consulta utilizando como ordenación el valor devuelto por la función.

   1: using (var context = new MusicaDataClassesDataContext())

   2: {

   3:     var canciones = from c in context.Canciones

   4:                     orderby context.GetNewId()

   5:                     select c;

   6:  

   7:     foreach (var item in canciones)

   8:     {

   9:         Console.WriteLine("{0} {1}", item.CancionId, item.Album);

  10:     }

  11: }

Entonces, ¿cómo podemos realizar esta consulta con Entity Framework?.

Entity Framework nos permite hacer uso de la expresión LET, que se utiliza para introducir una variable en el ámbito que puede ser utilizado por las siguientes cláusulas de consulta.  Similar a las variables locales en un cuerpo de método, esto le da una manera de evitar evaluar una expresión común de varias veces por almacenarlo en una variable.  Esto puede ser muy útil incluso en consultas mucho más simples.

Podemos crearnos una consulta con la expresión LET que contengan un valor aleatorio y este sería utilizado para la ordenación (algo parecido a la función que utilizamos en LINQ to SQL).

   1: using (var context = new MusicaEntities())

   2: {

   3:     var musica = from p in context.Canciones

   4:                  let guid = Guid.NewGuid()

   5:                  orderby guid

   6:                  select p;

   7:  

   8:     foreach (var item in musica)

   9:     {

  10:         Console.WriteLine("{0} - {1}", item.CancionId, item.Album);

  11:     }

  12: }

Entity Framework transforma el query anterior, con el let guid = Guid.NewGuid(), en la siguiente sentencia T-SQL, que contiene un subquery con la generación de nuestro GUID.

   1: SELECT 

   2: [Project1].[CancionId] AS [CancionId], [Project1].[Artista] AS [Artista], 

   3: [Project1].[Album] AS [Album], [Project1].[Titulo] AS [Titulo], [Project1].[AñoAlbum] AS [AñoAlbum], 

   4: [Project1].[AñoRanking] AS [AñoRanking], [Project1].[Ubicacion] AS [Ubicacion], [Project1].[Ranking99] AS [Ranking99]

   5: FROM ( SELECT 

   6:     [Extent1].[CancionId] AS [CancionId],     [Extent1].[Artista] AS [Artista], 

   7:     [Extent1].[Album] AS [Album],     [Extent1].[Titulo] AS [Titulo], 

   8:     [Extent1].[AñoAlbum] AS [AñoAlbum],     [Extent1].[AñoRanking] AS [AñoRanking], 

   9:     [Extent1].[Ubicacion] AS [Ubicacion],     [Extent1].[Ranking99] AS [Ranking99], 

  10:     NEWID() AS [C1]

  11:     FROM [dbo].[Canciones] AS [Extent1]

  12: )  AS [Project1]

  13: ORDER BY [Project1].[C1] ASC

Como vemos en la consulta transact, la función del CLR Guid.NewGuid se ha transformado en un NEWID() de T-SQL, con lo que se realiza la ordenación por ese campo aleatorio por fila.

Aunque no podamos utilizar la función que nos devuelve un valor aleatorio del tipo GUID, Entity Framework tiene la herramienta necesaria para realizar este método sin necesidad de utilizar la función.

 

Saludos a todos…

SharePoint, jQuery y la Experiencia de Usuario

jQuery es un  framework de Javascript que nos permite realizar ciertas maldades en SharePoint para mejorar la experiencia del usuario.

jquery

Una de las grandes ventajas de SharePoint es que es una aplicación extensible. Podemos crear o editar los formularios estándares de las listas, podemos crear Web Parts de usuarios, nuevos tipos de datos y definir cual es la interfaz html que implementa el tipo, etc.

Toda esta potencia en forma de extensibilidad no es nada si no le aplicamos una experiencia de usuario buena, y para esto podemos utilizar jQuery.

Por ejemplo, SharePoint nos permite definir campos de búsquedas que se renderizan como Combos y permiten al usuario seleccionar un elemento de una lista externa. Cuando tenemos más de 10 elementos a buscar, la usabilidad de ese Combo deja mucho que desear,  ¿por qué no añadir un elemento al formulario que nos permite buscar ese elemento y en el caso que se encuentren más de un resultado, que le de la elección al usuario, en un Modal Popup, con la lista de elementos encontrados?.

sharepoint_jquery_1

Cuando el usuario escribe un término de búsqueda y pulsa enter o hace click en la imagen, si el resultado no es único, obtiene un Modal Popup con el resultado de la búsqueda para que seleccione el elemento que estaba buscando.

sharepoint_jquery_2

Una vez seleccionado y para mejorar aún su experiencia, se le incluye un resumen a la derecha del formulario de alta.

sharepoint_jquery_3

Estas mejoras se han desarrollado modificando el formulario de alta NewForm.aspx inyectando código con jQuery. Se han utilizado varias librerías, entre las que se encuentra una librería que nos permite consultar elementos de listas de SharePoint utilizando jQuery y los servicios web de este (SPServices).

   1: <script language="javascript" type="text/javascript" src="/jquery/jquery-1.4.2.min.js"></script>

   2: <script language="javascript" type="text/javascript" src="/jQuery/jquery.SPServices-0.5.2.min.js" ></script>

   3: <script language="javascript" type="text/javascript" src="/jQuery/jquery-ui.js"></script>

   4: <script language="javascript" type="text/javascript" src="/jQuery/Load.js"></script>

Cuando se carga el formulario se inyecta el label, input y button para la búsqueda.

   1: $(document).ready(function() {

   2:  

   3:     var trow = $("<tr>");

   4:  

   5:     $("<h3>").addClass("ms-standardheader")

   6:         .text("Buscar")

   7:         .appendTo(

   8:             $("<td>").addClass("ms-formlabel")

   9:                 .css("width", "190")

  10:                 .attr("noWrap", "nowrap")

  11:                 .attr("vAlign", "top")

  12:                 .appendTo(trow)

  13:         );

  14:     $("<td>").addClass("ms-formbody")

  15:         .html("<div style='float:right; margin:2px 10px 0 0;'><img id='btSearch' src='_layouts/images/checkitems.gif' alt='Comprobar' title='Comprobar' style='cursor:pointer;' /></div>' +

  16:             "<span dir="none"><input id="tfBuscar" name="tfBuscar" title="Buscar" class="ms-long" type="text" maxLength="255" style='width:365px' /></span>' +

  17:             "<div id='progressSearch' style='display: none;'>Buscando...</div>')

  18:         .css("width", "400")

  19:         .attr("vAlign", "top")

  20:         .appendTo(trow);

  21:  

  22:     $($(".ms-formtable").children("tbody").children("tr")[0]).before(trow);

  23:  

  24:     $("#btSearch").click(function() {

  25:         clickImageButtonService("Buscar");

  26:     });

  27:     $("#tfBuscar").focus().keypress(function(event) {

  28:         if ((event.keyCode && event.keyCode == '13') || (event.which && event.which == '13')) {

  29:             event.preventDefault();

  30:             clickImageButtonService("Buscar");

  31:         }

  32:     });

La función de búsqueda utiliza AJAX para realizar la consulta a un servicio WCF JSON externo (obtiene los datos un sistema de información externo) y los muestra. Si el resultado de la búsqueda es mayor que uno, se muestra el Modal Popup.

   1: function clickImageButtonService(displayName) {

   2:     indicatorID = '#progressSearch'; 

   3:     var valueQuery = $("input:[title='" + displayName + "']").val();

   4:     var params = { "search": valueQuery };

   5:     var urlService =  "http://server/test/service.svc/json/Search";

   6:  

   7:     if (valueQuery != "" && valueQuery != "undefined") {

   8:         var incObj = $("#incResult");

   9:         if (incObj != null && incObj != undefined)

  10:             incObj.remove();    

  11:             

  12:         $.ajax({

  13:             type: "GET",

  14:             url: urlService,

  15:             data: params,

  16:             contentType: "application/json; charset=utf-8",

  17:             dataType: "json",

  18:             success: successFunction,

  19:             error: function(xhr, err) {

  20:                 alert("Se ha producido un error al realizar la búsqueda.rnrnEstado del proceso: " + 

  21:                 xhr.readyState + "rnCódigo de estado: " + xhr.status +

  22:                 "rnMensaje de respuesta: " + xhr.responseText); 

  23:             }

  24:         });

  25:     }

  26: }

Esta es la función que se ejecuta cuando la llamada asíncrona al servicio es satisfactoria.

   1: function successFunction(msg) {

   2:     if (msg != null) {

   3:         if (msg.d.length == 0) {

   4:             initFields();

   5:             var divObj = $("#errorMesage");

   6:             if (divObj.html() != null && divObj != undefined) {

   7:                 divObj.css("display","block");

   8:             }

   9:             else {

  10:                 $("#tfBuscar").parent("span")

  11:                     .append("<div id='errorMesage' class='ms-error'>No sé encontró ningún elemento. Realice una nueva búsqueda.</div>");

  12:             }

  13:         }

  14:         else {

  15:             var divObj = $("#errorMesage");

  16:             if (divObj != null && divObj != undefined) {

  17:                 divObj.css("display","none");

  18:             }

  19:             if (msg.d.length > 1) {

  20:                 createDynamicTableServices(msg.d);

  21:                 $("#modalPopUp td img")

  22:                     .click(function() {

  23:                         fillFieldsFromTable($(this).parent("td").parent("tr"));

  24:                         $("#modalPopUp").dialog('close');

  25:                     });

  26:                 $("#modalPopUp").dialog({ resizable: false, draggable: false, height: 400, width: 600, modal: true, closeOnEscape: false });

  27:             }

  28:             else {

  29:                     fillFieldsFromDataServices(msg.d);

  30:             }

  31:         }

  32:     }

  33: }

Para mostrar el resumen del elemento encontrado en la búsqueda, se realiza una llamada al método GetListItems del servicio web de lista de SharePoint (Lists.asmx). SPServices nos permite utilizar los servicios de SharePoint pero como si fueran funciones jQuery, tan solo tenemos que construir el CAML con la consulta y los campos que queremos recibir de la lista.

   1: function searchIncidencias(numero) {

   2:  

   3:     var queryString = "<Query><Where><And>" +

   4:         "<Contains><FieldRef Name='Numero' /><Value Type='Text'>" + numero + "</Value></Contains>" +

   5:         "<Or><Eq><FieldRef Name='Estado' /><Value Type='Lookup'>Iniciada</Value></Eq>" +

   6:         "<Eq><FieldRef Name='Estado' /><Value Type='Lookup'>En curso</Value></Eq></Or>" +

   7:         "</And></Where></Query>";

   8:  

   9:     var viewFields = "<ViewFields><FieldRef Name="ID"></FieldRef><FieldRef Name="Title"></FieldRef><FieldRef Name="Serie"></FieldRef>" +

  10:             "<FieldRef Name="Estado"></FieldRef><FieldRef Name="Fecha_x0020_Alta"></FieldRef></ViewFields>";

  11:     

  12:     $().SPServices({

  13:         operation: "GetListItems",

  14:         listName: "Solicitudes",

  15:         async: false,

  16:         CAMLViewFields: viewFields,

  17:         CAMLQuery: queryString, 

  18:         completefunc: completeSearchIncidencias

  19:     });

  20: }

  21:  

  22: function completeSearchIncidencias(xData, Status) {

  23:     var tdHtml = "<td valign="top"><div id="incResult" style='margin-left: 5px; margin-top: 29px;' > </div></td>';

  24:     $($("#onetIDListForm").children("tbody").children("tr")[0]).append(tdHtml);

  25:     var tbody = $("<table>").attr("cellspacing", "0").attr("cellpadding", "0");

  26:     var trow = $("<tr>");

  27:     $("<td>").addClass("ms-toolbarContainer")

  28:             .html("<span class="ms-toolbar" style='padding: 2px 2px 2px 2px; height = 18px;'>Incidencias activas</span>')

  29:             .attr("colspan", "2").appendTo(trow);

  30:     trow.appendTo(tbody);

  31:     $("<td>").html("<img height="5" width="1" alt="" src="/_layouts/images/blank.gif">").attr("colspan", "2").appendTo($("<tr>").appendTo(tbody));

  32:  

  33:     if ($(xData.responseXML).find("z\:row").length > 0) {

  34:         $(xData.responseXML).find("z\:row").each(function() {

  35:             trow = $("<tr>");

  36:             var incidencia = getEnlaceIncidencia($(this).attr("ows_ID"), $(this).attr("ows_Title"));

  37:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Número</h3>").appendTo(trow);

  38:             $("<td>").addClass("ms-formbody").html(incidencia).appendTo(trow);

  39:             trow.appendTo(tbody);

  40:             trow = $("<tr>");

  41:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Número serie</h3>").appendTo(trow);

  42:             $("<td>").addClass("ms-formbody").text($(this).attr("ows_N_x00fa_mero_x0020_Serie")).appendTo(trow);

  43:             trow.appendTo(tbody);

  44:             trow = $("<tr>");

  45:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Fecha Alta</h3>").appendTo(trow);

  46:             var fecha = $(this).attr("ows_Fecha_x0020_Alta");

  47:             formatDate(fecha);

  48:             $("<td>").addClass("ms-formbody").text(formatDate(fecha)).appendTo(trow);

  49:             trow.appendTo(tbody);

  50:             trow = $("<tr>");

  51:             $("<td>").addClass("ms-formlabel").html("<h3 class="ms-standardheader">Estado</h3>").appendTo(trow);

  52:             $("<td>").addClass("ms-formbody").text($(this).attr("ows_Estado").split("#")[1]).appendTo(trow);

  53:             trow.appendTo(tbody);

  54:             $("<td>").addClass("ms-formline").html("<img height="5" width="1" alt="" src="/_layouts/images/blank.gif">").attr("colspan", "2").appendTo($("<tr>").appendTo(tbody));

  55:         });

  56:     }

  57:     else {

  58:         trow = $("<tr>");

  59:         var incidencia = getEnlaceIncidencia($(this).attr("ows_ID"), $(this).attr("ows_Title"));

  60:         $("<td>").addClass("ms-formlabel").attr("colspan", "2").text("No encontradas incidencias activas").appendTo(trow);

  61:         trow.appendTo(tbody);

  62:         $("<td>").addClass("ms-formline").html("<img height="5" width="1" alt="" src="/_layouts/images/blank.gif">").attr("colspan", "2").appendTo($("<tr>").appendTo(tbody));

  63:     }

  64:     $("#incResult").append(tbody);

  65: }

  66:  

Gracias a jQuery hemos sido capaces de mejorar notablemente la experiencia del usuario y que nuestra lista de SharePoint sea más usable.

Aunque el mundo de jQuery, este código lo dice todo, es un poco singular, tengo que reconocer que la facilidad de realizar estas modificaciones me ha asombrado gratamente. Una vez que hemos realizado estos cambios, me he propuesto hacerle más seguimiento a jQuery y buscarle más utilidades en SharePoint.

 

Saludos a todos…

Operadores LINQ

LINQ (Language INtegrated Query) nos permite escribir consultas (parecidas a consultas SQL) sobre nuestras colecciones (List, IEnumerable, …). Con estos operadores estándares, podremos hacer consultas determinando si existe un tipo de elemento o realizar una suma de la secuencia. Básicamente nos permite filtrar, enumerar y crear proyecciones de varios tipos de colecciones.

En LINQ tenemos un amplio conjunto de operadores. La siguiente tabla muestra los operadores de LINQ y una breve descripción de la funcionalidad que ofrecen.

Operador

Descripción
Agregación  
Aggregate Realiza un método personalizado sobre una secuencia
Average Calcula el promedio de una secuencia de valores numéricos
Count Devuelve el número de los elementos de una secuencia como un int
LongCount Devuelve el número de los elementos de una secuencia como un largo
Min Encuentra el número mínimo de una secuencia de números
Max Encuentra el número máximo de una secuencia de números
Sum Suma de los números en una secuencia
   
Concatenación  
Concat Concatena dos secuencias en una secuencia
   
Conversión  
Cast Convierte elementos de una secuencia en otro tipo dado
OfType Filtra los elementos de una secuencia de un determinado tipo
ToArray Devuelve un Array de una secuencia
ToDictionary Devuelve un Dictionary de una secuencia
ToList Devuelve un List de una secuencia
ToLookup Devuelve un Lookup de una secuencia
ToSequence Devuelve un IEnumerable de una secuencia
   
Elemento  
DefaultIfEmpty Crea un elemento predeterminado para una secuencia vacía
ElementAt Devuelve el elemento de un índice determinado en una secuencia
ElementAtOrDefault Devuelve el elemento de un índice determinado en una secuencia o un valor predeterminado si el índice está fuera del intervalo
First Devuelve el primer elemento de una secuencia
FirstOrDefault Devuelve el primer elemento de una secuencia o un valor predeterminado si no se encuentra ningún elemento
Last Devuelve el último elemento de una secuencia
LastOrDefault Devuelve el último elemento de una secuencia o un valor predeterminado si no se encuentra ningún elemento
Single Devuelve el único elemento de una secuencia
SingleOrDefault Devuelve el único elemento de una secuencia o un valor predeterminado si no se encuentra ningún elemento
   
Igualdad  
SequenceEqual Compara dos secuencias para ver si son equivalentes
   
Generación  
Empty Genera una secuencia vacía
Range Genera una secuencia dado un rango
Repeat Genera una secuencia repitiendo un elemento un número determinado de veces
   
Agrupación  
GroupBy Agrupa elementos de una secuencia por una agrupación dada
   
Enlazando  
GroupJoin Realiza una combinación agrupada de dos secuencias
Join una combinación interna de dos secuencias
   
Ordenación  
OrderBy Ordena una secuencia por valor (s) en orden ascendente
OrderByDescending Ordena una secuencia por valor (s) en orden de descendente
ThenBy Ordena una secuencia que ya se ha ordenado en orden ascendente
ThenByDescending Ordena una secuencia que ya se ha ordenado en orden descendente
Reverse Invierte el orden de los elementos de una secuencia
   
Particionado  
Skip Devuelve una secuencia que omite un número determinado de elementos
SkipWhile Devuelve una secuencia que omite elementos que no cumplen una expresión
Take Devuelve una secuencia que toma un número determinado de elementos
TakeWhile Devuelve una secuencia que toma elementos que cumplen una expresión
   
Proyección  
Select Crea una proyección  a partir de una secuencia
SelectMany Crea una proyección One to Many de elementos de una secuencia
   
Cuantificadores  
All Determina si todos los elementos de una secuencia cumplen una condición
Any Determina si los elementos de una secuencia cumplen una condición
Contains Determina si una secuencia contiene un elemento determinado
   
Restricción  
Where Filtra los elementos de una secuencia
   
Conjuntos  
Distinct Devuelve una secuencia sin elementos duplicados
Except Devuelve una secuencia que representa la diferencia entre dos secuencias
Intersect Devuelve una secuencia que representa la intersección de dos secuencias
Union Devuelve una secuencia que representa la unión de dos secuencias

 Vamos a ver algunos ejemplos de código:

Average

   1:  int[] nums = new int[] {1,2,3,4,5,6,7,8,9};
   2:  double avg = nums.Average();
Calculamos la media de los enteros del array.
 

Where

   1:  IEnumerable<Customer> customersInTenerife = customers.Where(c => c.City == "Tenerife");

Buscamos los clientes cuya ciudad sea Tenerife.

 

Take

   1:  IEnumerable<Product> MostExpensive10 =
   2:      products.OrderByDescending(p => p.UnitPrice).Take(10);
En este ejemplo, ordenamos los productos descendentemente por UnitPrice y luego 
seleccionamos 10 productos.

 

Skip

   1:  IEnumerable<Product> AllButMostExpensive10 =
   2:      products.OrderByDescending(p => p.UnitPrice).Skip(10);

 

Aquí ordenamos, de nuevo, descendentemente por UnitPrice y seleccionamos todos los elementos menos los 10 más caros.

 

Join

   1:  var custOrders =
   2:      customers.Join(orders, c => c.CustomerID, o => o.CustomerID,
   3:                               (c, o) => new { c.Name, o.OrderDate, o.Total }
   4:                                );

 

Enlazamos los clientes con sus facturas y devolvemos una lista con el nombre del cliente, la fecha de la factura y el total facturado.

 

OrderBy/ThenBy

   1:  IEnumerable<Product> orderedProducts =
   2:      products.OrderBy(p => p.Category).
   3:                     ThenByDescending(p => p.UnitPrice).
   4:                     ThenBy(p => p.Name);

 

La siguiente consulta ordena la lista de productos por Category, luego por UnitPrice descendiente y por último por Name.

 

GroupBy

   1:  IEnumerable<IGrouping<string, Product>> productsByCategory =
   2:      products.GroupBy(p => p.Category);

 

Podemos agrupar los productos y obtener un IEnumerable agrupado que contenga la Category y los productos que corresponden a esa Category.

 

ToDictionary

   1:  Dictionary<int,Order> orders =
   2:      customers.SelectMany(c => c.Orders).
   3:                Where(o => o.OrderDate.Year == 2005).
   4:                ToDictionary(o => o.OrderID);

Creamos un Dictionary seleccionando las Facturas de los clientes del año 2005, utilizando como clave el OrderID.

 

Any

   1:  bool result = customers.Any(c => c.Name == "Pepe");

 

Devuelve True si encuentra algún cliente con el Name Pepe.

 

ForEach

   1:  List<customer> customers = GetCustomers();
   2:  customers.ForEach(p => Insertar(p));

Para cada cliente de la lista customers, ejecutamos el método Insertar pasándole por parámetro el elemento actual.

 

Si queremos más información sobre LINQ, lo mejor es pasarse por la MSDN y leernos la documentación o alguno de los tutoriales que tenemos sobre él.

Este es uno de los grandes avances que Microsoft incluyó con el Framework 3.5 y que nos facilita bastante la vida y nos permite programar mejor y más rápido.

Espero que disfrutéis tanto como yo escribiendo código y que lo hagáis más con LINQ, os lo recomiendo.

 

Saludos a todos…

¿EF vs NHibernate? ¿Esa es la pregunta?

El martes 16 estuve hablando de Entity Framework en el Webcast de SecondNug. La idea del Webcast era crear cultura de modelado de entidades sobre modelos relacionales y fomentar el uso de los ORM.

Una de las slides que presenté enseñaba una comparativa de uso entre Entity Framework y NHibernate.

efvsnh_1

Esta comparativa es del 2009 y ya han realizado una revisión de la misma, ya que los desarrolladores de NHibernate comentaron que no se realizó en igualdad de condiciones. Podemos consultar esta revisión en el siguiente post.

El objetivo de esta slide pasaba porque no le diéramos más importancia a un orm o a otro en función de su rendimiento, si analizamos los datos podemos extraer que EF es más potente en algunas cuestiones y que NHibernate lo es en otras. Aún así, siempre encontraremos algún método en el que utilizando algún tipo de artefacto que el otro no emplee se obtendrá un mejor rendimiento.

Creo que tanto NHibernate como Entity Framework son dos grandes ORM y que utilicemos uno u otro tendría que ser cuestión de evaluar las herramientas y diseñadores que tenemos para trabajar con ellos.

Igual que la gente de NHibernate comentó que el test no se realizó en igualdad de condiciones, los desarrolladores de Enfity Framework podrán decir que por qué no se realizaron los test con Entity Framework 4, que las consultas que utilizaron en el test no son las óptimas, etc.

Esta no es nuestra guerra, nuestra guerra pasa porque empecemos a utilizar un ORM en nuestras aplicaciones y que modelemos nuestros sistemas.

Me iba a poner a mejorar el código de test y actualizarlo a EF4, pero creo que no vale la pena seguir con estas comparativas. Mi elección va a ser Entity Framework 4 porque las herramientas que tenemos en Visual Studio 2010 son casi insuperables y porque la curva de aprendizaje es inferior a utilizar otro framework.

 

Saludos a todos…

Consolas SharePoint en x64

Ahora que se acerca la nueva versión de SharePoint 2010, empezaremos a prepara migraciones y scripts para pasar de una versión a otra, o incluso para obtener listados de sitios, usuarios, permisos, etc.

Para este tipo de cosas, solemos crearnos una aplicación de consola y así poder utilizar el modelo de objetos de SharePoint 2007 y realizar las consultas.

Si tenemos hecha la tarea y nuestra granja de SharePoint 2007 es una granja x64, cuidado con la siguiente excepción cuando ejecutemos estas aplicaciones de consola:

Unhandled Exception: System.IO.FileNotFoundException: The Web application at http://sp2007 could not be found. Verify that you have typed the URL correctly. If the URL should be serving existing content, the system administrator may need to add a new request URL mapping to the intended application.

No nos pongamos a cambiar la URL de nuestro código (SPSite site = new SPSite(SITEURL)), este no es el problema. Seguro que conocemos perfectamente la URL de nuestra granja y la estamos escribiendo bien (hay que revisarla por si acaso).

El problema reside en el Platform target de nuestra aplicación de consola. Nuestra aplicación se está compilando para x86 y no para x64 con lo que no le es posible acceder al modelo de objetos de nuestro servidor que está en x64.

sharepointx64console_1

Sólo tenemos que cambiar el Platform taget to x64 o Any CPU y listo, compilamos nuestra aplicación y la podemos ejecutar sin problemas en uno de los servidores de la granaja x64.

 

Saludos a todos…