首页 > 编程开发 > Objective-C编程 >
-
WF编程系列之调用方法及工作流 2
创建外部数据服务
我们现在来到了更加复杂的一节,我们的任务是为外部数据服务创建桥接代码。宿主必须有这些代码,它才能访问到工作流实例试图传递过来的数据。我们将使用工具来为工作流创建活动(这在下一节介绍),但对于宿主这边的通信连接来说,却没有现成的工具。
在这里,我们将创建一个稍微简化的连接桥版本(这是对于完整的连接桥架构来说)。该版本仅仅支持工作流到宿主的通信。(当我们学到17章时,我们将会创建一个可重用的通用双向连接桥。)我们在此将创建的连接桥被分成了两个部分:一是connector,它实现了我们前面已经开发好了的接口;二是service,除了别的事情外,它有一个职责是激发“data available”事件以及提供一个“read”方法,使用该方法来把数据从工作流中取出。
提示:该代码应由你而不是WF来提供。我在写本地数据交换服务时提供了该代码,但你要写的代码可以有所不同。唯一要求是本地数据交换服务实现了通信接口并提供一种机制,用于检索需要交换的数据。
为什么如此复杂?和传统的.NET对象不同,工作流实例在工作流运行时的范围内执行。因此进出工作流实例的事件都由工作流运行时进行代理。工作流运行时必须做这些工作,因为你的宿主应用程序不能把数据发送给已经被持久化或不处在执行状态下的工作流实例。
回到我们的连接桥上,该连接类包含一个字段,工作流将使用要被传回的数据来填充该字段。对于我们正在创建的本示例应用程序来说,我们不允许并发执行工作流实例,但这仅仅是出于方便。通常情况下,并没有阻止我们执行并发执行的工作流实例,这些我们将在第17章看到。
当然,每一个工作流实例可能会返回不同的数据,至少它传递的驾驶员会和另一个工作流实例不同。连接类的职责是实现我们开发的在宿主这边接口,以及不间断地保持这些数据。当宿主请求该数据时,连接类根据工作流实例ID来确定应正确返回的DataSet是否已经到达。
该服务类为你处理一些任务。首先,它使用工作流运行时注册该ExternalDataService,以便我们可在宿主和工作流实例间进行通信。它维护一个连接类的单例副本,并把它自己作为服务提供者绑定到该连接类。该服务类也充当了工厂(设计模式)的角色,确保我们有一个且仅有一个连接类(实例)。(假如我们实现了双向的接口,该服务类也会提供一个“write”方法。)我们现在就来创建这些类。
创建桥接器(bridge connector)类
1.在Visual Studio中打开MVDataService项目,定位到MVDataCnnector.cs文件,最后打开该文件。
2.在所定义的名称空间中添加下面的代码:
public sealed class MVDataConnector : IMVDataService
{
private DataSet _dataValue = null;
private static WorkflowMVDataService _service = null;
private static object _syncLock = new object();
}
字段_dataValue用来容纳工作流实例产生的数据。字段_service用来容纳数据服务对象的单一实例。_syncLock对象仅仅用来进行线程的同步。
3.下面,我们添加一个static属性来访问该服务对象的单一实例。代码如下:
WorkflowMVDataService
public static WorkflowMVDataService MVDataService
{
get { return _service; }
set
{
if (value != null)
{
lock (_syncLock)
{
// Re-verify the service isn't null
// now that we're locked
if (value != null)
{
_service = value;
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
} // lock
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
}
}
4.我们需要添加一个属性来访问该DataSet,代码如下:
public DataSet MVData
{
get { return _dataValue; }
}
5.因为连接器类从IMVDataService派生,因此我们必须实现MVDataUpdate方法:
public void MVDataUpdate(DataSet mvData)
{
// Assign the field for later recall
_dataValue = mvData;
// Raise the event to trigger host read
_service.RaiseMVDataUpdateEvent();
}
工作流使用这个方法来把DataSet保存到_dataValue字段中。它激发事件以通知宿主数据已经准备好了。该桥接器类的完整代码参见清单8-3。注意我们这时并没准备去编译整个应用程序,我们还有更多的代码需要添加。
清单8-3 完整的MVDataconnector.cs源文件 MVDataConnector
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace MVDataService
{
public sealed class MVDataConnector : IMVDataService
{
private DataSet _dataValue = null;
private static WorkflowMVDataService _service = null;
private static object _syncLock = new object();
public static WorkflowMVDataService MVDataService
{
get { return _service; }
set
{
if (value != null)
{
lock (_syncLock)
{
// Re-verify the service isn't null
// now that we're locked
if (value != null)
{
_service = value;
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
} // lock
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
}
}
public DataSet MVData
{
get { return _dataValue; }
}
// Workflow to host communication method
public void MVDataUpdate(DataSet mvData)
{
// Assign the field for later recall
_dataValue = mvData;
// Raise the event to trigger host read
_service.RaiseMVDataUpdateEvent();
}
}
}
创建桥接服务(bridge service)类
1.再次在Visual Studio中打开MVDataService项目,定位到WorkflowMVDataService.cs文件,打开该文件准备进行编辑。
2.我们创建好了MVDataConnector类,我们还要把下面的代码复制到WorkflowMVDataService.cs文件中:
public class WorkflowMVDataService
{
static WorkflowRuntime _workflowRuntime = null;
static ExternalDataExchangeService _dataExchangeService = null;
static MVDataConnector _dataConnector = null;
static object _syncLock = new object();
public event EventHandler<MVDataAvailableArgs> MVDataUpdate;
private Guid _instanceID = Guid.Empty;
}
3.我们需要具有从类的外部访问_instanceID的能力,因此添加下面的属性:
public Guid InstanceID
{
get { return _instanceID; }
set { _instanceID = value; }
}
4.我们现在要添加一个静态的工厂方法,我们将用它去创建本类的实例。我们这样做是为了确保在我们创建本桥接服务的时候,所有重要的事情都已完成。例如,我们需要确保ExternalDataService服务已被插入到了工作流运行时中。我们也将添加刚才已经创建好了的桥接器类,并把它作为一个可插拔服务以便工作流能访问到该数据连接器类。因此,我们在上面一步所添加的属性下面还要添加下面的方法:
CreateDataService
public static WorkflowMVDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime)
{
lock (_syncLock)
{
// If we're just starting, save a copy of the workflow runtime reference
if (_workflowRuntime == null)
{
// Save instance of the workflow runtime.
_workflowRuntime = workflowRuntime;
} // if
// If we're just starting, plug in ExternalDataExchange service
if (_dataExchangeService == null)
{
// Data exchange service not registered, so create an
// instance and register.
_dataExchangeService = new ExternalDataExchangeService();
_workflowRuntime.AddService(_dataExchangeService);
} // if
// Check to see if we have already added this data exchange service
MVDataConnector dataConnector = (MVDataConnector)workflowRuntime.
GetService(typeof(MVDataConnector));
if (dataConnector == null)
{
// First time through, so create the connector and
// register as a service with the workflow runtime.
_dataConnector = new MVDataConnector();
_dataExchangeService.AddService(_dataConnector);
} // if
else
{
// Use the retrieved data connector.
_dataConnector = dataConnector;
} // else
// Pull the service instance we registered with the connection object
WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
if (workflowDataService == null)
{
// First time through, so create the data service and
// hand it to the connector.
workflowDataService = new WorkflowMVDataService(instanceID);
MVDataConnector.MVDataService = workflowDataService;
} // if
else
{
// The data service is static and already registered with
// the workflow runtime. The instance ID present when it
// was registered is invalid for this iteration and must be
// updated.
workflowDataService.InstanceID = instanceID;
} // else
return workflowDataService;
} // lock
}
5.在前面一节(“创建桥接器类”)我们创建的连接器对象中保存有我们在第4步中创建的该桥接器对象。我们现在将添加一个静态方法,使用该方法可返回该桥接服务实例。尽管这些现在看来没有太大必要,但稍后会讲讲我们这样做的理由。代码如下:
GetRegisteredWorkflowDataService
public static WorkflowMVDataService GetRegisteredWorkflowDataService(Guid instanceID)
{
lock (_syncLock)
{
WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
if (workflowDataService == null)
{
throw new Exception("Error configuring data serviceservice cannot be null.");
} // if
return workflowDataService;
} // lock
}
6.下面我们将添加我们(私有属性)的构造器和析构器。有了桥接器类后,我们需要确保在桥接器对象和桥接服务对象间不会造成循环的引用。你需要添加下面的代码:
WorkflowMVDataService和~WorkflowMVDataService
private WorkflowMVDataService(Guid instanceID)
{
_instanceID = instanceID;
MVDataConnector.MVDataService = this;
}
~WorkflowMVDataService()
{
// Clean up
_workflowRuntime = null;
_dataExchangeService = null;
_dataConnector = null;
}
7.尽管我们为桥接服务类添加了一些重要的东西,但还没有把ExternalDataService引入工作流运行时中,我们仍然要添加一些代码,以使工作流运行时具有读取数据并返回给宿主应用程序的能力。桥接器对象实际上是维持该连接状态,但宿主使用这个服务来获得要访问的数据。下面是我们要添加的read方法:
public DataSet Read()
{
return _dataConnector.MVData;
}
8.要为我们的桥接服务添加的最后的功能块是一个方法,它激发“机动车数据更新(motor vehicle data update)”事件。工作流使用这个方法来为宿主发送一个通知,告知要挑选的数据已经获取完了。代码如下:
public void RaiseMVDataUpdateEvent()
{
if (_workflowRuntime == null)
_workflowRuntime = new WorkflowRuntime();
_workflowRuntime.GetWorkflow(_instanceID); // loads persisted workflow instances
if (MVDataUpdate != null)
{
MVDataUpdate(this, new MVDataAvailableArgs(_instanceID));
} // if
}
完整的桥接服务代码参见清单8-4:
清单8-4 完整的WorkflowMVDataService.cs源文件 WorkflowMVDataService
using System;
using System.Collections.Generic;
using System.Text;
using System.Workflow.Activities;
using System.Workflow.Runtime;
using System.Data;
namespace MVDataService
{
public class WorkflowMVDataService
{
static WorkflowRuntime _workflowRuntime = null;
static ExternalDataExchangeService _dataExchangeService = null;
static MVDataConnector _dataConnector = null;
static object _syncLock = new object();
public event EventHandler<MVDataAvailableArgs> MVDataUpdate;
private Guid _instanceID = Guid.Empty;
public Guid InstanceID
{
get { return _instanceID; }
set { _instanceID = value; }
}
public static WorkflowMVDataService CreateDataService(Guid instanceID, WorkflowRuntime workflowRuntime)
{
lock (_syncLock)
{
// If we're just starting, save a copy of the workflow runtime reference
if (_workflowRuntime == null)
{
// Save instance of the workflow runtime.
_workflowRuntime = workflowRuntime;
} // if
// If we're just starting, plug in ExternalDataExchange service
if (_dataExchangeService == null)
{
// Data exchange service not registered, so create an
// instance and register.
_dataExchangeService = new ExternalDataExchangeService();
_workflowRuntime.AddService(_dataExchangeService);
} // if
// Check to see if we have already added this data exchange service
MVDataConnector dataConnector = (MVDataConnector)workflowRuntime.
GetService(typeof(MVDataConnector));
if (dataConnector == null)
{
// First time through, so create the connector and
// register as a service with the workflow runtime.
_dataConnector = new MVDataConnector();
_dataExchangeService.AddService(_dataConnector);
} // if
else
{
// Use the retrieved data connector.
_dataConnector = dataConnector;
} // else
// Pull the service instance we registered with the connection object
WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
if (workflowDataService == null)
{
// First time through, so create the data service and
// hand it to the connector.
workflowDataService = new WorkflowMVDataService(instanceID);
MVDataConnector.MVDataService = workflowDataService;
} // if
else
{
// The data service is static and already registered with
// the workflow runtime. The instance ID present when it
// was registered is invalid for this iteration and must be
// updated.
workflowDataService.InstanceID = instanceID;
} // else
return workflowDataService;
} // lock
}
public static WorkflowMVDataService GetRegisteredWorkflowDataService(Guid instanceID)
{
lock (_syncLock)
{
WorkflowMVDataService workflowDataService = MVDataConnector.MVDataService;
if (workflowDataService == null)
{
throw new Exception("Error configuring data serviceservice cannot be null.");
} // if
return workflowDataService;
} // lock
}
private WorkflowMVDataService(Guid instanceID)
{
_instanceID = instanceID;
MVDataConnector.MVDataService = this;
}
~WorkflowMVDataService()
{
// Clean up
_workflowRuntime = null;
_dataExchangeService = null;
_dataConnector = null;
}
public DataSet Read()
{
return _dataConnector.MVData;
}
public void RaiseMVDataUpdateEvent()
{
if (_workflowRuntime == null)
_workflowRuntime = new WorkflowRuntime();
_workflowRuntime.GetWorkflow(_instanceID); // loads persisted workflow instances
if (MVDataUpdate != null)
{
MVDataUpdate(this, new MVDataAvailableArgs(_instanceID));
} // if
}
}
}
CallExternalMethod活动
你目前在本章看到过的所有代码都已支持一个特殊的WF活动:CallExternalMethod活动。CallExternalMethod活动的作用是可以接受一个接口及该接口所支持的方法,并来调用这个方法。现在的问题是,由谁来实现这个方法?
你可能会考虑由你的宿主应用程序来完成,但这不太正确。假如你向前看看前面的一节“创建桥接器类”,你实际上在那里会找到这个方法。数据连接器由ExternalDataService捆住实现了该方法。该数据服务依次把该方法的调用转换成一个宿主应用程序能识别的事件。
直接使用CallExternalMethod活动是允许的,你甚至可以绕开一些服务代码就可把它插入到你的应用程序中。但是绕开这些服务代码对你来说还有一组难题。你的宿主应用程序和你的工作流实例是一对一地联系在一起的。在这里使用数据服务来完成这件事要更适合一些,当你把该数据服务结合起来后,你就能使许多的应用程序实例从许多的工作流实例中进行数据访问,而绕过你创建好的那些数据服务后则不能做到这些。
对于直接使用CallExternalMethod活动,它通常更适合于创建自定义活动来为你调用外部方法。你可使用一个工具来自定义你的数据交换接口和创建派生自CallExternalMethod的活动,更恰当地对其命名,对它们的属性(接口和方法名称)进行配置。接下来我们就来看看该工具的使用方法。
创建和使用自定义外部数据服务活动
回头看看,我们刚刚写下的代码比目前整本书已写过的代码还要多。原因是WF事先不知道我们的工作流将和我们的宿主应用程序之间交换些什么信息。因此在二者之间毫无疑问必须做一些工作,以便对它们之间的差距进行填充。
但是,WF知悉所有的工作流活动,我们可愉快地使用一个工具来对我们的数据传送接口进行解释,使用ExternalDataExchange特性(attribute)来进行标记,自动地生成WF活动。
我们本章正生成的应用程序把数据从工作流中发送到宿主应用程序中,也就是说数据传送是单向的。我故意这样做是因为,我们只有积累了足够的知识,才能更好地学习并充分理解双向数据传送。我们将使用的Workflow Communication Activity生成器完全有能力创建那些发送和接受宿主数据的活动。对于本应用程序的特殊性,我们将“扔掉”它的输出部分,因为我们不需要它。(其实,将生成的活动是畸形的,因为我们的接口没有指定宿主到工作流的通信,这些我们将保留到第10章讲解。)
为此,我们就来执行wca.exe并创建一个可用来发送数据到我们的宿主应用程序的活动。
创建通信活动
1.为使wca.exe能生成符合我们要求的代码,我们需要确保有一个接口。因此,确保MVDataService项目生成时无错误(如生成时有错误,请纠正所有的错误)并已生成了MVDataService程序集。
2.点击操作系统的开始按钮,然后点击运行菜单项打开运行对话框。
3.在打开的组合框控件中输入“cmd”,然后点击确定进入命令提示符窗口。
4.更改起始目录以便我们能直接访问到“MVDataService”程序集。通常使用的是“cd”命令。
5.wca.exe文件默认情况下被安装到Program Files目录下的Windows SDK子目录中。(当然,假如你没有使用默认目录进行安装,你在此需要使用你安装Windws SDK的目录。)在命令行提示符下输入下面的命令来执行该工具(包含双引号):
“C:Program FilesMicrosoft SDKsWindowsv6.0ABinWca.exe” MVDataService.dll
按下回车键,该工具的输出结果和下面的类似:
6.在命令提示符中键入dir你可看到wca.exe创建的文件。
7.IMVDataService.Sinks.cs文件不是必须要的,可忽略它甚至是删除它,因为该文件只是包含了一些指示,没有代码。(在我们的通信接口中没有定义事件。)我们在第十章将再使用这个文件。对于另一个生成的文件:IMVDataService.Invokes.cs文件,是我们要保留的文件。它包含我们能使用的一个新活动的源代码,该活动可把数据从工作流中发送给宿主应用程序。因此,我们将重命名该文件以便更加好用。在命令提示符下输入“ren IMVDataService.Invokes.cs MVDataUpdate.cs”,然后按下回车键重命名该文件。
8.因为我们刚刚重命名的这个文件是一个工作流活动,因此我们需要把它从当前目录下移动到MVWorkflow目录下以便编译和使用。在命令提示符下输入“move MVDataUpdate.cs ......MVWorkflow”,然后按下回车键。
9.回到Visual Studio,我们需要把这个新创建的MVDataUpdate.cs文件添加我们的工作流项目中。
10.编译并生成MVWorkflow项目,假如出现编译错误的话,修正所有的错误。在你成功编译后,在视图设计器界面模式下打开Workflow1.cs文件将会在Visual Studio的工具箱中看到这个MVDataUpdate活动。
备注:假如MVDataUpdate因为某些原因没有添加进Visual Studio工具箱中,请关闭该解决方案,然后再重新打开它。
我们现在就准备好了一个活动,我们可使用它来把数据发送到我们的宿主应用程序中。该活动的基类是CallExternalMethod,它的作用是触发对工作流执行环境的外部调用。
添加并配置该工作流通信活动
1.在Visual Studio中以视图设计器的模式打开MVWorkflow项目中的Workflow1.cs文件。该工作流预先加载了两个活动:一个是Delay活动,用来模拟处理数据的等待时间;一个是Code活动,它创建并填充一个基于驾驶员姓名的DataSet。
2.打开Visual Studio工具箱,定位到MVDataUpdate活动。
3.把该活动拖拽到工作流视图设计器界面上,放到Code活动的下面使它在Code活动执行后执行。
4.我们的工作流在视图设计器上的设计工作这就完成了。现在该写少量代码来进行衔接。因此以代码视图模式打开Workflow1.cs文件。在Workflow1类中找到GenerateMVDData方法。这个方法是codeActivity1所执行的方法,在里面你会看到对GenerateVehicleTable和GenerateViolationTable两个方法的调用,它们创建并填充所要返回的DataSet。(其实,你可用一些外部服务来为驾驶员的信息进行一个调用,但我们在此对这些进行了模拟)。在生成了DataSet后,我们需要添加下面的代码以把DataSet返回给宿主:
// Assign the DataSet we just created as the host data
mvDataUpdate1.mvData = ds;
该服务类为你处理一些任务。首先,它使用工作流运行时注册该ExternalDataService,以便我们可在宿主和工作流实例间进行通信。它维护一个连接类的单例副本,并把它自己作为服务提供者绑定到该连接类。该服务类也充当了工厂(设计模式)的角色,确保我们有一个且仅有一个连接类(实例)。(假如我们实现了双向的接口,该服务类也会提供一个“write”方法。)我们现在就来创建这些类。
创建桥接器(bridge connector)类
1.在Visual Studio中打开MVDataService项目,定位到MVDataCnnector.cs文件,最后打开该文件。
2.在所定义的名称空间中添加下面的代码:
public sealed class MVDataConnector : IMVDataService
{
private DataSet _dataValue = null;
private static WorkflowMVDataService _service = null;
private static object _syncLock = new object();
}
字段_dataValue用来容纳工作流实例产生的数据。字段_service用来容纳数据服务对象的单一实例。_syncLock对象仅仅用来进行线程的同步。
3.下面,我们添加一个static属性来访问该服务对象的单一实例。代码如下:
WorkflowMVDataService
public static WorkflowMVDataService MVDataService
{
get { return _service; }
set
{
if (value != null)
{
lock (_syncLock)
{
// Re-verify the service isn't null
// now that we're locked
if (value != null)
{
_service = value;
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
} // lock
} // if
else
{
throw new InvalidOperationException("You must provide a service instance.");
} // else
}
}