One of the major, recurring complaints of the OData.NET libraries is the performance overhead of the serialization stack. We have done a lot of work to improve the serialization performance, but the existing architecture limits how far we can go. For this reason, we have started work on a new serialization stack for OData.NET libraries that addresses major performance concerns and also makes general improvements to usability. We plan to ship this new serializer as part of Microsoft.OData.Core
9.x library (the next major release) under a new namespace as an alternative to the existing ODataMessageWriter
and ODataMessageReader
.
But you don’t have to wait for Microsoft.OData.Core
9.x release, we have shipped the preview release to NuGet of the serializer as standalone package Microsoft.OData.Serializer
so you can start testing it and sharing feedback with us. Let’s write a quick demo to show how it works.
- To get started, create a new .NET Console application with .NET 8 or later.
- Add
Microsoft.OData.Core
package from NuGet. You can use either versions 8.x or 9.x preview. - Depending on your version of
Microsoft.OData.Core
, you may also need to add theMicrosoft.Extensions.DependencyInjection.Abstractions
package from NuGet. - Add
Microsoft.OData.Serializer
package from NuGet. The latest version is0.1.0-preview.1
at the time of this writing. If using Visual Studio, make sure to **Include prerelease** versions in your package search.
Then write the following code in your Program.cs
file:
// Setup EDM model that describes the OData service
var csdlSchema =
"""
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx">
<edmx:DataServices>
<Schema Namespace="ODataDemo" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="Product">
<Key>
<PropertyRef Name="ID"/>
</Key>
<Property Name="ID" Type="Edm.Int32" Nullable="false"/>
<Property Name="Name" Type="Edm.String"/>
<Property Name="Description" Type="Edm.String"/>
<Property Name="InStock" Type="Edm.Boolean"/>
<Property Name="Price" Type="Edm.Decimal"/>
</EntityType>
<EntityContainer Name="DemoService">
<EntitySet Name="Products" EntityType="ODataDemo.Product"/>
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
""";
var xmlReader = XmlReader.Create(new StringReader(csdlSchema));
IEdmModel model = CsdlReader.Parse(xmlReader);
// OData URI determines the kind of payload being requested
var odataUri = new ODataUriParser(
model,
new Uri("http://localhost/odata"),
new Uri("Products", UriKind.Relative))
.ParseUri();
// Prepare payload to write
List<Product> products = [
new Product
{
ID = 1,
Name = "Laptop",
Description = "A high-performance laptop.",
IsAvailable = false,
Price = 999.99m
},
new Product
{
ID = 2,
Name = "Smartphone",
Description = "A latest model smartphone.",
IsAvailable = true,
Price = 699.99m
}
];
// Initialize serializer options
var serializerOptions = new ODataSerializerOptions();
// Write the output to the console
using var outputStream = Console.OpenStandardOutput();
await ODataSerializer.WriteAsync(products, outputStream, odataUri, model, serializerOptions);
Console.ReadKey();
[ODataType("ODataDemo.Product")]
class Product
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[ODataPropertyName("InStock")]
public bool IsAvailable { get; set; }
public decimal Price { get; set; }
public int Version { get; set; } // Skipped because it's not in the schema
}
When you run the application, it should print output similar to the following (pretty-printed for clarity):
{
"@odata.context": "http://localhost/odata/$metadata#Products",
"value": [
{
"ID": 1,
"Name": "Laptop",
"Description": "A high-performance laptop.",
"InStock": false,
"Price": 999.99
},
{
"ID": 2,
"Name": "Smartphone",
"Description": "A latest model smartphone.",
"InStock": true,
"Price": 699.99
}
]
}
Understanding the sample code
The above sample application creates an OData schema containing a single entity type Product
and a single entity set Products
that returns a collection of Product
entity. The class also defines a .NET class called Product
that maps to the entity type in the service schema. This demonstrates built-in support for serializing plain CLR types (POCO classes). We create a list of products and use ODataSerializer.WriteAsync()
to serialize the product list and write to the output stream, which is the console standard output in this case. The WriteAsync
method receives the payload, the output stream, the IEdmModel
, the ODataUri
and ODataSerializerOptions
instance. The ODataUri
helps the serializer know the kind of payload and @odata.context
URL to write.
The ODataSerializerOptions
class is used to customize various settings of the serializer, such as the buffer size or how to handle specific types. It’s intended for use a singleton per OData service, shared by all requests of the same OData service. In this example we create a new instance without modifying any setting. Instead of customizing the ODataSerializerOptions
directly, we used the new OData-specific attribute [ODataType]
to tell the serializer which entity type in the IEdmModel
the .NET class maps to. We also used [ODataPropertyName]
attribute to tell serialize which OData property a CLR property maps to when they don’t have the same names.
How it works
The new ODataSerializer
works very differently from the existing ODataMessageWriter
. ODataSerializer
works by creating metadata that maps each input type like List<Product>
and the Product
class to specialized writer that knows how to serialize that type. In the example above, the metadata was generated automatically by the serializer using reflection, relying on the [ODataType]
attribute to find the right entity type to map it to. But we could also define this mapping manually using the ODataSerializerOptions.AddTypeInfo()
method. Here’s how the code would look like if we defined the mapping manually:
0 comments
Be the first to start the discussion.