WSS 3.0 / MOSS: Copiando datos entre listas utilizando un workflow!

Hacía bastante tiempo que la gente del CIIN no publicábamos nada (casi 1 mes desde el último post sobre LINQ), y la razón de esto es que entre WSS 3.0 y su hermano mayor MOSS apenas nos queda tiempo para contaros cosas interesantes que estamos probando y descubriendo. Asique he decidido darme un pequeño respiro y contaros como hemos solucionado un problema que nos ha surgido en una de las especificaciones del proyecto de MOSS en el que estamos involucrados.


El problema


El problema que se planteo es el siguiente: en un entorno de Extranet (implementado con plantillas de tipo Team Site) se pensó en utilizar la lista Issue Tracking para que los clientes puedan dar de alta las incidencias que tienen con los productos o servicios de la empresa dueña de la Extranet. La cuestión es que las incidencias que dan de alta los clientes tienen que tener un formato diferente al que ven los administradores o responsables de Help Desk de la Extranet (estos necesitan más información), por lo que con crear dos Content Types para cada formato de incidencia es suficiente. Hasta aquí no hay problema, con las funcionalidades que nos da WSS 3.0 / MOSS podemos conseguir lo anterior para una lista.


Ahora bien, sería deseable que el cliente sólo pudiera dar de alta incidencias con el formato específicamente creado para ellos, y que los administradores pudieran añadir a la incidencia creada más información que el cliente no puede modificar…y es aquí dónde está el problema, puesto que una lista de WSS 3.0 / MOSS no puede ser configurada (con la funcionalidad out-of-the-box) para que jugando con los permisos se muestre un content type u otro dependiendo del usuario que está loggeado en la Extranet, es decir, si la lista tiene dos content types, se mostrarán los dos cuando se va a crear un nuevo elemento en la misma.



¿Qué podemos hacer para lograr la funcionalidad requerida? La solución que ofrecimos fue crear dos listas, cada uno con su Content Type diferenciado, de manera que cuando un usuario cliente diese de alta una incidencia en la lista especifica, esta apareciera reflejada automáticamente en la lista de incidencias del servicio de atención al cliente reflejando la información introducida por el cliente, y con la posibilidad de poder añadir mucha más información para resolver la incidencia de manera adecuada. Además, y por simplicidad, estos content types se han creado de manera que hereden el uno del otro dado que comparten campos comunes.


¿Cómo movemos los datos entre las dos listas?


A priori pensamos dos formas (espero vuestras sugerencias para incluir formas adicionales a las que hemos pensado) que permitieran que el elemento creado en una lista se creara automáticamente en la otra:


·         Mediante un workflow, asociado a la lista y con la lógica necesaria para que al ser creado un elemento en la lista origen, creara una copia del mismo en la lista origen.


·         Utilizando un manejador de eventos (esta idea fue sugerencia de mi compañero Ángel), de manera que al lanzarse un evento de tipo ItemAdding o ItemAdded, se copiase el elemento creado en la lista origen a la lista destino.


Como la idea era dar una solución que aprovechase funcionalidades out-of-the-box de WSS 3.0 / MOSS y Sharepoint Designer 2007 (SD 2007), nos decantamos por la primera opción, es decir, para copiar un dato entre las dos listas nos basta con crear un workflow mediante SD 2007 y utilizando las acciones estándar que ofrece.


De la euforia a la desilusión


Dicho y hecho, con SD 2007 creamos un workflow sencillito vinculado a la lista origen que realizase lo anterior aprovechando que existe una acción denominada  (en la versión en español) Copiar elemento de lista.



Tras probar que el workflow, no contenía errores, pasamos a desplegarlo  y a probar que funcionaba correctamente. Efectivamente, como usuario administrador todo va como la seda, los ítems que creo en la lista origen aparecen automáticamente en la lista destino…pero, ¿qué sucede si el usuario que crea el elemento no tiene permisos para ver y menos aún para modificar elementos en la lista destino? Nos loggeamos como un usuario de tipo cliente, que no tiene permisos sobre la lista destino (ni siquiera la ve), y creamos una incidencia, tras guardar la incidencia este es el resultado:



La copia ha fallado, se ha producido un error en la ejecución del workflow. Tras quedarme un poco perplejo, pues como administrador todo iba bien, paso a investigar que está pasando. Como me suele decir Ángel, aunque sea un poco tedioso, lo mejor es ver que nos dicen los logs de WSS 3.0. Tras bucear un poco en el último log generado (en la carpeta 12LOGS del servidor de WSS 3.0 / MOSS) veo que efectivamente se han producido varios errores muy feos de tipo Workflow Infrestructure:







Microsoft.SharePoint.SPException: Esta lista no existe  La página seleccionada contiene una lista que no existe. Puede que otro usuario la haya eliminado. —> System.Runtime.InteropServices.COMException (0x81020026): Esta lista no existe  La página seleccionada contiene una lista que no existe. Puede que otro usuario la haya eliminado.     at Microsoft.SharePoint.Library.SPRequestInternalClass.GetListsWithCallback(String bstrUrl, Guid foreignWebId, String bstrListInternalName, Int32 dwBaseType, Int32 dwBaseTypeAlt, Int32 dwServerTemplate, UInt32 dwGetListFlags, UInt32 dwListFilterFlags, Boolean bPrefetchMetaData, Boolean bSecurityTrimmed, Boolean bGetSecurityData, ISP2DSafeArrayWriter p2DWriter, Int32& plRecycleBinCount)     at Microsoft.SharePoint.Library.SPRequest.GetListsWithCallback(S…  


…tring bstrUrl, Guid foreignWebId, String bstrListInternalName, Int32 dwBaseType, Int32 dwBaseTypeAlt, Int32 dwServerTemplate, UInt32 dwGetListFlags, UInt32 dwListFilterFlags, Boolean bPrefetchMetaData, Boolean bSecurityTrimmed, Boolean bGetSecurityData, ISP2DSafeArrayWriter p2DWriter, Int32& plRecycleBinCount)     — End of inner exception stack trace —     at Microsoft.SharePoint.Library.SPRequest.GetListsWithCallback(String bstrUrl, Guid foreignWebId, String bstrListInternalName, Int32 dwBaseType, Int32 dwBaseTypeAlt, Int32 dwServerTemplate, UInt32 dwGetListFlags, UInt32 dwListFilterFlags, Boolean bPrefetchMetaData, Boolean bSecurityTrimmed, Boolean bGetSecurityData, ISP2DSafeArrayWriter p2DWriter, Int32& plRecycleBinCount)     at Microsoft.SharePoint.SPListCollection.EnsureListsData(… 


 at Microsoft.SharePoint.SPListCollection.EnsureListsData(String strListName)     at Microsoft.SharePoint.SPListCollection.ItemByInternalName(String strInternalName, Boolean bThrowException)     at Microsoft.SharePoint.SPListCollection.GetListById(Guid uniqueID, Boolean bThrowException)     at Microsoft.SharePoint.SPListCollection.get_Item(Guid uniqueID)     at Microsoft.SharePoint.Workflow.SPWinOEWSSService.CopyListItem(Guid id, Guid listId, Int32 itemId, Guid toListId, Boolean overwrite)               



Es decir, el workflow iniciado bajo la cuenta user1 no ha podido copiar el elemento de la lista origen a la lista destino porque no encuentra esta última (lógico, pues no tiene permisos de acceso). Luego, aunque todo parecía muy bonito, como el workflow credo por SD 2007 al ejecutarse toma la identidad del cliente que creó el elemento en la lista origen, WSS  3.0 no va a permitir crearlo en la lista de destino puesto que el usuario no tiene permisos para ello…con lo bien que pintaban las cosas!. Tras intentar varias cosas con SD 2007 (como ver si se podía cambiar el valor del campo Created By), decidimos pasar a la alternativa dos para crear el workflow que copiase el elemento creado en la lista destino: crearlo con Visual Studio 2005, utilizando para ello las plantillas para la creación de workflows para WSS 3.0 que vienen con el SDK, y resolviendo el problema de permisos con el que nos hemos encontrado.


La solución


Por tanto, la idea era modelar un workflow para WSS 3.0 utilizando las plantillas para creación de workflows que vienen con el SDK de WSS 3.0 y que solventara el problema de permisos anterior a través de la correspondiente elevación de privilegios. El workflow creado es realmente sencillo, y está formado por dos actividades:


·         Una actividad de tipo OnWorkflowActivated que es obligatoria en todo workflow de WSS 3.0 pues es la actividad de inicio del mismo.


·         Una actividad de tipo code, en la que introduciremos la lógica necesaria para copiar el elemento de la lista origen a la lista destino en el momento en el que este se crea.



Una vez creado el workflow con el diseñador, pasamos a codificar los elementos necesarios, es decir, los manejadores de las dos actividades que integran el workflow.


Manejador para la actividad OnWorkflowActivated


Este manejador es realmente sencillo, y lo único que tiene que hacer es inicializar la propiedad pública workflowId que se crea al diseñar el workflow con la análoga de las propiedades de activación del workflow:







        private void ActivarWorkflow(object sender, ExternalDataEventArgs e)


        {


            workflowId = workflowProperties.WorkflowId;


        }


Las propiedades de activación del workflow representadas por el objeto workflowProperties que es una instancia de la clase SPWorkflowActivationProperties, y que representa las propiedades iníciales de la instancia del workflow en el momento en el que se lanza:


·         El usuario que añadió el workflow (propiedad OriginatorUser, de tipo SPUser).


·         La lista e ítem a las que se ha añadido el workflow: propiedades Item (de tipo SPListItem) y List (de tipo SPList).


·        


La idea es que cuando se lanza una nueva instancia del workflow, WSS para un objeto de tipo SPWorkflowActivatioProperties a la actividad OnWorkflowActivated de nuestro workflow.


Manejador para la actividad Code del workflow


Y es aquí donde llega lo bueno, porque en este punto tendremos que crear toda la lógica necesaria para implementar los dos requisitos funcionales:


·         Realizar una elevación de privilegios para que el elemento creado en la lista origen se pueda copiar en la lista destino.


·         Realizar la copia efectiva del elemento creado en la lista destino.


Para realizar la elevación de privilegios, tendremos que utilizar un método especial del objeto SPSecurity de WSS 3.0:







SPSecurity.RunWithElevatedPrivileges(delegate()


            {


                // Obtener un contexto de seguridad               


                using (SPSite site=new SPSite(workflowProperties.SiteId))


                {


                    using (SPWeb web = site.OpenWeb(workflowProperties.WebId))


                    {


                        //Lógica para copiar el ítem en la lista


                    }


                }


            });                              


Lo que hacemos con el método RunWithElevatedPrivileges de la clase SPSecurity es ejecutar toda la lógica que viene a continuación con permisos de control total aunque el usuario que inicia el workflow no los tenga. Además, especificamos que el código se ejecute en el site collection (objeto site de tipo SPSite, obtenido mediante la propiedad SiteId de workflowProperties) dónde se ha iniciado el workflow, y dentro de esta site collection en la web dónde este se ha iniciado (objeto web de tipo SPWeb, obtenido a través de la propiedad WebId de workflowProperties). Este método define un parámetro, que puede ser:


·         Un objeto de tipo SPSecurity.CodeToRunElevated, que representa un método que va a correr con elevación de privilegios.


·         Utilizando un método anónimo mediante delegate(), que es justo la opción que hemos utilizado.


Una vez que tenemos garantizado que podemos hacer operaciones contra un ítem (en este caso será una lista) de WSS 3.0 sobre el que se han establecido restricciones, pasamos a la parte “fácil” que es copiar el elemento de la lista origen a la lista destino. Este es el código que he utilizado (ya me diréis que os parece):







//Elementos de WSS 3.0 necesarios


SPList splListaOrigen = web.Lists[workflowProperties.ListId];


SPList splListaDestino = web.Lists[LISTA_DESTINO];


SPField spfCampo;


SPFile spfAttachment;


SPAttachmentCollection spacAttachments;


 


//Contadores para los bucles.


int i, j, k;


 


SPListItem spliItemACrear = splListaDestino.Items.Add();


SPListItem spliItemActual = workflowProperties.Item;


 


i = 0;


for (i = 0; i < spliItemActual.Fields.Count; i++)


{


    spfCampo = spliItemActual.Fields[i];


    try


    {


        j = 0;


        for (j = 0; j < spliItemACrear.Fields.Count; j++)


        {


            if (spfCampo.InternalName == spliItemACrear.Fields[j].InternalName)


            {


                if (spliItemActual[spfCampo.InternalName] != null &&


                    spfCampo.ReadOnlyField == false)


                {


                    if (spfCampo.InternalName != ATTACHMENT_FIELD)


                    {


                        spliItemACrear[spfCampo.InternalName] = spliItemActual[spfCampo.InternalName];


                    }


                    else


                    {


                        spacAttachments = spliItemActual.Attachments;


                        k = 0;


                        for (k = 0; k <= spacAttachments.Count – 1; k++)


                        {


                            spfAttachment = web.GetFile(spliItemActual.Attachments.UrlPrefix + spacAttachments[k]);


                            spliItemACrear.Attachments.Add(spfAttachment.Name, spfAttachment.OpenBinary());


                        }


                    }


                }


            }


        }


    }


    catch (Exception ex)


    {


        //Tratamiento de las excepciones que se puedan producir.


    }


}


//Creamos el elemento en la lista destino


try


{


    //Actualizamos los campos ID Internet y Fecha de alta en el item origen


    spliItemActual[ID_INTERNET_FIELD] = spliItemActual[ID_LISTA_FIELD];


    spliItemActual[FECHA_ALTA_FIELD] = DateTime.Now;


    spliItemActual.Update();


    //Creamos el item en destino


    spliItemACrear[ID_INTERNET_FIELD] = spliItemActual[ID_INTERNET_FIELD];


    spliItemACrear.Update();


}


catch (Exception ex)


{


    //Tratamiento de las excepciones que se puedan producir.


}


Analizando un poco el código anterior, destacaría los siguientes puntos:


·         La lista origen la vamos a tener en el objeto splListaOrigen que es de tipo SPList, y la obtenemos a través del método Lists (que obtiene la colección de listas de una web, y que admite del objeto web. A este método le especificamos que lista queremos abrir a través del Id de la lista que de nuevo obtenemos a partir de una de las propiedades de workflowProperties: ListId. La lista de destino la tendremos en el objeto splListaDestino utilizando el mismo método, pero esta vez a través de su nombre.







SPList splListaOrigen = web.Lists[workflowProperties.ListId];


SPList splListaDestino = web.Lists[LISTA_DESTINO];


 


·         El siguiente paso consiste en obtener de algún modo el ítem creado en la lista para poder copiarlo en el origen y preparar la lista destino. De nuevo lo obtenemos a partir de workflowProperties y la propiedad Item que lógicamente es de tipo ListItem. Para empezar a añadir elementos a la lista destino, utilizamos el método Add():







SPListItem spliItemACrear = splListaDestino.Items.Add();


SPListItem spliItemActual = workflowProperties.Item;


 


·         Para ir añadiendo los campos del ítem de la  lista origen en la lista destino he utilizado dos bucles for anidados. En el primero voy leyendo campo a campo del elemento a crear, y en el segundo me recorro los campos que forman un elemento en la lista destino (recordemos que las listas utilizan Content Types diferentes para la creación de elementos, por lo que un elemento de la lista origen no tendrá el mismo número de campos que un elemento de la lista destino). Es en este segundo nivel dónde, sabiendo que los Content Types de las listas están relacionados (el content type de la lista origen hereda de la lista destino) dónde se realiza la comparación campo a campo para añadir en la lista destino aquellos campos que realmente puedan ser añadidos:







            if (spfCampo.InternalName == spliItemACrear.Fields[j].InternalName)


            {


                   //Lógica de asignación


            }


               


Pero las comprobaciones no acaban aquí, además tendremos que comprobar que el campo a añadir tiene un contenido no nulo, y sobre todo que no tenga la propiedad ReadOnly a true, puesto que de ser así, el mismo campo en el destino no se podrá actualizar.







                if (spliItemActual[spfCampo.InternalName] != null &&


                    spfCampo.ReadOnlyField == false)


                {


                     //Lógica de asignación


                }


¿Se acabaron las comprobaciones? Pues no, hay un campo que merece un tratamiento especial: los attachments asociados al ítem creado. Para el resto de campos, la asignación es directa:







                    if (spfCampo.InternalName != ATTACHMENT_FIELD)


                    {


                        spliItemACrear[spfCampo.InternalName] = spliItemActual[spfCampo.InternalName];


                    }


                    else


                    {


                        spacAttachments = spliItemActual.Attachments;


                        k = 0;


                        for (k = 0; k <= spacAttachments.Count – 1; k++)


                        {


                            spfAttachment = web.GetFile(spliItemActual.Attachments.UrlPrefix + spacAttachments[k]);


                            spliItemACrear.Attachments.Add(spfAttachment.Name, spfAttachment.OpenBinary());


                        }


                    }


Por lo tanto, cuando el campo del ítem origen sea Attachments, lo que tenemos que hacer es utilizar un objeto de tipo SPAttachmentCollection, en el código spacAttacments, en el que recogeremos los attachments asociados al ítem creado en la lista origen (propiedad Attachments del objeto spliItemActual).  Una vez que tenemos los attachments, nos recorremos los ítems individuales de la colección de attachments y los vamos cogiendo mediante un objeto de tipo SPFile utilizando para ello el método GetFile del objeto web (recordemos que es de tipo SPWeb) al que le pasamos la ubicación del archivo (que obtenemos a través de la propiedad UrlPrefix de la colección de attachments del elemento de la lista origen y el nombre físico del attachment).


Sin más, añadimos cada attachment a los attachments del elemento a crear mediante el método Add() de la propiedad Attachments, al que le tenemos que indicar el nombre del attachment (propiedad Name del objeto SPFile) y un array de bystes (byte[]) que contiene el attachment y que se obtiene mediante el método OpenBinary() del objeto SPFile utilizado.


·         Sin más, una vez que tenemos preparado el ítem a crear, se lo añadimos de forma efectiva a la lista destino utilizando el método Update().







spliItemACrear.Update();


Y ya está, con este código podemos copiar el ítem creado en la lista de destino y solventando el problema de permisos con el que nos encontramos en SD 2007.


Desplegando y probando el workflow


Para probar el workflow en nuestra Extranet, lo primero es desplegarlo en el sitio en el que vayamos a utilizarlo (lo podemos desplegar a nivel de Site Collection o en un cierto sitio web). Para desplegarlo vamos a utilizar la opción que por defecto nos ofrece un proyecto de workflow para WSS 3.0 / MOSS: a través de una feature. Los pasos necesarios son:


·         Configurar el archivo feature.xml, en el que especificaremos cuál es el archivo de manifiesto en el que están descritos los elementos de nuestro workflow.







<Feature  Id=”4AA1EA9A-0A4F-41f9-A7CA-C1309C42A534″


          Title=”Copia de una lista origen a otra”


          Description=”Copia datos de la lista cliente a la lista Semicrol!”


          Version=”12.0.0.0″


          Scope=”Site”


          xmlns=”http://schemas.microsoft.com/sharepoint/”>


                <ElementManifests>


                               <ElementManifest Location=”workflow.xml” />


                </ElementManifests>


                <Properties>


                               <Property Key=”GloballyAvailable” Value=”true” />


                </Properties>


</Feature>


Lo más importante en este archivo es que nos aseguremos de utilizar un Id único, el cuál obtenemos a partir de la herramienta Create GUID de Visual Studio 2005, y que especifiquemos correctamente el nombre del archivo de manifiesto del workflow.



·         Configurar el archivo de manifiesto de nuestro workflow:







<Elements xmlns=”http://schemas.microsoft.com/sharepoint/”>


                <Workflow


                                Name=”Copiar item a lista”


                                Description=”Ejemplo de workflow que crea una tarea de revisión.”


                                Id=”ACD332EC-60EE-4dd6-8F17-222A8D88A3F3″


                                CodeBesideClass=”CopiarIncidencia.Workflow1″


                                CodeBesideAssembly=”CopiarIncidencia, Version=1.0.0.0,


                             Culture=neutral,PublicKeyToken=ac35d273278cd512″>


                               <Categories/>


                               <MetaData>


                                               <StatusPageUrl>_layouts/WrkStat.aspx</StatusPageUrl>


                               </MetaData>


                </Workflow>


</Elements>


 De nuevo tenemos que tener cuidado con la configuración de este archivo, prestando atención a los siguientes atributos:


o   Id, que tiene que ser único y se genera de nuevo con la utilidad Create GUID.


o   CodeBesideClass, en el que especificamos el nombre del proyecto y de la clase de nuestro workflow.


o   CodeBesideAssembly, en el que tenemos que especificar el nombre del assembly, su versión y su KeyToken exactos para no tener problemas. Para obtener esta información podemos utilizar la herramienta Reflector de Lutz Roeder.


·         Configurar el archivo de instalación de la feature, Install.Bat para que despliegue y active la feature dónde nosotros especifiquemos:







echo Copying the feature…


 


rd /s /q “%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATEFEATURESCopiarIncidencia”


mkdir “%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATEFEATURESCopiarIncidencia”


 


copy /Y feature.xml  “%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATEFEATURESCopiarIncidencia”


copy /Y workflow.xml “%CommonProgramFiles%Microsoft Sharedweb server extensions12TEMPLATEFEATURESCopiarIncidencia”


xcopy /s /Y *.aspx “%programfiles%Common FilesMicrosoft Sharedweb server extensions12TEMPLATELAYOUTS”


 


echo Adding assemblies to the GAC…


 


“%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe” -uf CopiarIncidencia


“%programfiles%Microsoft Visual Studio 8SDKv2.0Bingacutil.exe” -if binDebugCopiarIncidencia.dll


 


:: Note: 64-bit alternative to lines above; uncomment these to install on a 64-bit machine


::”%programfiles% (x86)Microsoft Visual Studio 8SDKv2.0Bingacutil.exe” -uf MyFeature


::”%programfiles% (x86)Microsoft Visual Studio 8SDKv2.0Bingacutil.exe” -if binDebugMyFeature.dll


 


 


echo Activating the feature…


 


pushd %programfiles%common filesmicrosoft sharedweb server extensions12bin


 


::Note: Uncomment these lines if you’ve modified your deployment xml files or IP forms


stsadm -o deactivatefeature -filename CopiarIncidenciafeature.xml -url http://MiSitio


stsadm -o uninstallfeature -filename CopiarIncidenciafeature.xml


 


stsadm -o installfeature -filename CopiarIncidenciafeature.xml -force


stsadm -o activatefeature -filename CopiarIncidenciafeature.xml -url http://MiSitio


 


 


echo Doing an iisreset…


popd


iisreset


 Analizando el archive de instalación, vemos que los pasos necesarios son:


i)      Crear una carpeta dentro de  12TEMPLATEFEATURES que es la ubicación dónde WSS 3.0 / MOSS almacena todas las features que utiliza.


ii)    Copiar los archivos feature.xml y workflow.cml a esta carpeta.


iii)   Copiar otros archivos que puedan ser necesarios (este no es el caso), cómo páginas aspx o formularios infopath para configurar /interactuar el / con el workflow.


iv)  Copiar el assembly en la GAC del servidor mediante el comando gacutil del SDK de Visual Studio.


v)    Instalar la feature utilizando el comando stsadm con las opciones filename indicando el asembly e installfeature  indicando el archivo xml que describe la feature.


vi)  Activar la feature en el sitio que deseemos utilizando de nuevo stsadm con las mismas opciones especificadas en la instalación.


vii) Por último, es necesario hacer un iisreset (se puede especificar que se haga sólo de un sitio de WSS 3.0 / MOSS y no todo el IISS).


Sin más, ya estamos listos para probar que todo funciona como esperábamos. Para ello, y tras añadir una instancia del workflow creado a la lista origen, creamos un ítem en la lista origen mediante el usuario user1 que no tenía permisos sobre la lista destino:



Ahora al crear la incidencia se ha desencadenado el workflow  (también podéis observar que user1 ni siquiera puede visualizar la lista de destino) y no ha producido ningún tipo de error como nos ocurría con el workflow creado con SD 2007:



Comprobemos que la incidencia se ha copiado en la lista destino, para ello nos logeamos como administradores en la Extranet (o como un usuario con el nivel de permisos adecuado para ver y modificar elementos de esa lista):



Cómo veis, la incidencia se ha creado en la lista destino que ahora si es visible puesto que estamos logeados como administrador y además ha sido creada por la Cuenta del sistema en lugar de por user1 debido al mecanismo de elevación de privilegios que hemos utilizado en el workflow.


Y hasta aquí llega lo que os quería contar sobre como copiar elementos entre dos listas de WSS 3.0 / MOSS desencadenando un workflow y solventando escenarios típicos de distintos niveles de permisos en las listas. Espero que el post os haya parecido interesante. Os dejo también el proyecto para que lo podáis descargar y utilizar.

Publicado por

Juan Carlos González

Juan Carlos es Ingeniero de Telecomunicaciones por la Universidad de Valladolid y Diplomado en Ciencias Empresariales por la Universidad Oberta de Catalunya (UOC). Cuenta con más de 12 años de experiencia en tecnologías y plataformas de Microsoft diversas (SQL Server, Visual Studio, .NET Framework, etc.), aunque su trabajo diario gira en torno a SharePoint & Office 365. Juan Carlos es MVP de Office Servers & Services desde 2015 (anteriormente fue reconocido por Microsoft como MVP de Office 365 y MVP de SharePoint Server desde 2008 hasta 2015), coordinador del grupo de usuarios .NET de Cantabria (Nuberos.Net, www.nuberos.es), co-fundador y coordinador del Grupo de Usuarios de SharePoint de España (SUGES, www.suges.es), así como co-director de la revista gratuita en castellano sobre SharePoint CompartiMOSS (www.compartimoss.com). Hasta la fecha, ha publicado 8 libros sobre SharePoint & Office 365 y varios artículos en castellano y en inglés sobre ambas plataformas.

21 comentarios en “WSS 3.0 / MOSS: Copiando datos entre listas utilizando un workflow!”

  1. Hola Juan Carlos,
    Si me permiten meter la cucharada, a mi me parece que un Event Handler, no un WorkFlow sería mucho mas fácil de programar e instalar en este caso, como te aseguraba Ángel. El Código se reduce a copiar campos de una Lista a otra usando un Impersonador, la instalación se puede realizar fácilmente con una Solución, y para la activación puedes hacer una aplicación de consola de 4 renglones de código, o usar alguno de los instaladores que hay regados por Internet. Por una u otra razón, yo sigo siendo un admirador de Event Handlers, y siempre he pensado que es una lástima que no los utilicemos con más frecuencia.
    En cualquier caso, felicitaciones por encontrar la solución al problema, y como dice el dicho, los caminos a Roma son multiples (es ese un dicho o me lo estoy inventando? 8-).
    Un saludo cordial,
    Gustavo

  2. Hola Gustavo!
    Se agradece la “cucharada” :P…respecto a si es más fácil hacer la copia de items con un workflow o con un Event Handler, en este caso da igual, puesto que el código añadido al manejador de la actividad code del workflow te sirve (adaptándolo) para el Event Handler…y puestos a hablar de prerencias, yo me quedo con el objeto de tipo SPWorkflowActivationProperties que facilita mucho copiar un elemento de una lista a otra en el momento en que se crea el elemento.
    JC
    p.d: Por cierto, leí tarde el artículo de Gavdnet sobre el error reconocido de WSS 3.0 sobre workflows en listas de tipo Survey…dos días que estuve con ello hasta que desistí :PPP

  3. Juan Carlos, una vez mas felicitaciones, muy buen artículo. Efectivamente existe el problema de los permisos haciendo necesaria la impersonalización o la ejecución con privilegios elevados.

    Pero para bordar el asunto (voy a meter la cuchara también, como dice mi buen amigo Gustavo), lo ideal (a mi entender) es hacer esa misma actividad e incluirla dentro del Sharepoint Designer, de forma que la complejidad se programe una vez y la lógica pueda cambiarse a nuestro antojo sin tener que realizar un programa cada vez.

    Un Saludo,
    cseg

  4. Hola Carlos!
    Completamente de acuerdo contigo, si no lo hemos hecho, ha sido por falta de tiempo, porque hay varios posts por ahí (uno tuyo) en el que se explica como hacer nuestras propias actividades personalizadas en SD 2007…de todos modos, habría refinar el código para que al menos se comporte como el Copy Item, es decir, que se pueda especificar la lista origen desde la que quieres copiar y la lista destino a la que quieres copiar el elemento.
    Un saludo
    JC

  5. Muy buen post este me ha ayudado a entender mejor como funcionan los flujos para share point, ahora tengo una duda que es realmente el CorrelationToken??? Por qué cuando en flujo que tengo creado y que lo unico que tiene es la onWorkflowActivated y un sendEmail adiciono un createTask y cuyo CorrelationToken pongo igual que las dos anteriores me da error??? El error que me da en los log es : WinWF Internal Error, terminating workflow Id# e5bba4c1-ee0f-4b92-87f8-2466668d34f8
    System.InvalidOperationException: Correlation value on declaration “workflowToken” is already initialized. at System.Workflow.Runtime.CorrelationToken.Initialize(Activity activity, ICollection`1 propertyValues) at System.Workflow.Activities.CorrelationService.InvalidateCorrelationToken(Activity activity, Type interfaceType, String methodName, Object[] messageArgs) at System.Workflow.Activities.CallExternalMethodActivity.Execute(ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext) at System.Workflow.ComponentModel.ActivityExecutorOperation.Ru…
    at System.Workflow.Runtime.Scheduler.Run()
    El visual studio no me deja ponerle otro valor que no sea ese. Así que me parece que no se que hacer 🙁

  6. Hola lilicitica,
    La mejor referencia de que es el correlation token la puedes encontrar en este enlace de msdn: http://msdn2.microsoft.com/en-us/library/ms475438.aspx…el resumen es que el correlation token es el canal de comunicación entre las actividades del workflow y el proceso de host que en este caso es WSS 3.0. Algo que tienes que tener en cuenta es que las actividades que pongas en un workflow de WSS 3.0 han de tener el mismo correlation token para que el host le pueda enviar la información al workflow y este sepa que secuencia tiene que ejecutar, por eso el correlation token de OnWorkflowActivated y SendEmail tiene que ser el mismo…Si entre medias utilizas actividades de tipo CreateTask OnTaskChange u otras, tienes que tener en cuenta que de alguna forma tienes que relacionar que estas actividades se refieren a una misma tarea de WSS 3.0, por lo que aqui tendrás que poner un correlation token común para estas actividades (p.e tasktoken). La referencia que te he puesto lo explica de esta manera…aqui tienes un how to para crear un workflow para WSS 3.0 paso a paso:
    http://msdn2.microsoft.com/en-us/library/ms580283.aspx

    Y aqui tienes otra referencia más de los correlation tokens:

    http://blogs.msdn.com/sharepoint/archive/2006/11/23/developing-workflows-in-vs-part-3-five-steps-for-developing-your-workflow.aspx

    Espero haberte ayudado y que consigas que funcione tu workflow.
    Un saludo

    JC

  7. Muchas gracias una vez más, disculpa las molestias que pueda ocacionarte con mis preguntas, pero acá te tengo otra, ¿se puede usar el servicio de tracking en flujos que esten en SharePoin?
    Gracias por todo.
    Lilian

  8. Hola!
    No me supone ninguna molestia, es más,agradezco las dudas y respuestas que puedan aparecer con respecto a los post que escribo…por supuesto que puedes usar el servicio de tracking en workflows de WSS 3.0, de hecho tiene una implementación propia de servicio que almacena la información de tracking en la BD de contenidos de WSS 3.0. Esta información es utilizada para luego ver en WSS 3.0 como ha sido la ejecución del workflow.

    Un saludo

    JC

  9. Muchas gracias por su ayuda y una vez más disculpeme por molestarlo pero como le asigno a varias personas una misma Tarea??
    me sucede que estoy haciendo un flujo que se levanta cuando inserto un elemento en una lista de acuerdos, estos acuerdos tienen responsables a los cuales el flujo debe ponerle en sus tareas una nueva tarea con la descripción de acuerdo entre otros datos del mismo, para esto creo un flujo que tiene una actividad createtask en el MethodInvoking de la misma tomo los datos que necesito del auerdo, entre ellos los responsables, esto funciona bien si el reponsable es uno solo pero si son varios crea la tarea pero no la asigna a nadie a continuación te pongo el codido del MethodInvoking:

    SPListItem AcuerdoNT = workflowProperties.Item;
    SPWorkflowTaskProperties TaskProperties = new SPWorkflowTaskProperties();
    SPFieldUserValueCollection Responsables = (SPFieldUserValueCollection)AcuerdoNT[“Responsable(s)”];

    foreach (SPFieldUserValue Usuario in Responsables)
    {
    asig = asig + “; ” + Usuario.User.LoginName;
    }
    asig = asig.Remove(0, 2);
    TaskProperties.AssignedTo = asig;
    TaskProperties.Title = “Nuevo Acuerdo: ” + AcuerdoNT[“Nro”].ToString();
    TaskProperties.SendEmailNotification = true;
    TaskProperties.StartDate = DateTime.Now;
    TaskProperties.DueDate = Convert.ToDateTime(AcuerdoNT[“Fecha Cumplimento”]);
    TaskProperties.Description = AcuerdoNT[“Descripcion”].ToString();
    CreateTask.TaskProperties = TaskProperties;

    desde ya gracias por la ayuda y una vez disculpa la moletia

    Lilian

  10. Hola!
    Ahora mismo no dispongo de las herramientas necesarias para tratar de contestar a tu pregunta…te paso mi e-mail para una mejor comunicación de estas dudas: jcgonzalez@ciin.es. De todos modos, te sugiero que intentes depurar el código bien directamente contra WSS a través del Attacht to Process de Visual Studio para ver que está sucediendo cuando trabajas con un grupo de usuarios en lugar de con un usuario individual.

    En saludo

    JC

  11. Hola de nuevo Lilian,
    Hemos estado viendo como funciona el campo Assigned To de WSS 3.0 / MOSS y aparentemente sólo permite un único valor que puede ser un usuario individual o un grupo, es decir, cuando la selección es exclusiva y sólo te permite uno. Esto explciaría porque no te funciona lo que estás haciendo. Mirando un poco más la propiedad AssignedTo dentro de la clase SPWorkflowTaskProperties, la conclusión es la misma: sólo se permite un único valor
    http://msdn2.microsoft.com/en-us/library/microsoft.sharepoint.workflow.spworkflowtaskproperties.assignedto.aspx

    Entonces, para lo que quieres hacer tendrías que construir el workflow para que te crease las tareas de forma recursiva. Se me ocurren dos formas:
    1) Que coloques la actividad CreateTask dentro de una actividad While y vayas creando las tareas correspondietnes hasta que no haya más usuarios en Responsables.
    2) Hacer lo mismo en una actividad de tipo Code, en este caso podrías utilizar el foreach de tu código y llamar de manera recursiva al método de creación de tareas.

    Un saludo

    JC

  12. Hola!!!!
    Magnifico POST. Estoy intentando reproducirlo y no consigo que funcione. Siempre me da Error al iniciar y se queda alli reintentando… La unica diferencia que tengo con este POST es que yo copio datos de un item de una biblioteca de documentos en un subsite, a una lista del site principal.
    Alguna recomendación????? Estoy pensando en hacerlo mediante handlers?

    Gracias por adelantado.

  13. Hola,
    enhorabuena por vuestro conocimiento y paciencia con los que estamos unos peldaños por detras 😉
    Queria vuestro punto de vista, he intentado reproducir el mismo workflow pero copiando datos de un item de una biblioteca documentos en un subsitio a una lista del sitio principal. Siempre obtengo el mismo error (ERROR AL INICIAR). De alli no consigo pasar. Es porque no puedo interactuar entre listas de diferentes sitios? Seria mas facil hacerlo por handlers?

  14. Muy buenas Deibeat,
    Pues en principio este código te debería servir para biblioteca de documentos, sólo que en lugar de utilizar objetos SPList tendrás que utilizar objetos SPDocumentLibrary…respecto a utilizar workflows o event handlers, pues te remito a los comentarios que se hicieron a este post…la aproximación es igual de buena para cada caso. Te paso algunos enlaces respecto al tema:
    http://msdn2.microsoft.com/en-us/library/ms954724.aspx
    http://photos.sys-con.com/story/res/45540/source.html

    En principio se podrían copiar datos entre listas de sitios distintos…respecto al problema que tienes de inicio del workflow:
    – Asegurate que los archivos feature.xml y workflow.xml están bien configurados con sus GUID únicos, versiones correctas, etc.
    – Puede que se trate de un problema de seguridad, entonces haz elevación de privilegios

    Espero que con estas ideas puedas solucionar tu problema.

    Un saludo y gracias

    JC’s

  15. Saludos

    Esta muy interesante el articulo, ha surgido una interrogante , se puede crear un item de una lista de un sitio a otra lista de otro sitio.

  16. Hola Ana María…pues la respuesta es que depende de lo que quieras hacer:
    – Si las listas pertenecen a sitios que están en el mismo servidor, puedes aplicar lo anterior sin problemas.
    – Si las listas están en servidores distintos, entonces tendrás que jugar con los servicios web de SharePoint.

    Un saludo

    JC’s

  17. Mi cuestión es la siguiente, ¿como puede afectar la creación de un flujo de trabajo para la copia de elementos entre listas a la hora de actualizar WSS 3.0, o cuando saquen una nueva versión?
    MArta

  18. Hola soy ezequiel estoy desarrollando una sitio sharepoint. Al entrar en una biblioteca de documentos dentro de un subsitio me sale el error que la lista no existe. pero dentro del sitio principal no tengo problema.
    Cual puede ser el error??
    Saludos
    ezequiel

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *