Make SPLongOperation postback to same page. (Well a workaround at least)


The title here “Make SPLongOperation postback to the same page. (Well a workaround at least)” is because there is no actual way of making the SPLongOperation postback to the same page. However I have been able to achieve a similar affect. First let’s explain what the SPLongOperation is.

The SPLongOperration class in SharePoint is a brilliant way to inform your users that a long operation is running in the background, without freezing or locking up the UI while the process is running. You may have seen the screen many times when creating a site.

The code to create this is very simple.


 using (SPLongOperation longOperation = new SPLongOperation(this.Page))

{

 //Show Text for the Top Line on the screen.

  longOperation.LeadingHTML = "Provisioning Sites";

 //Show text for the Bottom line on the screen.

  longOperation.TrailingHTML = "Please wait while the sites are being provisioned.";

  longOperation.Begin();

  try

 {

  //The code that will take it's time.

  Thread.Sleep(5000);

 //On complete navigate to your success/complete page.

  longOperation.End("MySuccessPage.aspx");

 }

 catch(ThreadAbortException)

 {

  //Don't do anything, this error can occur because the SPLongOperation.End

 // performs a Response.Redirect internally and doesn't take into account that other code might still be executed.

 }

 catch(Exception ex)

 {

  //When an exception happens, the page is redirected to the error page.

  //Here you can redirect to another custom page.

  SPUtitility.TransferToErroPage(ex.ToString());

 }

}

As you can see from above it is pretty simple. However the longOperation.End(“MySuccessPage.aspx”) redirects the user to another page. You can point it to the same page you are currently on such as longOperation.End(Request.Url.ToString()); but this doesn’t cause a post back.

SPLongOperation.End has 3 overloads.

  • SPLongOperation.End(string strRedirectPage)
  • SPLongOperation.End(string strProposedRedirect, SPRedirectFlags rgfRedirect, HttpContext context, string queryString)
  • SPLongOperation.End(string strProposedRedirect, SPRedirectFlags rgfRedirect, HttpContext context, string queryString, string strScript)

It is the third overload here that can help us achieve a postback like state.

The strScript is javascript that is run before the page gets redirected. If we reflect on the 3rd method we can see how this works.

public void End(string strProposedRedirect, SPRedirectFlags rgfRedirect, HttpContext context, string queryString, string strScript)

{

    string str;

    if (!SPUtility.DetermineRedirectUrl(strProposedRedirect, rgfRedirect, context, queryString, out str))

    {

        str = strProposedRedirect;

    }

    string str2 = SPHttpUtility.EcmaScriptStringLiteralEncode(str);

    string str3 = string.Format(CultureInfo.InvariantCulture, "window.location.replace(\"{0}\");", new object[] { str2 });

    if (strScript != null)

    {

        str3 = strScript + str3;

    }

    this.EndScript(str3);

}

In this method call, the javascript is added to the front of the windows.location.replace, so that your html/javascript is run first, then it redirects you to your strProposedRedirect URL. From this knowledge, if I put return false; as my strScript, it prevents any JavaScript running after and prevents the redirection of the page. However I’m still looking at the loading page.

So how does any of this help me? What am I trying to achieve?

My end goal is to perform a long process that gathers some results and then displays them on the same page once the long process has finished. Example screen shots below.

The above can be achieved by using Javascript to call a function that will post the user to the page with the required results within the post data. In my original example of how to use SPLongOperation you would replace the line Thread.Sleep(5000); with your method that would process the word file and return in a string the results. Then you need to create the javascript post form data using the results.


…

 try

 {

  //The code that will take it's time.

  WordProcessing wordprocess = new WordProcessing(FileUpload1.FileContent, FileUpload1.FileName);

//results is a global string variable.

 results = wordprocess.Process();

 //On complete navigate to your success/complete page.

  longOperation.End(Request.Url.ToString(), SPRedirectFlages.UseSource, HttpContext.Current, "", postBackToPage();

 }

 …

 

Now we need to create the JavaScript function post_Back_To_Page() which will be called.


private string postBackToPage()

        {

            StringBuilder str = new StringBuilder();

            str.Append("function post_Back_To_Page(){");

            str.Append("var method = 'post';");

            str.Append("var form = document.createElement('form');");

            str.Append("form.setAttribute('method', method);");

            str.Append("form.setAttribute('action', '" + Request.Url.ToString() + "');");

            str.Append("var data = document.createElement('input');");

            str.Append("data.setAttribute('type', 'hidden');");

            str.Append("data.setAttribute('name', 'results');");

            str.Append("data.setAttribute('value', '" + SPEncode.UrlEncode(results) + "');");

            str.Append("form.appendChild(data);");

            str.Append("document.body.appendChild(form);");

            str.Append("form.submit();");

            str.Append("};post_Back_To_Page();return false;");

            return str.ToString();

        }

Now on loading the page if there is a form data called results, we will want to grab and display that. So on your page load, add the following.


protected void Page_Load(object sender, EventArgs e)

{

  if (Request.Form["results"] != null)

 {

   txt.Results.Text = SPEncode.UrlDecodeAsUrl(Request.Form["results"]);

 }

}

At this stage, I was excited that a workaround was possible. However first time I ran this code I encountered the error “The security validation for this page is invalid.”

This is happening because I’m trying to insert data into a page. SharePoint thinks I’m a hacker, and therefore rejects the post. After extensive searching on the web, peoples suggestion was to disable the SPWebapplication.FormDigestSettings. Do not do this! It is a huge security risk to your farm. What SharePoint is after is the form digest control value.

According to MSDN http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.webcontrols.formdigest.aspx

“The FormDigest value is a generated security validation, or message digest, to help prevent the type of attack whereby a user is tricked into posting data to the server without knowing it. The security validation is specific to a user, site, and time period and expires after a configurable amount of time. When the user requests a page, the server returns the page with security validation inserted. When the user then submits the form, the server verifies that the security validation has not changed.”

We need to create a form digest value and add it to the form being posted in the Javascript. Replace the code in the method postBackToPage()


private string postBackToPage()

        {

          //Create the Form Digest String.

            StringBuilder formDigestString = new StringBuilder();

            StringWriter tw = new StringWriter(formDigestString);

            HtmlTextWriter textWriter = new HtmlTextWriter(tw);

            FormDigest formDigest = new FormDigest();

            formDigest.RenderControl(textWriter);

            string formDigestHtml = formDigestString.ToString();

            StringBuilder str = new StringBuilder();

            str.Append("function post_Back_To_Page(){");

            str.Append("var method = 'post';");

            str.Append("var form = document.createElement('form');");

            str.Append("form.setAttribute('method', method);");

            str.Append("form.setAttribute('action', '" + Request.Url.ToString() + "');");

           //Add formdigestHtml to the Form.

            str.Append("var digest = document.createElement('div');");
            str.Append("digest.innerHTML = '" + formDigestHtml + "';");

            str.Append("form.appendChild(digest);");

            str.Append("var data = document.createElement('input');");

            str.Append("data.setAttribute('type', 'hidden');");

            str.Append("data.setAttribute('name', 'results');");

            str.Append("data.setAttribute('value', '" + SPEncode.UrlEncode(results) + "');");

            str.Append("form.appendChild(data);");

            str.Append("document.body.appendChild(form);");

            str.Append("form.submit();");

            str.Append("};post_Back_To_Page();return false;");

            return str.ToString();

        }

As the title of this blog says “Well a workaround at least.” You aren’t actually posting back to the same page, but you can redirect back to the same page, passing back data that you might want to display. The form created in JavaScript can be extended to add extra elements for other data you wish to post back as well.

Advertisements

2 thoughts on “Make SPLongOperation postback to same page. (Well a workaround at least)

  1. In the line ” formDigest.RenderControl(textWriter);” , i am getting the error, value cannot be null. How to fix it?

    • Hi Venkat,
      Thank you for viewing my blog. In my code there are 3 lines leading up to the formDigest.RenderControl(textWriter). I would recommend you looking at these lines of code stepping through and seeing which bit is actually not reading in the value.

      StringBuilder formDigestString = new StringBuilder();
      StringWriter tw = new StringWriter(formDigestString);
      HtmlTextWriter textWriter = new HtmlTextWriter(tw);
      FormDigest formDigest = new FormDigest();
      formDigest.RenderControl(textWriter);

      If it’s textWriter that is null, then check that StringWriter tw isn’t null. In my example I’m passing nothing in at this point so, it’s all blank initialization of classes/objects.

      Good luck.

Comments are closed.