​En ocasiones cuando estamos efectuando alguna operación en SharePoint de manera programática dicha operación lleva un tiempo considerable en efectuarse, si la operación se está efectuando de forma sÃncrona el usuario verá que su navegador queda "congelado" mientras la operación se completa. Si en cambio, se está efectuando de forma asÃncrona el usuario puede no percibir que se haya efectuado la operación.
Para informar al usuario del procesamiento, el SDK de SharePoint nos proporciona la clase SPLongOperation que nos permite mostrar una ventana con un mensaje informando de la operación que se está efectuando mientras se está realizando la operación. Esta ventana es la ya conocida por los usuarios de SharePoint donde se muestra una animación y un texto.
La clase SPLongOperation es fácil de usar. En teorÃa basta con instanciar un objeto, fijar los textos a mostrar e invocar el método Begin(). A continuación se sitúa el código de larga duración y se acaba invocando End() ¿Fácil no?
1using (SPLongOperation longOp = new SPLongOperation(this.Page))2
1{2
1longOp.LeadingHTML = "Esto va a llevar un rato";2
1longOp.TrailingHTML = "Un poquito de paciencia por favor...";2
1longOp.Begin();2
1Â2
1// El código de larga duración va aquÃ2
1Â Â Â Â Â Â longOp.End(this.Page);2
1}2
Pero…cuando utilizamos el código anterior dentro de un EventReceiver de, por ejemplo una lista, el código anterior falla ya que "this" no tiene la propiedad Page… ¿Qué está pasando aqu�
Los eventReceivers permiten interceptar los eventos que se producen en SharePoint para añadir o modificar funcionalidad de forma programática. Dependiendo del evento que se esté interceptando, el eventReceiver corre de forma sÃncrona como en el caso de ItemAdding o itemDeleting o de forma asÃncrona como ItemAded o ItemDeleted.
Qué el eventReceiver se ejecute de forma sÃncrona implica que se ejecuta en el hilo de ejecución del GUI, si corre de forma asÃncrona se lanza un hilo de ejecución distinto lo que permite al GUI progresar.
De modo que si el evento donde tenemos nuestro código de larga duración se ejecuta de forma asÃncrona, no tenemos acceso al GUI lo que implica que en principio tampoco hay acceso a ese objeto page que necesitamos.
Nuestro objetivo entonces será lograr tener ese objeto Page, para ello vamos a tener que realizar un par de modificaciones:
Nuestro primer paso es lograr que el EventReceiver se ejecute de forma sÃncrona, si es que se ejecuta de forma asÃncrona por defecto, para tener acceso al contexto. Para ello dependiendo de cómo asociemos el evento a la lista realizaremos una de estas dos opciones:
1<Receiver>2
1 <Name>MiEventReceiverItemAdded</Name>2
1Â Â Â <Type>ItemAdded</Type>2
1Â Â Â <Assembly>$SharePoint.Project.AssemblyFullName$</Assembly>2
1 <Class>MiSolucion.MiEventReceiver.MiEventReceiver</Class>2
1 <Synchronization>Synchronous</Synchronization>2
1 <SequenceNumber>10000</SequenceNumber>2
1</Receiver>2
1SPEventReceiverDefinitionCollection eventReceivers = miSPWebSite.Lists["Nombre de la lista"].EventReceivers;2
1Â2
1SPEventReceiverDefinition evRec=eventReceivers.Add();2
1evRec.Assembly= Assembly.GetExecutingAssembly().FullName;2
1evRec.Class="MiSolucion.MiEventReceiver.MiEventReceiver";2
1Â2
1evRec.Synchronization=SPEventReceiverSynchronization.Synchronous;2
1Â2
1evRec.Type=SPEventReceiverType.ItemAdded;2
1evRec.Update();2
Ya tenemos el evento ejecutándose de forma sÃncrona pero aún falta conseguir referencias al contexto http, si intentamos conseguir una referencia a HttpContext.Current cuando se dispara el evento veremos que nos devuelve null.
Para conseguir una referencia al contexto declaramos en la clase manejadora de los eventos una propiedad del tipo HttpContext y en el constructor es donde podemos obtener la referencia al contexto actual.
1public class MiEventReceiver : SPItemEventReceiver2
1{2
1private SPContext _spContext;2
1private HttpContext _httpContext;2
1Â2
1Â2
1public MiEventReceiver()2
1{2
1_spContext = SPContext.Current;2
1_httpContext = HttpContext.Current;2
1}2
1// resto del manejador2
1}2
Con
esto ya disponemos de una referencia al contexto, con ella podemos conseguir la
página que hemos de pasar al constructor de SPLongOperation:
1Page pagina = (Page)_httpContext.CurrentHandler;2
Aun asà el código de SPLongOperation falla al ejecutarlo en el eventReceiver lanzando una excepción de referencia a objeto nulo. Pero, ¿No tenÃamos ya una referencia válida a la página?
Asà es, pero si examinamos el objeto SPLongOperation veremos que una propiedad interna llamada m_context está a null.
¿Cómo proporcionar ese contexto? No hay un método en SPLongOperation que permita fijarlo explÃcitamente pero resulta que esta clase usa el contexto actual para fijar internamente el valor, luego lo que tenemos que hacer es incluir antes de invocar el método Begin() la siguiente sentencia:
1HttpContext.Current = _httpContext;2
Tras
añadir esta sentencia y reexaminando el objeto vemos que ahora la propiedad
interna m_context si tiene un valor distinto de null.
Ahora el código si funciona y en el navegador se muestra la ventana de proceso con los mensajes que hemos incluido.
El código final en el método (obviando control de errores etc…) es el siguiente:
1public override void ItemAdded(SPItemEventProperties properties)2
1{2
1base.ItemAdded(properties);2
1Â2
1Page pagina = (Page)_httpContext.CurrentHandler;2
1HttpContext.Current = _httpContext;2
1Â2
1using (SPLongOperation longOp = new SPLongOperation(pagina))2
1{2
1try2
1{2
1longOp.LeadingHTML = "Procesando";2
1longOp.TrailingHTML = "Un poco de paciencia por favor...";2
1longOp.Begin();2
1Â2
1// El código de larga duración va aquà ;)2
1CalcularElPrimerMillonDeDecimalesDePi();2
1Â2
1Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â longOp.EndScript("window.frameElement.commitPopup();");2
1}2
1catch (ThreadAbortException)2
1{     // Este error puede ocurrir porque SPLongOperation.End efectúa una llamada interna a Response.Redirect internamente y no tiene en cuenta que se puede estar ejecutando otro código2
1}2
1}2
1}2
El elemento final consiste en invocar el método EndScript en lugar de End ya que en este caso el evento que se ha escogido es el de ItemAdded lo que implica que habrá una ventana modal donde el usuario habrá introducido los datos.
Invocando longOp.EndScript("window.frameElement.commitPopup();") conseguiremos que tras la ejecución del código al usuario se le cierre la ventana modal volviendo a la ventana del navegador donde se muestra la lista.
Nuestro usuario ya estará contento al ver una ventana que relaja su impaciencia mientras efectuamos esa operación que tanto trabajo lleva.
Claro que lo siguiente que el usuario dice es: ¿Y no puedes ir cambiando el mensaje según se van haciendo cosas?
Si intentamos ir cambiando la propiedad TrailingHTML el mensaje no se actualiza. Para este refinamiento tendremos que usar la clase SPStatefulLongOperation, ya que con esta clase podemos realizar la operación informando del progreso de la misma al usuario.
En este caso el código a incluir dentro del eventReceiver es el siguiente:
1SPStatefulLongOperation.Begin(2
1"Texto de cabecera",2
1"<span id='trailingSpan'></span>",2
1(op) =>2
1{2
1op.Run((estado) =>2
1{2
1String texto = "texto a actualizar";2
1estado.Status ="<script type='text/javascript'>" + "document.all.item('trailingSpan').innerText = '" + texto +"';" + "</script>";2
1Â Â Â Â Â Â2
1//aquà va parte del código de larga duración2
1Â2
1.....2
1Â2
1String texto = "actualizando texto..";2
1estado.Status ="<script type='text/javascript'>" + "document.all.item('trailingSpan').innerText = '" + texto +"';" + "</script>";2
1Â2
1//más código de larga duración2
1});2
1Â2
1op.EndScript("window.frameElement.commitPopup();");2
1});2
Alberto Carlos Escola Fiz Consultor técnico albertoescola@hotmail.com