Maroontress.Oxbind 2.1.0-alpha

This is a prerelease version of Maroontress.Oxbind.
dotnet add package Maroontress.Oxbind --version 2.1.0-alpha
                    
NuGet\Install-Package Maroontress.Oxbind -Version 2.1.0-alpha
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Maroontress.Oxbind" Version="2.1.0-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Maroontress.Oxbind" Version="2.1.0-alpha" />
                    
Directory.Packages.props
<PackageReference Include="Maroontress.Oxbind" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Maroontress.Oxbind --version 2.1.0-alpha
                    
#r "nuget: Maroontress.Oxbind, 2.1.0-alpha"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Maroontress.Oxbind@2.1.0-alpha
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Maroontress.Oxbind&version=2.1.0-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Maroontress.Oxbind&version=2.1.0-alpha&prerelease
                    
Install as a Cake Tool

Oxbind

Oxbind is a .NET library for deserializing XML documents to C# objects using constructor injection and a declarative attribute-based mapping. It targets .NET Standard 2.0.

Why Oxbind?

  • Type-Safe Mapping: Clear correspondence between XML schema and C# classes
  • Constructor-Driven: Promotes immutable object design
  • Declarative Mapping: Simple configuration through C# attributes
  • Detailed Error Reporting: Error messages with XML line and column information

Example

Deserialize the following XML document:

<?xml version="1.0" encoding="UTF-8"?>
<movie title="Avatar">
  <director name="James Cameron"/>
  <release year="2009"/>
  <cast>Sam Worthington</cast>
  <cast>Zoe Saldana</cast>
</movie>

The movie element has the director, release, and cast elements. Here, the director element occurs only once, the release element occurs zero or one times, and the cast element occurs zero or more times. The schema of this XML document can be described with XML Schema as follows:

  ...
  <xs:element name="movie">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="director" minOccurs="1" maxOccurs="1"/>
        <xs:element ref="release" minOccurs="0" maxOccurs="1"/>
        <xs:element ref="cast" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
      <xs:attribute name="title"/>
    </xs:complexType>
  </xs:element>

  <xs:element name="director">
    <xs:complexType>
      <xs:sequence/>
      <xs:attribute name="name"/>
    </xs:complexType>
  </xs:element>
  ...

Oxbind does not use XML Schema and its validation, but the example of the XML Schema is given to show the occurrence order of the elements is important.

First, creates a Movie class representing the movie element as follows:

using Maroontress.Oxbind;

[ForElement("movie")]
public record class Movie(
    [ForAttribute("title")] string? Title,
    [Required] Director TheDirector,
    [Optional] Release? MaybeRelease,
    [Multiple] IEnumerable<Cast> Casts);

The Movie class has the ForElement attribute with the argument "movie", which means it is associated with the movie element.

And the constructor has parameters with some attributes, which are corresponding to the schema of the root element. In this example, since a record class is used, the constructor parameters implicitly generate instance properties. Each parameter is as follows:

  • [ForAttribute("title")] string? Title represents the instance property Title, which is associated with the XML attribute title of the movie element. This means that the constructor's parameter with [ForAttribute(…)] is associated with the XML attribute whose name is the argument of the C# attribute.

  • [Required] Director TheDirector represents the instance property TheDirector, which is associated with the XML element director that occurs once. The type of Director is the class with the ForElement attribute with the argument "director".

  • [Optional] Release? MaybeRelease represents that the instance property MaybeRelease, which is associated with the XML element release that occurs zero or one times. The type of Release is the class with the ForElement attribute with the argument "release".

  • [Multiple] IEnumerable<Cast> Casts represents that the instance property Casts, which is associated with the XML element cast that occurs zero or more times. The type of Cast is the class with the ForElement attribute with the argument "cast".

Therefore, the Movie class has four properties: Title, TheDirector, MaybeRelease, and Casts.

Second, creates Director, Release and Cast classes representing director, release and cast elements, respectively, as follows:

[ForElement("director")]
public record class Director([ForAttribute("name")] string? Name);

[ForElement("release")]
public record class Release([ForAttribute("year")] string? Year);

[ForElement("cast")]
public record class Cast([ForText] string Name);

All the classes have the ForElement attribute, which means each class is associated with the element whose name is the argument of the attribute. For example, the Director class is associated with the director element, and so on.

The Director class has the constructor. The parameters of the constructor with some attributes is associated with the schema. [ForAttribute("name")] string? Name represents the instance property Name, which is associated with the XML attribute name of the director element.

The Release class is similar to the Director class, so a detailed explanation is omitted here.

The Cast class is also similar to the Director class, but its constructor has the parameter with the ForText attribute, which means the instance property Name is associated with the inner text of the cast element.

Finally, to obtain a Movie instance from the XML document, use the deserializer with the XML document and the associated classes as follows:

var reader = new StringReader(…);
var factory = new OxbinderFactory();
var binder = factory.Of<Movie>();
var movie = binder.NewInstance(reader);

See the result in .NET Fiddle

The examples above use record class for simplicity, but you can also use regular classes or primary constructors with Oxbind. Choose the style that best fits your coding preferences or project requirements:

[ForElement("movie")]
public sealed class Movie
{
    public Movie(
        [ForAttribute("id")] string? id,
        [ForAttribute("title")] string? title,
        [Required] Director theDirector,
        [Optional] Release? maybeRelease,
        [Multiple] IEnumerable<Cast> casts)
    {
        this.Id = id;
        this.Title = title;
        this.TheDirector = theDirector;
        this.MaybeRelease = maybeRelease;
        this.Casts = casts;
    }

    public string? Id { get; }
    public string? Title { get; }
    public Director TheDirector { get; }
    public Release? MaybeRelease { get; }
    public IEnumerable<Cast> Casts { get; }
}
[ForElement("movie")]
public sealed class Movie(
    [ForAttribute("id")] string? id,
    [ForAttribute("title")] string? title,
    [Required] Director theDirector,
    [Optional] Release? maybeRelease,
    [Multiple] IEnumerable<Cast> casts)
{
    public string? Id { get; } = id;
    public string? Title { get; } = title;
    public Director TheDirector { get; } = theDirector;
    public Release? MaybeRelease { get; } = maybeRelease;
    public IEnumerable<Cast> Casts { get; } = casts;
}

The examples above use record class (available in C# 9.0 and later) and primary constructors (C# 12 and later) for simplicity, but you can also use regular classes with Oxbind. The library itself targets .NET Standard 2.0 and does not require these newer language features.

Optimizing elements that have no attributes and contain only text

The cast element, which has no attributes and only contains text, can also be mapped directly in the Movie constructor using the ForChildElement attribute. This avoids creating a separate Cast class:

[ForElement("movie")]
public record class Movie(
    [ForAttribute("title")] string? Title,
    [Required] Director TheDirector,
    [Optional] Release? MaybeRelease,
    [Multiple][ForChildElement("cast")] IEnumerable<string> Casts);
/* The Cast class is no longer needed. */

This shows how ForChildElement simplifies binding for simple, text-only child elements.

For users familiar with System.Xml.Serialization, this optimization is analogous to mapping a text-only element directly to a string property. This avoids the need for a separate wrapper class that uses [XmlText].

For instance, with System.Xml.Serialization you could write:

public sealed class Movie
{
    ⋮
    [XmlElement("cast")]
    public List<string>? Casts { get; set; }
}

instead of:

public sealed class Movie
{
    ⋮
    [XmlElement("cast")]
    public List<Cast>? Casts { get; set; }
}

public sealed class Cast
{
    [XmlText]
    public string? Name { get; set; }
}

Limitations

Oxbind is designed to map XML structures to C# constructor parameters declaratively. This design principle leads to certain limitations on the XML structures it can handle.

Specifically, Oxbind requires child elements to appear in a fixed, sequential order, corresponding to the order of parameters in the C# constructor. This is analogous to the <xs:sequence> compositor in an XML Schema.

Consequently, structures that require choice or non-sequential ordering are not supported. This includes:

  • Choice of elements (like <xs:choice>): Where only one element from a group of different elements can appear.
  • Interleaved repeating elements (like <xs:choice maxOccurs="unbounded">): Where different types of child elements are mixed together, rather than being grouped by type.
  • Any order of elements (like <xs:all>): Where elements can appear in any order.

For example, Oxbind cannot deserialize a document where different types of elements are interleaved, as shown in the <library-contents> element below:


<library-contents>
  <book title="The Hobbit"/>
  <movie title="Avatar"/>
  <book title="Dune"/>
  <music-album artist="Queen" title="Greatest Hits"/>
  <movie title="The Lord of the Rings"/>
</library-contents>

This limitation is a direct consequence of mapping to a constructor's parameter list, which has a single, defined signature.

Getting started

Oxbind is available as the NuGet-logo NuGet package.

Install

dotnet add package Maroontress.Oxbind

How to create a class representing an XML element

See Attribute Specifications.

How to build

Requirements for build

Build instructions

git clone <URL>
cd Oxbind.CSharp
dotnet build --configuration Release

Get test coverage report with Coverlet

If the dotnet-reportgenerator-globaltool tool is not already installed:

dotnet tool install -g dotnet-reportgenerator-globaltool

Run the following command to generate a test coverage report with Coverlet:

dotnet test --configuration Release --no-build \
  --logger "console;verbosity=detailed" \
  --collect:"XPlat Code Coverage" \
  --results-directory MsTestResults
reportgenerator -reports:MsTestResults/*/coverage.cobertura.xml \
  -targetdir:Coverlet-html
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.0-alpha 132 8/10/2025
2.0.2 218 8/7/2025
2.0.2-beta 115 7/30/2025
2.0.2-alpha 235 7/26/2025
2.0.1 141 6/30/2025
2.0.1-beta 145 6/22/2025
2.0.1-alpha 296 6/11/2025
2.0.0 224 6/4/2025
2.0.0-beta 140 6/3/2025
2.0.0-alpha 201 5/29/2025
1.0.3 2,952 3/22/2019