J'essaie de sérialiser un objet Exception en C #. Cependant, il semble impossible car la classe Exception n'est pas marquée comme [Serializable]
. Y a-t-il un moyen de contourner cela?
Si quelque chose se passe mal pendant l'exécution de l'application, je veux être informé de l'exception qui s'est produite.
Mon premier réflexe est de le sérialiser.
J'ai déjà créé une classe d'erreur personnalisée. Cela encapsule toutes les informations pertinentes sur une exception et est sérialisable XML.
[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;
}
}
Créez une classe d'exception personnalisée avec l'attribut [Serializable ()] . Voici un exemple tiré de 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) { }
}
La classe Exception est marquée comme Serializable et implémente ISerializable. Voir MSDN: http: // msdn .Microsoft.com/en-us/library/system.exception.aspx
Si vous essayez de sérialiser en XML à l'aide de XmlSerializer
, vous rencontrerez une erreur sur tous les membres qui implémentent IDictionary
. C'est une limitation de XmlSerializer, mais la classe est certainement sérialisable.
mson a écrit: "Je ne sais pas pourquoi vous voudriez sérialiser l'exception ..."
Je sérialise les exceptions pour faire remonter l'exception, via un service Web, à l'objet appelant qui peut la désérialiser, puis la relancer, la journaliser ou la gérer autrement.
J'ai fait ça. J'ai simplement créé une classe wrapper Serializable qui remplace l'IDictionary par une alternative sérialisable (tableau KeyValuePair)
/// <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
}
Si vous essayez de sérialiser l'exception pour un journal, il peut être préférable de faire un .ToString (), puis de sérialiser cela dans votre journal.
Mais voici un article sur la façon de le faire et pourquoi. Fondamentalement, vous devez implémenter ISerializable sur votre exception. S'il s'agit d'une exception système, je pense qu'ils ont cette interface implémentée. S'il s'agit de l'exception de quelqu'un d'autre, vous pourrez peut-être la sous-classer pour implémenter l'interface ISerializable.
Juste au cas où quelqu'un d'autre tombe sur ce fil (c'est à la page 1 de Google à partir d'aujourd'hui), c'est une classe très utile pour sérialiser un objet Exception
en un XElement
(yay, LINQ) objet:
http://seattlesoftware.wordpress.com/2008/08/22/serializing-exceptions-to-xml/
Code inclus pour l'exhaustivité:
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;
})())
{ ; }
}
Créez un constructeur protected
comme celui-ci (vous devez également marquer votre classe Exception
[Serializable]
):
protected MyException(System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context):base(info,context)
{
}
Je ne sais pas pourquoi vous voudriez sérialiser l'exception ...
Si je voulais faire ce que vous spécifiez, je créerais une classe d'exception personnalisée qui implémente ISerializable. Vous pouvez choisir d'en faire un enfant d'exception ou vous pouvez avoir une classe entièrement personnalisée qui n'a et ne fait que ce dont vous avez besoin.
C'est un vieux fil, mais digne d'une autre réponse.
@mson s'est demandé pourquoi quelqu'un voudrait sérialiser une exception. Voici notre raison de le faire:
Nous avons une application Prism/MVVM avec des vues à la fois dans Silverlight et WPF, avec le modèle de données dans les services WCF. Nous voulons être sûrs que l'accès aux données et les mises à jour se produisent sans erreur. S'il y a une erreur, nous voulons le savoir immédiatement et informer l'utilisateur que quelque chose a peut-être échoué. Nos applications feront apparaître une fenêtre informant l'utilisateur d'une éventuelle erreur. L'exception réelle nous est ensuite envoyée par e-mail et stockée dans SpiceWorks pour le suivi. Si l'erreur se produit sur un service WCF, nous voulons obtenir l'exception complète au client afin que ce processus puisse se produire.
Voici la solution que j'ai trouvée qui peut être gérée par les clients WPF et Silverlight. Les méthodes ci-dessous dans une bibliothèque de classes "Common" de méthodes utilisées par plusieurs applications dans chaque couche.
n tableau d'octets est facilement sérialisé à partir d'un service WCF. Pratiquement n'importe quel objet peut être converti en un tableau d'octets.
J'ai commencé avec deux méthodes simples, Object2Bytes et Bytes2Object. Ceux-ci convertissent n'importe quel objet en un tableau d'octets et inversement. NetDataContractSerializer provient de la version Windows de l'espace de noms System.Runtime.Serialization.
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
À l'origine, nous renvoyions tous les résultats sous forme d'objet. Si l'objet revenant du service était un tableau d'octets, alors nous savions que c'était une exception. Ensuite, nous appellerions "Bytes2Object" et lèverions l'exception pour la gestion.
Le problème avec ce code est qu'il est incompatible avec Silverlight. Donc, pour nos nouvelles applications, j'ai conservé les anciennes méthodes pour les objets difficiles à sérialiser et créé une paire de nouvelles méthodes juste pour les exceptions. DataContractSerializer provient également de l'espace de noms System.Runtime.Serialization, mais il est présent dans les versions Windows et Silverlight.
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
Lorsqu'aucune erreur ne se produit, le service WCF renvoie 1. Si une erreur se produit, il transmet l'exception à une méthode qui appelle "ExceptionToByteArray", puis génère un entier unique à partir de l'heure actuelle. Il utilise cet entier comme clé pour mettre en cache le tableau d'octets pendant 60 secondes. Le service WCF renvoie ensuite la valeur de clé au client.
Lorsque le client voit qu'il a récupéré un entier autre que 1, il appelle la méthode "GetException" du service à l'aide de cette valeur de clé. Le service récupère le tableau d'octets du cache et le renvoie au client. Le client appelle "ByteArrayToException" et traite l'exception comme je l'ai décrit ci-dessus. 60 secondes, c'est beaucoup de temps pour que le client demande l'exception au service. En moins d'une minute, le MemoryCache du serveur est effacé.
Je pense que c'est plus facile que de créer une classe d'exception personnalisée. J'espère que cela aidera plus tard quelqu'un.