Single WCF Generic Endpoint for One-Way and Request-Reply Call Forwarding
Join the DZone community and get the full member experience.
Join For FreeImplementing a WCF forwarding router consists of providing an endpoint with a generic contract which accepts untyped messages. These messages can then be forwarded to another endpoint, which can in turn use a specific typed or a generic untyped contract.
An example of this scenario would be a bus (or router) which coordinates multiple services. These services might expose specific contracts, such as IOrderService, IShippingService, IPaymentService and many others. The intermediary in this case can expose a generic untyped contract to the clients, who will be none the wiser: They can still use the specific contract and communicate to the untyped intermediary which will perform the forwarding to the requested service. The intermediary doesn't have to be aware of all the different service contracts: It can communicate to the services through the generic contract. This is best (or my best) illustrated by the following diagram:
[img_assist|nid=3466|title=|desc=|link=none|align=none|width=521|height=330]
One of the challenges with implementing such an endpoint is that request-reply and one-way calls must be treated differently. A typical generic contract for the request-reply message exchange pattern will look like this:
[ServiceContract]
internal interface IGenericContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message Action(Message message);
}
On the other hand, the same generic contract for the one-way MEP will look like this:
[ServiceContract]
internal interface IOneWayGenericContract
{
[OperationContract(Action = "*", IsOneWay = true)]
void Action(Message message);
}
Since these are two contracts, we can't trivially expose them on the same endpoint. This is annoying because it means the client now has to use a different endpoint (or Via address) for R-R and one-way message exchanges. Furthermore, since a single contract might contain both R-R and one-way operations, it's highly inconvenient for a client to use different endpoints for different operations on the same contract.
A naive attempt to remedy this might involve trying to mix the two MEPs in the same generic contract, like so:
[ServiceContract]
internal interface ICombinedGenericContract
{
[OperationContract(Action = "*", ReplyAction = "*")]
Message Action(Message message);
[OperationContract(Action = "*", IsOneWay = true)]
void OneWayAction(Message message);
}
However, this will not work because at service load-time, the contract will fail to validate: It is exposing two different operations with the Action set to *, so WCF has no means of automatically choosing one of them.
What we need to do is to give WCF the ability to disambiguate the two operations. This means we are not going to use Action="*", but instead provide an IDispatchOperationSelector which will be in charge of choosing the appropriate routing method. The interface will look like this (note the absence of Action="*" in the operation contracts):
[ServiceContract]
internal interface IFinalGenericContract
{
[OperationContract(ReplyAction = "*")]
Message Action(Message message);
[OperationContract(IsOneWay = true)]
void OneWayAction(Message message);
}
The operation selector will have to know whether the incoming message is targeted at a one-way operation or not. This can be accomplished in several ways - for example, if we know the set of specific target contracts, then we can enumerate their contract descriptions at load time and build a cache of which operations are one-way. Alternatively, we can just check if the incoming message has a ReplyTo SOAP element. If it doesn't, it's a one-way call.
The following is a simple implementation of IDispatchOperationSelector which also implements IEndpointBehavior to install itself on the relevant dispatch runtimes. (It's also possible to make it a service behavior and specify it on the service itself.)
sealed class GenericDispatchOperationSelector : IDispatchOperationSelector, IEndpointBehavior { #region IEndpointBehavior Members public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { endpointDispatcher.ContractFilter = new MatchAllMessageFilter(); DispatchRuntime runtime = endpointDispatcher.DispatchRuntime; runtime.OperationSelector = this; } public void Validate(ServiceEndpoint endpoint) { } #endregion #region IDispatchOperationSelector Members public string SelectOperation(ref Message message) { string replyTo = message.Headers.ReplyTo; if (String.IsNullOrEmpty(replyTo)) return "OneWayAction"; //else return "Action"; } #endregion }
With this in place, we can move on to the implementation of the intermediary itself. We will need to install the endpoint behavior on the host's endpoint which exposes the generic contract. For example:
ServiceHost host = new ServiceHost(typeof(Intermediary));
ServiceEndpoint endpoint = host.AddServiceEndpoint(typeof(IFinalGenericContract), ...);
endpoint.Behaviors.Add(new GenericDispatchOperationSelector());
host.Open();
The final bit that needs to fall in its place is the forwarding logic. It might appear as if we can use the IFinalGenericContract devised above to communicate with one-way or R-R services alike. However, this isn't the case! If we use this generic contract, we effectively forward the need to use an operation selector and impose it on the services, which is not what we need. Instead, we need a pair of interfaces - the IGenericContract and IOneWayGenericContract outlined above, one for each MEP:
internal sealed class Intermediary : IFinalGenericContract
{
#region IFinalGenericContract Members
public Message Action(Message message)
{
//Request-reply case:
IGenericContract proxy = ...;
return proxy.Action(message);
}
public void OneWayAction(Message message)
{
//One-way case:
IOneWayGenericContract proxy = ...;
proxy.Action(message);
}
#endregion
}
This is all we need to implement forwarding logic in one place for the one-way and R-R patterns. The client code is exactly the same and the same endpoint can be used for both MEPs. Extending this sample to support the duplex MEP is left as an exercise for the reader.
Opinions expressed by DZone contributors are their own.
Trending
-
Java String Templates Today
-
Microservices Architecture: Advantages of Microservices
-
Beginner Intro to Real-Time Debugging for Mobile Apps: Tools and Techniques
-
Building Resilient Systems With Chaos Engineering
Comments