posts - 232, comments - 1625, trackbacks - 55, articles - 12
  博客园 :: 首页 ::  :: 联系 :: 订阅 订阅 :: 管理

[置顶]坚持学习WF文章索引

一:当我们在工作流中使用本地服务的事件的时候,WF运行时引擎将入站消息映射到实例中的特定的HandleExternalEventActivity活动,对实例的映射是在将工作流实例InstanceId传递到ExternalDataEventArgs构造函数时完成的。所以当工作流实例在本地服务接口上侦听相同事件的不同实例时,就无法确定该响应哪个事件。如下图:

correlation4

如何解决这个问题呢,我们就需要在工作流中使用关联,通过使用接口属性来定义关联,使用关联后通信活动会多出一个CorrelationToken属性(关联标记)。当宿主中要触发一个外部事件时,可以传递两个参数,一个是实例的ID号,一个是关联标记编号。这样就可以将事件路由到该工作流实例中正确的活动。

使用关联时要成对使用CallExternalMethodActivity与HandleExternalEventActivity。

下面看下关联的接口属性:

CorrelationParameterAttribute
用于指定在接口中定义的方法和事件的用于关联的参数名称。 如果方法或事件包含一个与该名称匹配的形参,则该参数定义该方法或事件上的相关值。 如果方法或事件没有此类参数,则方法或事件可以使用 CorrelationAliasAttribute 来定义相关值的位置。 此属性在一个接口中可以出现多次。

CorrelationInitializerAttribute
用于在方法或事件中指示相关参数的值是在调用该方法或引发该事件时初始化的。 对于给定的 CorrelationToken,必须在对话中的任何其他方法或事件执行之前调用或接收初始值设定项方法或事件。 任何可以初始化新对话(即新的相关令牌)的方法或事件都必须使用此属性进行标记。 对于每个相关令牌,方法或事件必须包含一个适当的命名参数或一个 CorrelationAliasAttribute。

CorrelationAliasAttribute
在方法或事件定义中用来重写该成员的 CorrelationParameter 设置。 CorrelationAliasAttribute 属性指定可用参数中可以获得相关值的位置。 该字符串参数是针对形参集的以点分隔的路径。 该参数指示在何处可以找到匹配数据值。 如果定义了多个相关令牌,还必须指定令牌 Name 命名参数。

二:下面是个小例子,该示例中工作流将创建两个任务,然后在这些任务完成时等待(同一本地服务事件)通知。 在这种情况下,当外部代码将事件引发到工作流时,本地服务基础结构必须依赖于所引发事件中的数据(相关值)将事件路由到工作流实例中相应的 HandleExternalEventActivity 活动。

每创建一项任务,任务服务就会显示一个消息框,通知用户任务已创建。 单击了“确定”按钮后,将为对应的任务 ID 引发用以完成任务的事件。 这些属性与 CreateTask 活动上设置的属性相同,因此事件与正确的 TaskCompleted 活动关联。

1.事件参数类TaskEventArgs:

[Serializable]
    
public class TaskEventArgs : ExternalDataEventArgs
    
{
        
string idValue;
        
string assigneeValue;
        
string textValue;

        
public TaskEventArgs(Guid instanceId, string id, string assignee, string text)
            :
base(instanceId)
        
{
            
this.idValue = id;
            
this.assigneeValue = assignee;
            
this.textValue = text;
        }


        
public string Id
        
{
            
get return this.idValue; }
            
set this.idValue = value; }
        }


        
public string Assignee
        
{
            
get return this.assigneeValue; }
            
set this.assigneeValue = value; }
        }


        
public string Text
        
{
            
get return this.textValue; }
            
set this.textValue = value; }
        }

    }

2.定义服务接口:   

[ExternalDataExchange]
    [CorrelationParameter(
"taskId")]
    
public interface ITaskService
    
{         
        [CorrelationInitializer]
        
void CreateTask(string taskId, string assignee, string text);

        [CorrelationAlias(
"taskId""e.Id")]
        event EventHandler<TaskEventArgs> TaskCompleted;
    }


注意:2.1 [CorrelationParameter("taskId")] 中的"taskId"和CreateTask方法中的string taskId要一致。
           2.2 [CorrelationAlias("taskId", "e.Id")] 关联参数的别名绑定。 

 

3.实现服务类:
public class TaskService : ITaskService
    
{
        
public void CreateTask(string taskId, string assignee, string text)
        
{
            Console.WriteLine(
"task " + taskId + " created for " + assignee);
            ThreadPool.QueueUserWorkItem(ShowDialog, 
new TaskEventArgs(WorkflowEnvironment.WorkflowInstanceId, taskId, assignee, text));
        }


        
public void RaiseEvent(TaskEventArgs args)
        
{
            EventHandler
<TaskEventArgs> taskCompleted = this.TaskCompleted;
            
if (taskCompleted != null)
                taskCompleted(
null, args);
        }


        
public void ShowDialog(object state)
        
{
            TaskEventArgs taskEventArgs 
= state as TaskEventArgs;

            MessageBox.Show(
string.Format("{0}, click OK when '{1}' completed.", taskEventArgs.Assignee, taskEventArgs.Text), string.Format("Task {0}", taskEventArgs.Id), MessageBoxButtons.OK);
            
            RaiseEvent(taskEventArgs);
        }


        
public event EventHandler<TaskEventArgs> TaskCompleted;
    }

4.创建自定义通信活动

4.1 CreateTask活动继承自CallExternalMethodActivity,来调用本地服务中的方法,在构造函数中设定InterfaceType和MethodName属性。代码如下:

[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
    
public partial class CreateTask : System.Workflow.Activities.CallExternalMethodActivity
    
{
        
// Properties on the task
        public static DependencyProperty AssigneeProperty = DependencyProperty.Register("Assignee"typeof(System.String), typeof(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask));
        
public static DependencyProperty TaskIdProperty = DependencyProperty.Register("TaskId"typeof(System.String), typeof(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask));
        
public static DependencyProperty TextProperty = DependencyProperty.Register("Text"typeof(System.String), typeof(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask));

        
private void InitializeComponent()
        
{

        }

    
        
public CreateTask()
        
{
            
this.InterfaceType = typeof(Microsoft.Samples.Workflow.CorrelatedLocalService.ITaskService);
            
this.MethodName = "CreateTask";
        }


        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(
true)]
        
public string Assignee
        
{
            
get
            
{
                
return ((string)(base.GetValue(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask.AssigneeProperty)));
            }

            
set
            
{
                
base.SetValue(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask.AssigneeProperty, value);
            }

        }


        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(
true)]
        
public string TaskId
        
{
            
get
            
{
                
return ((string)(base.GetValue(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask.TaskIdProperty)));
            }

            
set
            
{
                
base.SetValue(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask.TaskIdProperty, value);
            }

        }


        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(
true)]
        
public string Text
        
{
            
get
            
{
                
return ((string)(base.GetValue(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask.TextProperty)));
            }

            
set
            
{
                
base.SetValue(Microsoft.Samples.Workflow.CorrelatedLocalService.CreateTask.TextProperty, value);
            }

        }


        
protected override void OnMethodInvoking(EventArgs e)
        
{
            
this.ParameterBindings["taskId"].Value = this.TaskId;
            
this.ParameterBindings["assignee"].Value = this.Assignee;
            
this.ParameterBindings["text"].Value = this.Text;
        }

       
    }



4.2 TaskCompleted活动继承自HandleExternalEventActivity,来处理本地服务中的事件,在构造函数中设定
InterfaceType和EventName属性。代码如下:
[ToolboxItemAttribute(typeof(ActivityToolboxItem))]
    
public partial class TaskCompleted : System.Workflow.Activities.HandleExternalEventActivity
    
{
        
// properties
        public static DependencyProperty SenderProperty = System.Workflow.ComponentModel.DependencyProperty.Register("Sender"typeof(Object), typeof(TaskCompleted));
        
public static DependencyProperty EProperty = System.Workflow.ComponentModel.DependencyProperty.Register("E"typeof(TaskEventArgs), typeof(TaskCompleted));

        
public TaskCompleted()
        
{
            
this.EventName = "TaskCompleted";
            
this.InterfaceType = typeof(Microsoft.Samples.Workflow.CorrelatedLocalService.ITaskService);
        }

        
        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(
true)]
        
public object Sender
        
{
            
get
            
{
                
return ((Object)(base.GetValue(TaskCompleted.SenderProperty)));
            }

            
set
            
{
                
base.SetValue(TaskCompleted.SenderProperty, value);
            }

        }



        
public static DependencyProperty EventArgsProperty = System.Workflow.ComponentModel.DependencyProperty.Register("EventArgs"typeof(TaskEventArgs), typeof(TaskCompleted));


        [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
        [BrowsableAttribute(
true)]
        
public Microsoft.Samples.Workflow.CorrelatedLocalService.TaskEventArgs EventArgs
              
{
            
get
            
{
                
return ((TaskEventArgs)(base.GetValue(TaskCompleted.EventArgsProperty)));
            }

            
set
            
{
                
base.SetValue(TaskCompleted.EventArgsProperty, value);
            }

        }


        
protected override void OnInvoked(EventArgs e)
        
{
            EventArgs 
= e as TaskEventArgs;
        }

    }


5.实现工作流如下图:
correlation1 
完整代码如下:

 public sealed partial class CorrelatedLocalServiceWorkflow : SequentialWorkflowActivity
    
{
        
public CorrelatedLocalServiceWorkflow()
        
{
            InitializeComponent();
        }
        

        
private void OnTaskCompleted(object sender, ExternalDataEventArgs e)
        
{
            Console.WriteLine(
"task " + ((TaskEventArgs)e).Id + " done");
        }

    }


6.两个自定义活动关于关联的相关属性的设置,

 

CreateTask活动和TaskCompleted活动中多了如下属性,给这个关联提供一个唯一的名称.每个分支上的CorrelationToken设置要一致。

correlation2

7.宿主程序:  

 class WorkflowApplication
    
{
        
static AutoResetEvent waitHandle = new AutoResetEvent(false);
        
static TaskService taskService = new TaskService();
        
static WorkflowInstance instance;

        
static void Main()
        
{
            
using (WorkflowRuntime workflowRuntime = new WorkflowRuntime())
            
{
                ExternalDataExchangeService dataExchangeService 
= new ExternalDataExchangeService();
                workflowRuntime.AddService(dataExchangeService);

                dataExchangeService.AddService(taskService);
                workflowRuntime.StartRuntime();

                workflowRuntime.WorkflowCompleted 
+= OnWorkflowCompleted;
                workflowRuntime.WorkflowTerminated 
+= delegate(object sender, WorkflowTerminatedEventArgs e)
                
{
                    Console.WriteLine(e.Exception.Message);
                    waitHandle.Set();
                }
;

                instance 
= workflowRuntime.CreateWorkflow(typeof(CorrelatedLocalServiceWorkflow));
                instance.Start();

                waitHandle.WaitOne();
                workflowRuntime.StopRuntime();
            }

        }


        
static void OnWorkflowCompleted(object sender, WorkflowCompletedEventArgs instance)
        
{
            waitHandle.Set();
        }

    }

 

程序运行后结果如下:

correlation3

作者:生鱼片
         
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

Feedback

#1楼  回复 引用 查看   

2008-06-01 13:39 by veter      
抢个沙发先,呵呵,想问一下,如果活动中不存在复合活动的话有必要做这项工作吗?欢迎博主能加入Workflow研究小组.http://space.cnblogs.com/group/workflow/

#2楼  回复 引用 查看   

2008-06-01 13:40 by veter      
沙发不是我的?晕,这也来抢!

#3楼  回复 引用 查看   

2008-06-01 14:04 by 哦,奇怪      
哦,地板:)

#4楼  回复 引用   

2008-06-01 15:52 by 箱式变电站[未注册用户]
学习了

#5楼[楼主]  回复 引用 查看   

2008-06-01 19:13 by 生鱼片      
@veter
这和有没有复合活动没关系,要看你是否会监听相同事件的不同实例。

#6楼[楼主]  回复 引用 查看   

2008-06-01 19:14 by 生鱼片      
@哦,奇怪
@箱式变电站
o(∩_∩)o...

#7楼  回复 引用 查看   

2008-06-01 19:57 by GuoYong.Che      
学习

#8楼  回复 引用   

2008-10-28 10:19 by 混小子—123[未注册用户]
不错,学习,非常感谢能提供这么好的学习案例

#9楼  回复 引用   

2009-03-04 10:58 by E剑仙[未注册用户]
有一个小疑问,CreateTask的参数中没有工作流实例的InstanceId,因为在博主的简单例子中用了一个全局实例,但是实际使用时这是不可能的。所以在自定义的工作流类中还要加入一个Guid属性来记录该值,但是非常郁闷的是在创建一个工作流实例时你无法将它的Guid传给自定义的工作流类对象(因为是创建好后你才能获得这个Guid),于是为了传这个值还得加一个HandleExternalEvent和一个专门用于这项任务的Event……我晕,难道真的只能这样吗?

#10楼  回复 引用   

2009-03-10 13:00 by E剑仙[未注册用户]
噢原来CreateWorkflow方法有一个包含自己创建的Guid的重载……

#11楼  回复 引用 查看   

2011-05-18 20:35 by 阿胜      
好系列。我自己搞了个例子,可老报错。请指点下。

服务接口:
[CorrelationParameter("taskId")]
[ExternalDataExchange]
public interface IPersistenceDemo
{
[CorrelationInitializer]
void CreateTask(string taskId, string assignee, string text);

[CorrelationAlias("taskId", "e.Id")]
event EventHandler<TaskServiceEventArgs> TaskChanged;
}

实现:
public class PersistenceDemoService : IPersistenceDemo
{
public event EventHandler<TaskServiceEventArgs> TaskChanged;

public void CreateTask(string taskId, string assignee,string text)
{
string sql = "INSERT INTO TTask([Id],[Assignee],[Text]) values ('"+taskId+"' ,'"+assignee+"','"+text+"')";
SQLServerHelper.ExecuteNonQuery(SQLServerHelper.ConnectionStringLocalTransaction, System.Data.CommandType.Text, sql, null);
}

public void OnTaskChanged(string taskId, TaskServiceEventArgs args)
{
if (TaskChanged != null)
{
TaskChanged(null, args);
}
}
}

每次askChanged(null, args);的时候就报错。

无法为实例 ID“d5d9d4e4-db29-43f8-8060-9caa0f0dc99c”传递接口类型“SqlPersistenceWorkflowLibrary.IPersistenceDemo”上的事件“TaskChanged”。

InnerException是:
{"事件队列操作失败,队列“Message Properties\r\nInterface Type:SqlPersistenceWorkflowLibrary.IPersistenceDemo\r\nMethod Name:TaskChanged\r\nCorrelationValues:\r\n”的 MessageQueueErrorCode 为 QueueNotFound。"}

堆栈:
在 System.Workflow.Runtime.WorkflowQueuingService.GetQueue(IComparable queueID)
在 System.Workflow.Runtime.WorkflowQueuingService.EnqueueEvent(IComparable queueName, Object item)
在 System.Workflow.Runtime.WorkflowExecutor.EnqueueItem(IComparable queueName, Object item, IPendingWork pendingWork, Object workItem)
在 System.Workflow.Runtime.WorkflowInstance.EnqueueItem(IComparable queueName, Object item, IPendingWork pendingWork, Object workItem)
在 System.Workflow.Activities.WorkflowMessageEventHandler.EventHandler(Object sender, ExternalDataEventArgs eventArgs)

什么个情况?把工作流关联去掉,可以正常执行。是否工作流关联使用不正确??