Friday, January 15, 2010

Redirect users to a custom location when an Infopath form is closed

Scenario:
How to redirect users to a custom location when a web-based Infopath form is closed?

Explanation:
In SharePoint, when a web-based infopath form that is opened from a FormsLibrary is closed, by default the user is redirected to the location where the form is opened from.
It is possible to redirect users to a custom location.
When SharePoint opens a web based Infopath form when a user clicks on a form from FormsLibrary, it appends a QueryString parameter called "Source" to the QueryString. When the form is closed, the request is transferred back to the value in the "Source" parameter. It is by behaviour of SharePoint. SharePoint uses the same mechanism to remember previous locations. For ex: In Site Settings, when we navigate through galleries, you will notice this.

For an existing infopath form:

http://[Website Url]/_layouts/FormServer.aspx?XmlLocation=[Web]/[FormsLibraryName]/[FormName].xml&DefaultItemOpen=1&FileName=[FormName].xml&Source=[Custom redirection location]?[Optionalparameters]{Parameter=[Parameter]%26Parameter1=[Parameter1]}

For a new infopath form:


http://[Website Url]/_layouts/FormServer.aspx?XsnLocation=[Site Collection Url]/FormServerTemplates/[Infopath Form Name].xsn&SaveLocation=[Custom redirection location]&DefaultItemOpen=1&FileName=[FormName].xml&Source=[Custom redirection location]?[Optionalparameters]{Parameter=[Parameter]%26Parameter1=[Parameter1]}

SPLongOperation - delay in redirection after the operation

Scenario:
Many of us may be aware of the SPLongOperation object provided by SharePoint.
Just to refresh once again, SPLongOperation is SharePoint's “Operation in Progress” spin wheel.
It could be used when time taking operations such as creating a site, running a worklow, looping through large list of items etc.. take place on the page. There are many ways to accomplish the same through AJAX. However SPLongOperation scores points for it's SharePointy look.

It renders the below on screen:

void PerformALongOperation()        
{
  using (SPLongOperation operation = new SPLongOperation(this.Page))
  {
    operation.LeadingHTML = "Creating site";
    operation.TrailingHTML = "Please wait while the project site is being created for New Site";
    operation.Begin();

    //Create a site, some lists in it, and then start a workflow

    operation.End(urlToRedirect);
  }
}

So far so good. It looks very straight farward to use this class.

Problem:

After the operation ends, every time we encountered a delay of exactly 32 seconds before the redirection happened.

We have tried the below:
operation.End(urlToRedirect);
HttpContext.Current.Response.Redirect(urlToRedirect);
SPUtility.Redirect(urlToRedirect);

Hoewver nothing seemed to work for us.
So finally javascript redirection worked out for us:
ClientScript.RegisterClientScriptBlock(this.GetType(), "script", String.Format("", urlToRedirect));

Error: Updates are currently disallowed on GET requests when you try to start a workflow programatically through code

Scenario:
While trying to start a workflow programatically through code, I had encountered the error "Updates are currently disallowed on GET requests".

My scenario was as below:
User fills in an infopath form and saves it, then closes it. Upon closing the form, I need to redirect users to an application page where a workflow is triggered, then redirect users to another page based on what the workflow does.

Explanation:
Even after ensuring that the 'AllowUnsafeUpdates' property on SPWeb was set to true, thie error kept showing up.

After a bit of reading, it was made clear to me that the workflows have to be started on a postback and that was the reason for the above error.

The below code can be used to raise a postback (assuming that there is a control on the page that can cause a postback such as a button with runat="server" set on it
if(!Page.IsPostBack)
{
  //If this is the first request, simulate button click to trigger the workflow
  String buttonClickSimulatorScript = "ButtonClickSimulatorScript";
  Type csType = this.GetType();
  ClientScriptManager cs = Page.ClientScript;

  // Check to see if the startup script is already registered.
  if (!cs.IsStartupScriptRegistered(csType, buttonClickSimulatorScript))
  {
    String js = "document.getElementById('"+[Control used to To Raise Postback].ClientID+"').click();";
    cs.RegisterStartupScript(csType, buttonClickSimulatorScript, js, true);
  }
}

and the code to start the workflow through code:

SPWeb web = [Do whatever to get to the web];
SPList list = [Do whatever to get the list];
SPListItem item = [Do whatever to get the item on which the workflow has to run];
SPWorkflowManager objWorkflowManager = SPContext.Current.Site.WorkflowManager;
SPWorkflowAssociationCollection objWorkflowAssociationCollection = list.WorkflowAssociations;
foreach (SPWorkflowAssociation objWorkflowAssociation in objWorkflowAssociationCollection)
{
    if (String.Compare(objWorkflowAssociation.BaseId.ToString("B"), [GUID of thw workflow], true) == 0)
    {
        SPSecurity.RunWithElevatedPrivileges(delegate()
        {
            web.AllowUnsafeUpdates = true;
            //Start the workflow...
            objWorkflowManager.StartWorkflow(item,
                                         objWorkflowAssociation,
                                         objWorkflowAssociation.AssociationData,
                                         true);

            web.AllowUnsafeUpdates = false;
        });
        break;
    }
}
The above code assumes that the list containing the item on which the workflow runs has the workflow attached to it already