1
votes

I am trying to achieve the following. I have created long running workflows that wake up at scheduled interval i.e. delay activity and perform their job, populate the cache. These workflows are started from main application, which is a IIS 7 hosted custom WCF Data Service. The trigger for starting the workflows vary from User Login, Request for a particular Entity.

Currently, I've created a WCF Workflow Service, that listens for triggers that are part of a standard request contract. Once trigger received, it instantiates a WorkflowApplication with the Activity Definition and starts the workflow. This however has it's limitation, as persistence and rehydration has to be done manually.

I came across the sample of WorkflowServiceHost, which could act as a host that supports Persistence and Rehydration in case of failover, as we have a web farm environment. But, when I tried doing something like the below , It fails with various errors. So just wanted to know, if I am going in the right direction. OR this isn't possible. Please find the IWorkflowCreation contract implementation code based on MSDN sample:

`  public Guid StartWorkflowReturnGuid(Dictionary<string,object> parameters)
        {
            Guid id = Guid.Empty;
            try
            {


            System.ServiceModel.Activities.WorkflowServiceHost host = new System.ServiceModel.Activities.WorkflowServiceHost(this.workflow, new Uri("net.pipe://localhost"));

            //string myConnectionString =
            //   "Data Source=localhost\\SQLEXPRESS;Initial Catalog=DefaultSampleStore;Integrated Security=True;Asynchronous Processing=True";
            //SqlWorkflowInstanceStoreBehavior sqlWorkflowInstanceStoreBehavior =
            //    new SqlWorkflowInstanceStoreBehavior(myConnectionString);
            //sqlWorkflowInstanceStoreBehavior.HostLockRenewalPeriod = TimeSpan.FromSeconds(30);
            //sqlWorkflowInstanceStoreBehavior.RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(30);
            //host.Description.Behaviors.Add(sqlWorkflowInstanceStoreBehavior);

            WorkflowUnhandledExceptionBehavior workflowUnhandledExceptionBehavior =
                new WorkflowUnhandledExceptionBehavior();

            workflowUnhandledExceptionBehavior.Action = WorkflowUnhandledExceptionAction.Terminate;

            //Set the TimeToUnload to 0 to force the WF to be unloaded. To have a durable delay, the WF needs to be unloaded otherwise it will be thread as an in-memory delay.
            //WorkflowIdleBehavior workflowIdleBehavior = new WorkflowIdleBehavior()
            //{
            //    TimeToUnload = TimeSpan.FromSeconds(0)
            //};
            //host.Description.Behaviors.Add(workflowIdleBehavior);
            host.WorkflowExtensions.Add(new LoginExtensions());
            host.WorkflowExtensions.Add(new MarketDataExtensions());
            ResumeBookmarkEndpoint endpoint = new ResumeBookmarkEndpoint(new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), new EndpointAddress(string.Format("net.pipe://localhost/workflowCreationEndpoint/{0}",Guid.NewGuid())));
            host.AddServiceEndpoint(endpoint);
            host.Open();
            IWorkflowCreation client = new ChannelFactory<IWorkflowCreation>(endpoint.Binding, endpoint.Address).CreateChannel();
            //create an instance
            id = client.Create(parameters);
            Helper.LogInfo(string.Format("Workflow instance {0} created", id));
            }
            catch (Exception exception)
            {

                Helper.LogError(string.Format("Exception starting BatchWorkflowServiceHost for type {0}", this.workflow.GetType().FullName), exception);
            }
            return id;
        }
public class ResumeBookmarkEndpoint : WorkflowHostingEndpoint
{

    public ResumeBookmarkEndpoint(Binding binding, EndpointAddress address)
        : base(typeof(IWorkflowCreation), binding, address)
    {
    }

    protected override Guid OnGetInstanceId(object[] inputs, OperationContext operationContext)
    {
        //Create called
        if (operationContext.IncomingMessageHeaders.Action.EndsWith("Create"))
        {
            return Guid.Empty;
        }
        //CreateWithInstanceId or ResumeBookmark called. InstanceId is specified by client
        else if (operationContext.IncomingMessageHeaders.Action.EndsWith("CreateWithInstanceId")||
                operationContext.IncomingMessageHeaders.Action.EndsWith("ResumeBookmark"))
        {
            return (Guid)inputs[0];
        }
        else
        {
            throw new InvalidOperationException("Invalid Action: " + operationContext.IncomingMessageHeaders.Action);
        }
    }


    protected override WorkflowCreationContext OnGetCreationContext(object[] inputs, OperationContext operationContext, Guid instanceId, WorkflowHostingResponseContext responseContext)
    {
        WorkflowCreationContext creationContext = new WorkflowCreationContext();
        if (operationContext.IncomingMessageHeaders.Action.EndsWith("Create"))
        {
            Dictionary<string, object> arguments = (Dictionary<string, object>)inputs[0];
            if (arguments != null && arguments.Count > 0)
            {
                foreach (KeyValuePair<string, object> pair in arguments)
                {
                    //arguments for the workflow
                    creationContext.WorkflowArguments.Add(pair.Key, pair.Value);
                }
            }
            //reply to client with the InstanceId
            responseContext.SendResponse(instanceId, null);
        }
        else if (operationContext.IncomingMessageHeaders.Action.EndsWith("CreateWithInstanceId"))
        {
            Dictionary<string, object> arguments = (Dictionary<string, object>)inputs[0];
            if (arguments != null && arguments.Count > 0)
            {
                foreach (KeyValuePair<string, object> pair in arguments)
                {
                    //arguments for the workflow
                    creationContext.WorkflowArguments.Add(pair.Key, pair.Value);
                }
            }
        }
        else
        {
            throw new InvalidOperationException("Invalid Action: " + operationContext.IncomingMessageHeaders.Action);
        }
        return creationContext;
    }
    protected override System.Activities.Bookmark OnResolveBookmark(object[] inputs, OperationContext operationContext, WorkflowHostingResponseContext responseContext, out object value)
    {
        Bookmark bookmark = null;
        value = null;
        if (operationContext.IncomingMessageHeaders.Action.EndsWith("ResumeBookmark"))
        {
            //bookmark name supplied by client as input to IWorkflowCreation.ResumeBookmark
            bookmark = new Bookmark((string)inputs[1]);
            //value supplied by client as argument to IWorkflowCreation.ResumeBookmark
            value = (string) inputs[2];
        }
        else
        {
            throw new NotImplementedException(operationContext.IncomingMessageHeaders.Action);
        }
        return bookmark;
    }
}

//ServiceContract exposed on the endpoint
[ServiceContract(Name = "IWorkflowCreation")]
public interface IWorkflowCreation
{
    [OperationContract(Name = "Create")]
    Guid Create(IDictionary<string, object> inputs);

    [OperationContract(Name = "CreateWithInstanceId", IsOneWay=true)]
    void CreateWithInstanceId(Guid instanceId, IDictionary<string, object> inputs);

    [OperationContract(Name = "ResumeBookmark", IsOneWay = true)]
    void ResumeBookmark(Guid instanceId, string bookmarkName, string message);

}
1

1 Answers

1
votes

What you are trying to do is certainly possible. However your question raises a questions. You say you have a WCF Workflow Service so that means you are already using a WorkflowServiceHost.

As far as using the WorkfowServiceHost goes. Using a IWorkflowCreation to create and manage workflows yourself is possible, there are a few samples on what to do, but the normal way is to use the WCF-WF4 infrastructure to create workflows for you. Create a workflow, add a Receive activity and set the CanCreateInstance to true and sending a WCF request to that activity will create a new workflow instance for you.

If you want to use a WorklflowApplication with persistence that is also possible but then you have to manage each instance yourself. There is no automatic reloading, how could there be if you have to manage individual instances, so you check for expired timers and resume the instances. Failover will work just as well, it uses the same SqlWorkflowInstanceStore to do the actual saving and loading of the workflow state.