2

I have a custom exception:

[Serializable]
public class InternalException : Exception, ISerializable
{ 
    public InternalException() 
        : base()    
    { }

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

    public InternalException(string message, Exception innerException)
        : base(message, innerException)
    { }

    protected InternalException(SerializationInfo info, StreamingContext context)
    {  }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
    }
}

I am doing this so that I can wrap 3rd party exceptions, and I add instances in a memory cache server-side. The client then gets the exception by doing:

var resp = await client.GetAsync("url");

If I do the following:

throw await resp.Content.ReadAsAsync<InternalException>(ct);

I get an exception The member ClassName could not be found. So I tried:

var str = await resp.Content.ReadAsStringAsync();
var ex = Convert.DeserializeObject<InternalException>(str);
throw ex;

str is the correctly deserialized exception. However, when I do the above, ex is a completely new exception and its message is An exception of type "InternalException" was thrown.

Why is a new exception being created and how do I actually deserialize the original exception and throw it successfully?

Note: This is an internal api and client and I do need to return exceptions to the client.

Ivan-Mark Debono
  • 15,500
  • 29
  • 132
  • 263
  • @stuartd It does have a property `InnerException` because it inherits from `Exception`. And when I create an instance and set the inner exception, this inner exception might be a complex exception with further inner exceptions. – Ivan-Mark Debono Mar 15 '23 at 00:57

1 Answers1

2

In your streaming constructor, you need to invoke the base constructor:

[Serializable]
public class InternalException : Exception, ISerializable
{ 
    protected InternalException(SerializationInfo info, StreamingContext context) 
        : base(info, context) // ADD THIS
    {  }

    // Remainder unchanged

You need to do this because the base streaming constructor is what deserializes the base exception's fields -- including the message field. Json.NET uses the streaming constructor because, as explained in the docs,

ISerializable

Types that implement ISerializable and are marked with SerializableAttribute are serialized as JSON objects. When serializing, only the values returned from ISerializable.GetObjectData are used; members on the type are ignored. When deserializing, the constructor with a SerializationInfo and StreamingContext is called, passing the JSON object's values.

Notes:

Demo fiddle here.

dbc
  • 104,963
  • 20
  • 228
  • 340