HOWTO: Host a Workflow Service in IIS (the whole story)
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
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
First, you have to make sure you have IIS installed on the machine and that it's running. Therefore you need to go through the Control Panel | Programs and Features | Turn Windows features on and off and select Internet Information Services.
Open the Internet Information Services Manager by going to Control Panel | Administrative Tools | Internet Information Services (IIS) Manager.
Create a new virtual directory by right clicking on Default Web Site and selecting 'Add Virtual Directory'. Here you will need to give this virtual directory an alias name and point it to a physical directory on the machine. For the same of our sample, I'm going to give the alias the name of 'IISWorkflowHost' and point it to a directory I've already named on my machine 'C:\IISHostForWorkflow'. I have named the alias and directory differently the help you understand they don't need to necessary be named the same thing and to also show how we reference our service later on in the blog.
Double click on the Authentication icon in the IIS section. You need to enable Windows Authentication.
Looking in the IIS Manager (left hand tree view), you will see the new folder for IISWorkflowHost. This is just a directory however and to get this to work correctly you need to right click on the directory and select 'Convert to Application'. You can just select OK from the dialog box that pops up or you can change the AppPool if desired. For this example, we'll just accept the defaults. This step was one of the things I was missing when I tried to get it working initially.
Create a Bin directory under the C:\IISHostForWorkflow directory. This is where you will end up putting the workflow assembly that we will create later. Before we do any further additions to our virtual directory, let's go create our workflow service.
Creating the Workflow Service
Open Visual Studio 2008 and select File | New | Project | Visual C# | WCF | Sequential Workflow Service Library. Give it the name of WFToHostInIIS and put it in the directory C:\IISHostedWFExample. Select OK.
Rename Workflow1.cs to OrderProcessWF.cs.
Rename IWorkflow1.cs (the contract file) to IProductOrder.cs.
We need to replace ALL the code in the IProductOrder.cs file with:
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 = "https://ncdevguyblog/Workflow")]
public interface IProductOrder
{
[OperationContract]
void SubmitOrder(OrderInfo order);
[OperationContract]
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; }
}
[DataMember]
public string FName
{
get { return fName; }
set { fName = value; }
}
[DataMember]
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.
Open OrderProcessWF in the workflow designer. Click on the Receive activity that you see on the designer surface and rename it to RcvSubmitOrder.
Click on the ServiceOperationInfo property, select the ellipse on the right hand side and Import the IProductOrder contract. You may not actually need to import it, but if the SubmitOrder method doesn't show up in the Operations list, you'll need to import.
Select the SubmitOrder operation. Select OK.
Click on the 'order' parameter and then select the ellipse that will take you to the property/field binding dialog box. Select Bind to a new Member tab and then create a new property named WFOrderInfo. Select OK.
Open the app.config file. Everywhere you see 'IWorkflow1', replace that with 'IProductOrder'.
For the <service name="WFToHostInIIS.Workflow1", change this to <service name="WFToHostInIIS.OrderProcessWF".
For the base addresses:
<baseAddresses>
<add baseAddress="https://localhost:8731/Design_Time_Addresses/WFToHostInIIS/Workflow1/" />
</baseAddresses>
Change this to:
<baseAddresses>
<add baseAddress="https://localhost:8731/Design_Time_Addresses/WFToHostInIIS/OrderProcessWF/" />
</baseAddresses>
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>
<baseAddresses>
<add baseAddress="https://localhost:8731/Design_Time_Addresses/WFToHostInIIS/OrderProcessWF/" />
</baseAddresses>
</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
Since we are still within Visual Studio, we are going to create a new web.config file to put in the virtual directory/application we created. Just choose File | New | File | Web | Web Configuration File. Now what you are going to see is a configuration file that has a LOT of things that you don't need for this example. Just replace the entire contents of this file with:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
We need to add our own code to the web.config file within the <configuration> section. Paste in the following code:
<system.serviceModel>
<services>
<service name="WFToHostInIIS.OrderProcessWF" behaviorConfiguration="ServiceBehavior">
<endpoint address="ContextOverHttp" binding="wsHttpContextBinding" contract="WFToHostInIIS.IProductOrder" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<windowsAuthentication allowAnonymousLogons="false" includeWindowsGroups="true" />
</serviceCredentials>
<!-- Comment out the following behavior to disable persistence store -->
<workflowRuntime name="WorkflowServiceHostRuntime" validateOnCreate="true" enablePerformanceCounters="true">
<services>
<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" />
</services>
</workflowRuntime>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
<system.web>
<compilation>
<assemblies>
<add assembly="System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</assemblies>
</compilation>
<identity impersonate="false" />
</system.web>
<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:
- I have added the OOB SQL Persistence service to this example. It is not necessary to do that, so you can remove it if you want. If you do use it and you're using SQLExpress, you need to change the Data Source setting. This example also assumes you've installed and setup the .Net 3.5 Samples located at:
Windows Communication Foundation (WCF), Windows Workflow Foundation (WF) and Windows CardSpace Samples
https://www.microsoft.com/downloads/details.aspx?FamilyID=2611a6ff-fd2d-4f5b-a672-c002f1c09ccd&DisplayLang=en- Notice that the service name points to the same name as the app.config file in our workflow project.
- Notice that both endpoint contracts point to the IProductOrder contract.
Create a new file for the C:\IISHostForWorkflow directory named OrderProcess.svc. Do this within Visual Studio. I just started by creating a new text file and just renaming it.
Paste in the following code:
<%@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.
Save the file.
Copy your workflow assembly file (from the projects bin\Debug directory) to your virtual directories Bin directory.
To make sure that everything is working appropriately, open Internet Explorer and browse to:
https://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
Create a new Visual C# Windows Console Application named 'ConsoleServiceTest' and place it in the C:\TestWorkflowService directory.
Open a command prompt that will allow you to run svcutil.exe. I'm going to just open mine to the Windows SDK directory and run the command:
Svcutil /config:app.config https://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.
Add a reference to System.ServiceModel and System.Runtime.Serialization to the project.
Paste the following code into the Main method in Program.cs:
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);
}
You can step through this using any method you want.
Comments
Anonymous
January 02, 2008
PingBack from http://msdnrss.thecoderblogs.com/2008/01/02/Anonymous
September 09, 2008
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.Anonymous
September 09, 2008
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. LarryAnonymous
September 09, 2008
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, RAYAnonymous
September 10, 2008
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) { lock (mapsync) { 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. Larry
Anonymous
September 10, 2008
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 :) RAYAnonymous
September 10, 2008
Ray, 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). LarryAnonymous
September 14, 2008
Dear Larry, sorry for the late response, but thanks again and again for your help. :) I really appreciate it. RAYAnonymous
February 25, 2009
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.Anonymous
February 25, 2009
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). Larry