Click here to Skip to main content
Click here to Skip to main content

XGenPlus - A Flexible Tool to Generate Typed XML Serializers for your .NET Applications

, , 12 Nov 2007
Rate this:
Please Sign up or sign in to vote.
XGenPlus is a flexible tool to generate typed XML serializers for your .NET applications. It provides more flexibility than the sgen.exe tool combining the efficiency offered by Mvp.Xml.Xgen library.

Introduction

If you have ever used XML Serialization heavily in your projects, chances are that you have pulled your hair a couple of times before getting things right.

Memory Leaks

When you use XmlSeializer to serialize or deserialize an object, XmlSerializer will create a dynamic assembly on the fly containing the serialization code, specific to the type of that object. For instance, when you do something like:

XmlSerializer ser=new XmlSerializer(typeof(Customer))

Now, behind the scenes:

  • The XmlSerializer constructor will reflect the customer type you are passing to the XmlSerializer constructor, and generate the code for a typed serializer for the same.
  • The code for the typed serializer for Customer type is compiled by calling the compiler services at run time.
  • The cached assembly which contains the typed serializer for Customer type is loaded to the application domain, and is cached for future uses.

However, there is a known problem with XmlSerializer - few XMLSerializer constructors (other than the simple constructors) will regenerate the typed serializer assembly each time, instead of getting it back from the cache. Only these two constructors will get the serializer back from cache when subsequent calls are made:

  • System.Xml.Serialization.XmlSerializer(Type) 
  • System.Xml.Serialization.XmlSerializer(Type,String) 

In other words, XmlSerializer is not using the cache mechanism in all constructors. For example, assume that you are invoking XmlSerializer in a Web application. If you use any of those overloaded, 'feature rich' constructors of XmlSerializer, you are going to run out of memory.

XmlSerializer serializer = 
	new XmlSerializer(typeof(Customer), new XmlRootAttribute(""));

For each call, a new Serializer is created, and you may soon run out of memory if you have this piece of code in a Web application which is expected to scale up to a fair amount of users. In Microsoft's own words, "If you use any of the other constructors, multiple versions of the same assembly are generated and never unloaded, resulting in a memory leak and poor performance" (as cited in MSDN documentation for XmlSerializer). We still don't know whether this is 'by design' or this is a bug with XmlSerializer.

Poor Startup Performance

Even if you are planning to go with the simple constructors, still, this results in run time generation of typed serializers, at least for the first time when XmlSerializer is initialized with a specific type. The solution? Generate the typed serializers before you compile your application - so that you don't have to worry about run time typed serializer generation and the memory leaks.

Getting It Right

When we got into the problems of untyped XML Serialization, I frankly never thought we'll end up developing a utility for creating typed serializers. We were dealing with performance enhancements of a project which involved tones of XML serialization and deserialization. Initially, we decided to use Microsoft Sgen to create typed serializers, but we ran across a couple of problems.

  • You can't generate typed serializers for a selected set of types.
  • After generating typed serializer assemblies, you are expected to create a strong reference to the same from your project.
  • Failed to handle few scenarios.

Another tool we came across was Mvp.Xml.Xgen, which may help you to create typed serializers at design time. You can configure it so that you can run it as a custom task from within Visual Studio. However,

  • We never wanted the serializers as part of the main assembly.
  • We required more flexibility for selecting types to generate serializers.
  • We wanted a loosely coupled way to invoke serializers, without making any major changes to the existing code.

Introduction to XGenPlus

The result, we rolled out a small tool, XGenPlus, mainly combining the nice features of SGen and Mvp.Xml.Xgen. Here are a few features:

  • Provides a set of command line options for creating typed serializer libraries for all types or selected types in an assembly.
  • Allows programmers to create a typed serializer without actually referring the typed serializer library directly. For this, XGenPlus.SerializerLib can be used.
  • An MSBuild task is available to integrate XGenPlus in your Build script
  • You can run XGenPlus using a configuration file.

Using XGenPlus from Command Line

Here is a brief overview of what it can do:

Usage: XGenPlus /assembly:assemblyname [/exclude:namespace1,namespace2] 
	[/include:namespace1,namespace2] [/reference:assembly1,assembly2] 
	[/copyto:path] [/nocompile] [/nogenerate] [/getconfig:filename] 
	[/putconfig:filename] [/serializeall] [/from:namespace]

• /assembly:assemblyname - To specify the assembly
• /exclude:namespace1,namespace2 – Exclude types in the specified namespaces 
• /include:namespace1,namespace2 - Include types in the specified namespaces
• /reference:assembly1,assembly2 - Specify reference assemblies
• /nocompile - Won't compile the source files generated
• /nogen - Won't generate any source files
• /getconfig:filename - Run the application using the configuration specified 
			in the filename
• /putconfig:filename - Write the current configuration 
			(passed over command line) to the filename
• /copyto:path - Copy the generated assembly to the path
• /serializeall - Generate serializers for all types, 
		not only for classes with System.Serializable attribute applied
• /from:namespace - Generate serializers for types in namespaces 
			starting from the specified namespace. 
			Generated assembly name will be the namespace.dll

Most switches can be represented with short names. E.g. - you can use /a instead of assembly, and /i instead of include.

Usage Examples

Here are a few examples:

Case 1: The following command will generate serializers for types starting with the namespaces SomeName.Objects.DTO. The generated assembly name will be SomeName.Objects.DTO.dll.

xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll 
/i:SomeName.Objects.DTO.CalcUI 

Case 2: The following command will generate serializers for types starting with the namespaces SomeName.Objects.DTO and exclude everything else. This will also generate a configuration file with the name SomeName.Objects.config.

xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll 
/i:SomeName.Objects.DTO. CalcUI /putconfig:SomeName.Objects.config 

Case 3: The following command has the same effect as the above command once the config file is in place. The parameters will be taken from the config file.

xgenplus /getconfig:SomeName.Objects.config 

Case 4: The following command will just generate a configuration file from the command line parameters.

xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll 
/e:SomeName.Objects.DTO.State /putconfig:SomeName.Objects.config /nogen /nocomp 

Case 5: By default, serializers will be generated only for types marked with System.Serializable attribute. You may use the /serializeall switch to generate serializers for all classes.

xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll 
/i:SomeName.Objects.DTO.CalcUI /serializeall 

Case 6: The following command will generate a serializer DLL, named SomeName.Objects.DTO.CalcUI.dll.

xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll 
/f:SomeName.Objects.DTO.CalcUI 

Using XGenPlus as a MSBuild Task

Using XGenPlus as a task with Microsoft Build is rather easy. XGenPlusTask class in the XGenPlus.exe can be used directly from the build configuration. Modify your project file with the following information.

Make sure that the UsingTask declaration references the correct path where XGenPlus resides.

<UsingTask TaskName="XGenPlusTask" AssemblyFile="YourPath\XGenPlus.exe" />

And then define the task in the appropriate section (In this case, BeforeBuild).

<Target Name="BeforeBuild"> 
<XGenPlusTask AssemblyName="bin\debug\SomeName.Objects.DTO.dll" 
NoGenerate="false" NoCompile="false" 
IncludeList="SomeName.Objects.dto.calcui;SomeName.Objects.dto.cobrowse" 
ReferenceList="bin\debug\SomeName.Objects.messages.dll;System.Data.dll" /> </Target> 

How to Use the Typed Serializers

Step 1 - Use XGenPlus to generate serializer assemblies for your objects, and place the generated *.Serializer.dll file(s) in your applications bin folder.

Step 2 - Once you have your serializer libraries in the bin folder, the following call to the FactoryProxy class in XGenPlus.SerializerLib will return a typed serializer for your type (Make sure that you have a reference to XGenPlus.SerializerLib in your project).

XmlSerializer ser = FactoryProxy.GetSerializer(typeof(yourtype)); 

In the background, the XGenPlus.SerializerLib will do everything else.

Inside XGenPlus

Great, isn't it? Now let us have a very brief look inside the XGenPlus project. What XGenPlus does when you invoke it is, pass the types to an instance of XmlSerializer, and then steal away the code XmlSerializer generates Smile | :) . Pretty simple, isn't it? However, a couple of other things are also required to make things work the way we need it.

XGenPlus

The Runner class has a static method, InvokeRunnerInOwnAppDomain which actually creates an instance of the Runner class using reflection - and then iterates the types one by one to invoke the XmlSerializer by passing that type - To 'steal' the code generated by XmlSerializer.

Please note that, to steal the code of typed serializers generated by XmlSerializer, we should do something naughty. We should modify the config file to add a switch, XmlSerialization.Compilation, to tell XmlSerializer that it should leave behind the temporary files it generated, during the process of generating a typed serializer!!

<configuration> <system.diagnostics> 
<switches> 
<add name='XmlSerialization.Compilation' value='4'/> 
</switches> 
</system.diagnostics> 
</configuration> 

InvokeRunnerInOwnAppDomain is actually invoked either from the Program class (If you are invoking XGenPlus from the command line), or from the XGenPlusTask class. (If you are a invoking XGenPlus as an MSBuild task). The GenerateAndCompile method in the Runner class actually does the ground work (creating folders, load reference libraries to the application domain, etc.) and invoke GenerateCode method in the XmlSerializerGenerator class.

Other than just stealing away the code created by XmlSerializer, we are also generating code for a factory class in the generated serializer library. The code that we've stolen from XmlSerializer and the code we generated for factory class is compiled together to form the serializer library.

XGenPlus.SerializerLib

All the factories we generated are implementing the ISerializerFactory interface in XGenPlus.SerializerLib library. Also XGenPlus.SerializerLib library provides a convenient way for you to create serializers without actually referring directly to the serializer assemblies you generated with XGenPlus. (You may have multiple serializer assemblies in your project, isn't it?)

Here, the catch is the FactoryCache class - which has a static constructor, that loads all factories from the *.Serializer.dll files in your application's execution path. Optionally, you can use the SerializerDllPath setting in your config file, to specify the location of your generated serializer assemblies. And finally, GetFactory method in XGenPlus.SerializerLib.FactoryProxy will find the appropriate factory based on your type name, invoke the corresponding typed serializer, and may return the same to you.

Please feel free to download the source code and binaries attached with this article.

XGenPlus project is maintained in CodePlex and is distributed under GPL. Please check this link for updates.

History

  • 12th November, 2007: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Authors

Sivakumar G

United States United States
No Biography provided


Comments and Discussions

 
QuestionGetSerializer fails on arrays of serializable objects? PinmemberMember 336217112-Mar-12 6:24 
GeneralTeam Build example Pinmemberemil_schnabel1-Feb-09 20:31 
GeneralSerialising null fields PinmemberJay Gudgeon17-Oct-08 1:35 
QuestionFixed in 3.0 or 3.5...? PinmemberHealyje13-Nov-07 2:33 
AnswerRe: Fixed in 3.0 or 3.5...? PinmemberAn 'OOP'13-Nov-07 3:20 
GeneralRe: Fixed in 3.0 or 3.5...? PinmemberHealyje13-Nov-07 17:59 
It seems completely amazing this wouldn't have been fixed in 3.0...
 
I mean, wouldn't this typically constrain their own developers significantly?
GeneralRe: Fixed in 3.0 or 3.5...? PinmemberAn 'OOP' Madhusudanan13-Nov-07 23:36 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 12 Nov 2007
Article Copyright 2007 by Sivakumar G, Anoop Madhusudanan
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid