views:

324

answers:

2

Hello. I'm looking for a way to serialize a POCO that contains some read-only properties. In some Google and StackOverflow searches, I've seen the following suggestions:

  • use DataContractSerializer; or
  • use SoapFormatter or BinaryFormatter; or
  • replace my readonly properties by read/write properties;

My classes are very simple, they look like:

public class MyClass
{
    public int Id { get; private set; }
    public string Name { get; private set; }
    public MyClass(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

So,

  • I don't want to make my properties read/write. If they are read-only, it's because my domain model asks for read-only properties. The domain model cannot change just because of this.
  • I don't want to use DataContractSerializer, as this would pollute my domain model with serialization-related stuff.
  • BinaryFormatter is not a very good option, as the result is a byte[], and I would like to treat it as string (and I don't want to deal with Encondings and alike when Deserializing my object).

What I would really like is an XmlSerializer class capable of serializing read-only properties.

Do you know of any such implementation? Or any other convenient solution?

Thanks!

+3  A: 

Well, normally XmlSerializer can't serialize read-only properties... however there is a possibility to serialize properties with an internal set : you need to generate the XML serialization assembly, and declare it as a "friend" assembly using the InternalsVisibleTo attribute. You can automate this by adding the following code to your project file :

  <Target Name="AfterBuild"
          DependsOnTargets="AssignTargetPaths;Compile;ResolveKeySource"
          Inputs="$(MSBuildAllProjects);@(IntermediateAssembly)"
          Outputs="$(OutputPath)$(_SGenDllName)">
    <SGen BuildAssemblyName="$(TargetFileName)"
          BuildAssemblyPath="$(OutputPath)"
          References="@(ReferencePath)"
          ShouldGenerateSerializer="true"
          UseProxyTypes="false"
          KeyContainer="$(KeyContainerName)"
          KeyFile="$(KeyOriginatorFile)"
          DelaySign="$(DelaySign)"
          ToolPath="$(SGenToolPath)">
      <Output TaskParameter="SerializationAssembly"
              ItemName="SerializationAssembly" />
    </SGen>
  </Target>

And in AssemblyInfo.cs :

[assembly: InternalsVisibleTo("MyAssembly.XmlSerializers")]

Of course, you might not want the properties to have an internal set, but if you do, the solution above should work.

Thomas Levesque
Shouldn't it be the internals visible to System.Xml.Serialization?
Chris S
That's what I thought the first time I tried to do that... however, the assembly that actually performs the (de)serialization isn't System.Xml, it is a specific serialization assembly. Usually this assembly is dynamically generated by XmlSerializer at runtime, with a random name (so you can't use it with InternalsVisibleTo), but you can pre-generate it using the sgen.exe command line tool, or the SGen build task. In that case the serialization assembly will be named *YourAssemblyName*.XmlSerializers
Thomas Levesque
Very interesting. I would never have thought about that. However I would like to keep my properties with private setters. Merci!
Bruno Reis
@Bruno: and that seems easier than putting a few [DataMember] attributes on your properties/fields?
marc_s
@marc_s : I think it's not a question of easiness, but rather a question of dependency : putting [DataMember] attributes on the properties introduces a dependency to DataContractSerializer in the business domain
Thomas Levesque
Have you looked at Marc Gravell's "proto-buf" ? http://code.google.com/p/protobuf-net/ Not sure if it can handle private fields, but it's a separate, extensible serialization mechanism.
marc_s
protobuf.net serializes the object to binary, and Bruno apparently wants it as a string. Of course, it's always possible to write the binary data as base64 ;)
Thomas Levesque
+1  A: 

While it would be sweet if serialize could access private properties unfortunately as of today there is no easy way.

But there is another option in the way of an architecture solution. Do NOT destroy your business domain requirements, instead seperate your layers similar to a a nTeir design and implement DTO's...

If you seperate your business, datafacade/dataadaptor (factory pattern fits well here) and DataAccess layers into 3 projects you can control through referencing that business never know about your DTO's. Hense if you decided to delete or implement the serialization or swap it to saving to SQL server you would not affect anything in your business layer.

There is always one downfall, there is a bunch more code to write: * you have to write a object convertor both ways for each entity you wish to go to Dataaccess * you potentially destroy some of the OO hiding sinse a .Save method in business will need to be translated to the correct type in Dataface before moving on down to dataaccess

You can make this a whole lot more easy with something like nHybinate or similar. Cheers Choco

Choco Smith