I have a service that responds to messages on an MSMQ, using WCF. When the message is well formatted, it works as normal, and we're happy. When the message is intended for a different service, and the action is different, the message stays on the queue, we get log entries, we fix, and we're happy.
But when the message has the right action, but contains XML elements that cannot be deserialized by the server, the message disappears from the queue, we don't get log entries and we have next to no idea what's happened. we're not happy.
I've tried adding an IErrorHandler. I've tried adding a Faulted event handler. The only thing I can do is use the diagnostic logging built into WCF. Is there any way I can respond to an error like this in code, with my own logger or error handler?
My queues happen to be transactional, but it doesn't seem to any difference to this problem. This happens on MSMQ version 3.0, on Windows Server 2003.
This source code exhibits the problem:
class Program : IErrorHandler {
private static Uri QueueUri = new Uri("net.msmq://localhost/private/server_queue");
static void Main(string[] args) {
Program program = new Program();
if (args.Length > 0) {
if (args[0] == "server") {
program.Server();
} else if (args[0] == "bad") {
program.BadClient();
}
} else {
program.Client();
}
}
public void BadClient() {
using (var client = new BadServiceClient(QueueUri)) {
client.Do(new [] {new BadStuff { BadMessage = "hi" }});
}
}
public void Client() {
using (var client = new ServiceClient(QueueUri)) {
client.Do(new [] {new Stuff {Message = "hi"}});
}
}
public void Server() {
var serviceHost = new ServiceHost(typeof(Service));
serviceHost.AddServiceEndpoint(typeof (IService), new NetMsmqBinding(), QueueUri);
serviceHost.Open();
serviceHost.Faulted += serviceHost_Faulted;
serviceHost.UnknownMessageReceived += new EventHandler<UnknownMessageReceivedEventArgs>(serviceHost_UnknownMessageReceived);
foreach (ChannelDispatcher dispatcher in serviceHost.ChannelDispatchers) {
dispatcher.ErrorHandlers.Add(this);
}
Console.WriteLine("press the any key");
Console.ReadKey(true);
}
void serviceHost_UnknownMessageReceived(object sender, UnknownMessageReceivedEventArgs e) {
Console.WriteLine("unknown message: {0}", e);
}
private static void serviceHost_Faulted(object sender, EventArgs e) {
Console.WriteLine("fault: {0}", e);
}
public void ProvideFault(Exception error, MessageVersion version, ref Message fault) {
Console.WriteLine("handling error: {0}", error);
}
public bool HandleError(Exception error) {
Console.WriteLine("handling error: {0}", error);
return true;
}
}
[ServiceContract]
public interface IService {
[OperationContract(IsOneWay = true, Action = "do")]
void Do(IEnumerable<Stuff> stuffs);
}
[ServiceContract]
public interface IBadService {
[OperationContract(IsOneWay = true, Action = "do")]
void Do(IEnumerable<BadStuff> stuffs);
}
class ServiceClient : ClientBase<IService>, IService {
public ServiceClient(Uri uri) : base(new NetMsmqBinding(), new EndpointAddress(uri)) {}
public void Do(IEnumerable<Stuff> stuffs) {
Channel.Do(stuffs);
}
}
class BadServiceClient : ClientBase<IBadService>, IBadService {
public BadServiceClient(Uri uri) : base(new NetMsmqBinding(), new EndpointAddress(uri)) {}
public void Do(IEnumerable<BadStuff> stuffs) {
Channel.Do(stuffs);
}
}
[ServiceBehavior(TransactionTimeout = "00:5:00", InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
class Service : IService {
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
public void Do(IEnumerable<Stuff> stuffs) {
foreach (var stuff in stuffs) {
Console.WriteLine("service doing stuff, message: " + stuff.Message);
}
}
}
[DataContract]
public class Stuff {
[DataMember]
public string Message;
}
[DataContract]
public class BadStuff : Stuff {
[DataMember]
public string BadMessage;
}