Generate Mono XML Serialisers with genxs

Before you build a pipeline around genxs, find out whether your Mono install can still run this old generator at all. Then write a configuration file that names a .NET class and assembly, picks the generated reader and writer names, and leaves hooks for custom code. Allow about twenty minutes for a first configuration.

1. Check the installed tool

Record the binary and package version. These are ordinary read-only commands:

$ command -v genxs
/usr/bin/genxs
$ dpkg-query -W -f='${Package} ${Version}\n' mono-devel
mono-devel 6.8.0.105+dfsg-3.6ubuntu2

On this installation, /usr/bin/genxs invokes Mono with /usr/lib/mono/4.5/genxs.exe. The manual describes genxs as Mono-only because it uses internal runtime classes. Do not assume a different .NET runtime, or a newer replacement tool, shares the same configuration format.

Checkpoint: Run a harmless probe and save the result. Expect this on the guide machine:

$ genxs
An error occurred while generating serializers: System.Exception: This runtime does not support generation of serializers

The exact stack trace can vary. The installed command on this host says it cannot generate serializers, even before you give it a configuration file. Treat that as a prerequisite failure. A successful file write or an empty output directory does not mean serialisers were generated.

Warning: If your host reports the same error, use a supported Mono environment or stop here and sort out the runtime before you wire genxs into a build.

2. Prepare an assembly and an output directory

genxs reads a compiled assembly. It does not compile the class named by class. Replace the placeholder paths with real ones from your project, then create a fresh directory for generated files:

$ assembly_path="$PWD/build/Example.Models.dll"
$ output_dir="$PWD/build/generated"
$ test -r "$assembly_path" && echo "assembly is readable"
assembly is readable
$ mkdir -p "$output_dir"
$ find "$output_dir" -maxdepth 1 -type f -print

The final command should print nothing for a fresh directory. Keep generated output out of your source directory until you have reviewed it.

The destination argument is positional, so the command shape is genxs CONFIGURATION_FILE DESTINATION_FOLDER. Leave the destination out and genxs uses its own default behaviour. Give it an explicit directory instead of building a script around an undocumented default.

Checkpoint: Confirm the assembly really contains the fully qualified class name you plan to use. A metadata tool such as monodis may help, though its output is outside genxs's contract. The configuration name must include its namespace, for example Example.Models.Order.

3. Write the smallest useful configuration

Create genxs.xml beside the build script. This example asks for both generated classes and chooses their namespace and output file:

<configuration>
  <serializer class="Example.Models.Order" assembly="/absolute/path/to/Example.Models.dll">
    <reader>OrderReader</reader>
    <writer>OrderWriter</writer>
    <namespace>Example.Generated</namespace>
    <outFileName>OrderSerialiser.cs</outFileName>
  </serializer>
</configuration>

Use an absolute assembly path while you get the configuration working. The manual permits a complete path, and it removes any doubt about the current working directory. You can put several serializer elements under configuration when one run should cover several classes.

Make these defaults explicit:

4. Run generation safely

Run genxs with the configuration and the isolated destination:

$ genxs "$PWD/genxs.xml" "$output_dir"
$ status=$?
$ printf 'genxs exit status: %s\n' "$status"
genxs exit status: 0

A supported runtime may print no success message. The useful results are the exit status and the files in the destination:

$ find "$output_dir" -maxdepth 1 -type f -printf '%f\n'
OrderSerialiser.cs

The file name and generated class content depend on your configuration. Open the C# file and compile it in the same project or a suitable generated-code project. Do not paste generated text into a hand-maintained file without a clear reason: the point of genxs is that you can recreate the output after the source assembly changes.

Warning: A destination that already holds a file with the same name is a state-changing target. Preserve the old output before a deliberate replacement:

$ cp --preserve=all "$output_dir/OrderSerialiser.cs" "$output_dir/OrderSerialiser.cs.bak"
$ genxs "$PWD/genxs.xml" "$output_dir.new"
$ find "$output_dir.new" -maxdepth 1 -type f -print

Review and compile the new file before you replace the old directory. If generation fails, remove only the new temporary directory and keep the backup.

Destructive action: That cleanup is optional and destructive, so inspect the path before you use rm -rf.

5. Add hooks for what generated code cannot infer

Hooks are XML children under readerHooks or writerHooks. A hook can select a type, an attribute or a member, then replace the normal operation or insert code before or after it. This example validates every object after a reader has deserialised it:

<readerHooks>
  <hook type="type">
    <insertAfter>
      Example.Validation.Validate($OBJECT);
    </insertAfter>
  </hook>
</readerHooks>

For a narrower hook, add a select element:

The hook type can be attributes, elements, unknownAttribute, unknownElement, member or type.

genxs exposes special variables while it writes code:

Tip: A type-level reader hook using replace must assign the deserialised object to $OBJECT. A typo in hook source becomes a generated C# error, so compile straight after changing a hook.

6. Diagnose failures in the right layer

A non-zero exit status can come from an unreadable assembly, an invalid XML configuration, an unmatched class name or missing runtime support. Check in that order:

  1. Assembly: test -r on the file.
  2. Configuration: run it through an XML parser.
  3. Type: the fully qualified class name and assembly identity.
  4. Runtime: whether it supports serialiser generation at all.

Running as root does not repair a missing type or add serialiser support.

If a reader or writer is absent, inspect noReader and noWriter before changing hook code. If the output compiles but behaves wrongly, compare the generated class names, namespace and hook selection with the configuration. Keep the assembly, configuration and generated file from the same build revision, so a stale output file cannot hide a source change.

Done means