Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I serialize internal classes using XmlSerializer?

I'm building a library to interface with a third party. Communication is through XML and HTTP Posts. That's working.

But, whatever code uses the library does not need to be aware of the internal classes. My internal objects are serialized to XML using this method:

internal static string SerializeXML(Object obj) {     XmlSerializer serializer = new XmlSerializer(obj.GetType(), "some.domain");      //settings     XmlWriterSettings settings = new XmlWriterSettings();     settings.Indent = true;     settings.OmitXmlDeclaration = true;      using (StringWriter stream = new StringWriter())     {         using (XmlWriter writer = XmlWriter.Create(stream, settings))         {             serializer.Serialize(writer, obj);         }         return stream.ToString();     } } 

However, when I change my classes' access modifier to internal, I get an exception at runtime:

[System.InvalidOperationException] = {"MyNamespace.MyClass is inaccessible due to its protection level. Only public types can be processed."}

That exception happens in the first line of the code above.

I would like my library's classes not to be public because I do not want to expose them. Can I do that? How can I make internal types serializable, using my generic serializer? What am I doing wrong?

like image 966
Adriano Carneiro Avatar asked May 27 '11 19:05

Adriano Carneiro


People also ask

Why do we use XmlSerializer class?

XmlSerializer enables you to control how objects are encoded into XML. The XmlSerializer enables you to control how objects are encoded into XML, it has a number of constructors.

How does the XmlSerializer work C#?

The XmlSerializer creates C# (. cs) files and compiles them into . dll files in the directory named by the TEMP environment variable; serialization occurs with those DLLs. These serialization assemblies can be generated in advance and signed by using the SGen.exe tool.

Which class should be used to serialize an object in XML format?

Xml. Serialization namespace) class is used to serialize and deserialize. The class method Serialize is called. Since we have to serialize in a file, we create a " TextWriter ".

Is XmlSerializer thread safe?

Since XmlSerializer is one of the few thread safe classes in the framework you really only need a single instance of each serializer even in a multithreaded application. The only thing left for you to do, is to devise a way to always retrieve the same instance.


2 Answers

From Sowmy Srinivasan's Blog - Serializing internal types using XmlSerializer:

Being able to serialize internal types is one of the common requests seen by the XmlSerializer team. It is a reasonable request from people shipping libraries. They do not want to make the XmlSerializer types public just for the sake of the serializer. I recently moved from the team that wrote the XmlSerializer to a team that consumes XmlSerializer. When I came across a similar request I said, "No way. Use DataContractSerializer".

The reason is simple. XmlSerializer works by generating code. The generated code lives in a dynamically generated assembly and needs to access the types being serialized. Since XmlSerializer was developed in a time before the advent of lightweight code generation, the generated code cannot access anything other than public types in another assembly. Hence the types being serialized has to be public.

I hear astute readers whisper "It does not have to be public if 'InternalsVisibleTo' attribute is used".

I say, "Right, but the name of the generated assembly is not known upfront. To which assembly do you make the internals visible to?"

Astute readers : "the assembly name is known if one uses 'sgen.exe'"

Me: "For sgen to generate serializer for your types, they have to be public"

Astute readers : "We could do a two pass compilation. One pass for sgen with types as public and another pass for shipping with types as internals."

They may be right! If I ask the astute readers to write me a sample they would probably write something like this. (Disclaimer: This is not the official solution. YMMV)

using System; using System.IO; using System.Xml.Serialization; using System.Runtime.CompilerServices; using System.Reflection;  [assembly: InternalsVisibleTo("Program.XmlSerializers")]  namespace InternalTypesInXmlSerializer {     class Program     {         static void Main(string[] args)         {             Address address = new Address();             address.Street = "One Microsoft Way";             address.City = "Redmond";             address.Zip = 98053;             Order order = new Order();             order.BillTo = address;             order.ShipTo = address;              XmlSerializer xmlSerializer = GetSerializer(typeof(Order));             xmlSerializer.Serialize(Console.Out, order);         }          static XmlSerializer GetSerializer(Type type)         { #if Pass1             return new XmlSerializer(type); #else             Assembly serializersDll = Assembly.Load("Program.XmlSerializers");             Type xmlSerializerFactoryType = serializersDll.GetType("Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializerContract");              MethodInfo getSerializerMethod = xmlSerializerFactoryType.GetMethod("GetSerializer", BindingFlags.Public | BindingFlags.Instance);              return (XmlSerializer)getSerializerMethod.Invoke(Activator.CreateInstance(xmlSerializerFactoryType), new object[] { type });  #endif         }     }  #if Pass1     public class Address #else     internal class Address #endif     {         public string Street;         public string City;         public int Zip;     }  #if Pass1     public class Order #else     internal class Order #endif     {         public Address ShipTo;         public Address BillTo;     } }  

Some astute 'hacking' readers may go as far as giving me the build.cmd to compile it.

csc /d:Pass1 program.cs  sgen program.exe  csc program.cs 
like image 88
hemp Avatar answered Sep 22 '22 23:09

hemp


As an alternative you can use dynamically created public classes (which won't be exposed to the 3rd party):

static void Main() {     var emailType = CreateEmailType();      dynamic email = Activator.CreateInstance(emailType);      email.From = "[email protected]";     email.To = "[email protected]";     email.Subject = "Dynamic Type";     email.Boby = "XmlSerializer can use this!"; }  static Type CreateEmailType() {     var assemblyName = new AssemblyName("DynamicAssembly");      var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);      var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);      var typeBuilder = moduleBuilder.DefineType(         "Email",         (             TypeAttributes.Public |             TypeAttributes.Sealed |             TypeAttributes.SequentialLayout |             TypeAttributes.Serializable         ),         typeof(ValueType)     );      typeBuilder.DefineField("From", typeof(string), FieldAttributes.Public);     typeBuilder.DefineField("To", typeof(string), FieldAttributes.Public);     typeBuilder.DefineField("Subject", typeof(string), FieldAttributes.Public);     typeBuilder.DefineField("Body", typeof(string), FieldAttributes.Public);      return typeBuilder.CreateType(); } 
like image 29
drowa Avatar answered Sep 20 '22 23:09

drowa