Wie serialisiere ich ein Ausnahmeobjekt in C #?

74

Ich versuche, ein Ausnahmeobjekt in C # zu serialisieren. Es scheint jedoch unmöglich zu sein, da die Ausnahmeklasse nicht als markiert ist [Serializable]. Gibt es eine Möglichkeit, das zu umgehen?

Wenn während der Ausführung der Anwendung etwas schief geht, möchte ich mit der aufgetretenen Ausnahme informiert werden.

Mein erster Reflex ist, es zu serialisieren.

Martin
quelle
Die System.ExceptionKlasse wird als serialisierbar markiert msdn.microsoft.com/en-us/library/…
Jodrell

Antworten:

41

Ich habe zuvor eine benutzerdefinierte Fehlerklasse erstellt. Dies kapselt alle relevanten Informationen zu einer Ausnahme und ist XML-serialisierbar.

[Serializable]
public class Error
{
    public DateTime TimeStamp { get; set; }
    public string Message { get; set; }
    public string StackTrace { get; set; }

    public Error()
    {
        this.TimeStamp = DateTime.Now;
    }

    public Error(string Message) : this()
    {
        this.Message = Message;
    }

    public Error(System.Exception ex) : this(ex.Message)
    {
        this.StackTrace = ex.StackTrace;
    }

    public override string ToString()
    {
        return this.Message + this.StackTrace;
    }
}
Davogone
quelle
1
Siehe "Entwerfen benutzerdefinierter Ausnahmen" msdn.microsoft.com/en-us/library/ms229064(v=VS.100).aspx
5
Das Problem dabei ist, dass wenn es eine innere Ausnahme gibt, die tatsächlich die Details enthält, Sie diese nicht einschließen.
Greektreat
51

Erstellen Sie eine benutzerdefinierte Ausnahmeklasse mit dem Attribut [Serializable ()] . Hier ist ein Beispiel aus dem MSDN :

[Serializable()]
public class InvalidDepartmentException : System.Exception
{
    public InvalidDepartmentException() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, System.Exception inner) : base(message, inner) { }

    // Constructor needed for serialization 
    // when exception propagates from a remoting server to the client.
    protected InvalidDepartmentException(System.Runtime.Serialization.SerializationInfo info,
        System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
}
David Crow
quelle
Dies hat mich mit einem Problem mit Ausnahmen und Remoting geklärt.
0E322070
1
Was ist mit dem Nachrichteninhalt? Sollte der Serialisierungskonstruktor tatsächlich etwas tun, um die Nachricht aus dem Streaming-Kontext herauszuholen, oder so?
Lucas
Beachten Sie, dass bei Verwendung dieser Option mit einer inneren Ausnahme, die nicht als serialisierbar markiert ist, beim Serialisieren ein Laufzeitfehler angezeigt wird. Kein Problem, wenn die innere Ausnahme vom .NET Framework stammt, aber ein Fehlerpunkt, wenn Sie eine benutzerdefinierte Ausnahmeklasse verwenden und das Attribut Serializable vergessen.
Rybo103
1
System.Net.Sockets.SocketExceptionund System.Net.Http.HttpRequestException NICHT SERIALISIERBAR ?
Kiquenet
38

Die Exception-Klasse ist als Serializable gekennzeichnet und implementiert ISerializable. Siehe MSDN: http://msdn.microsoft.com/en-us/library/system.exception.aspx

Wenn Sie versuchen, mithilfe von XML in XML zu serialisieren XmlSerializer, wird bei allen implementierten Mitgliedern ein Fehler angezeigt IDictionary. Dies ist eine Einschränkung des XmlSerializer, aber die Klasse ist sicherlich serialisierbar.

Rex M.
quelle
Eine weitere Einschränkung: Wenn Sie Silverlight und WCF verwenden, können hin und her übergebene Objekte für ihre Serialisierung nicht ISerialisierbar sein. Ich habe versucht, ein Exception-Objekt zu übergeben, aber es hat nicht funktioniert, da ISerializable in Silverlight nicht verfügbar ist.
John Gilmer
17

mson schrieb: "Ich bin nicht sicher, warum Sie die Ausnahme serialisieren möchten ..."

Ich serialisiere Ausnahmen, um die Ausnahme über einen Webdienst an das aufrufende Objekt weiterzuleiten, das sie deserialisieren, dann erneut werfen, protokollieren oder auf andere Weise behandeln kann.

Ich tat dies. Ich habe einfach eine serialisierbare Wrapper-Klasse erstellt, die das IDictionary durch eine serialisierbare Alternative ersetzt (KeyValuePair-Array).

/// <summary>
/// A wrapper class for serializing exceptions.
/// </summary>
[Serializable] [DesignerCategory( "code" )] [XmlType( AnonymousType = true, Namespace = "http://something" )] [XmlRootAttribute( Namespace = "http://something", IsNullable = false )] public class SerializableException
{
    #region Members
    private KeyValuePair<object, object>[] _Data; //This is the reason this class exists. Turning an IDictionary into a serializable object
    private string _HelpLink = string.Empty;
    private SerializableException _InnerException;
    private string _Message = string.Empty;
    private string _Source = string.Empty;
    private string _StackTrace = string.Empty;
    #endregion

    #region Constructors
    public SerializableException()
    {
    }

    public SerializableException( Exception exception ) : this()
    {
        setValues( exception );
    }
    #endregion

    #region Properties
    public string HelpLink { get { return _HelpLink; } set { _HelpLink = value; } }
    public string Message { get { return _Message; } set { _Message = value; } }
    public string Source { get { return _Source; } set { _Source = value; } }
    public string StackTrace { get { return _StackTrace; } set { _StackTrace = value; } }
    public SerializableException InnerException { get { return _InnerException; } set { _InnerException = value; } } // Allow null to be returned, so serialization doesn't cascade until an out of memory exception occurs
    public KeyValuePair<object, object>[] Data { get { return _Data ?? new KeyValuePair<object, object>[0]; } set { _Data = value; } }
    #endregion

    #region Private Methods
    private void setValues( Exception exception )
    {
        if ( null != exception )
        {
            _HelpLink = exception.HelpLink ?? string.Empty;
            _Message = exception.Message ?? string.Empty;
            _Source = exception.Source ?? string.Empty;
            _StackTrace = exception.StackTrace ?? string.Empty;
            setData( exception.Data );
            _InnerException = new SerializableException( exception.InnerException );
        }
    }

    private void setData( ICollection collection )
    {
        _Data = new KeyValuePair<object, object>[0];

        if ( null != collection )
            collection.CopyTo( _Data, 0 );
    }
    #endregion
}
Antony Booth
quelle
3
Strg + K, Strg + D, dann können Sie aufhören, sich über den Zeilenumbruch zu beschweren.
Antony Booth
4
@AdamNaylor Betrachten Sie (zum Beispiel) die Eigenschaften. Ich finde es am bequemsten, nur 6 Codezeilen betrachten zu müssen, um alle Eigenschaften zu sehen, anstatt (mindestens 6x11 =) 66 Codezeilen. Sie drücken nur (so wie ich es gerade bin) aus taste. Daher Blurgh!sagt mehr über die Art und Weise Sie sich ausdrücken , dann sagt es über die richtige Code - Formatierung. Aber ich denke, Sie waren sich dessen bereits bewusst ...
Mike de Klerk
VS wird natürlich versuchen, langen Codezeilen zu widerstehen. Der Geschmack spielt keine Rolle.
Adam Naylor
Ich habe gerade einen Ersatz vorgeschlagen, damit Ihr Code die Serialisierung einiger unserialisierbarer Ausnahmen wie SqlException unterstützt. Vielen Dank für das Teilen Ihres Codes.
Julio Nobre
Was passiert aus Neugier, wenn Dataein Wert enthalten ist, der nicht serialisiert werden kann?
Kanon
9

Wenn Sie versuchen, die Ausnahme für ein Protokoll zu serialisieren, ist es möglicherweise besser, .ToString () auszuführen und diese dann in Ihr Protokoll zu serialisieren.

Aber hier ist ein Artikel darüber, wie und warum. Grundsätzlich müssen Sie ISerializable für Ihre Ausnahme implementieren. Wenn es sich um eine Systemausnahme handelt, haben sie meiner Meinung nach diese Schnittstelle implementiert. Wenn es sich um eine Ausnahme einer anderen Person handelt, können Sie diese möglicherweise in Unterklassen unterteilen, um die ISerializable-Schnittstelle zu implementieren.

mmr
quelle
4
Jetzt, fast zwei Jahre später, bekomme ich eine Ablehnung. Möchtest du das erklären, Downvoter?
mmr
6
Vielleicht, weil der Artikellink defekt ist?
Thepirat000
8

Hier ist eine sehr nützliche Klasse zum Serialisieren eines ExceptionObjekts in ein XElement(yay, LINQ) -Objekt:

http://seattlesoftware.wordpress.com/2008/08/22/serializing-exceptions-to-xml/

Der Vollständigkeit halber Code enthalten:

using System;
using System.Collections;
using System.Linq;
using System.Xml.Linq;
 
public class ExceptionXElement : XElement
{
    public ExceptionXElement(Exception exception)
        : this(exception, false)
    { ; }
 

    public ExceptionXElement(Exception exception, bool omitStackTrace)
        : base(new Func<XElement>(() =>
        {
            // Validate arguments
            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }
            
            // The root element is the Exception's type
            XElement root = new XElement(exception.GetType().ToString());
            if (exception.Message != null)
            {
                root.Add(new XElement("Message", exception.Message));
            }
            
            // StackTrace can be null, e.g.:
            // new ExceptionAsXml(new Exception())
            if (!omitStackTrace && exception.StackTrace != null)
            {
                vroot.Add(
                    new XElement("StackTrace",
                    from frame in exception.StackTrace.Split('\n')
                    let prettierFrame = frame.Substring(6).Trim()
                    select new XElement("Frame", prettierFrame))
                );
            }
            
            // Data is never null; it's empty if there is no data
            if (exception.Data.Count > 0)
            {
                root.Add(
                    new XElement("Data",
                        from entry in exception.Data.Cast<DictionaryEntry>()
                        let key = entry.Key.ToString()
                        let value = (entry.Value == null) ? "null" : entry.Value.ToString()
                        select new XElement(key, value))
                );
            }
            
            // Add the InnerException if it exists
            if (exception.InnerException != null)
            {
                root.Add(new ExceptionXElement(exception.InnerException, omitStackTrace));
            }
            return root;
        })())
    { ; }
}
Tieson T.
quelle
5

Erstellen Sie einen protectedKonstruktor wie diesen (auch Sie sollten Ihre ExceptionKlasse markieren [Serializable]):

protected MyException(System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context):base(info,context)
{
}
Wai Ha Lee
quelle
1

Ich bin nicht sicher, warum Sie die Ausnahme serialisieren möchten ...

Wenn ich das tun wollte, was Sie angegeben haben, würde ich eine benutzerdefinierte Ausnahmeklasse erstellen, die ISerializable implementiert. Sie können wählen, ob Sie es zu einem Kind der Ausnahme machen möchten, oder ob es eine vollständig benutzerdefinierte Klasse sein soll, die nur das hat und tut, was Sie benötigen.

mson
quelle
1

Dies ist ein alter Thread, der jedoch eine andere Antwort verdient.

@mson fragte sich, warum jemand eine Ausnahme serialisieren möchte. Hier ist unser Grund dafür:

Wir haben eine Prism / MVVM-Anwendung mit Ansichten in Silverlight und WPF mit dem Datenmodell in WCF-Diensten. Wir möchten sicherstellen, dass Datenzugriff und Aktualisierungen fehlerfrei erfolgen. Wenn ein Fehler auftritt, möchten wir ihn sofort informieren und den Benutzer darüber informieren, dass möglicherweise ein Fehler aufgetreten ist. Unsere Anwendungen öffnen ein Fenster, das den Benutzer über einen möglichen Fehler informiert. Die eigentliche Ausnahme wird dann per E-Mail an uns gesendet und zur Verfolgung in SpiceWorks gespeichert. Wenn der Fehler bei einem WCF-Dienst auftritt, möchten wir die vollständige Ausnahme an den Client zurückgeben, damit dieser Prozess stattfinden kann.

Hier ist die von mir entwickelte Lösung, die sowohl von WPF- als auch von Silverlight-Clients verarbeitet werden kann. Die folgenden Methoden befinden sich in einer "allgemeinen" Klassenbibliothek von Methoden, die von mehreren Anwendungen in jeder Schicht verwendet werden.

Ein Byte-Array kann einfach von einem WCF-Dienst serialisiert werden. So ziemlich jedes Objekt kann in ein Byte-Array konvertiert werden.

Ich habe mit zwei einfachen Methoden begonnen, Object2Bytes und Bytes2Object. Diese konvertieren jedes Objekt in ein Byte-Array und zurück. NetDataContractSerializer stammt aus der Windows-Version des System.Runtime.Serialization-Namespace.

Public Function Object2Bytes(ByVal value As Object) As Byte()
    Dim bytes As Byte()
    Using ms As New MemoryStream
        Dim ndcs As New NetDataContractSerializer()
        ndcs.Serialize(ms, value)
        bytes = ms.ToArray
    End Using
    Return bytes
End Function

Public Function Bytes2Object(ByVal bytes As Byte()) As Object
    Using ms As New MemoryStream(bytes)
        Dim ndcs As New NetDataContractSerializer
        Return ndcs.Deserialize(ms)
    End Using
End Function

Ursprünglich würden wir alle Ergebnisse als Objekt zurückgeben. Wenn das vom Dienst zurückkommende Objekt ein Byte-Array war, wussten wir, dass es eine Ausnahme war. Dann würden wir "Bytes2Object" aufrufen und die Ausnahme für die Behandlung auslösen.

Das Problem mit diesem Code ist, dass er nicht mit Silverlight kompatibel ist. Für unsere neuen Anwendungen habe ich die alten Methoden für schwer zu serialisierende Objekte beibehalten und nur für Ausnahmen ein paar neue Methoden erstellt. DataContractSerializer stammt ebenfalls aus dem System.Runtime.Serialization-Namespace, ist jedoch sowohl in der Windows- als auch in der Silverlight-Version vorhanden.

Public Function ExceptionToByteArray(obj As Object) As Byte()
    If obj Is Nothing Then Return Nothing
    Using ms As New MemoryStream
        Dim dcs As New DataContractSerializer(GetType(Exception))
        dcs.WriteObject(ms, obj)
        Return ms.ToArray
    End Using
End Function

Public Function ByteArrayToException(bytes As Byte()) As Exception
    If bytes Is Nothing OrElse bytes.Length = 0 Then
        Return Nothing
    End If
    Using ms As New MemoryStream
        Dim dcs As New DataContractSerializer(GetType(Exception))
        ms.Write(bytes, 0, bytes.Length)
        Return CType(dcs.ReadObject(ms), Exception)
    End Using
End Function

Wenn keine Fehler auftreten, gibt der WCF-Dienst 1 zurück. Wenn ein Fehler auftritt, übergibt er die Ausnahme an eine Methode, die "ExceptionToByteArray" aufruft, und generiert dann eine eindeutige Ganzzahl aus der aktuellen Zeit. Diese Ganzzahl wird als Schlüssel verwendet, um das Bytearray 60 Sekunden lang zwischenzuspeichern. Der WCF-Dienst gibt dann den Schlüsselwert an den Client zurück.

Wenn der Client feststellt, dass er eine andere Ganzzahl als 1 zurückerhalten hat, ruft er die "GetException" -Methode des Dienstes mit diesem Schlüsselwert auf. Der Dienst ruft das Byte-Array aus dem Cache ab und sendet es an den Client zurück. Der Client ruft "ByteArrayToException" auf und verarbeitet die Ausnahme wie oben beschrieben. 60 Sekunden sind ausreichend Zeit für den Client, um die Ausnahme vom Dienst anzufordern. In weniger als einer Minute wird der MemoryCache des Servers gelöscht.

Ich denke, das ist einfacher als das Erstellen einer benutzerdefinierten Ausnahmeklasse. Ich hoffe, dass dies später jemandem hilft.

D'Hag
quelle
1
Das Problem bei einer solchen Implementierung besteht darin, dass auf dem Client nicht unbedingt dieselben Assemblys wie auf dem Server geladen sind und beim Versuch, eine innere Ausnahme zu deserialisieren, möglicherweise eine SerializationException ausgelöst wird.
Christoph
-1
[Serializable]
public class CustomException: Exception
{
    public CustomException: ( Exception exception ) : base(exception.Message)
    {             
        Data.Add("StackTrace",exception.StackTrace);
    }      
}

So serialisieren Sie Ihre benutzerdefinierte Ausnahme:

JsonConvert.SerializeObject(customException);
Guru
quelle