I am working with WCF to exchange messages with a third party company. The messages n开发者_如何学Pythoneed to be sent and received in an envelope that matches the ebXML specification. Ideally I would like to use as much of the WCF stack as possible and avoid the one method to process them all approach as in this case that would mean writing much of the infrastructure of WCF again.
As far as I can see from my initial research this would require me to write my own custom binding but I am struggling to find clarity in the documentation in MSDN.
I have been able to find lots of detailed documents about in individual implementations of each of these but very little about how to put it all together end to end. It would appear that the books I have are similarly light on these topics too with no mention of this in "Pro WCF" by Peiris and Mulder.
What I am aiming for is something like the following.
The messages being sent and received MUST be formatted like below where the name of the first element is the name of the operation that is to be executed and the child element is the payload of the request message would be of the form:
<?xml version="1.0" encoding="UTF-8"?>
<op:DoSomething xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com">
<op:AnObject>
<payload:ImportantValue>42</payload:ImportantValue>
</op:AnObject>
</op:DoSomething>
And the response would be:
<?xml version="1.0" encoding="UTF-8"?>
<op:AcknowledgementResponse xmlns:op="http://my.ebXML.schema.com" xmlns:payload="http://payload.company.com">
<op:ResponseObject>
<payload:Ok>True</payload:Ok>
</op:ResponseObject>
</op:AcknowledgementResponse>
As the messages are all described by XML schemas I have used XSD.exe to convert these to strongly typed objects. See https://gist.github.com/740303 for the schemas. Note that these are example schemas. I am not able to post the real schemas without breaching client confidentiality agreements (nor would you want me too as they are huge).
I would now like to be able to write the service implementation as follows:
public class MyEndpoint : IMyEndpoint
{
public AcknowledgementResponse DoSomething(AnObject value)
{
return new AcknowledgementResponse
{
Ok = True;
};
}
}
Any help would be much appreciated.
Details of my implementation of Tim's answer
I needed to write this up for the client I am currently working for so I thought I might as well post it up here too. I hope it helps someone. I've created a sample client and service that I used to try out some of these ideas. I've cleaned it up and added it to github. You can download it here.
The following things needed to be implemented to allow WCF to be used in the way that I required:
- WCF to not expect a SOAP message
- The ability to format the request and response messages exactly as required
- All messages to be considered for processing
- Incoming messages to be routed to the correct operation to handle them
1. Configure WCF to not expect a SOAP message
The first step was getting the incoming message through the TextMessageEncoder. This was achieved by using a custom binding with the MessageVersion.None setting on the textMessageEncoding element.
<customBinding>
<binding name="poxMessageBinding">
<textMessageEncoding messageVersion="None" />
<httpTransport />
</binding>
</customBinding>
2. Format the Message Correctly
The message formatter is required as the incoming message refused to be de-serialized by the existing XML formatter without the addition of additional attributes on the message contracts. This wouldn't normally be a problem but running my clients ebXML schemas through XSD.exe generates a 33000 line cs file and I didn't want to have to modify this in any way. Besides people would forget to re-add the attributes in future so this hopefully makes maintenance easier too.
The custom formatter expects to convert the incoming message to the type of the first parameter and to convert the return type into a response message. It inspects the implementing method to determine the types of the first parameter and return value in the constructor.
public SimpleXmlFormatter(OperationDescription operationDescription)
{
// Get the request message type
var parameters = operationDescription.SyncMethod.GetParameters();
if (parameters.Length != 1)
throw new InvalidDataContractException(
"The SimpleXmlFormatter will only work with a single parameter for an operation which is the type of the incoming message contract.");
_requestMessageType = parameters[0].ParameterType;
// Get the response message type
_responseMessageType = operationDescription.SyncMethod.ReturnType;
}
It then simply uses the XmlSerializer to serialize and deserialize the data. An interesting part of this for me was the use of a custom BodyWriter to serialize the object into the Message object. Below is part of the implementation for the service serializer. The client implementation is the reverse.
public void DeserializeRequest(Message message, object[] parameters)
{
var serializer = new XmlSerializer(_requestMessageType);
parameters[0] = serializer.Deserialize(message.GetReaderAtBodyContents());
}
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
return Message.CreateMessage(MessageVersion.None, _responseMessageType.Name,
new SerializingBodyWriter(_responseMessageType, result));
}
private class SerializingBodyWriter : BodyWriter
{
private readonly Type _typeToSerialize;
private readonly object _objectToEncode;
public SerializingBodyWriter(Type typeToSerialize, object objectToEncode) : base(false)
{
_typeToSerialize = typeToSerialize;
_objectToEncode = objectToEncode;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteStartDocument();
var serializer = new XmlSerializer(_typeToSerialize);
serializer.Serialize(writer, _objectToEncode);
writer.WriteEndDocument();
}
}
3. Process All Incoming Messages
In order to instruct WCF to allow all incoming messages to be processed I needed to set the ContractFilter property on the endpointDispatcher to an instance of the MatchAllMessageFilter. Here is a snippet illustrating this from my endpoint behaviour configuration.
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
endpointDispatcher.ContractFilter = new MatchAllMessageFilter();
// Do more config ...
}
4. Selecting The Correct Operation
This was achieved by creating a class that implements IDispatchOperationSelector. In the SelectOperation method I inspect the incoming message. Here I'm looking for two things: 1. A sanity check that the namespace of the root element is the same as the namespace of the service contract 2. The name of the root element (note the use of LocalName to remove any namespace prefix)
The name of the root element is the return value and that will map to any operation on the contract with a matching name or where the action attribute has a matching value.
public string SelectOperation(ref Message message)
{
var messageBuffer = message.CreateBufferedCopy(16384);
// Determine the name of the root node of the message
using (var copyMessage = messageBuffer.CreateMessage())
using (var reader = copyMessage.GetReaderAtBodyContents())
{
// Move to the first element
reader.MoveToContent();
if (reader.NamespaceURI != _namespace)
throw new InvalidOperationException(
"The namespace of the incoming message does not match the namespace of the endpoint contract.");
// The root element name is the operation name
var action = reader.LocalName;
// Reset the message for subsequent processing
message = messageBuffer.CreateMessage();
// Return the name of the action to execute
return action;
}
}
Wrapping It All Up
To make it easier to deploy I created an endpoint behaviour to handle the configuration of the message formatter, contract filter and operation selector. I could also have created a binding to wrap up the custom binding configuration but I didn't think that part was too difficult to remember.
One interesting discovery for me was that the endpoint behaviour can set the messages formatter for all the operations in the endpoint to use a custom message formatter. This saves the need to configure these separately. I picked this up from one of the Microsoft samples.
Helpful Documentation Links
The best references I've found so far are the Service Station MSDN magazine articles (Google "site:msdn.microsoft.com service station WCF").
WCF Bindings in Depth - Very useful information on configuring bindings
Extending WCF with Custom Behaviours - The best source of information on dispatcher integration points that I have yet found and they contain some really useful diagrams that illustrate all the integration points and where they occur in the order of processing.
Microsoft WCF samples - There is a lot in here that isn't documented very well elsewhere. I found reading through the source code for some of these very instructive.
I don't think that you need to do anything with bindings. I'm assuming that you need to send the ebXML formatted message over HTTP anyway?
@ladislav's answer is one approach, but I think message encoders are designed to work at a much lower level than what you're trying to achieve. They're essentially the pieces that encode the messages to and from the underlying stream (i.e., how the message is represented as bytes on the stream).
I think what you need to do is implement a custom Message Formatter. In particular, since you say that you want to submit the messages to a third-party, then I think it's only the IClientMessageFormatter
interface that you'll need to implement. The other interface (IDispatchMessageFormatter
) is used on the server side.
You'll also need to implement an appropriate ServiceBehavior and OperationBehavior to install the formatter into the stack, but the code for this will be minimal (the bulk of the code will be in implementing the above mentioned interface).
Once implemented, you could use the "one method to process them all" approach to test and debug your formatter. Simply take the received message and dump it out to the console for you to review, and then also send an ebXML response back. You could also use the same approach to build up your unit-testing.
For custom message format you need Custom MessageEncoder. MSDN contains example how to create custom encoder. If you use Reflector you will find several encoder implementations so you can learn how to write it.
You can also check what will happen if you try to use TextMessageEncoder with MessageVersion.None (I have never tryed it).
精彩评论