senfo

I recently read the MSDN magazine article, Windows Workflow Foundation, Part 2 (http://msdn.microsoft.com/msdnmag/issues/06/04/CuttingEdge/), where Dino Esposito demonstrates how to work with workflows in ASP.NET. Dinos' demonstration provides a helpful solution to working with Sequential workflows; however, it left me a little confused with how to work with a State Machine workflow.

In the article, Dino demonstrates how to pass parameters to a Sequential workflow by using a Dictionary object, which is then passed as a parameter to the CreateWorkflow() method. The problem is that State Machine workflows pass parameters through event arguments.

Using the OrderWorkflows (beta 2, lab 4) example as a starting point, is it possible to use the methods in the OrderLocalService class to raise the events

Thank you in advance...


Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

Marcos Torres

Hi.
I don¡¯t fully understand your final question, this is my best guess.
In the lab example inside : private void btnOrderCreated_Click(object sender, EventArgs e) method there is a method inside firing the event : 

   // Raise an OrderCreated event using the Order Local Service
   _OrderService.RaiseOrderCreatedEvent(strOrderId, WorkflowInstanceId);

Here you are raising the event, the state machine handles this event and then passes the state to the defined one inside the state machine.

Then the example becomes more complex, but here:

switch (strButtonName)
   {
    case "btnOrderShipped":
     // Raise an OrderShipped event using the Order Local Service
     _OrderService.RaiseOrderShippedEvent(strOrderId, WorkflowInstanceId);
     break;


    case "btnOrderUpdated":
     // Raise an OrderUpdated event using the Order Local Service
     _OrderService.RaiseOrderUpdatedEvent(strOrderId, WorkflowInstanceId);
     break;

    case "btnOrderCanceled":
     // Raise an OrderCanceled event using the Order Local Service
     _OrderService.RaiseOrderCanceledEvent(strOrderId, WorkflowInstanceId);
     break;


    case "btnOrderProcessed":
     // Raise an OrderProcessed event using the Order Local Service
     _OrderService.RaiseOrderProcessedEvent(strOrderId, WorkflowInstanceId);
     break;
   }

shows how each event is fired.
Hope this helps a little.
Marcos.






Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

senfo

Thank you for the response.

I understood the lab; but it wasn't clear to me how to make it work in ASP.NET. Below is essentially the code I'm using (diced up a bit, so hopefully it still compiles cleanly).

private void StartWorkflowRuntime()
{
Type t;
WorkflowInstance instance;
OrderLocalServices.OrderService LocalServices = new OrderLocalServices.OrderService();
WorkflowRuntime wr = WorkflowWebRequestContext.Current.WorkflowRuntime;

wr.WorkflowStarted += new EventHandler<WorkflowEventArgs>(wr_WorkflowStarted);
wr.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(wr_WorkflowCompleted);
wr.WorkflowTerminated += new EventHandler<WorkflowTerminatedEventArgs>(wr_WorkflowTerminated);
wr.AddService(LocalServices);

t = typeof(OrderWorkflows.Workflow1);
instance = wr.CreateWorkflow(t);
instance.Start();

LocalServices.RaiseOrderCreatedEvent(strOrderId,
instance.InstanceId);
}

I set a break point on the RaiseOrderCreatedEvent() method. When I step through the code, it checks to make sure that the OrderCreated event is not null. In my case, this evaluates false, so the event is never raised. This is obviously an indication that there are no subscribers to the event; however, the Workflow DOES subscribe to the event in the OrderCreated state.

My guess is that I'm passing the wrong GUID for the Workflow to the RaiseOrderCreatedEvent() method, which results in the Runtime being unable to locate a Workflow with the ID I pass.

The winform application executes the workflow as expected. I'm just having trouble implementing it in ASP.NET

Thank you again...




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

Tom Lake

Can you try adding the following two lines after instance.Start and before LocalServices.RaiseOrderCreatedEvent:

ManualWorkflowSchedulerService scheduler = wr.GetService<ManualWorkflowSchedulerService>();

        scheduler.RunWorkflow(instance.InstanceId);

 

Also, take a look at the ASP.NET Workflow example that can be found at http://www.windowsworkflow.net/Downloads/Examples/ASPNETWorkflowExample%20-%202006-01-14.exe






Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

senfo

Nope, that didn't do it. scheduler is null after the call to wr.GetService()

I have a suspicion on what's causing it. I'll give it a try and get back.

Thank you for the response...




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

Tom Lake

Make sure you have the following in your web.config file:

<WorkflowRuntime Name="WorkflowServiceContainer">

<Services>

<add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

<add type="System.Workflow.Runtime.Hosting.DefaultWorkflowTransactionService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

</Services>

</WorkflowRuntime>






Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

senfo

That fixed the null reference problem, but it's still as if I have no subscribers to the event (although, like I said earlier, the Workflow does subscribe to the OrderCreated event and it works fine in my winform application).

I haven't touched the original workflow since the tutorial. Is there anything that I need to do to it to make it work with ASP.NET I've looked at a few tutorials (like the one you posted), but I can't figure out what I'm not doing right.




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

senfo

The sample ASP.NET application in the article by Dino exposed an Authenticate() method as a web service. Will I need to take a similar approach for the OrderWorkflow lab to make it work in ASP.NET

The intentions of the web service were unclear to me in the article.




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

GUYO

Don't feel like the lone ranger -- I have exactly the same problem -- no event subscribers. My worlfows are all dressed up with nobody to talk to






Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

senfo

Are you at least able to start a new instance of the runtime In my case, instance.InstanceId appears to have a valid GUID representing the instance ID for the Workflow, which I assume means that the Workflow object was successfully created. As far as I can tell, the only thing that isn't working for me is the raising of events (which pretty much leaves my workflow useless ).




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

Tom Lake

You will need to have some of the files from the Hands on Labs, which can be found at http://www.microsoft.com/downloads/details.aspx FamilyId=5DF74E3B-FB51-4A94-A11D-DFF70288A8BB&displaylang=en, to put this together.< xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

  1. Create a new Web Site project.
  2. Add a new Empty Workflow Project to the solution.
  3. Add the empty workflow project as a reference to the web site.
  4. Add IOrderService.cs and OrderService.cs from the hands on lab 4, that can be found under Resources\OrderLocalServices, to the workflow project.
  5. Add Workflow1.cs also from the hands on lab 4, that can be found under Completed\Exercise 2\OrderWorkflows\OrderWorkflows, to the workflow project.
  6. Replace the content for the default Default.aspx with the following:

 

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="Default2" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Order Service Sample</title>

</head>

<body>

    <form id="form2" runat="server">

    <div>

        <br />

        Order Number &nbsp; &nbsp;

        <asp:TextBox ID="txtOrderNumber" runat="server" TabIndex="1">12345</asp:TextBox><br />

        <br />

        Workflow InstanceId -

        <asp:Label ID="lblWorkflowInstanceId" runat="server" Width="462px" ForeColor="#00C000"></asp:Label><br />

        <br />

        Workflow Status -

        <asp:Label ID="lblOrderStatus" runat="server" Width="245px" ForeColor="Blue"></asp:Label><br />

        <br />

        &nbsp;<asp:Button ID="btnCreateOrder" runat="server" Text="Create Order" OnClick="btn_Click" TabIndex="2" Enabled="False" />

        &nbsp; &nbsp;

        <asp:Button ID="btnProcessOrder" runat="server" OnClick="btn_Click" TabIndex="3"

            Text="Process Order" Enabled="False" />

        &nbsp; &nbsp;

        <asp:Button ID="btnShipOrder" runat="server" OnClick="btn_Click" Text="Ship Order" Enabled="False" /></div>

    </form>

</body>

</html>

 

7.         Replace the content for the default Default.aspx.cs with the following:

 

using System;

using System.Web.UI.WebControls;

using System.Workflow.Runtime;

using OrderLocalServices;

using System.Workflow.Runtime.Hosting;

using System.Workflow.Activities;

using System.Web;

 

public partial class Default2 : System.Web.UI.Page

{

    private void StartWorkflow()

    {   

        WorkflowRuntime workflowRuntime = Application["WorkflowRuntime"] as WorkflowRuntime;

 

        // Now get a reference to the ManualWorkflowSchedulerService

        ManualWorkflowSchedulerService scheduler =

            workflowRuntime.GetService<ManualWorkflowSchedulerService>();

 

        ExternalDataExchangeService dataService = workflowRuntime.GetService<ExternalDataExchangeService>();

        OrderLocalServices.OrderService orderService = workflowRuntime.GetService<OrderLocalServices.OrderService>();

        if (orderService == null)

        {

            orderService = new OrderLocalServices.OrderService();

            dataService.AddService(orderService);

        }

 

        //// Attach to the WorkflowCompleted event

        workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(WorkflowRuntime_WorkflowCompleted);

        WorkflowInstance workflowInstance = workflowRuntime.CreateWorkflow(typeof(OrderWorkflows.Workflow1));

        workflowInstance.Start();

 

        // Now run the workflow.  This is necessary when

        // ...using the ManualWorkflowSchedulerService

        scheduler.RunWorkflow(workflowInstance.InstanceId);

 

        this.lblWorkflowInstanceId.Text = workflowInstance.InstanceId.ToString();

        this.lblOrderStatus.Text = GetCurrentState(workflowInstance.InstanceId);

    }

 

    private string GetCurrentState(Guid instanceId)

    {

        StateMachineWorkflowInstance stateInstance = new StateMachineWorkflowInstance(Application["WorkflowRuntime"] as WorkflowRuntime, instanceId);

        return stateInstance.CurrentStateName;

    }

 

    void WorkflowRuntime_WorkflowCompleted(object sender, System.Workflow.Runtime.WorkflowCompletedEventArgs e)

    {

       HttpContext.Current.Response.Redirect(string.Format("OrderCompleted.aspx OrderNumber={0}&InstanceID={1}", txtOrderNumber.Text, lblWorkflowInstanceId.Text));

    }

 

    protected void Page_Load(object sender, EventArgs e)

    {

        if (!IsPostBack )

        {

            this.StartWorkflow();

            this.btnCreateOrder.Enabled = true;

        }

    }

    protected void btn_Click(object sender, EventArgs e)

    {

        Button currentButton = sender as Button;

        Guid instanceId = new Guid(this.lblWorkflowInstanceId.Text);

        OrderService orderService = (Application["WorkflowRuntime"] as WorkflowRuntime).GetService<OrderService>();

        ManualWorkflowSchedulerService scheduler = (Application["WorkflowRuntime"] as WorkflowRuntime).GetService<ManualWorkflowSchedulerService>();

 

        switch (currentButton.ID)

        {

            case "btnCreateOrder":

                currentButton.Enabled = false;

 

                orderService.RaiseOrderCreatedEvent(txtOrderNumber.Text, instanceId);

                scheduler.RunWorkflow(instanceId);

                lblOrderStatus.Text = GetCurrentState(instanceId);

 

                this.btnProcessOrder.Enabled = true;

                break;

            case "btnProcessOrder":

                currentButton.Enabled = false;

 

                orderService.RaiseOrderProcessedEvent(txtOrderNumber.Text, instanceId);

                scheduler.RunWorkflow(instanceId);

                lblOrderStatus.Text = GetCurrentState(instanceId);

 

                this.btnShipOrder.Enabled = true;

                break;

            case "btnShipOrder":

                currentButton.Enabled = false;

                orderService.RaiseOrderShippedEvent(txtOrderNumber.Text, instanceId);

                scheduler.RunWorkflow(instanceId);

                break;

        }

    }

}

 

8.         Add a new web page named OrderCompleted.aspx to the web project.

9.         Replace the content of the .aspx page with the following:

 

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="OrderCompleted.aspx.cs" Inherits="OrderCompleted" %>

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

 

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Order Completed</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

        Order Completed<br />

        <br />

        <asp:Button ID="btnNewOrder" runat="server" OnClick="btnNewOrder_Click" Text="New Order" /></div>

    </form>

</body>

</html>

 

10.      Replace the content of the .aspx.cs page with the following:

 

using System;

using System.Web;

 

public partial class OrderCompleted : System.Web.UI.Page

{

    protected void btnNewOrder_Click(object sender, EventArgs e)

    {

        HttpContext.Current.Response.Redirect("Default.aspx");

    }

    protected void Page_Load(object sender, EventArgs e)

    {

        lblOrderNumber.Text = HttpContext.Current.Request.QueryString["OrderNumber"];

        lblWorkflowInstanceId.Text = HttpContext.Current.Request.QueryString["InstanceID"];

    }

}

 

11.      Add a Web.Config file to the web site and change the content to the following:

 

< xml version="1.0" >

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

      <configSections>

            <section name="WorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

      </configSections>

  <appSettings/>

  <connectionStrings/>

      <system.web>

            <authentication mode="Windows"/>

            <httpModules>

                  <add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="WorkflowHost"/>

            </httpModules>

            <compilation debug="true">

                  <assemblies>

                        <add assembly="System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                        <add assembly="System.Drawing.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                        <add assembly="System.Transactions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

                        <add assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

                        <add assembly="System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

                        <add assembly="System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

                        <add assembly="Microsoft.Build.Tasks, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                        <add assembly="System.Messaging, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                        <add assembly="System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

                        <add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                       <add assembly="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>

                        <add assembly="Microsoft.Build.Utilities, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                        <add assembly="Microsoft.Build.Framework, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>

                  </assemblies>

            </compilation>

      </system.web>

      <WorkflowRuntime Name="WorkflowServiceContainer">

            <Services>

                  <add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

      <add type="System.Workflow.Activities.ExternalDataExchangeService, System.Workflow.Activities, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

    </Services>

      </WorkflowRuntime>

</configuration>

 

  1. Add a Global.asax file to the web site and update the Application_Start and End to the following:

 

    void Application_Start(object sender, EventArgs e)

    {

        // NOTE:  This requires the configuration section to be named "WorkflowRuntime".

        System.Workflow.Runtime.WorkflowRuntime workflowRuntime = new System.Workflow.Runtime.WorkflowRuntime("WorkflowRuntime");

        Application["WorkflowRuntime"] = workflowRuntime;

        workflowRuntime.StartRuntime();

    }

   

    void Application_End(object sender, EventArgs e)

    {

        System.Workflow.Runtime.WorkflowRuntime workflowRuntime = Application["WorkflowRuntime"] as System.Workflow.Runtime.WorkflowRuntime;

        workflowRuntime.StopRuntime();

    }

 

You should now be able to build and run the project.

 

On default you can enter and order number, a default is provided.  When the page is loaded the workflow is started.  Click the enabled button and the updated state will be displayed.  When the workflow completes you are taken to the order completed page and given a chance to create another order.

 

Hope this helps you to better understand how to get this working.  Let me know if you have any questions.

You can find the competed sample here.






Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

HowardRichards

At step 10 you say replace the .cs file but the code that follows is markup not C# - can you post the missing code




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

HowardRichards

Well having looked at the example, I assume it's just Response.Redirect("Default.aspx") in the click handler, so scrub that request




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

senfo

Tom, where do you live I owe you a beer! Heck, I'm so happy I might take you out to dinner! Thank you very much! You were right on with the scheduler recommendation from earlier. I didn't realize I had to call it again after the event is raised.

Thank you very much!




Re: Windows Workflow Foundation ASP.NET And a State Machine Workflow

Peter Y

Well done, Tom! This is exactly what I have been scrathing my head over for the past three weeks! I had yet too find a clear-cut example of implementing a state workflow in asp.net and this is EXACTLY the type of example that I needed!

Forget the single beer, I'll send you a complete case! (Ok, not really, but it is the thought that counts, yeah )



OOOPS!!!!!

Tom-

In the example that you posted, you are using the WorkflowWebRequestContext object, but this object was removed in Beta 2.2.  In addition, when trying to follow your instructions with Beta 2.2, the Lab code also fails to compile.  Can you offer any suggestions as to how to resolve

Thanks.


EDIT:
OK--

To resolve the code in the Labs and make it compile, jsut make public the
 OrderSender and.OrderEventArgs objects.

I am still looking at the WorkflowWebRequestContext problem, but I THINK that there will be no recourse but to implement the workflow as a web service.  Grrrr...