Windows Communication Foundation (WCF) enables non-connection communication between clients and services. The client publishes messages to the queue, and the service will process these messages later. This interaction mode creates a programming model different from the default request/response mode, which is expected to better balance the load, improve availability, and make compensation, bringing many benefits to users. This topic briefly introduces windowsThe queuing call function of the Communication Foundation raises an interesting question about how to obtain results from queuing calls, then I found a solution through some cool windows Communication Foundation programming technology and the helper classes I wrote for it.
Queuing call
Windows Communication Foundation uses netmsmqbinding to support queuing calls. Windows Communication Foundation transmits messages through Microsoft instead of TCP or HTTP.Message Queue (MSMQ ). The client does not send windows Communication Foundation messages to an online service, but to the MSMQ queue. All clients face and interact with queues rather than service endpoints. Therefore, the call is asynchronous in nature and is not connected. These calls will not be executed until the service processes messages at a certain time point in the future.
Note that Windows Communication Foundation messages are not directly mapped to MSMQ messages. An MSMQ message can contain one or more windows Communication Foundation messages, depending on the contract session mode. For the required session mode, multiple Windows Communication Foundation calls can coexist in one MSMQ message. For the allow or disallow session mode (used by monotonous and Singleton services ), each windows Communication Foundation call is located in a separate MSMQ message.
The client interacts with the proxy, just like the windows Communication Foundation services, as shown in figureFigure 1. Because the proxy has been configured to bind with MSMQ, the proxy does not send windows Communication Foundation messages to any specific service, but instead converts the call to MSMQ messages, then, publish these messages to the queue specified by the endpoint address.
Figure 1 Architecture of WCF queuing call
On the server side, when a service host with a queuing endpoint is started, the host installs a queue listener.Program. The queue listener detects and queues messages in the queue, and then creates a listener chain with the scheduling program as the destination. The scheduler calls the service instance as usual. If the client publishes multiple messages to the queue, the listener will create a new instance as the message leaves the queue and end with asynchronous and non-connection concurrent calls.
If the host is offline, the message remains to be processed in the queue. When the next host goes online, the message will be forwarded to the service.
A queue-oriented call that may be in a non-connection status cannot return any value because no service logic is called when the message is scheduled to the queue. In addition, the call may be scheduled to the service after the client application stops running, and the client cannot process the returned value at all. Similarly, the call cannot return any server exception to the client, and no client can capture or handle the exception. The client will not be blocked because of calling operations. More specifically, the client will only be blocked when messages are sent to the queue for a moment. Therefore, from the client's perspective, queuing is essentially an asynchronous call. These are typical features of one-way calls. Therefore, any contract provided by the endpoint using netmsmqbinding can only have one-way operation. The Windows Communication Foundation verifies this when loading services and Proxies:
// You can only call [servicecontract] interface imycontract {[operationcontract (isoneway = true)] void mymethod ();}
As the interaction with MSMQ is encapsulated in the bindingCodeOr the client Call Code does not contain any content related to the call queue. The Service Code and client code look the same as any other Windows Communication Foundation client code and Service Code, as shown in 2.
When defining an endpoint for a Queuing Service, the endpoint address must contain the queue name and queue type (public or private ):
<Endpointaddress = "net. MSMQ: // localhost/private/myservicequeue" binding = "netmsmqbinding".../>
Finally, MSMQ is the transaction Resource Manager of Windows Communication Foundation. If the queue is transactional, the messages published by the client will be rolled back when the client's transaction is aborted. On the server, a new transaction is started when a message is read from the queue. If the Service participates in and terminates the transaction (which may be caused by exceptions), the message will be rolled back to the queue and wait for the next retry. The Windows Communication Foundation provides comprehensive Fault Detection and virus message processing support functions. This topic does not cover this topic.
Response Service
So far, the queuing call programming model we have introduced is one-sided: the client publishes a one-way message to the queue, and then the service processes the message. If the queued operation is actually a one-way call, this model is sufficient to meet the requirements. However, queuing services sometimes need to report the call results, returned results, or even errors to their clients in turn. However, this cannot be implemented by default. Windows Communication Foundation equate queuing calls with one-way calls, and one-way calls prohibit any such responses in essence. In addition, the Queuing Service (and its clients) may not be connected. If the client publishes a queue call to the unconnected service, when the service finally obtains and processes these messages, no client may receive the value because the client may already be offline. The solution to this problem is to allow the Service to return the Report to the Queuing service provided by the client. I call such a service a response service.Figure 3Shows the architecture of such solutions.
Figure 3 Response Service
The response service is another Queuing Service in the system. It may also be disconnected from the client and managed by a separate process or computer, or it may share the client process. If the response service shares the client process, the response service starts to process the queued response when the client starts. Hosting a response service by a client-independent process (or even a computer) further isolates the lifetime of the response service from the client that uses the response service.
Design response service contract
Just like using any windows Communication Foundation Service, clients and services need to pre-agree on a response contract and its intended objects (for example, returned values and error messages, or only returned values ). Please note that you can also split the response service into two services, one for the response result and the other for the response error. For example, assume that the following icalculator contract is implemented by queuing mycalculator services:
[Servicecontract] interface icalculator {[operationcontract (isoneway = true)] void add (INT number1, int number2 );... // more operations} [servicebehavior (instancecontextmode = instancecontextmode. percall)] class mycalculator: icalculator {...}
The mycalculator service is required to respond to the client with the computing result and report all errors. The calculation result is in integer format, and the error is represented in Windows Communication Foundation exceptiondetail data contract format. For response services, icalculatorresponse contracts can be defined as follows:
[Servicecontract] interface icalculatorresponse {[operationcontract (isoneway = true)] void onaddcompleted (INT result, exceptiondetail error );}
The response service that supports icalculatorresponse needs to check the returned error information, notify the client application, user, or application administrator at the end of the method, and provide the results to relevant parties. The following is a simple response service that supports icalculatorresponse:
[Servicebehavior (instancecontextmode = instancecontextmode. percall)] class mycalculatorresponse: icalculatorresponse {[operationbehavior (transactionscoperequired = true)] public void onaddcompleted (INT result, exceptiondetail error) {MessageBox. show ("result =" + result, "mycalculatorresponse"); If (error! = NULL) {// handle error }}}
Implementation of mycalculator and mycalculatorresponse will directly lead to two problems. The first problem is that the same response service may be used to process the response (or complete) of multiple calls on Multiple Queuing services, while mycalculatorresponse (more importantly, the client of the Service) these responses cannot be differentiated. The solution to this problem is to assign a unique ID to the call by the client sending the original queue call as a flag. The Queuing Service mycalculator needs to pass this ID to mycalculatorresponse so that it can apply a custom logic related to this ID.
The second problem is how the Queuing Service finds the address of the response service. Unlike two-way callback, the Windows Communication Foundation does not support passing response service references to the service. It is not wise to put the address in the server host configuration file (in the client Section), because the same Queuing service may be called by multiple clients, each client has its own dedicated response service and address.
One possible solution is to explicitly pass the ID managed by the client and the required response service address as parameters to each operation based on the Queuing service contract:
[Servicecontract] interface icalculator {[operationcontract (isoneway = true)] void add (INT number1, int number2, string responseaddress, string methodid );}
Similarly, the Queuing Service can explicitly pass the method ID of the response service as a parameter to each operation based on the queuing response contract:
[Servicecontract] interface icalculatorresponse {[operationcontract (isoneway = true)] void onaddcompleted (INT result, exceptiondetail error, string methodid );}
Use a Message Header
Although passing addresses and IDS as explicit parameters can solve the above problems, this will change the original contract, in addition, pipeline-level parameters are introduced in the same operation on the basis of business-level parameters. Therefore, a better solution is to store the response address and operation ID in the outgoing message header of the call. This method is a common technique used to transmit out-of-band information (information that appears in the service contract only in this way) to the service.
The operation context provides a set of incoming and outgoing headers. These sets can be obtained through the incomingmessageheaders and outgoingmessageheaders attributes:
Public sealed class operationcontext:... {public messageheaders incomingmessageheaders {Get;} public messageheaders outgoingmessageheaders {Get;}... // more Members}
Each set is of the messageheaders type, representing the set of messageheader objects:
Public sealed class messageheaders: ienumerable <...> ,... {public void add (messageheader header); Public t getheader <t> (INT index); Public t getheader <t> (string name, string NS );... // more Members}
Instead of using the messageheader class to directly interact with application developers, you should use the class security that can be provided and easily integrate the Common Language Runtime (CLR) type conversion to messageheader <t> class:
Public abstract class messageheader :... {...} public class messageheader <t> {public messageheader (); Public messageheader (T content); Public t content {Get; set;} public messageheader getuntypedheader (string name, string NS );... // more Members}
For messageheader <t> type parameters, you can use any serializable type or data contract type. You can construct messageheader <t> around the CLR type, and then use the getuntypedheader method to convert it to messageheader and store it in the outgoing message header. Getuntypedheader requires that you provide the names and namespaces of common type parameters. The name and namespace are used to find the header from the header set. You can use the getheader <t> method of messageheaders to perform the search operation. Call getheader to obtain the value of the messageheader type parameter.
Responsecontext class
Because the client needs to pass the address and method ID in the message header, only one primitive type parameter cannot meet the requirements. Instead, use the responsecontext class (4, some error handling code is omitted ).
Responsecontext provides a location for storing the response address and ID. In addition, if the client needs to use a separate error response service, responsecontext also provides a field to store the error response service address. (Figure 4This is not reflected. For the complete code, see the download content of this issue .)
The client is responsible for constructing a responsecontext instance with a unique ID. Although the client can provide this ID as a constructor, it can also use the constructor of responsecontext (only the response address parameter is accepted) and ask the constructor to generate a guid for this ID. An important attribute of responsecontext is the static current attribute. It completely encapsulates the interaction with the message header. By accessing the current attribute, you get an ideal Programming Model: The get accesser reads the responsecontext instance from the incoming message header, and the Set accesser stores the responsecontext instance in the outgoing header.
Client Programming
The client can provide an ID for each method call by using different responsecontext instances for each call. The client needs to store the responsecontext instance in the outgoing message header. In addition, the client must perform the operation in the context of the new operation, instead of using its existing operation context. This requirement applies to both services and non-service clients. The Windows Communication Foundation enables the client to adopt a new operation context for the current thread through the operationcontextscope class defined below:
Public sealed class operationcontextscope: idisposable {public operationcontextscope (icontextchannel channel); Public operationcontextscope (operationcontext context); Public void dispose ();}
Operationcontextscope is a conventional technique for converting new contexts when the existing context is not appropriate. Operationcontextscope constructor replaces the operation context of the current thread with the new context. Call dispose on the operationcontextscope instance to restore the original context (even if it is empty ). If dispose is not called, it may destroy other objects in the same thread that need to use the previous context. Therefore, operationcontextscope is dedicated to using statements and only provides a new operation context (even if an exception occurs) for a code scope ).
When constructing a new operationcontextscope instance, it should provide its constructor with an internal channel for calling the proxy used. The client needs to create a new operationcontextscope and assign it to responsecontext. Current Within the scope. Step 5.
Encapsulation is required to simplify and automate the work of the client.Figure 5Shows the Proxy Base class for the response service setting step. Unlike two-way callback, Windows Communication Foundation does not provide such a proxy class, so you must create a proxy class manually. To simplify this task, I wrote the responseclientbase defined in 6.
To use responseclientbase <t>, you must derive a specific class from it and provide the queuing contract type for the type parameter. Unlike the normal proxy, instead of deriving subclass from the contract, a set of similar methods should be provided. These methods will return the string of the method ID, these strings cannot be void. (This is why you cannot derive a subclass from the contract, because contract-based operations do not return any content, and all operations are unidirectional ). For example, Figure 7 shows the corresponding proxy process for identifiable response services using this Queuing service contract:
[Servicecontract] interface icalculator {[operationcontract (isoneway = true)] void add (INT number1, int number2);... // more operations}
After using responseclientbase <t>,Figure 5The code shown in is simplified:
String responseaddress = "net. MSMQ: // localhost/private/mycalculatorresponsequeue "; calculatorclient proxy = new calculatorclient (responseaddress); string methodid = proxy. add (2, 3); proxy. close ();
The virtual method generatemethodid of responseclientbase <t> uses the guid of the method id. The subclass of responseclientbase <t> can overwrite it and provide any other unique string, such as an increasing integer.
The constructor of responseclientbase <t> accepts the response address and common proxy parameters, such as the endpoint name, address, and binding. The constructor stores the response address in a read-only public field. Responseclientbase <t> is derived from conventional clientbase <t>. Therefore, all constructors are delegated to their basic constructors.
The core of responseclientbase <t> is the enqueue method. Enqueue will accept the operation name and operation parameters to call (actually arrange messages), create a new operation context scope, and generate a new method ID, and store the ID and response address in responsecontext. It assigns responsecontext to the outgoing message header by setting responsecontext. Current, and then uses reflection to call the provided operation name. Responseclientbase <t> does not support contract hierarchies or reload operations because reflection and later binding are used. In these cases, manual encoding is required, suchFigure 5.
Server programming
The Queuing Service accesses the incoming message header Through responsecontext. Current, and reads the response address and method ID from it. The service needs to use this address to construct the response service proxy, and the ID must be provided to the response service. The Service generally does not directly use the ID.
The service can use the same technology as the client to pass the method ID to the response service: Use the outgoing message header to send the ID to the response service, without using explicit parameters. Like the client, the Service must use the new operation context through operationcontextscope to modify the outgoing header set. The service can programmatically construct the response service proxy and provide the response address and the netmsmqbinding instance to the proxy. The service can even read binding settings from the configuration file. Step 8.
The Service will capture all exceptions caused by business logic operations, and use the exceptiondetail object to wrap each exception. The Service will not trigger an exception again, because the exception will abort the transaction used to release the response message into the response service queue, and the response will be canceled if the exception is thrown again.
In a finally statement, the service responds whether an exception occurs or not. It uses the address from the response context and the new instance of netmsmqbinding to construct the response service proxy. The Service uses the internal channel of the proxy to sow the new operationcontextscope. In the new scope, the service will set response. the same response context received is added to the outgoing header of the new environment, and then the response service proxy is called. In fact, the response is put into the queue. Then, the Service releases the context scope and closes the proxy.
To simplify and automate the work of the Service to extract response parameters from the header, I created a responsescope <t> class:
Public class responsescope <t>: idisposable where T: Class {public readonly t response; Public responsescope (); Public responsescope (string bindingconfiguration); Public responsescope (netmsmqbinding binding ); public void dispose ();}
Responsescope <t> is a releasable object. It will install a new operation context. When it is released, the scope will be restored to the original operation context. Responsescope <t> should be used in the using statement for automation (even if exceptions occur.
Responsescope <t> accepts type parameters that represent the response contract and provides read-only public fields of the same type for response. Response is the proxy of the response service. The client of responsescope <t> uses response to call the operation in the response service. Response does not need to be released because responsescope <t> will do so in dispose. Responsescope <t> instantiates response based on the default endpoint in the configuration file or the binding information provided to the constructor (configure the node name or actually bound instance. Figure 9 shows the implementation of responsescope <t>.
After using responsescope <t>,Figure 8The finally statement is simplified:
Finally {using (responsescope <icalculatorresponse> scope = new responsescope <icalculatorresponse> () {scope. response. onaddcompleted (result, error );}}
The responsescope <t> constructor uses responsecontext. Current to extract the incoming response to the downstream document. Then, use the channel factory to initialize response through the Response Service proxy. The key to implementing responsescope <t> is not to use operationcontextscope in using statements. Responsescope <t> creates a new operation context by constructing a new operationcontextscope. Responsescope <t> encapsulates operationcontextscope and saves the newly created operationcontextscope. After responsescope <t> is released in the using statement, the original context is restored. Then, responsescope <t> will use responsecontext. Current to add the response context to the outgoing header of the new operation context, and then return.
Response Service Programming
The response service accesses the set of incoming message headers, reads method IDs from them, and responds accordingly. Figure 10 shows a possible implementation of such a response service.
The response service uses responsecontext. Current to access the operation context to pass in the header. Like the Queuing service, it extracts the method ID and processes the operation completion or error.
Conclusion
The Queuing service is applicable to multiple application environments. If your application environment requires the service to report operation completion, errors, and results to the client, you can use the helper class provided by this column to extend the basic functions of the Windows Communication Foundation, they added this unsupported feature through one or two lines of code.
This topic describes the Windows Communication Foundation technology (for example, interaction with message headers, replacement of Operation context, compiling of Custom agent base classes, and custom context) it is also useful in many other cases, especially for custom frameworks.