La información aquí ofrecida es contenido traducido automáticamente que los miembros de la comunidad pueden editar. Quienes deseen contribuir a mejorar la traducción, pueden hacer clic en el vínculo “editar” asociado a cualquiera de los siguientes enunciados.
In a previous column (see “Workflow Communications” in the September 2007 issue of MSDN Magazine at msdn.microsoft.com/magazine/cc163365.aspx), I wrote about the core communication architecture in Windows Workflow Foundation 3 (WF3).
One topic I did not cover is the local communications activities that are one abstraction on top of this communication architecture.
If you look at .NET Framework 4 Beta 1, you will notice no HandleExternalEvent activity.
In fact, with WF4, the communications activities included are built on Windows Communication Foundation (WCF).
This month, I’ll show you how to use WCF for communication between a workflow and a host application in Windows Workflow Foundation 3.
Gaining this knowledge should help with your development efforts using WF3 and prepare you for WF4, where WCF is the only abstraction over queues (referred to as “bookmarks” in WF4) that ships with the framework.
(For basic information on Workflow Services in WF3, see my Foundations column in the Visual Studio 2008 Launch issue of MSDN Magazine, at msdn.microsoft.com/magazine/cc164251.aspx.)
Overview
Communication between host applications and workflows proves challenging for some developers because they can easily overlook the fact that the workflow and host often execute on different threads.
The design of the communication architecture is intended to shield developers from having to worry about managing thread context, marshaling data and other low-level details.
One abstraction over the queuing architecture in WF is the WCF messaging integration that was introduced in Version 3.5 of the .NET Framework.
Most examples and labs show how the activities and extensions to WCF can be used to expose a workflow to clients that are external to the hosting process, but this same communication framework can be used to communicate within the same process.
Implementing the communication involves several steps, but the work does not amount to much more than what you would have to do with the local communication activities.
Before you can do anything else, you need to define (or minimally begin to define in an iterative approach) the contracts for communication using WCF service contracts.
Next, you need to use those contracts in your workflows to model the communication points in the logic.
Finally, to hook it all together, the workflow and other services need to be hosted as WCF services with endpoints configured.
Modeling Communication
The first step in modeling communication is to define the contracts between your host application and the workflow.
WCF services use contracts to define the collection of operations that make up the service and the messages that are sent and received.
In this case, because you are communicating from the host to the workflow and from the workflow to the host, you need to define two service contracts and related data contracts, as shown in Figure 1.
Figure 1 Contracts for Communication
[ServiceContract(
Namespace = "urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IHostInterface
{
[OperationContract]
void OrderStatusChange(Order order, string newStatus, string oldStatus);
}
[ServiceContract(
Namespace="urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IWorkflowInterface
{
[OperationContract]
void SubmitOrder(Order newOrder);
[OperationContract]
bool UpdateOrder(Order updatedOrder);
}
[DataContract]
public class Order
{
[DataMember]
public int OrderID { get; set; }
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public double OrderTotal { get; set; }
[DataMember]
public string OrderStatus { get; set; }
}
With the contracts in place, modeling the workflow using the Send and Receive activities works as it does for remote communication.
That is one of the beautiful things about WCF: remote or local, the programming model is the same.
As a simple example, Figure 2 shows a workflow with two Receive activities and one Send activity modeling the communication between the workflow and the host.
The receive activities are configured with the IWorkflowInterface service contract, and the Send activity uses the IHostInterface contract.
So far, using WCF for local communications is not much different from using WCF for remote communications and is very similar to using the local communications activities and services.
The main difference comes in how the host code is written to start the workflow and handle communication coming from the workflow.
Figure 2 Workflow Modeled Against Contracts
Hosting the Services
Because we want communication to flow both ways using WCF, we need to host two services—the workflow service to run the workflow and a service in the host application to receive messages from the workflow.
In my example, I built a simple Windows Presentation Foundation (WPF) application to act as the host and used the App class’s OnStartup and OnExit methods to manage the hosts.
Your first inclination might be to create the WorkflowServiceHost class and open it right in the OnStartup method.
Since the Open method does not block after the host is open, you can continue processing, load the user interface and begin interacting with the workflow.
Because WPF ( and other client technologies) uses a single thread for processing, this soon leads to problems because both the service and the client call cannot use the same thread, so the client times out.
To avoid this, the WorkflowServiceHost is created on another thread using the ThreadPool, as shown in Figure 3.
Figure 3 Hosting the Workflow Service
ThreadPool.QueueUserWorkItem((o) =>
{
//host the workflow
workflowHost = new WorkflowServiceHost(typeof(
WorkflowsAndActivities.OrderWorkflow));
workflowHost.AddServiceEndpoint(
"Contracts.IWorkflowInterface", LocalBinding, WFAddress);
try
{
workflowHost.Open();
}
catch (Exception ex)
{
workflowHost.Abort();
MessageBox.Show(String.Format(
"There was an error hosting the workflow as a service: {0}",
ex.Message));
}
});
The next challenge you encounter is choosing the appropriate binding for local communication.
Currently, there is no in-memory or in-process binding that is extremely lightweight for these kinds of scenarios.
The best option for a lightweight channel is to use the NetNamedPipeBinding with security turned off.
Unfortunately, if you try to use this binding and host the workflow as a service, you get an error informing you that the host requires a binding with the Context channel present because your service contract may require a session.
Further, there is no NetNamedPipeContextBinding included with the .NET Framework, which ships with only three context bindings: BasicHttpContextBinding, NetTcpContextBinding and WSHttpContextBinding.
Fortunately, you can create your own custom bindings to include the context channel.
Figure 4 shows a custom binding that derives from the NetNamedPipeBinding class and injects the ContextBindingElement into the binding.
Communication in both directions can now use this binding in the endpoint registration by using different addresses.
Figure 4 NetNamedPipeContextBinding
public class NetNamedPipeContextBinding : NetNamedPipeBinding
{
public NetNamedPipeContextBinding() : base(){}
public NetNamedPipeContextBinding(
NetNamedPipeSecurityMode securityMode):
base(securityMode) {}
public NetNamedPipeContextBinding(string configurationName) :
base(configurationName) {}
public override BindingElementCollection CreateBindingElements()
{
BindingElementCollection baseElements = base.CreateBindingElements();
baseElements.Insert(0, new ContextBindingElement(
ProtectionLevel.EncryptAndSign,
ContextExchangeMechanism.ContextSoapHeader));
return baseElements;
}
}
With this new binding, you can create an endpoint on the WorkflowServiceHost and open the host with no more errors.
The workflow is ready to receive data from the host using the service contract.
To send that data, you need to create a proxy and invoke the operation, as shown in Figure 5.
Figure 5 Host Code to Start a Workflow
App a = (App)Application.Current;
IWorkflowInterface proxy = new ChannelFactory<IWorkflowInterface>(
a.LocalBinding, a.WFAddress).CreateChannel();
proxy.SubmitOrder(
new Order
{
CustomerName = "Matt",
OrderID = 0,
OrderTotal = 250.00
});
Because you’re sharing the contracts, there is no proxy class, so you have to use the ChannelFactory<TChannel> to create the client proxy.
While the workflow is hosted and ready to receive messages, it still needs to be configured to send messages to the host.
Most important, the workflow needs to be able to get a client endpoint when using the Send activity.
The Send activity allows you to specify the endpoint name, which is typically a mapping to a named endpoint in the configuration file.
Although putting the endpoint information in a configuration file works, you can also use the ChannelManagerService (as discussed in my August 2008 column at msdn.microsoft.com/magazine/cc721606.aspx) to hold the client endpoints used by your Send activities in the workflow.
Figure 6 shows the hosting code to create the service, provide it with a named endpoint, and add it to the WorkflowRuntime hosted in the WorkflowServiceHost.
Figure 6 Adding the ChannelManagerService to the Runtime
ServiceEndpoint endpoint = new ServiceEndpoint
(
ContractDescription.GetContract(typeof(Contracts.IHostInterface)),
LocalBinding, new EndpointAddress(HostAddress)
);
endpoint.Name = "HostEndpoint";
WorkflowRuntime runtime =
workflowHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().
WorkflowRuntime;
ChannelManagerService chanMan =
new ChannelManagerService(
new List<ServiceEndpoint>
{
endpoint
});
runtime.AddService(chanMan);
Having the workflow service hosted provides the ability to send messages from the host to the workflow, but to get messages back to the host, you need a WCF service that can receive messages from the workflow.
This service is a standard WCF service self-hosted in the application.
Because the service is not a workflow service, you can use the standard NetNamedPipeBinding or reuse the NetNamedPipeContextBinding shown previously.
Finally, because this service is invoked from the workflow, it can be hosted on the UI thread, making interaction with UI elements simpler.
Figure 7 shows the hosting code for the service.
Figure 7 Hosting the Host Service
ServiceHost appHost = new ServiceHost(new HostService());
appHost.AddServiceEndpoint("Contracts.IHostInterface",
LocalBinding, HostAddress);
try
{
appHost.Open();
}
catch (Exception ex)
{
appHost.Abort();
MessageBox.Show(String.Format(
"There was an error hosting the local service: {0}",
ex.Message));
}
With both services hosted, you can now run the workflow, send a message and receive a message back.
However, if you try to send a second message using this code to the second receive activity in the workflow, you will receive an error about the context.
Handling Instance Correlation
One way to handle the context problem is to use the same client proxy for every invocation of the service.
This enables the client proxy to manage the context identifiers (using the NetNamedPipeContextBinding) and send them back to the service with subsequent requests.
In some scenarios, it’s not possible to keep the same proxy around for all requests.
Consider the case where you start a workflow, persist it to a database and close the client application.
When the client application starts up again, you need a way to resume the workflow by sending another message to that specific instance.
The other common use case is when you do want to use a single client proxy, but you need to interact with several workflow instances, each with a unique identifier.
For example, the user interface provides a list of orders, each with a corresponding workflow, and when the user invokes an action on a selected order, you need to send a message to the workflow instance.
Letting the binding manage the context identifier will not work in this scenario because it will always be using the identifier of the last workflow with which you interacted.
For the first scenario—using a new proxy for each call—you need to manually set the workflow identifier into the context by using the IContextManager interface.
IContextManager is accessed through the GetProperty<TProperty> method on the IClientChannel interface.
Once you have the IContextManager, you can use it to get or set the context.
The context itself is a dictionary of name-value pairs, the most important of which is the instanceId value.
The following code shows how you retrieve the ID from the context so it can be stored by your client application for later, when you need to interact with the same workflow instance.
In this example, the ID is being displayed in the client user interface rather than being stored in a database:
IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
string wfID = mgr.GetContext()["instanceId"];
wfIdText.Text = wfID;
Once you make the first call to the workflow service, the context is automatically populated with the instance ID of the workflow by the context binding on the service endpoint.
When using a newly created proxy to communicate with a workflow instance that was previously created, you can use a similar method to set the identifier in the context to ensure your message is routed to the correct workflow instance, as shown here:
IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
mgr.SetContext(new Dictionary<string, string>{
{"instanceId", wfIdText.Text}
});
When you have a newly created proxy, this code works fine the first time but not if you try to set the context a second time for invoking another workflow instance.
The error you get tells you that you cannot change the context when automatic context management is enabled.
Essentially, you are told that you can’t have your cake and it eat too.
If you want the context to be managed automatically, you can’t manipulate it manually.
Unfortunately, if you want to manage the context manually, you fail to get automatic management, which means you cannot retrieve the workflow instance ID from the context as I showed previously.
To deal with this mismatch, you handle each case separately.
For the initial call to a workflow, you use a new proxy, but for all subsequent calls to an existing workflow instance, you use a single client proxy and manage the context manually.
For the initial call, you should use a single ChannelFactory<TChannel> to create all the proxies.
This results in better performance because the creation of the ChannelFactory has some overhead you do not want to duplicate for every first call.
Using code like that shown earlier in Figure 5, you can use a single ChannelFactory<TChannel> to create the initial proxy.
In your calling code, after using the proxy, you should follow the best practice of calling the Close method to release the proxy.
This is standard WCF code for creating your proxy using the channel factory method.
Because the binding is a context binding, you get automatic context management by default, which means you can extract the workflow instance identifier from the context after making the first call to the workflow.
For making subsequent calls, you need to manage the context yourself, and this entails using WCF client code that is not as frequently used by developers.
To set the context manually, you need to use an OperationContextScope and create the MessageContextProperty yourself.
The MessageContextProperty is set on the message as it is being sent, which is equivalent to using the IContextManager to set the context, with the exception that using the property directly works even when the context management is disabled.
Figure 8 shows the code to create the proxy using the same ChannelFactory<TChannel> that was used for the initial proxy.
The difference is that in this case, the IContextManager is used to disable the automatic context management feature and a cached proxy is used rather than creating a new one on each request.
Figure 8 Disabling Automatic Context Management
App a = (App)Application.Current;
if (updateProxy == null)
{
if (factory == null)
factory = new ChannelFactory<IWorkflowInterface>(
a.LocalBinding, a.WFAddress);
updateProxy = factory.CreateChannel();
IContextManager mgr =
((IClientChannel)updateProxy).GetProperty<IContextManager>();
mgr.Enabled = false;
((IClientChannel)updateProxy).Open();
}
Once the proxy is created, you need to create an OperationContextScope and add the MessageContextProperty to the outgoing message properties on the scope.
This enables the property to be included on the outgoing messages during the duration of the scope.
Figure 9 shows the code to create and set the message property using the OperationContextScope.
Figure 9 Using OperationContextScope
using (OperationContextScope scope =
new OperationContextScope((IContextChannel)proxy))
{
ContextMessageProperty property = new ContextMessageProperty(
new Dictionary<string, string>
{
{“instanceId”, wfIdText.Text}
});
OperationContext.Current.OutgoingMessageProperties.Add(
"ContextMessageProperty", property);
proxy.UpdateOrder(
new Order
{
CustomerName = "Matt",
OrderID = 2,
OrderTotal = 250.00,
OrderStatus = "Updated"
});
}
This might seem like quite a bit of work just to talk between the host and the workflow.
The good news is that much of this logic and the management of identifiers can be encapsulated in a few classes.
However, it does involve coding your client in a particular way to ensure that the context is managed correctly for those cases in which you need to send more than one message to the workflow instance.
In the code download for this article, I have included a sample host for a workflow using local communications that attempts to encapsulate much of the complexity, and the sample application shows how to use the host.
A Word About User Interface Interaction
One of the main reasons you send data from the workflow to the host is that you want to present it to a user in the application interface.
Fortunately, with this model you have some options to take advantage of user interface features, including data binding in WPF.
As a simple example, if you want your user interface to use data binding and update the user interface when data is received from the workflow, you can bind your user interface directly to the host’s service instance.
The key to using the service instance as the data context for your window is that the instance needs to be hosted as a singleton.
When you host the service as a singleton, you have access to the instance and can use it in your UI.
The simple host service shown in Figure 10 updates a property when it receives information from the workflow and uses the INotifyPropertyChangedInterface to help the data binding infrastructure pick up the changes immediately.
Notice the ServiceBehavior attribute indicating that this class should be hosted as a singleton.
If you look back to Figure 7, you can see the ServiceHost instantiated not with a type but with an instance of the class.
Figure 10 Service Implementation with INotifyPropertyChanged
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
internal class HostService : IHostInterface, INotifyPropertyChanged
{
public void OrderStatusChange(Order order, string newStatus,
string oldStatus)
{
CurrentMessage = String.Format("Order status changed to {0}",
newStatus);
}
private string msg;
public string CurrentMessage {
get { return msg; }
set
{
msg = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(
"CurrentMessage"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
To databind to this value, the DataContext of the window, or a particular control in the window, can be set with the instance.
The instance can be retrieved by using the SingletonInstance property on the ServiceHost class, as shown here:
HostService host = ((App)Application.Current).appHost.SingletonInstance as HostService;
if (host != null)
this.DataContext = host;
Now you can simply bind elements in your window to properties on the object, as shown with this TextBlock:
<TextBlock Text="{Binding CurrentMessage}" Grid.Row="3" />
As I said, this is a simple example of what you can do.
In a real application, you likely would not bind directly to the service instance but instead bind to some objects to which both your window and the service implementation had access.
Looking Ahead to WF4
WF4 introduces several features that will make local communications over WCF even easier.
The primary feature is message correlation that does not rely on the protocol.
That is, the use of a workflow instance identifier will still be an option, but a new option will enable messages to be correlated based on the content of the message.
So, if each of your messages contain an order ID, a customer ID or some other piece of data, you can define correlations between those messages and not have to use a binding that supports context management.
Additionally, the fact that both WPF and WF build on the same core XAML APIs in .NET Framework Version 4 might open up some interesting possibilities for integrating the technologies in new ways.
As we get closer to the release of .NET Framework 4, I will provide more details on integrating WF with WCF and WPF, along with other content on the inner workings of WF4.
Matt Milner is a member of the technical staff at Pluralsight, where he focuses on connected systems technologies (WCF, Windows Workflow Foundation, BizTalk, “Dublin,” and the Azure Services Platform).
Matt is also an independent consultant specializing in Microsoft .NET application design and development.
He regularly shares his love of technology by speaking at local, regional and international conferences such as Tech·Ed.
Microsoft has recognized Milner as an MVP for his community contributions around connected systems technology.
You can contact him via his blog at pluralsight.com/community/blogs/matt.
|
En una columna anterior (consulte emitir “ Workflow Communications ” en septiembre de 2007 de de MSDN Magazine en msdn.microsoft.com/magazine/cc163365.aspx de ), escribí acerca de la arquitectura de comunicación núcleo en Windows Workflow Foundation 3 (WF3).
Un tema que no trataré es las actividades de comunicaciones locales que son una abstracción en la parte superior de esta arquitectura de comunicación.
Si observa 4 Beta 1 de .NET Framework, no observará ninguna actividad HandleExternalEvent.
De hecho, con WF4, incluidas las actividades de comunicaciones integradas en Windows Communication Foundation (WCF).
Este mes, mostraré cómo usar WCF para la comunicación entre un flujo de trabajo y una aplicación host en Windows Workflow Foundation 3.
Obtenga este conocimiento debe ayudarle con sus esfuerzos de desarrollo mediante WF3 y prepararse para WF4, donde WCF es la única abstracción sobre colas (se denomina “ marcadores ” en WF4) que se incluye con el marco de trabajo.
(Para obtener información básica en Workflow Services en WF3, consulte mi columna Foundations en el número de inicio de Visual Studio 2008 de de MSDN Magazine, en msdn.microsoft.com/magazine/cc164251.aspx de ).
Información general
Comunicación entre aplicaciones de host y flujos de trabajo demuestra un reto para algunos desarrolladores porque fácilmente pueden pasar por alto el hecho de que el flujo de trabajo y el host a menudo se ejecutan en subprocesos diferentes.
El diseño de la arquitectura de comunicación está pensado para los programadores de tener que preocuparse acerca de la administración de contexto del subproceso, cálculo de referencias de datos y otros detalles de bajo nivel de protección.
Una abstracción sobre la arquitectura de cola en WF es la integración de mensajería de WCF que se introdujo en la versión 3.5 de .NET Framework.
La mayoría de los ejemplos y laboratorios muestran cómo las actividades y las extensiones de WCF pueden utilizarse para exponer un flujo de trabajo a los clientes que son externos al proceso de alojamiento, pero este mismo marco de comunicación puede utilizarse para la comunicación dentro del mismo proceso.
Implementación de la comunicación implica varios pasos, pero el trabajo no importe para mucho más que lo que tendría que hacer con las actividades de comunicación local.
Antes de que puede hacer nada más, deberá definir los contratos para la comunicación mediante contratos de servicio WCF (o mínimamente empezar a definir en un método iterativo).
A continuación, deberá utilizar esos contratos en los flujos de trabajo para modelar los puntos de comunicación en la lógica.
Por último, para enlazar todos juntos, el flujo de trabajo y otros servicios deben alojarse como los servicios WCF con extremos configurados.
Modelado de comunicación
El primer paso en el modelado de comunicación es definir los contratos entre la aplicación host y el flujo de trabajo.
Servicios WCF utilizan contratos para definir la colección de operaciones que componen el servicio y los mensajes que se envían y reciben.
En este caso, porque se está comunicando desde el host para el flujo de trabajo y desde el flujo de trabajo para el host, deberá definir dos contratos de servicio y contratos de datos relacionados, como se muestra en de figura 1.
Figura 1 de contratos para la comunicación
[ServiceContract(
Namespace = "urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IHostInterface
{
[OperationContract]
void OrderStatusChange(Order order, string newStatus, string oldStatus);
}
[ServiceContract(
Namespace="urn:MSDN/Foundations/LocalCommunications/WCF")]
public interface IWorkflowInterface
{
[OperationContract]
void SubmitOrder(Order newOrder);
[OperationContract]
bool UpdateOrder(Order updatedOrder);
}
[DataContract]
public class Order
{
[DataMember]
public int OrderID { get; set; }
[DataMember]
public string CustomerName { get; set; }
[DataMember]
public double OrderTotal { get; set; }
[DataMember]
public string OrderStatus { get; set; }
}
Con los contratos en su lugar, el flujo de trabajo mediante las actividades Send y Receive de modelado funciona que para la comunicación remota.
Es una de las cosas hermosas sobre WCF: local o remoto, el modelo de programación es el mismo.
Por ejemplo, de figura 2 muestra un flujo de trabajo con dos actividades de recepción y una actividad Send modelar la comunicación entre el host y el flujo de trabajo.
Las actividades de recepción están configuradas con el contrato de servicio IWorkflowInterface y la actividad Send utiliza el contrato de IHostInterface.
Hasta ahora, el uso de WCF para comunicaciones locales no es muy diferente del uso de WCF para comunicaciones remotas y es muy similar al uso de las actividades de comunicaciones local y servicios.
La diferencia principal viene en cómo se escribe el código de host para iniciar el flujo de trabajo y controlar la comunicación procedente de flujo de trabajo.
Figura 2 de Workflow modelo contra contratos
Los servicios de alojamiento
Porque queremos comunicación fluya de ambas maneras mediante WCF, debemos host dos servicios: el servicio de flujo de trabajo para ejecutar el flujo de trabajo y un servicio en la aplicación host para recibir mensajes de flujo de trabajo.
En mi ejemplo, he creado una sencilla aplicación de Windows Presentation Foundation (WPF) para que actúe como el host y se utiliza OnStartup y OnExit métodos la clase App para administrar los hosts.
Para crear la clase WorkflowServiceHost y abra lo derecha en el método OnStartup podría ser la primera tendencia.
Puesto que el método Open no se bloquea después de que el host está abierto, puede continuar el procesamiento, cargar la interfaz de usuario y empezar a interactuar con el flujo de trabajo.
Dado que WPF (y otras tecnologías de cliente) utiliza un único subproceso para su procesamiento, pronto Esto conduce a problemas porque el servicio y la llamada del cliente no pueden utilizar el mismo subproceso, por lo que agota el cliente.
Para evitar esto, se crea el WorkflowServiceHost en otro subproceso utilizar ThreadPool, tal como se muestra en figura 3.
Figura 3 de alojar el servicio de flujos de trabajo
ThreadPool.QueueUserWorkItem((o) =>
{
//host the workflow
workflowHost = new WorkflowServiceHost(typeof(
WorkflowsAndActivities.OrderWorkflow));
workflowHost.AddServiceEndpoint(
"Contracts.IWorkflowInterface", LocalBinding, WFAddress);
try
{
workflowHost.Open();
}
catch (Exception ex)
{
workflowHost.Abort();
MessageBox.Show(String.Format(
"There was an error hosting the workflow as a service: {0}",
ex.Message));
}
});
El siguiente desafío que encontrar es elegir el enlace apropiado para la comunicación local.
Actualmente, no hay ninguna en memoria o en proceso de enlace es extremadamente ligera para estos tipos de escenarios.
La mejor opción para un canal ligero es utilizar el NetNamedPipeBinding con seguridad desactivado.
Desgraciadamente, si intenta utilizar este enlace y el host el flujo de trabajo como un servicio, obtendrá un error que le informa de que el host requiere un enlace con el canal de contexto presente porque su contrato de servicio puede requerir una sesión.
Además, no hay ningún NetNamedPipeContextBinding incluido con .NET Framework, que se suministra con sólo tres de los enlaces de contexto: BasicHttpContextBinding, NetTcpContextBinding y WSHttpContextBinding.
Afortunadamente, puede crear sus propios enlaces personalizados para incluir el canal de contexto.
Figura 4 muestra un enlace personalizado que se deriva de la clase NetNamedPipeBinding y inyecta la ContextBindingElement en el enlace.
Comunicación en ambos sentidos ahora puede utilizar este enlace en el registro de extremo mediante direcciones diferentes.
Figura 4 de NetNamedPipeContextBinding
public class NetNamedPipeContextBinding : NetNamedPipeBinding
{
public NetNamedPipeContextBinding() : base(){}
public NetNamedPipeContextBinding(
NetNamedPipeSecurityMode securityMode):
base(securityMode) {}
public NetNamedPipeContextBinding(string configurationName) :
base(configurationName) {}
public override BindingElementCollection CreateBindingElements()
{
BindingElementCollection baseElements = base.CreateBindingElements();
baseElements.Insert(0, new ContextBindingElement(
ProtectionLevel.EncryptAndSign,
ContextExchangeMechanism.ContextSoapHeader));
return baseElements;
}
}
Con este nuevo enlace, puede crear un extremo en el WorkflowServiceHost y abrir el host con no más errores.
El flujo de trabajo está listo para recibir datos desde el host mediante el contrato de servicio.
Para enviar datos, deberá crear a un proxy e invocar la operación, como se muestra en de figura 5.
Figura 5 de código de host para iniciar un flujo de trabajo
App a = (App)Application.Current;
IWorkflowInterface proxy = new ChannelFactory<IWorkflowInterface>(
a.LocalBinding, a.WFAddress).CreateChannel();
proxy.SubmitOrder(
new Order
{
CustomerName = "Matt",
OrderID = 0,
OrderTotal = 250.00
});
Porque comparte los contratos, no hay ninguna clase de proxy, por lo que tiene que utilizar ChannelFactory < TChannel > para crear al proxy de cliente.
Mientras el flujo de trabajo está alojado y preparado para recibir mensajes, aún necesita configurarse para enviar mensajes a los host.
Más importante, el flujo de trabajo debe ser capaz de obtener un extremo de cliente cuando se utiliza la actividad Send.
La actividad Send le permite especificar el nombre del extremo, que normalmente es una asignación a un extremo con nombre en el archivo de configuración.
Aunque pone la información de extremo en un archivo de configuración funciona, puede utilizar también la ChannelManagerService (tal como se analiza en mi columna de agosto de 2008 en msdn.microsoft.com/magazine/cc721606.aspx de ) para contener los extremos de cliente utilizados por las actividades de envío en el flujo de trabajo.
Figura 6 muestra el código de alojamiento para crear el servicio, proporcionar con un extremo con nombre y agregarlo a WorkflowRuntime alojado en el WorkflowServiceHost.
Figura 6 de Agregar el ChannelManagerService al tiempo de ejecución
ServiceEndpoint endpoint = new ServiceEndpoint
(
ContractDescription.GetContract(typeof(Contracts.IHostInterface)),
LocalBinding, new EndpointAddress(HostAddress)
);
endpoint.Name = "HostEndpoint";
WorkflowRuntime runtime =
workflowHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>().
WorkflowRuntime;
ChannelManagerService chanMan =
new ChannelManagerService(
new List<ServiceEndpoint>
{
endpoint
});
runtime.AddService(chanMan);
El servicio de flujo de trabajo alojado ofrece la capacidad para enviar mensajes desde el host para el flujo de trabajo, pero para volver los mensajes para el host, necesitará un servicio WCF que puede recibir mensajes desde el flujo de trabajo.
Este servicio es un servicio WCF estándar autohospedado en la aplicación.
Dado que el servicio no es un servicio de flujo de trabajo, puede utilizar el estándar NetNamedPipeBinding o reutilizar el NetNamedPipeContextBinding mostrada anteriormente.
Por último, ya que este servicio se invoca desde el flujo de trabajo, puede alojarse en el subproceso de interfaz de usuario, lo facilita la interacción con elementos de la interfaz de usuario.
Figura 7 muestra el código de alojamiento para el servicio.
Figura 7 de alojar el servicio de host
ServiceHost appHost = new ServiceHost(new HostService());
appHost.AddServiceEndpoint("Contracts.IHostInterface",
LocalBinding, HostAddress);
try
{
appHost.Open();
}
catch (Exception ex)
{
appHost.Abort();
MessageBox.Show(String.Format(
"There was an error hosting the local service: {0}",
ex.Message));
}
Con ambos servicios alojados, puede ahora ejecutar el flujo de trabajo, enviar un mensaje y recibir un mensaje de vuelta.
Sin embargo, si intenta enviar un segundo mensaje mediante este código para el segundo recibir actividad en el flujo de trabajo, recibirá un error sobre el contexto.
Correlación de la instancia de control
Una forma de tratar el problema de contexto es utilizar al mismo proxy de cliente para cada invocación del servicio.
Esto habilita el proxy de cliente administrar los identificadores de contexto (mediante el NetNamedPipeContextBinding) y enviarlos al servicio con las solicitudes posteriores.
En algunos escenarios, no es posible mantener al mismo proxy alrededor de todas las solicitudes.
Considere el caso donde iniciar un flujo de trabajo, conservar a una base de datos y cerrar la aplicación cliente.
Cuando la aplicación cliente inicia nuevo, necesita una forma para reanudar el flujo de trabajo enviando otro mensaje a esa instancia específica.
Otro caso de uso común es cuando desea utilizar a un proxy de cliente único, pero que necesita para interactuar con varias instancias de flujo de trabajo, cada uno con un identificador único.
Por ejemplo, la interfaz de usuario proporciona una lista de pedidos, cada uno con un flujo de trabajo correspondiente, y cuando el usuario invoca una acción en un pedido seleccionada, necesita enviar un mensaje a la instancia de flujo de trabajo.
Lo que permite el enlace de administrar el identificador de contexto no funcionará en este escenario porque lo siempre utilizará el identificador del último flujo de trabajo con el que interactuaban.
Para el primer escenario: usar un nuevo proxy para cada llamada, deberá establecer manualmente el identificador del flujo de trabajo en el contexto mediante la interfaz de IContextManager.
IContextManager se tiene acceso mediante el método GetProperty < TProperty > en la interfaz IClientChannel.
Una vez que tenga el IContextManager, puede utilizar para obtener o establecer el contexto.
El contexto de sí mismo es un diccionario de pares de nombre y valor, el más importante es el valor instanceId.
El código siguiente muestra cómo recuperar el identificador desde el contexto de modo que pueda almacenarse por la aplicación de cliente para su posterior, cuando se necesita para interactuar con la misma instancia de flujo de trabajo.
En este ejemplo, se muestra el ID en la interfaz de usuario del cliente en lugar de que se almacena en una base de datos:
IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
string wfID = mgr.GetContext()["instanceId"];
wfIdText.Text = wfID;
Una vez que la primera llamada al servicio de flujo de trabajo, el contexto se rellena automáticamente con el identificador de instancia del flujo de trabajo mediante el enlace de contexto en el extremo de servicio.
Cuando se utiliza a un proxy recién creado para comunicarse con una instancia de flujo de trabajo que se creó anteriormente, puede utilizar un método similar para establecer el identificador en el contexto para asegurarse de su mensaje se enruta a la instancia de flujo de trabajo correcto, como se muestra aquí:
IContextManager mgr = ((IClientChannel)proxy).GetProperty<IContextManager>();
mgr.SetContext(new Dictionary<string, string>{
{"instanceId", wfIdText.Text}
});
Cuando tiene un proxy recién creado, este código funciona bien la primera vez, pero no si se intenta establecer el contexto de una segunda vez para invocar a otra instancia de flujo de trabajo.
El error que obtendrá le indica que no se puede cambiar el contexto cuando está habilitada la administración automática de contexto.
Básicamente, se le indica que no se puede tener el pastel y sitios donde comer demasiado.
Si desea que el contexto se administre automáticamente, no puede manipular manualmente.
Desgraciadamente, si desea administrar manualmente el contexto, no puede obtener la administración automática, lo que significa que no se puede recuperar el identificador de instancia de flujo de trabajo desde el contexto como mostré anteriormente.
Para solucionar esta inconsistencia, controlar por separado cada caso.
Para la llamada inicial a un flujo de trabajo, utilice a un nuevo proxy, pero para todas las llamadas subsiguientes a una instancia de flujo de trabajo existente, utilice a un proxy de cliente único y administrar el contexto manualmente.
Para la llamada inicial, debe utilizar un único ChannelFactory < TChannel > para crear a todos los servidores proxy.
Esto da como resultado un mejor rendimiento porque la creación de ChannelFactory tiene alguna sobrecarga que no desea duplicar para cada llamada primera.
Utilizando código como el que se muestra anteriormente en de la figura 5, puede utilizar un único ChannelFactory < TChannel > para crear al proxy inicial.
En su código de llamada después de utilizar al proxy, debe seguir la práctica de llamar al método Close para liberar al proxy.
Esto es código WCF estándar para crear a su proxy mediante el método de fábrica del canal.
Dado que el enlace es un enlace de contexto, obtendrá administración automática de contexto de forma predeterminada, lo que significa que puede extraer el identificador de instancia de flujo de trabajo desde el contexto después de realizar la primera llamada al flujo de trabajo.
Para las llamadas posteriores, necesita administrar usted mismo el contexto y esto conlleva utilizando código de cliente WCF que no como con frecuencia utilizan los desarrolladores.
Para establecer el contexto manualmente, deberá utilizar un OperationContextScope y crear usted mismo el MessageContextProperty.
El MessageContextProperty está establecida en el mensaje que se envía, que es equivalente a usar el IContextManager para establecer el contexto, con la excepción que mediante la propiedad directamente funciona incluso cuando se deshabilita la administración de contexto.
Figura 8 muestra el código para crear al proxy utilizando el mismo ChannelFactory < TChannel > que se utilizó para el proxy inicial.
La diferencia es que en este caso, se utiliza el IContextManager para deshabilitar la característica de administración automática de contexto y se utiliza un proxy en caché en lugar de crear uno nuevo en cada solicitud.
Figura 8 de deshabilitar la administración automática de contexto
App a = (App)Application.Current;
if (updateProxy == null)
{
if (factory == null)
factory = new ChannelFactory<IWorkflowInterface>(
a.LocalBinding, a.WFAddress);
updateProxy = factory.CreateChannel();
IContextManager mgr =
((IClientChannel)updateProxy).GetProperty<IContextManager>();
mgr.Enabled = false;
((IClientChannel)updateProxy).Open();
}
Una vez creado el proxy, debe crear un OperationContextScope y agregar el MessageContextProperty a las propiedades del mensaje saliente en el ámbito.
Esto permite la propiedad que se incluirán en los mensajes salientes durante la duración del ámbito.
Figura 9 muestra el código para crear y establecer la propiedad de mensaje utilizando el OperationContextScope.
Figura 9 Using OperationContextScope
using (OperationContextScope scope =
new OperationContextScope((IContextChannel)proxy))
{
ContextMessageProperty property = new ContextMessageProperty(
new Dictionary<string, string>
{
{“instanceId”, wfIdText.Text}
});
OperationContext.Current.OutgoingMessageProperties.Add(
"ContextMessageProperty", property);
proxy.UpdateOrder(
new Order
{
CustomerName = "Matt",
OrderID = 2,
OrderTotal = 250.00,
OrderStatus = "Updated"
});
}
Esto puede parecer bastante un poco de trabajo simplemente para hablar entre el host y el flujo de trabajo.
La buena noticia es que gran parte de esta lógica y la administración de identificadores puede encapsularse en unas cuantas clases.
Sin embargo, implica la codificación de su cliente de una manera determinada para garantizar que el contexto se administra correctamente los casos en que necesita enviar más de un mensaje a la instancia de flujo de trabajo.
En la descarga de código de este artículo, he incluido un host de ejemplo para un flujo de trabajo utilizando comunicaciones locales que intenta encapsulan gran parte de la complejidad y la aplicación de ejemplo muestra cómo utilizar el host.
Un Word acerca de la interacción de interfaz de usuario
Una de las razones principales que se envían datos desde el flujo de trabajo para el host es que desea presentar al usuario en la interfaz de aplicación.
Afortunadamente, con este modelo tiene algunas opciones para aprovechar las características de interfaz de usuario, incluido el enlace de datos en WPF.
Por ejemplo, si desea que la interfaz de usuario para utilizar el enlace de datos y actualizar la interfaz de usuario cuando se reciben datos desde el flujo de trabajo, puede enlazar la interfaz de usuario directamente con servicio de instancia del host.
La clave para utilizar la instancia de servicio como el contexto de datos de la ventana es que la instancia debe estar alojado como un singleton.
Al que el servicio de host como un singleton, tener acceso a la instancia y puede utilizar en su interfaz de usuario.
El servicio de host simple mostrado en de figura 10 actualiza una propiedad cuando recibe información desde el flujo de trabajo y utiliza el INotifyPropertyChangedInterface para ayudar a la infraestructura de enlace de datos que recoja los cambios inmediatamente.
Observe el atributo ServiceBehavior que indica que esta clase debe estar alojada como un singleton.
Si observa volver a de figura 7, puede ver el ServiceHost crea una instancia no con un tipo pero con una instancia de la clase.
Figura 10 de implementación de servicio con INotifyPropertyChanged
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
internal class HostService : IHostInterface, INotifyPropertyChanged
{
public void OrderStatusChange(Order order, string newStatus,
string oldStatus)
{
CurrentMessage = String.Format("Order status changed to {0}",
newStatus);
}
private string msg;
public string CurrentMessage {
get { return msg; }
set
{
msg = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(
"CurrentMessage"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Enlazar datos a este valor, el DataContext de la ventana o un control determinado en la ventana, se puede establecer con la instancia.
La instancia se puede recuperar mediante la propiedad SingletonInstance en la clase ServiceHost, tal como se muestra aquí:
HostService host = ((App)Application.Current).appHost.SingletonInstance as HostService;
if (host != null)
this.DataContext = host;
Ahora puede simplemente enlazar elementos en la ventana a las propiedades del objeto, como se muestra con este TextBlock:
<TextBlock Text="{Binding CurrentMessage}" Grid.Row="3" />
Como dije anteriormente, esto es un ejemplo sencillo de lo que puede hacer.
En una aplicación real, es probable que lo haría no enlazar directamente a la instancia de servicio pero en su lugar enlace a algunos objetos para que la ventana y la implementación del servicio ha tenido acceso.
Buscando Ahead para WF4
WF4 presenta varias características que simplifican las comunicaciones locales sobre WCF incluso.
La característica principal es la correlación de mensajes que no se basa en el protocolo.
Es decir, el uso de un identificador de instancia de flujo de trabajo continuará siendo una opción, pero una nueva opción permite a los mensajes se correlacionar en función del contenido del mensaje.
Por tanto, si cada uno de los mensajes contiene un ID de pedido, un ID de cliente o alguna otra parte de datos, puede definir las correlaciones entre esos mensajes y no tiene que utilizar un enlace que admita la administración de contexto.
Además, el hecho de que WPF y WF generación en el mismo núcleo API XAML en .NET Framework versión 4 se podría abrir algunos interesantes posibilidades para integrar las tecnologías de nuevas formas.
Tal como nos acercamos a la versión de .NET Framework 4, proporcionaré más detalles acerca de la integración de WF con WCF y WPF, junto con otro contenido en el funcionamiento interno de WF4.
Matt Milner es un miembro del personal técnico de Pluralsight, donde principalmente se dedica a las tecnologías de sistemas conectados (WCF, Windows Workflow Foundation, BizTalk, “ Dublin ” y la plataforma de servicios de Azure).
Matt también es un consultor independiente especializado en diseño de la aplicación de Microsoft .NET y desarrollo.
Con regularidad comparte su amor de tecnología hablando en conferencias locales, regionales e internacionales, como Tech·Ed.
Microsoft ha reconocido Milner como MVP para su contribución comunidad alrededor de tecnología de sistemas conectados.
Puede ponerse en contacto con él a través de su blog en pluralsight.com/community/blogs/matt de.
|
|
Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.
|