Does MsmqIntegrationBinding support multiple service operations

389 views Asked by At

I'm learning about MsmqIntegrationBinding. All the samples and guidelines I've seen so far were covering scenario where, there is just one operation with one data contract. I tried to add another contract and the service started successfully. However I cannot figure out how to reach the second operation. Is such thing even possible with this binding?

[ServiceContract]
[ServiceKnownType(typeof(Data1))]
[ServiceKnownType(typeof(Data2))]
public interface ISampleService
{
    [OperationContract(IsOneWay = true, Action = "*")]
    void Operation1(MsmqMessage<Data1> msg);

    [OperationContract(IsOneWay = true)]
    void Operation2(MsmqMessage<Data2> msg);
}

public class SampleService : ISampleService
{
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public void Operation1(MsmqMessage<Data1> msg)
    {
        var data = msg.Body;
    }

    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public void Operation2(MsmqMessage<Data2> msg)
    {
        var data = msg.Body;
    }
}

Calling code

var queue = new MessageQueue(@".\private$\samplequeue");
var body = new Data1() { Data = "some data" };
var message = new Message(body);
message.Label = "some label";
queue.Send(body, MessageQueueTransactionType.Single);

This will fire the Operation1 which has Action set to "*".

2

There are 2 answers

2
tom redfern On BEST ANSWER

This is a very interesting question.

The Action OperationContractAttribute is normally used by the WCF stack to populate the WS-Addressing soap headers. It's use is obviously overridden in some way by the queued bindings.

It is possible that there is an undocumented feature of WCF which allows for some mapping of msmq message headers to the operation based on the Action attribute acting like a filter, but if there is I don't know what form it would take.

I think the simplest explanation is: no, it's not possible, and the reason for that is that the msmqIntegrationBinding is exactly what it says on the tin: it's about interop over functionality.

Because you are forced to call the operation with a MsmqMessage wrapper it kind of makes this binding semantically one-dimensional, and this lends to my theory that it is intended to wrap a single endpoint operation to support interop with legacy COM and ActiveX clients.

Anyhow, there's no law saying that a binding must support multiple operations, just like certain bindings don't support callbacks, and certain others only one-way operations.

Appreciate this doesn't answer your question directly.

0
AliC On

Not sure this is an answer and I don't have the required 50 reputation to comment.

Taking inspiration from this answer: https://stackoverflow.com/a/33154517/1095296 we're doing the following.

[ServiceContract]
public interface IMSMQueueHandler
{
    [OperationContract(IsOneWay = true, Action = "*")]
    void Handle(MsmqMessage<object> message);
}

Then we have a constructor on a class wrapping the service host

    public MSMQueueServiceHost(IMSMQConfig msmqConfig, IMSMQueueHandler handler)
    {
        _hostService = new ServiceHost(handler);

        AddHostServiceEndPoint(msmqConfig);

        _hostService.Open();
    }

    private void AddHostServiceEndPoint(IMSMQConfig msmqConfig)
    {
        ServiceMetadataBehavior smb = new ServiceMetadataBehavior { HttpGetEnabled = false };
        _hostService.Description.Behaviors.Add(smb);

        MsmqIntegrationBinding binding = new MsmqIntegrationBinding(MsmqIntegrationSecurityMode.None);
        binding.SerializationFormat = MsmqMessageSerializationFormat.Stream;
        binding.ReceiveErrorHandling = ReceiveErrorHandling.Move;

        ServiceEndpoint endpoint = _hostService.AddServiceEndpoint(
            typeof(IMSMQueueHandler),
            binding, 
            string.Format("msmq.formatname:DIRECT=OS:{0}", msmqConfig.MsmqPath));

        // enforce ServiceBehaviours and OperationBehaviours so we dont have to decorate all the handlers
        _hostService.Description.Behaviors.Find<ServiceBehaviorAttribute>().InstanceContextMode = InstanceContextMode.Single;
        _hostService.Description.Behaviors.Find<ServiceBehaviorAttribute>().ConcurrencyMode = ConcurrencyMode.Single;

        AddKnownTypes(endpoint);
    }

    private static void AddKnownTypes(ServiceEndpoint endpoint)
    {
        foreach(OperationDescription operation in endpoint.Contract.Operations)
        {
            operation.KnownTypes.Add(typeof(XElement));
            operation.Behaviors.Find<OperationBehaviorAttribute>().TransactionScopeRequired = true;
            operation.Behaviors.Find<OperationBehaviorAttribute>().TransactionAutoComplete = true;
        }
    }

The key lines of code here to make it work are:

[OperationContract(IsOneWay = true, Action = "*")]
void Handle(MsmqMessage<object> message);

binding.SerializationFormat = MsmqMessageSerializationFormat.Stream;

operation.KnownTypes.Add(typeof(XElement));

The reason for the Stream format is we were seeing the XML in the message body wrapped in braces (smells like JSON but we saw no reason why).

Finally, and the reason I'm not sure this is an answer because it's not using a WCF DataContract and the built-in WCF serialization, we pass a handler containing the following method to the constructor:

    public void Handle(MsmqMessage<object> message)
    {
        object msmqType = Serializer.Deserialize(message.Body);

        _bus.Publish(msmqType);
    }

If it wasn't obvious, we're using XML serialization for the messages.