32

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?

Mike Corcoran
  • 14,072
  • 4
  • 37
  • 49
Adriano Carneiro
  • 57,693
  • 12
  • 90
  • 123

5 Answers5

23

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
Matthieu
  • 4,605
  • 4
  • 40
  • 60
hemp
  • 5,602
  • 29
  • 43
1

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 = "x@xpto.com";
    email.To = "y@acme.com";
    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();
}
drowa
  • 682
  • 5
  • 13
0

The no headache solution is to use the NetBike.Xml NuGet package (.NET Standard 2.0) which is compatible with System.Xml.XmlSerializer XML attributes and support internal types out of the box.

Usage is straightforward, here's an excerpt from the README:

var serializer = new XmlSerializer();
var xml = "<Foo><Id>1</Id><Name>test</Name></Foo>";
var foo = serializer.Deserialize<Foo>(new StringReader(xml));
0xced
  • 25,219
  • 10
  • 103
  • 255
-2

This may help you: MRB_ObjectSaver

This project helps you save/load/clone any object in c# to/from a file/string. In compare to "c# serialization" this method keeps reference to objects and link between objects will not break. (see: SerializeObjectTest.cs for an example) Furthermore, the type have not be marked as [Serializable]

mrbm
  • 1,136
  • 1
  • 12
  • 36
  • 1
    A link to a solution is welcome, but please ensure your answer is useful without it: [add context around the link](//meta.stackexchange.com/a/8259) so your fellow users will have some idea what it is and why it’s there, then quote the most relevant part of the page you're linking to in case the target page is unavailable. [Answers that are little more than a link may be deleted.](//stackoverflow.com/help/deleted-answers) – Filnor Nov 24 '17 at 16:19
-3

You can also use something called xgenplus (http://xgenplus.codeplex.com/) this generates the code that would normally get executed at runtime. Then you add that to your solution, and compile it as part of your solution. At that point, it doesn't matter if your object is internal - you can add the pre-generated code in the same namespace. The performance for this is blazing fast, as its all pre-generated.

M.R.
  • 4,737
  • 3
  • 37
  • 81
  • Are you suggesting me to use a tool dated of November, 7 2007 with 12 (twelve) downloads? – Adriano Carneiro May 27 '11 at 19:45
  • Anyway, let me ask you this: if I am to change my approach and use a tool like this, will I have one unique serialization code OR one serialization for each of my internal classes? – Adriano Carneiro May 27 '11 at 19:45
  • @john: I think it answers it - using this tool, it doesn't matter if the class is internal or not, and thereby eliminating that problem. – M.R. May 27 '11 at 20:40
  • @adrian: yes, it will generate a serialization for each of the classes. I don't know if performance is an issue for you or not, but serialization in general is pretty expensive, and this bypasses that, and bypasses the issue you are having, since its precompiled and part of the same namespace. And it has more than 12 downloads (1602, to be exact - you are only looking at the last 7 days). – M.R. May 27 '11 at 20:43
  • @adrian: I can understand your hesitation in using something from 2007, but this is not an application per-se, its a tool to generate c# serialization code - the code which will then get compiled into your 2011 application, so it doesn't make a difference. – M.R. May 27 '11 at 20:44
  • @MR Performance is not an issue, I'm not processing hundreds of objects at once here, so I'm looking for a general serialization solution, not an object by object one. – Adriano Carneiro May 27 '11 at 20:46
  • @adrian: it 'generates' object by object, but you don't have to physically do one by one - just point it to an assembly, and it will take all the classes in that assembly. You can name individual assemblies if you want. – M.R. May 27 '11 at 20:55
  • 2
    @M.R. Q: "how do I use tool x to do y", A: "don't use tool x, use tool z". Not. – John Saunders May 27 '11 at 21:06
  • 4
    @john:that wasn't the question - the question was, "I'm trying to do X with Y, and it isn't working" A: "Do X, not with Y, but with Z". There is more than one answer to a problem, and the direct answer is often not the best suited answer. – M.R. May 27 '11 at 21:54
  • @M.R. Sorry, I disagree in this case because he was so specific about the technology. Your answer might better have been a comment: "it's better to not use the XmlSerializer. Would you consider using Z"? If he said yes, I'd recommend the question be closed and a new one created, "what's the best way to serialize internal classes as XML?" – John Saunders May 27 '11 at 21:56
  • 1
    @John: I don't see how that is relevent - the goal of the question being answered is to solve a problem, not just the 'question being answered'. Besides, we are still using the same technology, just in a different way. – M.R. May 27 '11 at 21:59
  • @M.R. In what way is it the same technology? Is it using the XmlSerializer under the covers? – John Saunders May 27 '11 at 22:30
  • @john: yes, it is - as I mentioned, xgenplus is only a code gen tool - it basically does a one time generation of the classes for the serialization. It does that by using xmlserializer. However, that wasn't my point - my point was that the my goal in trying to answer the question is to solve the problem, and offer any other information that I deem useful, not just for the sake of 'answering the question'. – M.R. May 27 '11 at 22:38
  • @M.R.: How did you determine that it uses XmlSerializer? – John Saunders May 27 '11 at 23:02
  • 1
    @john: It was mentioned in the forum when I first heard about it. somebody there used a reflector to see what it was doing. But as I said, you are missing the point - it is basically the same technology, but even it wasn't, the bigger picture being that it should solve the problem the OP is having... – M.R. May 27 '11 at 23:09