Acceso a OperationContext desde un servicio de flujo de trabajo
Este tema es aplicable a Windows Workflow Foundation 4.
Para tener acceso a OperationContext en un servicio de flujo de trabajo, debe implementar la interfaz IReceiveMessageCallback en una propiedad de ejecución personalizada. Invalide el método OnReceiveMessage, al que se pasa una referencia a OperationContext. Este tema le guiará en la implementación de esta propiedad de ejecución para recuperar un encabezado personalizado, así como una actividad personalizada que mostrará esta propiedad a Receive en tiempo de ejecución. La actividad personalizada implementará el mismo comportamiento que una actividad Sequence, con la salvedad de que, cuando Receive se coloque dentro, se llamará a IReceiveMessageCallback y se recuperará la información de OperationContext. Este tema también muestra cómo tener acceso a OperationContext en el lado del cliente para agregar encabezados de salida a través de la interfaz ISendMessageCallback.
Implementar IReceiveMessageCallback en el lado del servicio
Cree una solución de Visual Studio 2010 vacía.
Agregue a la solución una nueva aplicación de consola denominada
Service
.Agregue referencias a los siguientes ensamblados:
System.Runtime.Serialization
System.ServiceModel
System.ServiceModel.Activities
Agregue una nueva clase denominada
ReceiveInstanceIdCallback
e implemente IReceiveMessageCallback como se muestra en el siguiente ejemplo.class ReceiveInstanceIdCallback : IReceiveMessageCallback { public const string HeaderName = "InstanceIdHeader"; public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext"; public void OnReceiveMessage(System.ServiceModel.OperationContext operationContext, System.Activities.ExecutionProperties activityExecutionProperties) { try { Guid instanceId = operationContext.IncomingMessageHeaders.GetHeader<Guid>(HeaderName, HeaderNS); Console.WriteLine("Received a message from a workflow with instanceId = {0}", instanceId); } catch (MessageHeaderException) { Console.WriteLine("This message must not be from a workflow."); } } }
Este código utiliza el objeto OperationContext pasado al método para tener acceso a los encabezados del mensaje entrante.
Implementar una actividad nativa en el lado del servicio para agregar la implementación de IReceiveMessageCallback a NativeActivityContext
Agregue una nueva clase derivada de NativeActivity denominada
ReceiveInstanceIdScope
.Agregue variables locales para realizar el seguimiento de las actividades secundarias, variables, el índice de actividad actual y una devolución de llamada CompletionCallback.
public sealed class ReceiveInstanceIdScope : NativeActivity { Collection<Activity> children; Collection<Variable> variables; Variable<int> currentIndex; CompletionCallback onChildComplete; }
Implemente el constructor
public ReceiveInstanceIdScope() : base() { this.children = new Collection<Activity>(); this.variables = new Collection<Variable>(); this.currentIndex = new Variable<int>(); } }
Implemente las propiedades
Activities
yVariables
public Collection<Activity> Activities { get { return this.children; } } public Collection<Variable> Variables { get { return this.variables; } }
Invalide CacheMetadata
protected override void CacheMetadata(NativeActivityMetadata metadata) { //call base.CacheMetadata to add the Activities and Variables to this activity's metadata base.CacheMetadata(metadata); //add the private implementation variable: currentIndex metadata.AddImplementationVariable(this.currentIndex); }
Invalide Execute
protected override void Execute( NativeActivityContext context) { context.Properties.Add("ReceiveInstanceIdCallback", new ReceiveInstanceIdCallback()); InternalExecute(context, null); } void InternalExecute(NativeActivityContext context, ActivityInstance instance) { //grab the index of the current Activity int currentActivityIndex = this.currentIndex.Get(context); if (currentActivityIndex == Activities.Count) { //if the currentActivityIndex is equal to the count of MySequence's Activities //MySequence is complete return; } if (this.onChildComplete == null) { //on completion of the current child, have the runtime call back on this method this.onChildComplete = new CompletionCallback(InternalExecute); } //grab the next Activity in MySequence.Activities and schedule it Activity nextChild = Activities[currentActivityIndex]; context.ScheduleActivity(nextChild, this.onChildComplete); //increment the currentIndex this.currentIndex.Set(context, ++currentActivityIndex); }
Implementar el servicio de flujo de trabajo
Abra la clase
Program
existente.Defina las constantes siguientes:
class Program { const string addr = "https://localhost:8080/Service"; static XName contract = XName.Get("IService", "http://tempuri.org"); }
Agregue un método estático llamado
GetWorkflowService
que cree el servicio del flujo de trabajo.static Activity GetServiceWorkflow() { Variable<string> echoString = new Variable<string>(); Receive echoRequest = new Receive { CanCreateInstance = true, ServiceContractName = contract, OperationName = "Echo", Content = new ReceiveParametersContent() { Parameters = { { "echoString", new OutArgument<string>(echoString) } } } }; return new ReceiveInstanceIdScope { Variables = { echoString }, Activities = { echoRequest, new WriteLine { Text = new InArgument<string>( (e) => "Received: " + echoString.Get(e) ) }, new SendReply { Request = echoRequest, Content = new SendParametersContent() { Parameters = { { "result", new InArgument<string>(echoString) } } } } } }; }
En el método
Main
existente, hospede el servicio del flujo de trabajo.static void Main(string[] args) { string addr = "https://localhost:8080/Service"; using (WorkflowServiceHost host = new WorkflowServiceHost(GetServiceWorkflow())) { host.AddServiceEndpoint(contract, new BasicHttpBinding(), addr); host.Open(); Console.WriteLine("Service waiting at: " + addr); Console.WriteLine("Press [ENTER] to exit"); Console.ReadLine(); host.Close(); } }
Implementar ISendMessageCallback en el lado del cliente
Agregue a la solución una nueva aplicación de consola denominada
Service
.Agregue referencias a los siguientes ensamblados:
System.Runtime.Serialization
System.ServiceModel
System.ServiceModel.Activities
Agregue una nueva clase denominada
SendInstanceIdCallback
e implemente ISendMessageCallback como se muestra en el siguiente ejemplo.class SendInstanceIdCallback : ISendMessageCallback { public const string HeaderName = "InstanceIdHeader"; public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext"; public Guid InstanceId { get; set; } public void OnSendMessage(System.ServiceModel.OperationContext operationContext) { operationContext.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader(HeaderName, HeaderNS, this.InstanceId)); } }
Este código utiliza el objeto OperationContext pasado en el método para agregar un encabezado personalizado al mensaje entrante.
Implementar una actividad nativa en el lado del cliente para agregar la implementación de ISendMessageCallback en el lado del cliente a NativeActivityContext
Agregue una nueva clase derivada de NativeActivity denominada
SendInstanceIdScope
.Agregue variables locales para realizar el seguimiento de las actividades secundarias, variables, el índice de actividad actual y una devolución de llamada CompletionCallback.
public sealed class SendInstanceIdScope : NativeActivity { Collection<Activity> children; Collection<Variable> variables; Variable<int> currentIndex; CompletionCallback onChildComplete; }
Implemente el constructor
public SendInstanceIdScope() : base() { this.children = new Collection<Activity>(); this.variables = new Collection<Variable>(); this.currentIndex = new Variable<int>(); }
Implemente las propiedades
Activities
yVariables
public Collection<Activity> Activities { get { return this.children; } } public Collection<Variable> Variables { get { return this.variables; } }
Invalide CacheMetadata
protected override void CacheMetadata(NativeActivityMetadata metadata) { //call base.CacheMetadata to add the Activities and Variables to this activity's metadata base.CacheMetadata(metadata); //add the private implementation variable: currentIndex metadata.AddImplementationVariable(this.currentIndex); }
Invalide Execute
protected override void Execute( NativeActivityContext context) { context.Properties.Add("SendInstanceIdCallback", new SendInstanceIdCallback() { InstanceId = context.WorkflowInstanceId }); InternalExecute(context, null); } void InternalExecute(NativeActivityContext context, ActivityInstance instance) { //grab the index of the current Activity int currentActivityIndex = this.currentIndex.Get(context); if (currentActivityIndex == Activities.Count) { //if the currentActivityIndex is equal to the count of MySequence's Activities //MySequence is complete return; } if (this.onChildComplete == null) { //on completion of the current child, have the runtime call back on this method this.onChildComplete = new CompletionCallback(InternalExecute); } //grab the next Activity in MySequence.Activities and schedule it Activity nextChild = Activities[currentActivityIndex]; context.ScheduleActivity(nextChild, this.onChildComplete); //increment the currentIndex this.currentIndex.Set(context, ++currentActivityIndex); } protected override void Execute( NativeActivityContext context) { context.Properties.Add("ReceiveInstanceIdCallback", new ReceiveInstanceIdCallback()); InternalExecute(context, null); } void InternalExecute(NativeActivityContext context, ActivityInstance instance) { //grab the index of the current Activity int currentActivityIndex = this.currentIndex.Get(context); if (currentActivityIndex == Activities.Count) { //if the currentActivityIndex is equal to the count of MySequence's Activities //MySequence is complete return; } if (this.onChildComplete == null) { //on completion of the current child, have the runtime call back on this method this.onChildComplete = new CompletionCallback(InternalExecute); } //grab the next Activity in MySequence.Activities and schedule it Activity nextChild = Activities[currentActivityIndex]; context.ScheduleActivity(nextChild, this.onChildComplete); //increment the currentIndex this.currentIndex.Set(context, ++currentActivityIndex); }
Implementar un cliente de flujo de trabajo
Cree un nuevo proyecto de aplicación de consola denominado
Client
.Agregue referencias a los siguientes ensamblados:
System.Activities
System.ServiceModel
System.ServiceModel.Activities
Abra el archivo Program.cs generado y agregue un método estático llamado
GetClientWorkflow
para crear el flujo de trabajo del cliente.static Activity GetClientWorkflow() { Variable<string> echoString = new Variable<string>(); // Define the endpoint Endpoint clientEndpoint = new Endpoint { Binding = new BasicHttpBinding(), AddressUri = new Uri("https://localhost:8080/Service") }; // Configure the Send activity used to send a message Send echoRequest = new Send { Endpoint = clientEndpoint, ServiceContractName = XName.Get("IService", "http://tempuri.org"), OperationName = "Echo", Content = new SendParametersContent() { Parameters = { { "echoString", new InArgument<string>("Hello, World") } } } }; // Place the Send activity in a SendInstanceIdScope. This hooks up the ISendMessageCallback // implementation to the client workflow. return new SendInstanceIdScope { Variables = { echoString }, Activities = { new CorrelationScope { Body = new Sequence { Activities = { // Send the request message echoRequest, // Receive the reply from the service new ReceiveReply { Request = echoRequest, Content = new ReceiveParametersContent { Parameters = { { "result", new OutArgument<string>(echoString) } } } } } } }, new WriteLine { Text = new InArgument<string>( (e) => "Received Text: " + echoString.Get(e) ) }, } }; }
Agregue el código de hospedaje siguiente al método
Main()
.static void Main(string[] args) { Activity workflow = GetClientWorkflow(); WorkflowInvoker.Invoke(workflow); WorkflowInvoker.Invoke(workflow); Console.WriteLine("Press [ENTER] to exit"); Console.ReadLine(); }
Ejemplo
A continuación, se muestra el código fuente completo que se emplea en este tema.
// ReceiveInstanceIdScope.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System.Activities;
using System.Collections.ObjectModel;
namespace Microsoft.Samples.AccessingOperationContext.Service
{
public sealed class ReceiveInstanceIdScope : NativeActivity
{
Collection<Activity> children;
Collection<Variable> variables;
Variable<int> currentIndex;
CompletionCallback onChildComplete;
public ReceiveInstanceIdScope()
: base()
{
this.children = new Collection<Activity>();
this.variables = new Collection<Variable>();
this.currentIndex = new Variable<int>();
}
public Collection<Activity> Activities
{
get
{
return this.children;
}
}
public Collection<Variable> Variables
{
get
{
return this.variables;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
//call base.CacheMetadata to add the Activities and Variables to this activity's metadata
base.CacheMetadata(metadata);
//add the private implementation variable: currentIndex
metadata.AddImplementationVariable(this.currentIndex);
}
protected override void Execute(
NativeActivityContext context)
{
context.Properties.Add("ReceiveInstanceIdCallback", new ReceiveInstanceIdCallback());
InternalExecute(context, null);
}
void InternalExecute(NativeActivityContext context, ActivityInstance instance)
{
//grab the index of the current Activity
int currentActivityIndex = this.currentIndex.Get(context);
if (currentActivityIndex == Activities.Count)
{
//if the currentActivityIndex is equal to the count of MySequence's Activities
//MySequence is complete
return;
}
if (this.onChildComplete == null)
{
//on completion of the current child, have the runtime call back on this method
this.onChildComplete = new CompletionCallback(InternalExecute);
}
//grab the next Activity in MySequence.Activities and schedule it
Activity nextChild = Activities[currentActivityIndex];
context.ScheduleActivity(nextChild, this.onChildComplete);
//increment the currentIndex
this.currentIndex.Set(context, ++currentActivityIndex);
}
}
}
// ReceiveInstanceIdScope.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.ServiceModel;
using System.ServiceModel.Activities;
namespace Microsoft.Samples.AccessingOperationContext.Service
{
class ReceiveInstanceIdCallback : IReceiveMessageCallback
{
public const string HeaderName = "InstanceIdHeader";
public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext";
public void OnReceiveMessage(System.ServiceModel.OperationContext operationContext, System.Activities.ExecutionProperties activityExecutionProperties)
{
try
{
Guid instanceId = operationContext.IncomingMessageHeaders.GetHeader<Guid>(HeaderName, HeaderNS);
Console.WriteLine("Received a message from a workflow with instanceId = {0}", instanceId);
}
catch (MessageHeaderException)
{
Console.WriteLine("This message must not be from a workflow.");
}
}
}
}
// Service.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.Activities;
using System.Activities.Statements;
using System.ServiceModel;
using System.ServiceModel.Activities;
using System.Xml.Linq;
namespace Microsoft.Samples.AccessingOperationContext.Service
{
class Program
{
const string addr = "https://localhost:8080/Service";
static XName contract = XName.Get("IService", "http://tempuri.org");
static void Main(string[] args)
{
string addr = "https://localhost:8080/Service";
using (WorkflowServiceHost host = new WorkflowServiceHost(GetServiceWorkflow()))
{
host.AddServiceEndpoint(contract, new BasicHttpBinding(), addr);
host.Open();
Console.WriteLine("Service waiting at: " + addr);
Console.WriteLine("Press [ENTER] to exit");
Console.ReadLine();
host.Close();
}
}
static Activity GetServiceWorkflow()
{
Variable<string> echoString = new Variable<string>();
Receive echoRequest = new Receive
{
CanCreateInstance = true,
ServiceContractName = contract,
OperationName = "Echo",
Content = new ReceiveParametersContent()
{
Parameters = { { "echoString", new OutArgument<string>(echoString) } }
}
};
return new ReceiveInstanceIdScope
{
Variables = { echoString },
Activities =
{
echoRequest,
new WriteLine { Text = new InArgument<string>( (e) => "Received: " + echoString.Get(e) ) },
new SendReply
{
Request = echoRequest,
Content = new SendParametersContent()
{
Parameters = { { "result", new InArgument<string>(echoString) } }
}
}
}
};
}
}
}
// SendInstanceIdCallback.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.ServiceModel.Activities;
using System.ServiceModel.Channels;
namespace Microsoft.Samples.AccessingOperationContext.Client
{
class SendInstanceIdCallback : ISendMessageCallback
{
public const string HeaderName = "InstanceIdHeader";
public const string HeaderNS = "http://Microsoft.Samples.AccessingOperationContext";
public Guid InstanceId { get; set; }
public void OnSendMessage(System.ServiceModel.OperationContext operationContext)
{
operationContext.OutgoingMessageHeaders.Add(MessageHeader.CreateHeader(HeaderName, HeaderNS, this.InstanceId));
}
}
}
// SendInstanceIdScope.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System.Activities;
using System.Collections.ObjectModel;
namespace Microsoft.Samples.AccessingOperationContext.Client
{
public sealed class SendInstanceIdScope : NativeActivity
{
Collection<Activity> children;
Collection<Variable> variables;
Variable<int> currentIndex;
CompletionCallback onChildComplete;
public SendInstanceIdScope()
: base()
{
this.children = new Collection<Activity>();
this.variables = new Collection<Variable>();
this.currentIndex = new Variable<int>();
}
public Collection<Activity> Activities
{
get
{
return this.children;
}
}
public Collection<Variable> Variables
{
get
{
return this.variables;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
//call base.CacheMetadata to add the Activities and Variables to this activity's metadata
base.CacheMetadata(metadata);
//add the private implementation variable: currentIndex
metadata.AddImplementationVariable(this.currentIndex);
}
protected override void Execute(
NativeActivityContext context)
{
context.Properties.Add("SendInstanceIdCallback", new SendInstanceIdCallback() { InstanceId = context.WorkflowInstanceId });
InternalExecute(context, null);
}
void InternalExecute(NativeActivityContext context, ActivityInstance instance)
{
//grab the index of the current Activity
int currentActivityIndex = this.currentIndex.Get(context);
if (currentActivityIndex == Activities.Count)
{
//if the currentActivityIndex is equal to the count of MySequence's Activities
//MySequence is complete
return;
}
if (this.onChildComplete == null)
{
//on completion of the current child, have the runtime call back on this method
this.onChildComplete = new CompletionCallback(InternalExecute);
}
//grab the next Activity in MySequence.Activities and schedule it
Activity nextChild = Activities[currentActivityIndex];
context.ScheduleActivity(nextChild, this.onChildComplete);
//increment the currentIndex
this.currentIndex.Set(context, ++currentActivityIndex);
}
}
}
// Client.cs
//----------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//----------------------------------------------------------------
using System;
using System.Activities;
using System.Activities.Statements;
using System.ServiceModel;
using System.ServiceModel.Activities;
using System.Xml.Linq;
namespace Microsoft.Samples.AccessingOperationContext.Client
{
class Program
{
static void Main(string[] args)
{
Activity workflow = GetClientWorkflow();
WorkflowInvoker.Invoke(workflow);
WorkflowInvoker.Invoke(workflow);
Console.WriteLine("Press [ENTER] to exit");
Console.ReadLine();
}
static Activity GetClientWorkflow()
{
Variable<string> echoString = new Variable<string>();
Endpoint clientEndpoint = new Endpoint
{
Binding = new BasicHttpBinding(),
AddressUri = new Uri("https://localhost:8080/Service")
};
Send echoRequest = new Send
{
Endpoint = clientEndpoint,
ServiceContractName = XName.Get("IService", "http://tempuri.org"),
OperationName = "Echo",
Content = new SendParametersContent()
{
Parameters = { { "echoString", new InArgument<string>("Hello, World") } }
}
};
return new SendInstanceIdScope
{
Variables = { echoString },
Activities =
{
new CorrelationScope
{
Body = new Sequence
{
Activities =
{
echoRequest,
new ReceiveReply
{
Request = echoRequest,
Content = new ReceiveParametersContent
{
Parameters = { { "result", new OutArgument<string>(echoString) } }
}
}
}
}
},
new WriteLine { Text = new InArgument<string>( (e) => "Received Text: " + echoString.Get(e) ) },
}
};
}
}
}
Comentarios opcionales.
Vea también
Tareas
Obtener acceso a OperationContext
Conceptos
Crear flujos de trabajo mediante código imperativo
Otros recursos
Servicios de flujo de trabajo
Workflow Services Samples (WF)