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






4.70/5 (10 votes)
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 theXmlSerializer
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 integrateXGenPlus
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 :). 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