Archive

Posts Tagged ‘SPLongOperation’

SharePoint: SPLongOperation inside a Modal Dialog Window

September 10th, 2012 No comments

    In one of my posts I wrote about using SPLongOperation. When a lengthy operation has been accomplished the usual behavior of the SPLongOperation is to redirect user to another web page. In SharePoint 2010, however, the web page containing long operation could be opened in a Modal Dialog Window. In this case, instead of redirection, we often need to close the dialog right after the long operation is completed.

SPLongOperation.EndScript method

Fortunately, the SPLongOperation class provides us with a new useful method, EndScript, which notifies server that the operation has been completed and sends a specified JavaScript into the user browser. A typical use is presented below:

using (SPLongOperation operation = new SPLongOperation(this.Page))
{ 
    operation.LeadingHTML  = "Long operation";
    operation.TrailingHTML = "Please wait while the long operation is in progress";
 
    operation.Begin();
 
    // the code of the lengthy operation
    ...
 
    // check if the current page is opened in dialog
    if(SPContext.Current.IsPopUI)
        // signal that the lengthy operation is completed and 
        // send a script to close the current dialog
        operation.EndScript("window.frameElement.commitPopup();");
    else
        // signal that the lengthy operation is completed and 
        // redirect to another page
        operation.End("anotherPage.aspx");
}

The window.frameElement.commitPopup() script allows to close the dialog and pass SP.UI.DialogResult.OK as the result. The SPContext.Current.IsPopUI flag indicates if the current page is going to be opened in dialog and should be rendered in an appropriate way.

Wrapper around SPLongOperation

In the same old post I demonstrated a handy wrapper around SPLongOperation. Taking into account the dealing with dialogs, the updated version of the wrapper looks as follows:

public class SPLongOperationExecutionParams
{
    public string LeadingHtml  { get; set; }
    public string TrailingHtml { get; set; }
    public string RedirectUrl  { get; set; }
    public bool   CloseDialog  { get; set; }
}

public static void DoInSPLongOperationContext(SPLongOperationExecutionParams executionParams, Action action)
{
    if (HttpContext.Current.CurrentHandler is Page)
    {
        using (SPLongOperation operation = new SPLongOperation(HttpContext.Current.CurrentHandler as Page))
        {
            operation.LeadingHTML  = !string.IsNullOrEmpty(executionParams.LeadingHtml)  ? executionParams.LeadingHtml  : "Long operation";
            operation.TrailingHTML = !string.IsNullOrEmpty(executionParams.TrailingHtml) ? executionParams.TrailingHtml : "Please wait while the long operation is in progress";

            operation.Begin();

            if (action != null)
                action();

            try
            {
                if (executionParams.CloseDialog)
                    operation.EndScript("try { window.frameElement.commitPopup(); } catch (e) {}");
                else
                    operation.End(executionParams.RedirectUrl, SPRedirectFlags.DoNotEndResponse | SPRedirectFlags.Trusted, HttpContext.Current, "");
            }
            catch (ThreadAbortException)
            {
                // This exception is thrown because the SPLongOperation.End
                // calls a Response.End internally
            }
        }
    }
    else
        throw new ApplicationException("Couldn't find a host page!");
}

Below is an example of how to use it:

void startLongOperationButton_Click(object sender, EventArgs e)
{
    SPLongOperationExecutionParams executionParams = new SPLongOperationExecutionParams() 
    { 
        LeadingHtml  = "Creation of a new list item", 
        TrailingHtml = "Please wait while the item is being created", 
        CloseDialog  = SPContext.Current.IsPopUI, // if the page is in a dialog, the latter will be closed
        RedirectUrl  = "anotherPage.aspx" // if it's not a dialog, user will be redirected to that page
    };

    DoInSPLongOperationContext(executionParams, delegate()
    {
        // the code of the lengthy operation
        Thread.Sleep(5000);
    });
}

SharePoint: How to use SPLongOperation

August 11th, 2011 No comments

     If you need to run a lengthy server operation, it’s reasonable to use SPLongOperation. SPLongOperation shows a spinning wheel indicator with a specified text on the web page during a lengthy operation. You have probably seen SPLongOperation at work while creating new web applications or site collection using SharePoint Central Administration.

SPLongOperation - spin wheel with associated text

To use SPLongOperation, you need to create a new object of the SPLongOperation type, set the LeadingHTML and TrailingHTML properties to tell user what’s going on at the moment and call the SPLongOperation.Begin method to start the process. Then run the code of the lengthy server operation. Once the operation is done, call the SPLongOperation.End method passing the URL where user will be redirected to. Here is a typical code that utilizes SPLongOperation on a web page is:

void startLongOperationButton_Click(object sender, EventArgs e)
{
    using (SPLongOperation operation = new SPLongOperation(this.Page))
    {

        operation.LeadingHTML  = "Long operation";
        operation.TrailingHTML = "Please wait while the long operation is in progress";

        operation.Begin();

        // the code of the lengthy operation

        // redirecting to a page that, for example, informs user that the long operation has successfully completed
        operation.End("SomePage.aspx");
    }
}

I use the SPLongOperation, for example, when I need to create a list item with a huge number of unique permissions. It can take dozens of seconds, and it’s a good practice to use the spinning wheel indicator with a descriptive text to tell an impatient user that the application is in progress and not hung on him.

I’ve wrapped interaction with SPLongOperation into the following class and method:

/// <summary>
/// Represents all settings that can be specified to use SPLongOperation
/// </summary> 
public class SPLongOperationExecutionParams
{
    public string LeadingHtml  { get; set; }
    public string TrailingHtml { get; set; }
    public string RedirectUrl  { get; set; }
}

/// <summary>
/// Invokes an action inside SPLongOperation
/// </summary>
/// <param name="executionParams">Settings to use SPLongOperation</param>
/// <param name="action">User specified action</param>
public static void DoInSPLongOperationContext(SPLongOperationExecutionParams executionParams, Action action)
{
    if (HttpContext.Current.CurrentHandler is Page)
    {
        using (SPLongOperation operation = new SPLongOperation(HttpContext.Current.CurrentHandler as Page))
        {
            operation.LeadingHTML  = !string.IsNullOrEmpty(executionParams.LeadingHtml)  ? executionParams.LeadingHtml  : "Long operation";
            operation.TrailingHTML = !string.IsNullOrEmpty(executionParams.TrailingHtml) ? executionParams.TrailingHtml : "Please wait while the long operation is in progress";

            operation.Begin();

            if (action != null)
                action();

            try
            {
                operation.End(executionParams.RedirectUrl, SPRedirectFlags.Trusted, HttpContext.Current, "");
            }
            catch (System.Threading.ThreadAbortException)
            {
                // This exception is thrown because the SPLongOperation.End 
                // calls a Response.End internally
            }
        }
    }
    else
        throw new ApplicationException("Couldn't find a host page!");
}

Here is an example of how to use the wrapper:

void startLongOperationButton_Click(object sender, EventArgs e)
{
    SPLongOperationExecutionParams spLongOperationExecutionParams = new SPLongOperationExecutionParams() { LeadingHtml = "Creation of a new list item", TrailingHtml = "Please wait while the item is being created", RedirectUrl = "SomePage.aspx" };
    DoInSPLongOperationContext(spLongOperationExecutionParams, delegate()
    {
        // the code of the lengthy operation
        Thread.Sleep(5000);
    });
}

As you have probably noticed, I call the SPLongOperation.End inside the try…catch. I do so because this method may cause ThreadAbortException. The SPLongOperation.End internally calls Response.End, which stops execution of the page by means of the Thread.CurrentThread.Abort method. Here is the code of the Response.End method extracted by Reflector:

public void End()
{
    if (this._context.IsInCancellablePeriod)
    {
        InternalSecurityPermissions.ControlThread.Assert();
        Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false)); (*)
    }
    else if (!this._flushing)
    {
        this.Flush();
        this._ended = true;
        if (this._context.ApplicationInstance != null)
        {
            this._context.ApplicationInstance.CompleteRequest();
        }
    }
}

The Thread.Abort method raises ThreadAbortException in the thread where it is invoked to begin the process of terminating the thread. I catch those exceptions and just do nothing about them.

In some blogs, people tell that SPLongOperation.End calls Response.Redirect internally, but Reflector shows that it’s not so in the reality. SPLongOperation.End calls Response.End directly. The redirection is accomplished through a special JavaScript, which is added to the web page right after SPLongOperation.End is invoked. I emphasize this fact, because you may attempt to pass the SPRedirectFlags.DoNotEndResponse flag to the SPLongOperation.End method. This flag makes sense only when we use the Response.Redirect, because only in that case Response.End wouldn’t be invoked, and, consequently, ThreadAbortException wouldn’t be thrown. But unfortunately, as I said above, SPLongOperation.End doesn’t use Response.Redirect and, therefore, SPRedirectFlags.DoNotEndResponse will be ignored, and the exception will be thrown.

Please also note that SPLongOperation keeps connection between client and server alive and the Response stream open. Therefore, if your long operation takes longer than the value of httpRuntime.executionTimeout defined in Web.config (or Machine.config) you will receive the ‘Request Timed Out’ exception. Therefore, to perform a really long operation (for example, longer than the default time-out value, which is 90 seconds), you would have to refuse SPLongOperation and look for some other approach.