What is the correct way to make a custom .NET Exception serializable? -


more specifically, when exception contains custom objects may or may not serializable.

take example:

public class myexception : exception {     private readonly string resourcename;     private readonly ilist<string> validationerrors;      public myexception(string resourcename, ilist<string> validationerrors)     {         this.resourcename = resourcename;         this.validationerrors = validationerrors;     }      public string resourcename     {         { return this.resourcename; }     }      public ilist<string> validationerrors     {         { return this.validationerrors; }     } } 

if exception serialized , de-serialized, 2 custom properties (resourcename , validationerrors) not preserved. properties return null.

is there common code pattern implementing serialization custom exception?

base implementation, without custom properties

serializableexceptionwithoutcustomproperties.cs:

namespace serializableexceptions {     using system;     using system.runtime.serialization;      [serializable]     // important: attribute not inherited exception, , must specified      // otherwise serialization fail serializationexception stating     // "type x in assembly y not marked serializable."     public class serializableexceptionwithoutcustomproperties : exception     {         public serializableexceptionwithoutcustomproperties()         {         }          public serializableexceptionwithoutcustomproperties(string message)              : base(message)         {         }          public serializableexceptionwithoutcustomproperties(string message, exception innerexception)              : base(message, innerexception)         {         }          // without constructor, deserialization fail         protected serializableexceptionwithoutcustomproperties(serializationinfo info, streamingcontext context)              : base(info, context)         {         }     } } 

full implementation, custom properties

complete implementation of custom serializable exception (myserializableexception), , derived sealed exception (myderivedserializableexception).

the main points implementation summarized here:

  1. you must decorate each derived class [serializable] attribute — attribute not inherited base class, , if not specified, serialization fail serializationexception stating "type x in assembly y not marked serializable."
  2. you must implement custom serialization. [serializable] attribute alone not enough — exception implements iserializable means derived classes must implement custom serialization. involves 2 steps:
    1. provide serialization constructor. constructor should private if class sealed, otherwise should protected allow access derived classes.
    2. override getobjectdata() , make sure call through base.getobjectdata(info, context) @ end, in order let base class save own state.

serializableexceptionwithcustomproperties.cs:

namespace serializableexceptions {     using system;     using system.collections.generic;     using system.runtime.serialization;     using system.security.permissions;      [serializable]     // important: attribute not inherited exception, , must specified      // otherwise serialization fail serializationexception stating     // "type x in assembly y not marked serializable."     public class serializableexceptionwithcustomproperties : exception     {         private readonly string resourcename;         private readonly ilist<string> validationerrors;          public serializableexceptionwithcustomproperties()         {         }          public serializableexceptionwithcustomproperties(string message)              : base(message)         {         }          public serializableexceptionwithcustomproperties(string message, exception innerexception)             : base(message, innerexception)         {         }          public serializableexceptionwithcustomproperties(string message, string resourcename, ilist<string> validationerrors)             : base(message)         {             this.resourcename = resourcename;             this.validationerrors = validationerrors;         }          public serializableexceptionwithcustomproperties(string message, string resourcename, ilist<string> validationerrors, exception innerexception)             : base(message, innerexception)         {             this.resourcename = resourcename;             this.validationerrors = validationerrors;         }          [securitypermissionattribute(securityaction.demand, serializationformatter = true)]         // constructor should protected unsealed classes, private sealed classes.         // (the serializer invokes constructor through reflection, can private)         protected serializableexceptionwithcustomproperties(serializationinfo info, streamingcontext context)             : base(info, context)         {             this.resourcename = info.getstring("resourcename");             this.validationerrors = (ilist<string>)info.getvalue("validationerrors", typeof(ilist<string>));         }          public string resourcename         {             { return this.resourcename; }         }          public ilist<string> validationerrors         {             { return this.validationerrors; }         }          [securitypermissionattribute(securityaction.demand, serializationformatter = true)]         public override void getobjectdata(serializationinfo info, streamingcontext context)         {             if (info == null)             {                 throw new argumentnullexception("info");             }              info.addvalue("resourcename", this.resourcename);              // note: if "list<t>" isn't serializable may need work out             //       method of adding list, show...             info.addvalue("validationerrors", this.validationerrors, typeof(ilist<string>));              // must call through base class let save own state             base.getobjectdata(info, context);         }     } } 

derivedserializableexceptionwithadditionalcustomproperties.cs:

namespace serializableexceptions {     using system;     using system.collections.generic;     using system.runtime.serialization;     using system.security.permissions;      [serializable]     public sealed class derivedserializableexceptionwithadditionalcustomproperty : serializableexceptionwithcustomproperties     {         private readonly string username;          public derivedserializableexceptionwithadditionalcustomproperty()         {         }          public derivedserializableexceptionwithadditionalcustomproperty(string message)             : base(message)         {         }          public derivedserializableexceptionwithadditionalcustomproperty(string message, exception innerexception)              : base(message, innerexception)         {         }          public derivedserializableexceptionwithadditionalcustomproperty(string message, string username, string resourcename, ilist<string> validationerrors)              : base(message, resourcename, validationerrors)         {             this.username = username;         }          public derivedserializableexceptionwithadditionalcustomproperty(string message, string username, string resourcename, ilist<string> validationerrors, exception innerexception)              : base(message, resourcename, validationerrors, innerexception)         {             this.username = username;         }          [securitypermissionattribute(securityaction.demand, serializationformatter = true)]         // serialization constructor private, class sealed         private derivedserializableexceptionwithadditionalcustomproperty(serializationinfo info, streamingcontext context)             : base(info, context)         {             this.username = info.getstring("username");         }          public string username         {             { return this.username; }         }          public override void getobjectdata(serializationinfo info, streamingcontext context)         {             if (info == null)             {                 throw new argumentnullexception("info");             }             info.addvalue("username", this.username);             base.getobjectdata(info, context);         }     } } 

unit tests

mstest unit tests 3 exception types defined above.

unittests.cs:

namespace serializableexceptions {     using system;     using system.collections.generic;     using system.io;     using system.runtime.serialization.formatters.binary;     using microsoft.visualstudio.testtools.unittesting;      [testclass]     public class unittests     {         private const string message = "the widget has unavoidably blooped out.";         private const string resourcename = "resource-a";         private const string validationerror1 = "you forgot set whizz bang flag.";         private const string validationerror2 = "wally cannot operate in 0 gravity.";         private readonly list<string> validationerrors = new list<string>();         private const string username = "barry";          public unittests()         {             validationerrors.add(validationerror1);             validationerrors.add(validationerror2);         }          [testmethod]         public void testserializableexceptionwithoutcustomproperties()         {             exception ex =                 new serializableexceptionwithoutcustomproperties(                     "message", new exception("inner exception."));              // save full tostring() value, including exception message , stack trace.             string exceptiontostring = ex.tostring();              // round-trip exception: serialize , de-serialize binaryformatter             binaryformatter bf = new binaryformatter();             using (memorystream ms = new memorystream())             {                 // "save" object state                 bf.serialize(ms, ex);                  // re-use same stream de-serialization                 ms.seek(0, 0);                  // replace original exception de-serialized 1                 ex = (serializableexceptionwithoutcustomproperties)bf.deserialize(ms);             }              // double-check exception message , stack trace (owned base exception) preserved             assert.areequal(exceptiontostring, ex.tostring(), "ex.tostring()");         }          [testmethod]         public void testserializableexceptionwithcustomproperties()         {             serializableexceptionwithcustomproperties ex =                  new serializableexceptionwithcustomproperties(message, resourcename, validationerrors);              // sanity check: make sure custom properties set before serialization             assert.areequal(message, ex.message, "message");             assert.areequal(resourcename, ex.resourcename, "ex.resourcename");             assert.areequal(2, ex.validationerrors.count, "ex.validationerrors.count");             assert.areequal(validationerror1, ex.validationerrors[0], "ex.validationerrors[0]");             assert.areequal(validationerror2, ex.validationerrors[1], "ex.validationerrors[1]");              // save full tostring() value, including exception message , stack trace.             string exceptiontostring = ex.tostring();              // round-trip exception: serialize , de-serialize binaryformatter             binaryformatter bf = new binaryformatter();             using (memorystream ms = new memorystream())             {                 // "save" object state                 bf.serialize(ms, ex);                  // re-use same stream de-serialization                 ms.seek(0, 0);                  // replace original exception de-serialized 1                 ex = (serializableexceptionwithcustomproperties)bf.deserialize(ms);             }              // make sure custom properties preserved after serialization             assert.areequal(message, ex.message, "message");             assert.areequal(resourcename, ex.resourcename, "ex.resourcename");             assert.areequal(2, ex.validationerrors.count, "ex.validationerrors.count");             assert.areequal(validationerror1, ex.validationerrors[0], "ex.validationerrors[0]");             assert.areequal(validationerror2, ex.validationerrors[1], "ex.validationerrors[1]");              // double-check exception message , stack trace (owned base exception) preserved             assert.areequal(exceptiontostring, ex.tostring(), "ex.tostring()");         }          [testmethod]         public void testderivedserializableexceptionwithadditionalcustomproperty()         {             derivedserializableexceptionwithadditionalcustomproperty ex =                  new derivedserializableexceptionwithadditionalcustomproperty(message, username, resourcename, validationerrors);              // sanity check: make sure custom properties set before serialization             assert.areequal(message, ex.message, "message");             assert.areequal(resourcename, ex.resourcename, "ex.resourcename");             assert.areequal(2, ex.validationerrors.count, "ex.validationerrors.count");             assert.areequal(validationerror1, ex.validationerrors[0], "ex.validationerrors[0]");             assert.areequal(validationerror2, ex.validationerrors[1], "ex.validationerrors[1]");             assert.areequal(username, ex.username);              // save full tostring() value, including exception message , stack trace.             string exceptiontostring = ex.tostring();              // round-trip exception: serialize , de-serialize binaryformatter             binaryformatter bf = new binaryformatter();             using (memorystream ms = new memorystream())             {                 // "save" object state                 bf.serialize(ms, ex);                  // re-use same stream de-serialization                 ms.seek(0, 0);                  // replace original exception de-serialized 1                 ex = (derivedserializableexceptionwithadditionalcustomproperty)bf.deserialize(ms);             }              // make sure custom properties preserved after serialization             assert.areequal(message, ex.message, "message");             assert.areequal(resourcename, ex.resourcename, "ex.resourcename");             assert.areequal(2, ex.validationerrors.count, "ex.validationerrors.count");             assert.areequal(validationerror1, ex.validationerrors[0], "ex.validationerrors[0]");             assert.areequal(validationerror2, ex.validationerrors[1], "ex.validationerrors[1]");             assert.areequal(username, ex.username);              // double-check exception message , stack trace (owned base exception) preserved             assert.areequal(exceptiontostring, ex.tostring(), "ex.tostring()");         }     } } 

Comments

Popular posts from this blog

c++ - No viable overloaded operator for references a map -

java - Custom OutputStreamAppender not run: LOGBACK: No context given for <MYAPPENDER> -

java - Cannot secure connection using TLS -