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

  1. Cree una solución de Visual Studio 2010 vacía.

  2. Agregue a la solución una nueva aplicación de consola denominada Service.

  3. Agregue referencias a los siguientes ensamblados:

    1. System.Runtime.Serialization

    2. System.ServiceModel

    3. System.ServiceModel.Activities

  4. 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

  1. Agregue una nueva clase derivada de NativeActivity denominada ReceiveInstanceIdScope.

  2. 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;
    }
    
  3. Implemente el constructor

    public ReceiveInstanceIdScope()
                : base()
            {
                this.children = new Collection<Activity>();
                this.variables = new Collection<Variable>();
                this.currentIndex = new Variable<int>();
            }
    }
    
  4. Implemente las propiedades Activities y Variables

    public Collection<Activity> Activities
    {
         get { return this.children; }
    }
    
    public Collection<Variable> Variables
    {
        get { return this.variables; }
    }
    
  5. 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);
    }   
    
  6. 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

  1. Abra la clase Program existente.

  2. Defina las constantes siguientes:

    class Program
    {
       const string addr = "https://localhost:8080/Service";
       static XName contract = XName.Get("IService", "http://tempuri.org");
    }
    
  3. 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) } } 
                            }
                        }
                    }
                };
            }
    
  4. 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

  1. Agregue a la solución una nueva aplicación de consola denominada Service.

  2. Agregue referencias a los siguientes ensamblados:

    1. System.Runtime.Serialization

    2. System.ServiceModel

    3. System.ServiceModel.Activities

  3. 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

  1. Agregue una nueva clase derivada de NativeActivity denominada SendInstanceIdScope.

  2. 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;
    }
    
  3. Implemente el constructor

    public SendInstanceIdScope()
                : base()
            {
                this.children = new Collection<Activity>();
                this.variables = new Collection<Variable>();
                this.currentIndex = new Variable<int>();
            }
    
  4. Implemente las propiedades Activities y Variables

    public Collection<Activity> Activities
    {
         get { return this.children; }
    }
    
    public Collection<Variable> Variables
    {
        get { return this.variables; }
    }
    
  5. 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);
    }   
    
  6. 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

  1. Cree un nuevo proyecto de aplicación de consola denominado Client.

  2. Agregue referencias a los siguientes ensamblados:

    1. System.Activities

    2. System.ServiceModel

    3. System.ServiceModel.Activities

  3. 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) ) },                    
                    }
                };
            }
    
  4. 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)