How to Resume Suspended Workflows in .NET WF 4.0

Wednesday, 28 April 2010 05:57 AM
by Coose

So we’ve been on WF 4.0 for a while now, and we have been anxiously awaiting Dublin (AppFabric).  One of the things we really wanted out of AppFabric was the ability to resume workflows that have suspended due to unhandled exceptions.

Well, I discovered that you don’t need AppFabric.  The sql instance store already gives us that functionality…there’s just no GUI for it (that I can find).

So, start by creating the databases.  The scripts are in the framework folder:

C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en\SqlWorkflowInstanceStoreSchema.sql
C:\Windows\Microsoft.NET\Framework\v4.0.30319\SQL\en\SqlWorkflowInstanceStoreLogic.sql

Now, in the service behavior for the workflow xamlx, add some entries:

          <sqlWorkflowInstanceStore connectionStringName="Workflow"

                                    hostLockRenewalPeriod="00:00:05"

                                    runnableInstancesDetectionPeriod="00:00:02"

                                    instanceCompletionAction="DeleteAll"

                                    instanceLockedExceptionAction="AggressiveRetry"

                                    instanceEncodingOption="None" />

          <workflowIdle timeToPersist="00:00:02"

                        timeToUnload="00:00:05"/>

Make sure your connection string name matches your connection string to the database tables you created from the above sql scripts.

One more configuration item to add.  The workflow control endpoint needs to be added to the xamlx workflow service.

<service name="MyWorkflowService"

         behaviorConfiguration="myServiceBehavior">

  <endpoint address=""

            binding="customBinding"

            bindingConfiguration="myBindingConfiguration"

            contract="IMyContract" />

  <endpoint address="wce"

            binding="basicHttpBinding"

            kind="workflowControlEndpoint"/>

</service>

The endpoint with ‘kind=”workflowControlEndpoint”’ (new for .NET 4.0) is the key here.  That creates the workflow control endpoint on your workflow service.

Now, after I get an exception in a workflow (and on my development machine, there has been many), the InstancesTable in the instance store contains the information we need.  Running the sql:

select Id, SuspensionExceptionName, SuspensionReason, ExecutionStatus
from [System.Activities.DurableInstancing].InstancesTable
where IsSuspended = 1

I get these records:

image

So, here I can see the suspended workflows.  What I need from here is the Id.

Now, let’s call the endpoint we created above.  It’s a piece of cake:

You need a reference to System.ServiceModel.Activities v4.  On my machine it’s found at C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\System.ServiceModel.Activities.dll.  This assembly contains the classes WorkflowControlEndpoint and WorkflowControlClient.

Guid workflowId = SelectWorkflowIdSomehow();

 

WorkflowControlEndpoint ep = new WorkflowControlEndpoint(

    new BasicHttpBinding(),

    new EndpointAddress("http://localhost/myWorkflowService.xamlx/wce")

);

WorkflowControlClient client = new WorkflowControlClient(ep);

client.Unsuspend(workflowId);

That’s it.  The WorkflowControlEndpoint does the work, and the workflow is unsuspended and resumed from the last persisted state before the exception happened.  Essentially, everything that happened between un-persistence and the exception is gone, and the previous state is loaded.

Cool, huh?  And you don’t even need AppFabric.  AppFabric does add a nice layer of UI for persistence and tracking, so I’m still looking forward to it! :)

Enjoy.  Or don’t.  Whatever.

Comment on this
Development
|

Standard Bindings Custom Binding Equivalents

Tuesday, 02 June 2009 06:19 PM
by Coose

Ever wanted to know the CustomBinding equivalent of a standard binding?  Use this tool to see them.

[More Click here to see the tool]



How’s it done?

Create the binding and save with the ServiceContractGenerator.  That writes to a config file.  Then I read it back.

So I created a web user control with a combo, literal and a button:

<asp:DropDownList ID="BindingList" runat="server">

    <asp:ListItem Text="BasicHttpBinding" Value="System.ServiceModel.BasicHttpBinding" />

    <asp:ListItem Text="CustomBinding" Value="System.ServiceModel.Channels.CustomBinding" />

    <asp:ListItem Text="MsmqIntegrationBinding" Value="System.ServiceModel.MsmqIntegration.MsmqIntegrationBinding" />

    <asp:ListItem Text="NetNamedPipeBinding" Value="System.ServiceModel.NetNamedPipeBinding" />

    <asp:ListItem Text="NetPeerTcpBinding" Value="System.ServiceModel.NetPeerTcpBinding" />

    <asp:ListItem Text="NetTcpBinding" Value="System.ServiceModel.NetTcpBinding" />

    <asp:ListItem Text="WSDualHttpBinding" Value="System.ServiceModel.WSDualHttpBinding" />

    <asp:ListItem Text="WSHttpBinding" Value="System.ServiceModel.WSHttpBinding" />

    <asp:ListItem Text="WS2007HttpBinding" Value="System.ServiceModel.WS2007HttpBinding" />

    <asp:ListItem Text="WSFederationHttpBinding" Value="System.ServiceModel.WSFederationHttpBinding" />

    <asp:ListItem Text="WS2007FederationHttpBinding" Value="System.ServiceModel.WS2007FederationHttpBinding" />

</asp:DropDownList>

<asp:Button ID="GoButton" runat="server" Text="Evaluate"

    onclick="GoButton_Click" />

<br />

<pre><asp:Literal runat="server" ID="BindingMarkup" /></pre>

And in the button handler:

protected void GoButton_Click(object sender, EventArgs e)

{

    Assembly asm = Assembly.Load("System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");

    Type t = asm.GetType(BindingList.SelectedValue);

    Binding b = Activator.CreateInstance(t) as Binding;

 

    string configFile = Server.MapPath("~/App_Data/__bindings.config");

 

    Configuration machineConfig = ConfigurationManager.OpenMachineConfiguration();

    ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();

    fileMap.ExeConfigFilename = configFile;

    fileMap.MachineConfigFilename = machineConfig.FilePath;

    Configuration config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);

    config.NamespaceDeclared = true;

 

    ServiceContractGenerator gen = new ServiceContractGenerator(config);

    string sectionName, configName;

    gen.GenerateBinding(new CustomBinding(b.CreateBindingElements()), out sectionName, out configName);

    config.Save();

 

    XmlDocument doc = new XmlDocument();

    doc.LoadXml(File.ReadAllText(configFile));

 

    StringBuilder xml = new StringBuilder();

    using (XmlWriter writer = XmlWriter.Create(xml, new XmlWriterSettings() { Indent = true, IndentChars = "  ", NewLineChars = Environment.NewLine, NewLineOnAttributes = true }))

    {

        doc.Save(writer);

    }

 

    this.BindingMarkup.Text = HttpUtility.HtmlEncode(xml.ToString());

    File.Delete(configFile);

}

Comment on this
Development
|