views:

149

answers:

2

If i got a service definition like this:

[PoisonErrorBehavior]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MsgQueue: IMsgQueue
{
    public void ProcessMsg(CustomMsg msg)
    {
       throw new Exception("Test");
    }
}

( where ProcessMsg is the registered method for incoming msmq-messages )

and i want to handle the exception with my error handler ( i took the code from msdn as a template for mine ):

public sealed class PoisonErrorBehaviorAttribute : Attribute, IServiceBehavior
    {
        MsmqPoisonMessageHandler poisonErrorHandler;

        public PoisonErrorBehaviorAttribute()
        {
            this.poisonErrorHandler = new MsmqPoisonMessageHandler();
        }

        void IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
        }

        void IServiceBehavior.AddBindingParameters(ServiceDescription description, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, BindingParameterCollection parameters)
        {
        }

        void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
        {
            foreach (ChannelDispatcherBase channelDispatcherBase in serviceHostBase.ChannelDispatchers)
            {
                ChannelDispatcher channelDispatcher = channelDispatcherBase as ChannelDispatcher;
                channelDispatcher.ErrorHandlers.Add(poisonErrorHandler);
            }
        }
    }

    class MsmqPoisonMessageHandler : IErrorHandler
    {
        public void ProvideFault(Exception error, MessageVersion version, ref System.ServiceModel.Channels.Message fault)
        {
        }

        public bool HandleError(Exception error)
        {
            string test = error.GetType().ToString();
            //
            // The type of the exception is never MsmqPoisonMessageException !!!
            //
            MsmqPoisonMessageException poisonException = error as MsmqPoisonMessageException;
            if (null != poisonException)
            {
                long lookupId = poisonException.MessageLookupId;
                Console.WriteLine(" Poisoned message -message look up id = {0}", lookupId);
            }
       }

then i got the problem that the exception is never of type MsmqPoisonMessageException. I would have expected .NET to magically encapsulate my "new Exception("Test")" in a MsmqPoisonMessageException, but the exception catched in my errorhandler is always of the same type as the exception i threw.

Am i missunderstanding this whole poison message behavior? I thought if an unhandled exception was thrown by my message-handling-code then the exception would turn out to be a MsmqPoisonMessageException, because otherwise i would'nt have a chance to get the lookup-id of msg in the queue.

Thank you all.

A: 

WCF encapsulates exceptions in a fault exception.

http://msdn.microsoft.com/en-us/library/system.servicemodel.faultexception.aspx

You must also specify which exceptions are to be thrown in the Interface / Contract.

Shiraz Bhaiji
A: 

First of all, you need to be retrieving the messages inside of a transaction, otherwise they won't be put back to the queue when there is an exception thrown from your code. Add this to the ProcessMessage function:

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]

Also, you need to make sure that binding is set to fault when poison messages are detected, and that the retry count and time are small enough that you'll see it in your testing.

Try these steps (using VS 2008):

  1. Open the WCF Configuration tool for your app.config file
  2. Select Bindings in the tree, and click "New Binding Configuration" in the tasks area
  3. Select the binding type of your endpoint (probably netMsmqBinding or msmqIntegrationBinding)
  4. Set the name of the new binding configuration
  5. Set the ReceiveErrorHandling property to "Fault"
  6. Set the ReceiveRetryCount property to 2
  7. Set the RetryCycleDelay to "00:00:10"
  8. Select the endpoint to your service and set the binding configuration to the name you specified in step 4.

(You will probably want different values for ReceiveRetryCount and RetryCycleDelay for your production configuration.)

Jesse McDowell