Miguel Angel Morán

Machines take me by surprise with great frecuency...

Sharepoint: Mi único punto de vista oficial

 

     

     

     SharePoint.

    Mi único comentario

     

    Uso el twitter como un confidente virtual, en él generalmente pongo cosas poco relevantes de mi vida, desde mis corajes con los franeleros, hasta las canciones que canto al trabajar, poemitas lerdos y pensamientos aleatorios. En ocasiones he twitteado sobre SharePoint y las frustraciones con las que me he encontrado como desarrollador, o al enfrentarme con los requerimientos del usuario aplicados a SharePoint.

     

    Derivado de estos twitts he recibido ya varios comentarios de mis frustraciones al desarrollar con SharePoint y creo que ante esto, lo menos que puedo hacer es justificar desde un punto de vista técnico lo que a mi NO me gusta de SharePoint y también lo que SÍ me gusta del mismo.

     

    Creo que es necesaria una clarificación, sin duda alguna es TOTALMENTE INJUSTO decir que SharePoint apesta por el sólo hecho de decirlo. Mi contexto es que al ser una persona que durante 13 años ha dedicado su vida profesional  a trabajar con la plataforma Microsoft, SharePoint, se ha convertido en el primer (y único) producto al cual le pido mucho más de lo que tiene, insisto como plataforma de desarrollo, no como producto Out of the box. No es una satanización del producto ni mucho menos.

     

    Este post tiene como objetivo clarificar mi visión y mi propuesta sobre SharePoint. Un producto tan exitoso como controvertido, excelentemente posicionado y que sigue con una inercia impresionante implantándose en muchas organizaciones.

     

    Debo empezar con algo:

     

    SharePoint (tanto WSS como MOSS) es un EXCELENTE producto para usuario final, en minutos es posible crear intranets completas, colaborativas, búsquedas empresariales, calendarios, documentos, flujos de trabajo y muchas cosas más sin una sóla línea de código.

     

    SharePoint es una plataforma habilitadora para la colaboración, creo que la competencia de Microsoft no tiene algo similar. SharePoint es un cohete para generar intranets y hasta sitios web en cuestión completamente funcionales en cuestión no de días, sino de horas. SharePoint es un HABILITADOR de automatización y del trabajo en equipo mediante el uso de la tecnología

     

    En el lugar donde se me emplea, se usa SharePoint para el control de las horas y el presupuesto, he vendido SharePoint en muchas empresas como la base tecnológica para sus necesidades de colaboración. Cuando se trata de colaborar o de hacer intranets, mi primera opción siempre es recomendar SharePoint, estoy totalmente convencido, he hecho muchas propuestas a grandes organizaciones proponiendo SharePoint como su plataforma de colaboración. El ecosistema modular de SharePoint (MOSS, Forms, PerformancePoint, Project Server, ahora hasta Commerce Server y un largo etc.) utiliza la plataforma WSS para generar aplicaciones funcionales en cuestión de horas, crece y se ajusta a las necesidades de la empresa.

     

    Además de esto Microsoft ha hecho otro EXCELENTE trabajo al posicionar la plataforma en muchas empresas y cada vez es más el furor por esta plataforma, que independientemente de la mercadotecnia, es un gran producto colaborativo, y así de sencillo: EL MEJOR (Notes, Domino, Community Server, Opengroupware y muchas otras plataformas se quedan totalmente cortas ante el poder habilitador de SharePoint)

     

    SharePoint es, desde mi punto de vista, una idea maravillosa, hecha realidad en muchos aspectos. Pero ¿qué le hace falta para convertirse en una verdadera plataforma de desarrollo?

     

    Yo opino que es HACER AMIGABLE SHAREPOINT AL DESARROLLADOR. No hablo del producto ni de la idea. Hablo exclusivamente de la experiencia del developer con la plataforma.

     

     SharePoint es una excelente herramienta, pero cuando hay que llevar más allá un requerimiento al funcionamiento out of the box, uno tiene que convertirse en un verdadero experto y conocer las tripas tripas tripas del producto, lo cual no es malo, pero sin duda impedirá el posicionamiento de MOSS/WSS como una verdadera plataforma de desarrollo.

     

    ¿Qué peros le veo al producto como plataforma de desarrollo?

     

     

  1. Es necesario programar en una versión de servidor de Windows. Esto es complicado, ya que en muchos corporativos (lo digo porque ya van 3 veces que me pasa) hay políticas restrictivas  respecto a las computadoras y no se pueden estar levantando ambientes a la ligera.
  2. Nativamente la programación nunca es visual (hay parches de terceros como smartpart por ejemplo) pero generalmente todo es "a manita"
  3. La instalación de un cambio, feature, workflow etc. es una experiencia complicada, desde  los archivos de configuración y xml, los strong names, el stsadm y su gran número de modificadores.
  4. El modelo de objetos es confuso, recibir GUIDS como parámetros para casi todo es una experiencia complicadísima y poco amigable, cosas realmente triviales se convierten en complicadas. Caso de ejemplo: El comportamiento de los calendarios de Sharepoint, permite nativamente introducir muchas citas al mismo tiempo, sin embargo el requerimiento dice que sólo se puede agendar una cita simultánea. ¿Se oye complicado el requerimiento?... Bueno, la solución a algo tan trivial, en SharePoint se convierte a algo más o menos así:
  5. using System;

    002.using Microsoft.SharePoint;

    003.  

    004.  

    005.namespace Webcoda.WSS.Calendar.Events

    006.{

    007.    class PreventDoubleBooking: SPItemEventReceiver

    008.    {

    009.        /// <summary>

    010.        /// This event is triggered when the user adds a new item

    011.        /// </summary>

    012.        /// <param name="properties"></param>

    013.        public override void ItemAdding(SPItemEventProperties properties)

    014.        {

    015.            //Our query string variable

    016.            string strQuery = null;

    017.  

    018.            try

    019.            {

    020.                //Get the Sharepoint site instance

    021.                using (SPWeb oWebsite = new SPSite(properties.SiteId).OpenWeb(properties.RelativeWebUrl))

    022.                {

    023.                      

    024.                    //Get the collection of properties for the Booking item

    025.                    SPListItemCollection collItems = oWebsite.Lists[properties.ListTitle].Items;

    026.  

    027.                    //Get the Calendar List that we will be querying against

    028.                    SPList calendar = oWebsite.Lists[properties.ListId];

    029.  

    030.                    //Get the internal name of the fields we are querying. 

    031.                    //These are required for the CAML query

    032.                    string start_internal = collItems.List.Fields["Start Time"].InternalName;

    033.                    string end_internal = collItems.List.Fields["End Time"].InternalName;

    034.                    string MeetingRoom_Internal = collItems.List.Fields["Meeting Room"].InternalName;

    035.  

    036.                    //Get the query string parameters

    037.                    string start_str = properties.AfterProperties[start_internal].ToString();

    038.                    string end_str = properties.AfterProperties[end_internal].ToString();

    039.                    string MeetingRoom_str = properties.AfterProperties[MeetingRoom_Internal].ToString();

    040.  

    041.                    //Construct a CAML query

    042.                    SPQuery query = new SPQuery();

    043.  

    044.                    //Create the CAML query string that checks to see if the booking we are attemping

    045.                    //to add will overlap any existing bookings

    046.                    strQuery = string.Format(@"

    047.  

    048.    <Where>

    049.      

    050.        <And>

    051.            <Or>

    052.              

    053.                <Or>

    054.                    <And>

    055.                       <Leq>

    056.                          <FieldRef Name='EventDate' />

    057.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    058.                       </Leq>

    059.  

    060.                       <Gt>

    061.                          <FieldRef Name='EndDate' />

    062.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    063.                       </Gt>

    064.                    </And>

    065.  

    066.                    <And>

    067.                       <Lt>

    068.                          <FieldRef Name='EventDate' />

    069.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    070.                       </Lt>

    071.  

    072.                       <Geq>

    073.                          <FieldRef Name='EndDate' />

    074.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    075.                       </Geq>

    076.                    </And>

    077.                </Or>

    078.                  

    079.                <Or>

    080.                    <And>

    081.                       <Leq>

    082.                          <FieldRef Name='EventDate' />

    083.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    084.                       </Leq>

    085.  

    086.                       <Geq>

    087.                          <FieldRef Name='EndDate' />

    088.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    089.                       </Geq>

    090.                    </And>

    091.  

    092.                    <And>

    093.                       <Geq>

    094.                          <FieldRef Name='EventDate' />

    095.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    096.                       </Geq>

    097.  

    098.                       <Leq>

    099.                          <FieldRef Name='EndDate' />

    100.                          <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    101.                       </Leq>

    102.                    </And>

    103.                </Or>

    104.                  

    105.            </Or>

    106.          

    107.            <Eq>

    108.                <FieldRef Name='Meeting_x0020_Room' />

    109.                <Value Type='Choice'>{2}</Value>

    110.            </Eq>

    111.              

    112.        </And>

    113.          

    114.    </Where>

    115.    <OrderBy>

    116.        <FieldRef Name='EventDate' />

    117.    </OrderBy>

    118.", start_str, end_str, MeetingRoom_str);

    119.  

    120.                    //Set the query string for the SPQuery object

    121.                    query.Query = strQuery;

    122.  

    123.                    //Execute the query against the Calendar List

    124.                    SPListItemCollection existing_events = calendar.GetItems(query);

    125.                      

    126.                    //Check to see if the query returned any overlapping bookings

    127.                    if (existing_events.Count > 0)

    128.                    {

    129.                        //Cancels the ItemAdd action and redirects to error page

    130.                        properties.Cancel = true;

    131.  

    132.                        //Edit the error message that will display on the error page

    133.                        properties.ErrorMessage += "This booking cannot be made because of one or more bookings in conflict. <BR><BR>";

    134.  

    135.                        //Here you can loop through the results of the query

    136.                        //foreach (SPListItem oListItem in existing_events)

    137.                        //{

    138.                        //   ....

    139.                        //}

    140.  

    141.                        properties.ErrorMessage += "Please go back and schedule a new time.";

    142.                    }

    143.                      

    144.                }

    145.            }

    146.            catch (Exception ex)

    147.            {

    148.                //Cancels the ItemAdd action and redirects to error page

    149.                properties.Cancel = true;

    150.  

    151.                //Edit the error message that will display on the error page

    152.                properties.ErrorMessage = "Error looking for booking conflicts: " + ex.Message;

    153.            }

    154.            

    155.        }

    156.  

    157.        /// <summary>

    158.        /// This event is triggered when the user edits an calendar item

    159.        /// </summary>

    160.        /// <param name="properties"></param>

    161.        public override void ItemUpdating(SPItemEventProperties properties) {

    162.  

    163.            string strQuery = null;

    164.  

    165.            try {

    166.  

    167.                //Get the Sharepoint site instance

    168.                using (SPWeb oWebsite = new SPSite(properties.SiteId).OpenWeb(properties.RelativeWebUrl)) {

    169.  

    170.                    //Get the collection of properties for the Booking item

    171.                    SPListItemCollection collItems = oWebsite.Lists[properties.ListTitle].Items;

    172.  

    173.                    //Get the Calendar List that we will be querying against

    174.                    SPList calendar = oWebsite.Lists[properties.ListId];

    175.  

    176.                    //Get the internal name of the fields we are querying. 

    177.                    //These are required for the CAML query

    178.                    string start_internal = collItems.List.Fields["Start Time"].InternalName;

    179.                    string end_internal = collItems.List.Fields["End Time"].InternalName;

    180.                    string MeetingRoom_Internal = collItems.List.Fields["Meeting Room"].InternalName;

    181.                    string guid_internal = collItems.List.Fields["GUID"].InternalName;

    182.  

    183.                    //Get the query string parameters

    184.                    string start_str = properties.AfterProperties[start_internal].ToString();

    185.                    string end_str = properties.AfterProperties[end_internal].ToString();

    186.                    string MeetingRoom_str = properties.AfterProperties[MeetingRoom_Internal].ToString();

    187.                    string guid_str = properties.AfterProperties[guid_internal].ToString();

    188.  

    189.                    //Construct a CAML query

    190.                    SPQuery query = new SPQuery();

    191.  

    192.                    //Create the CAML query string that checks to see if the booking we are attemping

    193.                    //to change will overlap any existing bookings, OTHER THAN ITSELF

    194.                    strQuery = string.Format(@"

    195.  

    196.    <Where>

    197.        <And>

    198.          

    199.            <And>

    200.                <Or>

    201.                  

    202.                    <Or>

    203.                        <And>

    204.                           <Leq>

    205.                              <FieldRef Name='EventDate' />

    206.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    207.                           </Leq>

    208.  

    209.                           <Gt>

    210.                              <FieldRef Name='EndDate' />

    211.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    212.                           </Gt>

    213.                        </And>

    214.  

    215.                        <And>

    216.                           <Lt>

    217.                              <FieldRef Name='EventDate' />

    218.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    219.                           </Lt>

    220.  

    221.                           <Geq>

    222.                              <FieldRef Name='EndDate' />

    223.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    224.                           </Geq>

    225.                        </And>

    226.                    </Or>

    227.                      

    228.                    <Or>

    229.                        <And>

    230.                           <Leq>

    231.                              <FieldRef Name='EventDate' />

    232.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    233.                           </Leq>

    234.  

    235.                           <Geq>

    236.                              <FieldRef Name='EndDate' />

    237.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    238.                           </Geq>

    239.                        </And>

    240.  

    241.                        <And>

    242.                           <Geq>

    243.                              <FieldRef Name='EventDate' />

    244.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{0}</Value>

    245.                           </Geq>

    246.  

    247.                           <Leq>

    248.                              <FieldRef Name='EndDate' />

    249.                              <Value Type='DateTime' IncludeTimeValue='TRUE'>{1}</Value>

    250.                           </Leq>

    251.                        </And>

    252.                    </Or>

    253.                      

    254.                </Or>

    255.              

    256.                <Eq>

    257.                    <FieldRef Name='Meeting_x0020_Room' />

    258.                    <Value Type='Choice'>{2}</Value>

    259.                </Eq>

    260.                  

    261.            </And>

    262.          

    263.            <Neq>

    264.                <FieldRef Name='GUID' />

    265.                <Value Type='GUID'>{3}</Value>

    266.            </Neq>

    267.          

    268.        </And>

    269.          

    270.    </Where>

    271.      

    272.    <OrderBy>

    273.        <FieldRef Name='EventDate' />

    274.    </OrderBy>

    275.", start_str, end_str, MeetingRoom_str, guid_str);

    276.  

    277.                    //Set the query string for the SPQuery object

    278.                    query.Query = strQuery;

    279.  

    280.                    //Execute the query against the Calendar List

    281.                    SPListItemCollection existing_events = calendar.GetItems(query);

    282.  

    283.                    //Check to see if the query returned any overlapping bookings

    284.                    if (existing_events.Count > 0) {

    285.  

    286.                        //Cancels the ItemAdd action and redirects to error page

    287.                        properties.Cancel = true;

    288.  

    289.                        //Edit the error message that will display on the error page

    290.                        properties.ErrorMessage += "This booking cannot be made because of one or more bookings in conflict. <BR><BR>";

    291.  

    292.                        //Here you can loop through the results of the query

    293.                        //foreach (SPListItem oListItem in existing_events)

    294.                        //{

    295.                        //   ....

    296.                        //}

    297.  

    298.                        properties.ErrorMessage += "Please go back and schedule a new time.";

    299.                    }

    300.  

    301.                }

    302.            } catch (Exception ex) {

    303.  

    304.                //Cancels the ItemAdd action and redirects to error page

    305.                properties.Cancel = true;

    306.  

    307.                //Edit the error message that will display on the error page

    308.                properties.ErrorMessage = "Error looking for booking conflicts: " + ex.Message;

    309.            }

    310.  

    311.        }

    312.  

    313.          

    314.    }

    315.}

     

    Copiado del sitio:  http://blog.sharepointsydney.com.au/post/Setting-up-multiple-calendars-for-meeting-room-bookings-prevent-double-booking.aspx

     

    Desde mi punto de vista códigos como el anterior son completamente improductivos y hablan de la complejidad del modelo de programación de SP.

  6. CAML ¿CAML? Para qué un lenguaje adicional para hacer Querys. (además complicado y poco natural) Commerce Server, tambien de MS, por ejemplo utiliza en su modelo de objetos una función .Where donde se pasa como parámetro algo semejante a T-SQL, lo cual reutiliza el conocimiento del programador ¿Para que OTRO lenguaje? No ya es suficientemente difícil conocer asp.net, c#, javascript, css, xaml, html, xhtml, xml, sql, xpath, xquery y muchos más para programar una página web ¿necesitamos OTRO lenguajito basado en XML?. Esto va precisamente en contra de la propuesta de LINQ:  Dotar de nuevo a los lenguajes de programación la sintaxis necesaria para interactuar con objetos de la capa de datos.
  7. Poca o nula integración a Visual Studio, hay que bajar plugins para el poco soporte que existe
  8. El debuggeo es también muy complicado, estarse atacheando a procesos del server es poco natural y como quiera que sea, SÍ QUITA TIEMPO.
  9. Cuando truena… TRUENA, y da mensajes de error super crípticos, como una gran caja negra, hay que meterse a los logs, que tampoco dan mucha información.
  10. A nivel de diseño gráfico la personalización es complicada.
  11. Hacer algo más allá de una simple forma de captura en infopath se vuelve una experiencia tortuosa, hacer que infopath tenga fuentes de datos y muestre dinámicamente  controles se vuelve un mar de clics clics y clics que se desconfiguran al hacer un deployment de la aplicación y hay que volver a hacer todo otra vez.
  12. Eso de usar SharePoint Designer para una cosa, Visual Studio para otra, una herramienta 3rd party para el CAML, otra 3rd party para el attacheo de eventos, y el Notepad para los .bats de deployment huele como a trabajar en Eclipse /Java donde se requiere un programita de diferente proveedor para cada cosa. Esto siempre critico. El poder de la plataforma MS es precisamente la maravillosa integración de sus productos.
  13.  

    Estos y muchos temas más se han discutido ya mucho en Internet, hay muchos posts debatiendo esto y estas razones han sido discutidas 50000 veces antes de esto. Este post no es investigativo, es una reflexión personal.

     

    Yo siempre he dicho (por eso mi afición a VB y a proyectos como Astoria, entity framework, jasper etc) que a estas alturas ya no podemos permitir que los desarrolladores pasen una gran cantidad de tiempo investigando cómo hacer las cosas. Los desarrolladores cuestan dinero, y mucho. Una de las características de la plataforma Microsoft ha sido facilitar enormemente la tarea del desarrollador con tecnologías de alto desempeño e intuitivas.

     

    Con sharepoint es posible generar de una manera rápida y efectiva intranets , el único problema se da cuando por requerimiento del usuario se necesita extender la funcionalidad ya incluida dentro de la plataforma, entonces es cuando entra al rol del desarrollador y todo el tiempo que se gana al implementar la intranet de una manera rápida puede perderse cuando el equipo de desarrollo tiene que estar batallando con códigos como el de arribal.

     

    El objetivo de este pots insisto, es meramente propositivo, RECALCO OTRA VEZ SHAREPOINT ES UN EXCELENTE PRODUCTO. Lo único que digo es que Microsoft debería invertir más en en Sharepoint una verdadera plataforma de desarrollo amigable con las nuevas tendencias tecnológicas que aparecen dentro de la siguiente entrega de Visual studio y en general de la plataforma. Net , no sé porque tengo la impresión de que los equipos de ASP.NET y Sharepoint no se comunican.

     

    Yo me imagino a un Sharepoint más integrado con el IDE de VS, con un modelo basado en objetos o en WSDL mucho más simple, que explotara LINQ, entity framework y los pusiera como provedores de datos, algo que se pareciera más al enterprise library, con menos complejidad en todos los aspectos.

     

    Conclusión:

    SharePoint es una plataforma efectiva y poderosa de colaboración. El retorno de inversión en Intranets pequeñas/medianas es excelente. Es un maravilloso habilitador del trabajo en equipo. Para consolidar a SharePoint como una plataforma sustentable que crezca con el negocio y sea fácilmente extendible, es necesaria más inversión desde el punto de vista del desarrollador. Desde mi HUMILDE opinión, le falta el toque que caracteriza a la tecnología Microsoft: hacer la vida más facil al desarrollador, por eso amo .NET por eso estoy convencido de que .NET es superior en casi todo a su competencia, y creo que C# 4.0 es el MEJOR lenguaje disponible en el mercado.

    Espero que Sharepoint 2010, otorgue ése "algo" que le falta como una verdadera experiencia de developer, si es así, en este espacio escribiré al respecto.

     

Posted: 08-03-2009 9:17 PM por Bichi | con no comments
Archivado en: ,,