views:

4656

answers:

6

I want to write an Exception to an MS Message Queue. When I attempt it I get an exception. So I tried simplifying it by using the XmlSerializer which still raises an exception, but it gave me a bit more info:

{"There was an error reflecting type 'System.Exception'."}

with InnerException:

{"Cannot serialize member System.Exception.Data of type System.Collections.IDictionary, because it implements IDictionary."}

Sample Code:

        Exception e = new Exception("Hello, world!");
        MemoryStream stream = new MemoryStream();
        XmlSerializer x = new XmlSerializer(e.GetType()); // Exception raised on this line

        x.Serialize(stream, e);
        stream.Close();

EDIT: I tried to keep this a simple as possible, but I may have overdone it. I want the whole bit, stack trace, message, custom exception type, and custom exception properties. I may even want to throw the exception again.

+3  A: 

Why? Are you instantiating an Exception upon retrieving it from the message queue? If not, just send the exception message (as string)...

Otávio Décio
Or even better, send the StackTrace property as a string
Rich
Agreed, that would work too.
Otávio Décio
>Are you instantiating an Exception upon retrieving it from the message queue? Yes.
CrashCodes
A: 

You cannot serialize a dictionary to xml. Do what the other guy said and send the message, that is the important bit anyway.

Ed Swangren
+3  A: 

I think you basically have two options:

  1. Do your own manual serialization (probably do NOT want to do that). XML serialization will surely not work due to the exact message you get in the inner exception.
  2. Create your own custom (serializable) exception class, inject data from the thrown Exception into your custom one and serialize that.
Kon
+3  A: 

Commentary:

Serializing exceptions is a common task when remoting or interacting with systems across process boundaries. Don't listen to anyone who says otherwise; they have probably never written a remoting library.

Solution:

I have plumbed remoting to do this before by creating a custom, base exception class. The problem I ran into was that System.Exception does not serialize easily so I had to inherit from it. The way I handled this was by creating my own exceptions that did serialize (through ISerializable), and wrapped any System.Exception in a custom exception.

Throughout your server code you should use custom exceptions anyway, and these can all be based on your serializable base type. It is not much work, and you will quickly build up a common library of exceptions to through.

The layer you write out to the queue (and read from) should do all the exception serialization/hydration. You might consider something like this:

public class WireObject<T, E>
{
  public T Payload{get;set;}
  public E Exception{get;set;}
}

The server and client layers that talk to your queue will wrap the object you are sending in the Payload, or attach an exception (if any). When the data is consumed from the queue, the client layer can check for an exception and re-throw it if present, else hand you your data.

This is a very simple version of what I have written before, and what I have seen others write. Good luck on your project.

Jason Jackson
+2  A: 

I was looking at Jason Jackson's answer, but it didn't make sense to me that I'm having problems with this even though System.Exception implements ISerializable. So I bypass the XmlSerializer by wrapping the exception in a class that uses a BinaryFormatter instead. When the XmlSerialization of the MS Message Queuing objects kicks in all it will see is a class with a public byte array.

Here's what I came up with:

    public class WrappedException
    {
        public byte[] Data;

        public WrappedException()
        {
        }

        public WrappedException(Exception e)
        {
            SetException(e);
        }

        public Exception GetException()
        {
            Exception result;
            BinaryFormatter bf = new BinaryFormatter();
            MemoryStream stream = new MemoryStream(Data);
            result = (Exception)bf.Deserialize(stream);
            stream.Close();
            return result;
        }

        public void SetException(Exception e)
        {
            MemoryStream stream = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(stream, e);
            Data = stream.ToArray();
            stream.Close();
        }
    }

The first test worked perfectly, but I was still concerned about custom exceptions. So I tossed together my own custom exception. Then I just dropped a button on a blank form. Here's the code:

    [Serializable]
    public class MyException : Exception, ISerializable
    {
        public int ErrorCode = 10;
        public MyException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            ErrorCode = info.GetInt32("ErrorCode");
        }

        public MyException(string message)
            : base(message)
        {
        }


        #region ISerializable Members

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("ErrorCode", ErrorCode);
        }

        #endregion
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MyException ex = new MyException("Hello, world!");
        ex.ErrorCode = 20;

        WrappedException reply = new WrappedException(ex);
        XmlSerializer x = new XmlSerializer(reply.GetType());
        MemoryStream stream = new MemoryStream();
        x.Serialize(stream, reply);

        stream.Position = 0;
        WrappedException reply2 = (WrappedException)x.Deserialize(stream);
        MyException ex2 = (MyException)reply2.GetException();
        stream.Close();

        Text = ex2.ErrorCode.ToString(); // form shows 20

        // throw ex2;

    }

Although it seemed like all of other exception types that I looked up are marked with the SerializableAttribute, I'm going to have to be careful about custom exceptions that are not marked with the SerializableAttribute.

EDIT: Getting ahead of myself. I didn't realize that BinaryFormatter is not implemented on CF.

EDIT: Above code snippets were in a desktop project. In the CF version, the WrappedException will basically look the same I just need to implement my own BinaryFormater, but I'm very open to suggestions on that one.

CrashCodes
A: 

Well, XML serialization is limited in its uses. It cannot, for example, serialize this specific scenario. For exact serialization and deserialization of Exceptions you would have to use a BinaryFormatter, which would work as long as your own exceptions are marked [Serializable]. All .Net exceptions are marked with this SerializableAttribute, meaning they can be serialized with the BinaryFormatter.

There's a gotcha there you must look out for, though.
If your exception is not serializable it will obviously fail. But it will also fail when your exception contains a field that is not serializable. You should watch out to make sure that isn't possible in your custom exceptions.

configurator
If .NET exceptions are marked [Serializable], why don't they serialize with a TextFormatter? Or at least, why how does the framework deal with them when it chucks them out to the browser when you're debugging a webservice?
I don't know what a TextFormatter is, so I really am not qualified to answer that. The SerializableAttribute, however, is only used in binary serialization as far as I know. Also, if the exception has an object field, and that field contains a non-serializable datum, serialization would fail.
configurator
[...] What would the framework do then, when debugging a web service?
configurator