Adding Visio diagram to existing Visual Studio workflow – part 3

This article series

  1. Adding Visio diagram to existing Visual Studio workflow – part 1
  2. Adding Visio diagram to existing Visual Studio workflow – part 2
  3. Adding Visio diagram to existing Visual Studio workflow – part 3

In part 2 of this topic I explained how to decorate the existing workflow with ISPActivityDetailsSerializable and implement the GetActivityDetail method, as this method would be called by the WorkflowStatus control in WrkStat.aspx.

As mentioned the WorkflowStatus control is a delegate control with Id WorkflowStatusVisualization. As a delegate control it means we can replace the control with our own implementation using a feature. Looking at the implementation of the OOB WorkflowStatus UserControl is defined in ~/_controltemplates/workflowstatus.ascx, in only contains a VisioWebAccess web part and a hidden field.

Inspecting its code behind in reflector, the control would try to find ShowPreview & PreviewHref in the Workflow Association Data. As explained in part 1 these settings would tell the control to display the Visio Diagram, the control also called the GetActivityDetails method of the workflow instance and stores its value and  then it registers a javascript located in _layouts/VisioWebAccess/workflowstatus.js. The javascript contains functions to render the overlays on the Visio Diagram. The most important part of the control is a method to initialize the javascript variables using values return by the GetActivityDetails method.

Having this knowledge I prefer to extend the OOB WorkflowStatus control to be able to process my custom Activity Detail. Below is steps for implementing our custom WorkflowStatus Control

1. In the VS 2010 add reference to Microsoft.Office.Visio.Server.dll the project and add a new UserControl, this will add a new User Control under CONTROLTEMPLATES.

2. Create a Web Scope feature to provision the Delegate Control. below is the element xml of the feature.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Control Id="WorkflowStatusVisualization" Sequence="80" ControlSrc="~/_controltemplates/DL.WorkflowControl/CustomWorkflowStatus.ascx" />
</Elements>

3.Copy the content (anything under @Control directive tag) from OOB WorkflowStatus.ascx in the CONTROLTEMPLATES folder to the content to our CustomWorkflowStatus.ascx.  Also copy and @Register TagPrefix=VisioWebControls to our custom control. And put below code as its code behind

public partial class CustomWorkflowStatus : WorkflowStatus
{
    protected override void OnPreRender(EventArgs e)
    {
        this.VisioWebAccess1.DiagramPath = this.DiagramPath;
        if (IsCustomActivityDetail)
            this.AddActivityDetailsGenerationScript();
        else base.OnPreRender(e);
    }

    private string DomObjectID
    {
        get
        {
            return ("WebPart" + this.VisioWebAccess1.Qualifier);
        }
    }

    private bool IsCustomActivityDetail
    {
        get
        {
            if (this.ActivityDetailsArray != null &&
               ActivityDetailsArray.Length > 0 &&
               !string.IsNullOrEmpty(ActivityDetailsArray[0].ExtendedActivityDetails))
            {
                //get the first activityDetail
                XElement customActDetail = XElement.Parse(this.ActivityDetailsArray[0].ExtendedActivityDetails);
                return customActDetail.Name == "MyActivities";
            }
            return false;
        }
    }

    private void AddActivityDetailsGenerationScript()
    {
        string key = "WorkflowVWAActivityDetailsScript";
        if (!this.Page.ClientScript.IsClientScriptBlockRegistered(key))
        {
            StringBuilder script = new StringBuilder();
            script.Append("<script type=\"text/javascript\">\n");
            script.Append("//<![CDATA[\n");
            script.Append("vwaControlName= \"" + this.DomObjectID + "\"; \n");

            XElement customActDetail = XElement.Parse(this.ActivityDetailsArray[0].ExtendedActivityDetails);
            ActivityDetailsArray = (from act in customActDetail.Descendants("Activity")
                                    select new SPWorkflowActivityDetails()
                                    {
                                        Name = string.Empty,
                                        ShapeId = act.Attribute("Guid").Value,
                                        Status = (SPWorkflowActivityStatus)Enum.Parse(typeof(SPWorkflowActivityStatus), act.Attribute("Status").Value)
                                    }).ToArray();

            script.Append(" var statusDetails; \n");
            int num2 = 0;
            for (int i = 0; i < this.ActivityDetailsArray.Length; i++)
            {
                SPWorkflowActivityDetails ad = this.ActivityDetailsArray[i];
                if (ad != null)
                {
                    SPWorkflowActivityDetails ad = this.ActivityDetailsArray[i];
                    if (ad != null)
                    {
                        script.Append(string.Concat(new object[] { "statusDetails = new ActivityDetails('", ad.Name, "','", ad.Status, "','", ad.ShapeId, "'); \n" }));
                        script.Append("wfActivityDetails[" + num2 + "] = statusDetails; \n");
                        num2++;
                    }
                }
            }
            script.Append("var shapeCount = " + 1); //changed num3 to 2 as num3 sometimes return RTL(right to left) to true

            script.Append("//]]>\n");
            script.Append("</script>");
            this.Page.ClientScript.RegisterClientScriptBlock(base.GetType(), key, script.ToString());
        }

    }

}

Code explanation:

As we can see the CustomWorkflowStatus inherits from OOB WorkflowStatus class. In our custom class we override the OnPreRender method. In the method we set the DiagramPath of the VisioWebAccess web part, and then the most important thing is checking if the workflow has custom activity details, if not it just calls the OOB PreRender implementation.

Most of the class initialization has been done in the OnLoad event of the parent class, one of them is setting  this.ActivityDetailsArray = workflow.GetActivityDetails(). So in our IsCustomActivityDetail property in line 19, we check the ActivityDetailArray. As our workflow implements ISPActivityDetailSerializable, the first element of the array should have non-null ExtendedActivityDetail property. Then using LINQ we check if the value of the property’s root element is MyActivities (our custom xml element).

In line 35, we parse our custom xml from the ExtendedActivityDetail property and initialize the javascript used by workflowstatus.js to draw the status overlays on the Visio Diagram.

The result is that we can visualize our existing visual studio workflow using any Visio Diagram as below.

Summary:
To add visualization to existing visual studio workflow we need to decorate the existing workflow using ISPActivityDetailsSerializable and implement GetActivityDetails method to generate a custom xml that can be consumed by our custom WorkflowStatus control. The control would add Status overlay on the Visio diagram.

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part

CustomAction: Open UrlAction in Modal Dialog

As we know that CustomAction can have UrlAction that points to an application page. In SharePoint 2007, whenever the CustomAction is clicked it simply navigates to the application page defined in UrlAction.

As I am migrating to SharePoint 2010 I want to have some of my CustomAction especially the ECB (Edit Control Block) ones to open in modal dialog. SharePoint 2010 provides several javascript methods to open an url in a modal dialog. One of them is OpenPopUpPageWithTitle method defined in core.js. Below is the signature of the method:

OpenPopUpPageWithTitle(url, callback, width, height,title)

The method parameters are self explained. Internally the OpenPopUpPageWithTitle method is calling SP.UI.ModalDialog.showModalDialog(options) where the options are the parameters passed to the method.  To see how it works below is my ECB definition.

<CustomAction Id="Custom.ProvisionGroup"
                Location="EditControlBlock"
                Title="My Custom Action"
                RegistrationType="List"
                RegistrationId="20002"
                Rights="ManagePermissions" 
               ImageUrl="/_layouts/images/editicon.gif">
 <UrlAction Url="javascript:OpenPopUpPageWithTitle('{SiteUrl}/_layouts/myCustom/ProvisionGroup.aspx?List={ListId}&ID={ItemId}', RefreshOnDialogClose, 600, 400,'My Custom Action')"/>
  </CustomAction>

Basically it is an ECB for a custom list definition, on the UrlAction I use the OpenPopUpPageWithTitle method where the url is a custom application page, the callback is a SharePoint method called RefreshOnDialogClose (this method is defined in core.js, it will refresh the underlying page when the dialog close). Basically the callback can be anything such as display notifaction(SP.UI.Notify). The other parameters specified the width, height and title of the dialog box. Below is the screen shot of the my custom ECB.

CustomAction EditControlBlock

CustomAction ECB

CustomAction Modal Dialog

CustomAction Modal Dialog

In the custom application page we need to close the dialog box whenever it has finished executing. Below is the code to close the dialog box. It needs to be called at the end of the execution.

private void ClosePopUp()
{
            this.Context.Response.Write("<script type='text/javascript'>window.frameElement.commitPopup();</script>");
            this.Context.Response.Flush();
            this.Context.Response.End();
}

Off-course, we can achieve the same result by using SP.UI.ModalDialog directly as shown in this post as OpenPopUpPageWithTitle method internally is using SP.UI.ModalDialog

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part

Migrating custom webpart containing Chart control

I got the following error after migrating from SharePoint 2007 a webpart containing chart controls.

System.Web.UI.DataVisualization.Charting.DefaultImageHandler.System.Web.UI.DataVisualization.
Charting.IChartStorageHandler.Save(String key, Byte[] data) +527.

[NullReferenceException: Object reference not set to an instance of an object.] System.Web.UI.DataVisualization.Charting.DefaultImageHandler.System.Web.UI.DataVisualization.Charting.IChartStorageHandler.Save(String key, Byte[] data) +527 System.Web.UI.DataVisualization.Charting.ChartHttpHandler.GetChartImageUrl(MemoryStream stream, String imageExt) +635Basically the Chart control couldn’t access the Session variable. After googling I found this Mark Arend’s blog, it explained that SharePoint 2010 by default disabled ASP.NET Session State. So as advised I run this command in PowerShell

Enable-SPSessionStateService -DefaultProvision

It fixed. I could see my chart. But when one of the chart did postback, I got following different error:

[HttpException (0x80004005): No http handler was found for request type 'POST']
System.Web.HttpApplication.MapIntegratedHttpHandler(HttpContext context, String requestType, VirtualPath path, …) +529
System.Web.HttpServerUtility.Execute(String path, TextWriter writer, Boolean preserveForm) +947

[HttpException (0x80004005): No http handler was found for request type 'POST'] System.Web.HttpApplication.MapIntegratedHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, Boolean useAppConfig, Boolean convertNativeStaticFileModule) +529

It is because the http handler configuration for the Chart Control doesn’t have POST in its verb by default. Having added POST in the verb attribute, my chart control work. Below is the handler setting in web.config. It is under httpHandler in the system.web configuration.

<add verb="GET,HEAD,POST" path="ChartImg.axd" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />

But as my farm consists of several web front-ends, I prefer to change the web.config using SharePoint API. In SharePoint 2007 we can use feature to modify web.config but in SharePoint 2010, PowerShell is better tool to do the job. Based on this good post from Scripting Guy, below is PowerShell script to do web.config modification using SPWebConfigModification and this is another post about how to use it.
function Enable-ChartingPostVerb{
param(
 [Parameter(Mandatory=$true, ValueFromPipeline=$true, Position=0)]
 [Microsoft.SharePoint.PowerShell.SPWebApplicationPipeBind]
   $WebApplication
)
begin{
  Enable-SPSessionStateService -DefaultProvision
}
process {     
   $WebApp = $WebApplication.Read()
   # SPWebConfigModification to enable BlobCache
   $configMod1 = New-Object Microsoft.SharePoint.Administration.SPWebConfigModification
   $configMod1.Path = "configuration/system.web/httpHandlers/add[@path=`"ChartImg.axd`"]"
   $configMod1.Name = "verb"
   $configMod1.Owner = "ChartingConfig"
   ## SPWebConfigModificationType.EnsureChildNode -> 0
   ## SPWebConfigModificationType.EnsureAttribute -> 1
   ## SPWebConfigModificationType.EnsureSection -> 2
   $configMod1.Type = 1
   $configMod1.Value = "GET,HEAD,POST"
   
   # Add mods, update, and apply
   $WebApp.WebConfigModifications.Add($configMod1)
   $WebApp.Update()
   $WebApp.Parent.ApplyWebConfigModifications()
  }
}

To run the script, for example if the script file name is ChartConfig.ps1.
- on the Shell you can do (this will load the function) PS1> . ./ChartConfig.ps1
- then you can call the function Enable-ChartingPostVerb or call the function as part of a pipeline.

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part

Close modal dialog when SPLongOperation finish

I have been  using SPLongOperation in forms, webparts or a custom pages to enclose any long execution. As it will display SharePoint’s loading animation instead of the UI freezing waiting for the operation to finish.

SPLongOperation SharePoint 2010

In SharePoint 2007 when a long running operation finished the page will redirect to any specified page. But in SharePoint 2010 as a page can be displayed as a modal dialog so it is expected that the modal dialog would be closed when the long operation finished and refresh the underlying page. Below is the code of Long Operation for SharePoint 2010.

protected override void OnClick(EventArgs e)
{
 try{
    using (SPLongOperation longOp = new SPLongOperation(this.Page))
    {
      longOp.LeadingHTML = "Long Operation Title";
      longOp.TrailingHTML = "This may take few seconds.";
      longOp.Begin();
      //--------------------------
      //code for long running operation is here
      //---------------------
      EndOperation(longOp);
    }
  }
  catch (ThreadAbortException) { /* Thrown when redirected */}
  catch (Exception ex)
  {
     SPUtility.TransferToErrorPage(ex.ToString());
   }
}
protected void EndOperation(SPLongOperation operation)
{
  HttpContext context = HttpContext.Current;
  if (context.Request.QueryString["IsDlg"] != null)
  {
    context.Response.Write("<script type='text/javascript'>window.frameElement.commitPopup();</script>");
   context.Response.Flush();
   context.Response.End();
  }
  else
  {
    string url = SPContext.Current.Web.Url;
    operation.End(url, SPRedirectFlags.CheckUrl, context, string.Empty);
  }
}

At the EndOperation method it would check if it is the a modal dialog by checking the request’s query string as in SP 2010 all modal pages would have query string IsDlg=1. If it is a modal dialog it would write a javascript to close the dialog and terminate the current response stream. Otherwise it would do normal redirection as that in SharePoint 2007.

Related Post: Open a custom application page in modal dialog

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part