I started out in my attempt to figure out how to host a workflow service in IIS by looking at the following MSDN article:
How to: Host a Workflow Service in IIS
http://msdn2.microsoft.com/en-us/library/bb675264.aspx
This gave me 'most' of the information I needed but as is usual for me, it's the small missing pieces of information that end up creating the most grief for me. So, I thought I would put down all the steps it took, from start to finish in hopes that this may help someone else.
What we are going to be doing is hosting an existing, pre-compiled workflow as a service in IIS (on Vista). What this implies is that we are going to create a workflow library project. This is what tripped me up because there is a particular way you need to do this otherwise you have to end up adding a lot of your own code.
Setting up things in IIS
Creating the Workflow Service
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.ServiceModel;
namespace WFToHostInIIS
{
//Service contract
[ServiceContract(Namespace = "http://ncdevguyblog/Workflow")]
public interface IProductOrder
[OperationContract]
void SubmitOrder(OrderInfo order);
OrderInfo GetOrder(string orderID);
}
//Data type (contract)
[DataContract]
public class OrderInfo
private string orderID = default(string);
private string fName = default(string);
private string lName = default(string);
[DataMember]
public string OrderID
get { return orderID; }
set { orderID = value; }
public string FName
get { return fName; }
set { fName = value; }
public string LName
get { return lName; }
set { lName = value; }
This service exposes two methods, one for submitting an order, the other for getting order info. For simplicity, we are only going to implement SubmitOrder.
<baseAddresses>
<add baseAddress="http://localhost:8731/Design_Time_Addresses/WFToHostInIIS/Workflow1/" />
</baseAddresses>
Change this to:
<add baseAddress="http://localhost:8731/Design_Time_Addresses/WFToHostInIIS/OrderProcessWF/" />
Note that your port number may be different in your case. This does not matter. In the end, your app.config would look something like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<system.serviceModel>
<services>
<service name="WFToHostInIIS.OrderProcessWF" behaviorConfiguration="WFToHostInIIS.Workflow1Behavior">
<host>
</host>
<endpoint address=""
binding="wsHttpContextBinding"
contract="WFToHostInIIS.IProductOrder">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<identity>
<dns value="localhost"/>
</identity>
</endpoint>
<endpoint address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WFToHostInIIS.Workflow1Behavior" >
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<windowsAuthentication
allowAnonymousLogons="false"
includeWindowsGroups="true" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Finishing the configuration for IIS
<?xml version="1.0" encoding="UTF-8"?>
<service name="WFToHostInIIS.OrderProcessWF" behaviorConfiguration="ServiceBehavior">
<endpoint address="ContextOverHttp" binding="wsHttpContextBinding" contract="WFToHostInIIS.IProductOrder" />
<behavior name="ServiceBehavior">
<serviceDebug includeExceptionDetailInFaults="true" />
<windowsAuthentication allowAnonymousLogons="false" includeWindowsGroups="true" />
<!-- Comment out the following behavior to disable persistence store -->
<workflowRuntime name="WorkflowServiceHostRuntime" validateOnCreate="true" enablePerformanceCounters="true">
<add type="System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionString="Data Source=localhost;Initial Catalog=NetFx35Samples_ServiceWorkflowStore;Integrated Security=True;Pooling=False" LoadIntervalSeconds="1" UnLoadOnIdle="true" />
</workflowRuntime>
<compilation>
<assemblies>
<add assembly="System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</assemblies>
</compilation>
<identity impersonate="false" />
<system.webServer>
<directoryBrowse enabled="true" />
</system.webServer>
3. Save this file to C:\IISHostForWorkflow.
A couple of things to note regarding this web.config file:
Windows Communication Foundation (WCF), Windows Workflow Foundation (WF) and Windows CardSpace Sampleshttp://www.microsoft.com/downloads/details.aspx?FamilyID=2611a6ff-fd2d-4f5b-a672-c002f1c09ccd&DisplayLang=en
<%@ServiceHost language=c# Debug="true" Service="WFToHostInIIS.OrderProcessWF" Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>
Notice how the service that we are referencing here is our workflow.
http://localhost/IISWorkflowHost/OrderProcess.svc.
What you should see is a window similar to what you would see if you browsed to a web service location.
Creating a Test Client
Svcutil /config:app.config http://localhost/IISWorkflowHost/OrderProcess.svc. Two files will be created in the directory where you are running svcutil from, an app.config file and an OrderProcessWF.cs file. Add these two files to your console app project. You could also just right click on the project and choose 'Add Service Reference' and find the service.
WFToHostInIIS.OrderInfo oInfo = new WFToHostInIIS.OrderInfo();
oInfo.FName = "John";
oInfo.LName = "Doe";
oInfo.OrderID = "1234";
try
ProductOrderClient client = new ProductOrderClient();
client.SubmitOrder(oInfo);
catch (Exception e)
Console.WriteLine("Exception: {0}",e.Message);
PingBack from http://msdnrss.thecoderblogs.com/2008/01/02/
Good day,
I have read your article, and it was really useful, but i have a question, can i host the workflow in ASP.NET web application? is it the same as console applicaiton? I would really apprecaite your help in this, since i've been searching for too long for it.
Thank you for your time.
RAY,
Yes you can host in ASP.Net. As a matter of fact, there is a tutorial here on MSDN:
http://msdn.microsoft.com/en-us/library/bb153577.aspx
Let me know if this was what you were looking for.
Larry
Larry,
I really appreciate you fast response, it saved me alot of trouble, I'm going through the tutorial right now, and it seems good so far :)
Thanks again,
RAY
Ray,
One more thing you may want to watch out for. Lets say that you have a button that you click on your form and that button code is where the workflow is kicked off:
----------------------------------------------------
//get the workflow runtime instance from the config file
WorkflowRuntime workflowRuntime = Application["WorkflowRuntime"] as WorkflowRuntime;
ManualWorkflowSchedulerService scheduler = (Application["WorkflowRuntime"] as WorkflowRuntime).GetService<ManualWorkflowSchedulerService>();
//register the WorkflowCompleted event
workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(WorkflowRuntime_WorkflowCompleted);
//create the workflow, passing in the parameters
WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow(typeof(MyNamespace.MyWorkflow));
//start the workflow
workflowInstance.Start();
this.lblWorkflowED.Text = workflowInstance.InstanceId.ToString();
Guid instanceId = new Guid(workflowInstance.InstanceId.ToString());
scheduler.RunWorkflow(instanceId);
//take the workflow output and display it on the screen
this.txtOutput.Text = this.strOutput;
//unregister the WorkflowCompleted event
workflowRuntime.WorkflowCompleted -= new EventHandler<WorkflowCompletedEventArgs>(WorkflowRuntime_WorkflowCompleted);
------------------------------------------------
Notice how I subscribe to, and unsubscribe from the WorkflowCompleted event? Now, suppose multiple people are hitting this page. Which workflow is being completed when? See all we have is one method to capture the workflow completed so if you are looking for some specific output from a specific workflow, you are going to have to keep track of the instance ID.
One way this could be done is to use a hashtable to collect and then disperse of these instance Ids.
----------------------------------------------
public class WorkflowInstanceMap
private static Dictionary<Guid, AutoResetEvent> map = new Dictionary<Guid,AutoResetEvent>();
private static Object mapsync = new Object();
private static EventHandler<WorkflowCompletedEventArgs> wfce = null;
public static void Add(WorkflowRuntime wfrt, Guid InstanceID, AutoResetEvent e)
lock (mapsync)
map.Add(InstanceID, e);
if (wfce == null)
wfce = new EventHandler<WorkflowCompletedEventArgs>(WorkflowInstanceMap.WorkflowCompleted);
wfrt.WorkflowCompleted += wfce;
private static void Signal(Guid InstanceId)
AutoResetEvent e;
if (map.TryGetValue(InstanceId, out e))
map.Remove(InstanceId);
e.Set();
private static void WorkflowCompleted(object sender, WorkflowCompletedEventArgs args)
Signal(args.WorkflowInstance.InstanceId);
To use this class, you would do something like this in your button code:
--------------------------------------------------
WorkflowInstance instance = this.WorkflowRuntime.CreateWorkflow(typeof(MyNamespace.MyWorkflow));
// Create a wait handle and then add it to the WorkflowInstanceMap
AutoResetEvent MyWaitHandle = new AutoResetEvent(false);
WorkflowInstanceMap.Add(this.WorkflowRuntime, instance.InstanceId, MyWaitHandle);
instance.Start();
scheduler.RunWorkflow(instance.InstanceId);
// Wait for it to complete
MyWaitHandle.WaitOne();
---------------------------------------------
There should be other ways to do this to, but the point is, if you just dump out the output to the webpage, you aren't going to know who's output it is.
Hope this helps.
Dear Larry,
I didn't get there yet, but i'm sure it'll be of great use for me. I'm still figuring out what code should be done in the aspx pages since the tutorial on MSDN is missing some of the code blocks..
Thank you very much for your time and help :)
In that previous comment I posted to you, there is some code right at the top, that is the kind of code that goes into the ASPX page (the code where CreateWorkflow is).
sorry for the late response, but thanks again and again for your help. :) I really appreciate it.
I have followed all the steps mentioned . But when I browse the .svc file from IE, I get the following error.
The contract name '<libname.serivcecontract>' could not be found in the list of contracts implemented by the service '<workflow>'.
Please let me know what could be wrong. Help required urgently.
Lisha,
I am not sure which release of VS2008 you are using, but I am using .Net 3.5 SP1. If you are using that, then you can just go to my last blog posting on the topic (Oct. 2008) and follow those steps. I followed the steps this morning that it appears you have followed and did not have any problem.
If it cannot find the service contract then that means it could be the contract name that is incorrect in your app.config or web.config file (IProductOrder).
I am using .Net 3.5. Did not have SP1. But installed it and tried again. But still has the same problem.
I have a workflow workflow1. There is a contract IWorkflow1.
namespace IncidentService
[ServiceContract]
public interface IWorkflow1
void StartupService();
int Add(int n1);
The ReceiveActivity of workflow
implements IWorkflow1.Add().
In App.Config,
<service name="IncidentService.Workflow1" behaviorConfiguration="IncidentService.Workflow1Behavior">
<add baseAddress="http://localhost:8731/Design_Time_Addresses/IncidentService/Workflow1/" />
contract="IncidentService.IWorkflow1">
IN the host, web.config
<service name="IncidentService.Workflow1" behaviorConfiguration="WorkFlowService.Service1Behavior">
<!-- Service Endpoints -->
<endpoint address="ContextOverHttp" binding="wsHttpContextBinding" contract="IncidentService.IWorkflow1">
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
.svc file has
<%@ServiceHost language=c# Debug="true" Service="IncidentService.Workflow1" Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>
It works from the self hosted testing application.
Thanks,
Lisha
One thing is, the endpoint address in the host is different than the address in the client app.config, but I realize you may have changed this for posting sake.
Also, is your Add operation tied to anything in the workflow? Your contract should only have operations tied to the workflow.
Other than that, I do not see anything that jumps out at me that may be wrong.
Thanks for the article, it was a valid help.
Please be aware (if you are doing the steps with vista), of a couple of errors IIS throws when testing the solutions.
The errors are related to a incomplete IIS configurations.
the related solutions are:
http://blogs.microsoft.co.il/blogs/bursteg/archive/2008/12/01/wcf-on-iis7-on-vista-adding-svc-handler.aspx
and
http://michael-arnett.spaces.live.com/blog/cns!5AA848FF3F707F99!1093.entry?sa=656217328