{"id":5215,"date":"2023-03-14T14:41:17","date_gmt":"2023-03-14T21:41:17","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5215"},"modified":"2023-03-14T14:41:17","modified_gmt":"2023-03-14T21:41:17","slug":"enable-cbor-within-asp-net-core-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/enable-cbor-within-asp-net-core-odata\/","title":{"rendered":"Enable CBOR within ASP.NET Core OData"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>CBOR, which stands for <strong>Concise Binary Object Representation<\/strong>, is\u00a0a data format whose design goals include the possibility of extremely small code size, small message size, and extensibility without the need for version negotiation (from <a href=\"https:\/\/cbor.io\/\">cbor.io<\/a>). CBOR is based on the wildly successful <strong>JSON<\/strong> (aka, JavaScript Object Notation) data model, and is a binary data\u00a0representation of JSON. Here&#8217;s an example of a simple JSON value in plain text and binary representation.<\/p>\n<p><img decoding=\"async\" width=\"1624\" height=\"484\" class=\"wp-image-5243\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-screenshot-font-numbe-1.png\" alt=\"JSON Representations\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-screenshot-font-numbe-1.png 1624w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-screenshot-font-numbe-1-300x89.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-screenshot-font-numbe-1-1024x305.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-screenshot-font-numbe-1-768x229.png 768w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-screenshot-font-numbe-1-1536x458.png 1536w\" sizes=\"(max-width: 1624px) 100vw, 1624px\" \/><\/p>\n<p>OData library, by default, uses the plain text JSON representation for OData requests and response data serialization (writing) and deserialization (reading). But it\u2019s also designed to enable developers to customize other JSON representations. I&#8217;d use this post to share with you the process to enable CBOR representation serialization and deserialization using ASP.NET Core OData. Let\u2019s get started.<\/p>\n<h2>Prerequisites<\/h2>\n<p>Let\u2019s start to create an ASP.NET Core Web API application named <strong><em>ODataCborExample<\/em><\/strong>\u00a0with <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\" target=\"_blank\" rel=\"noopener\">Microsoft.AspNetCore.OData<\/a>\u00a0(version-8.0.12) installed. For simplicity, I reuse the \u2018<strong>Book\u2019<\/strong> entity and its controller from <a href=\"https:\/\/devblogs.microsoft.com\/odata\/customize-odata-payload-serialization-format-within-asp-net-core-odata\/\">this<\/a> post. I\u2019m omitting those codes in this post and please refer to <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/ODataCborExample\/ODataCborExample\/Controllers\/BooksController.cs\">here<\/a> for detailed implementations.<\/p>\n<p>I need a library to finish the CBOR binary data reading and writing. You can find various implementations of CBOR for most computer languages from <a href=\"https:\/\/cbor.io\/impls.html\">here<\/a>. In this post, I choose to use <strong>System.Formats.Cbor<\/strong>, since it\u2019s the built-in solution for .Net and everyone can install\/consume this package from <a href=\"https:\/\/www.nuget.org\/packages\/System.Formats.Cbor\">Nuget.org<\/a>. Here&#8217;s <strong><em>ODataCborExample<\/em><\/strong> project configuration:<\/p>\n<p><img decoding=\"async\" width=\"1698\" height=\"210\" class=\"wp-image-5244\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-2-1.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-2-1.png 1698w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-2-1-300x37.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-2-1-1024x127.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-2-1-768x95.png 768w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-2-1-1536x190.png 1536w\" sizes=\"(max-width: 1698px) 100vw, 1698px\" \/><\/p>\n<h2>CBOR serialization<\/h2>\n<p><strong>System.Formats.Cbor<\/strong> provides <strong><a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.formats.cbor.cborwriter?view=dotnet-plat-ext-6.0\">CborWriter<\/a><\/strong> to finish CBOR encoded JSON binary writing. To inject <strong>CborWriter<\/strong> into OData JSON serialization, we should customize an OData JSON writer to use it.<\/p>\n<h2>CBOR OData JSON writer<\/h2>\n<p>The OData JSON serialization process relies on two interfaces:<\/p>\n<ul>\n<li>public interface IJsonWriter {}<\/li>\n<li>public interface IJsonWriterAsync {}<\/li>\n<\/ul>\n<p>Let\u2019s create the following class named <strong>CborODataWriter<\/strong> which implements the above two interfaces as below:<\/p>\n<pre class=\"lang:c# decode:true\">using System.Formats.Cbor;\r\n\r\npublic partial class CborODataWriter : IJsonWriter, IJsonWriterAsync\r\n{\r\n    private CborWriter _cborWriter;\r\n    private Stream _stream;\r\n    private bool _isIeee754Compatible;\r\n    private Encoding _encoding;\r\n\r\n    public CborODataWriter(Stream stream, bool isIeee754Compatible, Encoding encoding)\r\n    {\r\n        _stream = stream;\r\n        _isIeee754Compatible = isIeee754Compatible;\r\n        _encoding = encoding;\r\n        _cborWriter = new CborWriter();\r\n    }\r\n}\r\n<\/pre>\n<p>The constructor of <strong>CborODataWriter<\/strong> accepts the writing stream, an encoding and <em>Ieee754Compatible<\/em> Boolean value (the last two are not used in this post for simplicity) and creates an instance of <strong>System.Formats.Cbor.CborWriter<\/strong>.<\/p>\n<p>We must implement the methods defined in the OData JSON writer interface. The implementation is straightforward as:<\/p>\n<pre class=\"lang:c# decode:true\">public partial class CborODataWriter : IJsonWriter, IJsonWriterAsync\r\n{\r\n    \/\/ \u2026\u2026\r\n    public void StartArrayScope()\r\n    {\r\n        _cborWriter.WriteStartArray(null);\r\n    }\r\n\r\n    public void StartObjectScope()\r\n    {\r\n        _cborWriter.WriteStartMap(null);\r\n    }\r\n\r\n    public void EndArrayScope()\r\n    {\r\n        _cborWriter.WriteEndArray();\r\n    }\r\n\r\n    public void EndObjectScope()\r\n    {\r\n        _cborWriter.WriteEndMap();\r\n    }\r\n\r\n    public void WriteValue(bool value)\r\n    {\r\n        _cborWriter.WriteBoolean(value);\r\n    }\r\n\r\n    \/\/ \u2026\u2026 Omit other writing methods.\r\n    public Task StartArrayScopeAsync()\r\n    {\r\n        StartArrayScope();\r\n        return Task.CompletedTask;\r\n    }\r\n\r\n    public Task StartObjectScopeAsync()\r\n    {\r\n        StartObjectScope();\r\n        return Task.CompletedTask;\r\n    }\r\n\r\n    \/\/ \u2026\u2026 omit other async methods.\r\n}\r\n<\/pre>\n<p>We simply delegate each writing action in <strong>CborODataWriter<\/strong> to the corresponding writing method in <strong>CborWriter<\/strong> since CBOR is the binary representation of JSON.<\/p>\n<p>Here\u2019s a simple mapping table for your reference between OData JSON writing methods and CBOR writing methods:<\/p>\n<table style=\"width: 100%; text-align: center;\">\n<tbody>\n<tr>\n<td><strong>OData JSON Writer<\/strong><\/td>\n<td><strong>CborWriter<\/strong><\/td>\n<td><strong>Things to write<\/strong><\/td>\n<\/tr>\n<tr>\n<td>StartArrayScope()<\/td>\n<td>WriteStartArray()<\/td>\n<td>[<\/td>\n<\/tr>\n<tr>\n<td>EndArrayScope()<\/td>\n<td>WriteEndArray()<\/td>\n<td>]<\/td>\n<\/tr>\n<tr>\n<td>StartObjectScope()<\/td>\n<td>WriteStartMap()<\/td>\n<td>{<\/td>\n<\/tr>\n<tr>\n<td>EndObjectScope()<\/td>\n<td>WriteEndMap()<\/td>\n<td>}<\/td>\n<\/tr>\n<tr>\n<td>WriteValue(bool value)<\/td>\n<td>WriteBoolean(value)<\/td>\n<td>true\/false<\/td>\n<\/tr>\n<tr>\n<td>\u2026<\/td>\n<td>\u2026<\/td>\n<td>\u2026<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For the async methods, let\u2019s simply call the synchronous methods respectively.<\/p>\n<h2>CBOR OData JSON Writer factory<\/h2>\n<p>Once we have <strong>CborODataWriter<\/strong>, we must register it into the OData JSON serialization process. OData library uses the <strong><em>Factory<\/em><\/strong> pattern to inject JSON writer. So far, the OData library has the following three factories to &#8220;<em>build<\/em>&#8221; JSON writer.<\/p>\n<ul>\n<li>public interface IJsonWriterFactory<\/li>\n<li>public interface IJsonWriterFactoryAsync<\/li>\n<li>public interface IStreamBasedJsonWriterFactory<\/li>\n<\/ul>\n<p>I&#8217;d use <strong>IStreamBasedJsonWriterFactory<\/strong> to build the CBOR OData JSON writer factory since it is consumed prior to the other two, and most importantly, it provides the <strong>Stream<\/strong> which is needed in <strong>CborODataWriter<\/strong>.<\/p>\n<p>Here\u2019s my CBOR OData JSON writer factory:<\/p>\n<pre class=\"lang:c# decode:true\">public class CborODataJsonWriterFactory : IStreamBasedJsonWriterFactory\r\n{\r\n    private ODataMessageInfo _messageInfo;\r\n    private IJsonWriterFactoryAsync _jsonWriterFactory;\r\n\r\n    public CborODataJsonWriterFactory(ODataMessageInfo messageInfo, IJsonWriterFactoryAsync jsonWriterFactory)\r\n    {\r\n        _messageInfo = messageInfo;\r\n        _jsonWriterFactory = jsonWriterFactory;\r\n    }\r\n\r\n    public IJsonWriter CreateJsonWriter(Stream stream, bool isIeee754Compatible, Encoding encoding)\r\n        =&gt; throw new NotImplementedException();\r\n\r\n    public IJsonWriterAsync CreateAsynchronousJsonWriter(Stream stream, bool isIeee754Compatible, Encoding encoding)\r\n    {\r\n        if (_messageInfo.MediaType.Type == \"application\" &amp;&amp; _messageInfo.MediaType.SubType == \"cbor\")\r\n        {\r\n            return new CborODataWriter(stream, isIeee754Compatible, encoding);\r\n        }\r\n\r\n        \/\/ delegate to default JSON writer\r\n        TextWriter textWriter = new StreamWriter(stream, encoding);\r\n        return _jsonWriterFactory.CreateAsynchronousJsonWriter(textWriter, isIeee754Compatible);\r\n    }\r\n}\r\n<\/pre>\n<p>Where,<\/p>\n<ol>\n<li>We only need to implement async interface method \u2018<strong><em>CreateAsynchronousJsonWriter\u2019<\/em><\/strong><\/li>\n<li>An <strong>ODataMessageInfo<\/strong> is injected into the constructor to provide the media type which is used to create plain text JSON writer or CBOR JSON writer.<\/li>\n<li>An <strong>IJsonWriterFactoryAsync<\/strong> is also injected into the constructor to create plain text JSON writer if the media type is not <strong>application\/cbor<\/strong>.<\/li>\n<\/ol>\n<p>We can inject this factory into the service container at startup as:<\/p>\n<pre class=\"lang:c# decode:true\">builder.Services.AddControllers().\r\n    AddOData(opt =&gt;\r\n        opt.AddRouteComponents(\"odata\", EdmModelBuilder.GetEdmModel(),\r\n           services =&gt; services.AddScoped&lt;IStreamBasedJsonWriterFactory, CborODataJsonWriterFactory&gt;());\r\n<\/pre>\n<h2>CBOR Media Type resolver<\/h2>\n<p>A media type resolver is needed to resolve <strong>ODataFormat<\/strong>\u00a0based on the <strong>application\/cbor<\/strong> media type. Here\u2019s an implementation for your reference:<\/p>\n<pre class=\"lang:c# decode:true\">public class CborMediaTypeResolver : ODataMediaTypeResolver\r\n{\r\n    private readonly ODataMediaTypeFormat[] _mediaTypeFormats =\r\n    {\r\n        new ODataMediaTypeFormat(new ODataMediaType(\"application\", \"cbor\"), ODataFormat.Json)\r\n    };\r\n\r\n    public override IEnumerable&lt;ODataMediaTypeFormat&gt; GetMediaTypeFormats(ODataPayloadKind payloadKind)\r\n    {\r\n        if (payloadKind == ODataPayloadKind.Resource || payloadKind == ODataPayloadKind.ResourceSet)\r\n        {\r\n            return _mediaTypeFormats.Concat(base.GetMediaTypeFormats(payloadKind));\r\n        }\r\n\r\n        return base.GetMediaTypeFormats(payloadKind);\r\n    }\r\n}\r\n<\/pre>\n<p>Where we build a mapping between <strong>application\/cbor<\/strong> and OData JSON format.<\/p>\n<p>We also need to inject it into the service container at startup as:<\/p>\n<pre class=\"lang:c# decode:true\">builder.Services.AddControllers().\r\n    AddOData(opt =&gt;\r\n        opt.AddRouteComponents(\"odata\", EdmModelBuilder.GetEdmModel(), services =&gt;\r\n            services.AddScoped&lt;IStreamBasedJsonWriterFactory, CborODataJsonWriterFactory&gt;()\r\n                    .AddSingleton&lt;ODataMediaTypeResolver&gt;(sp =&gt; new CborMediaTypeResolver());\r\n<\/pre>\n<p>We register the writer factor using &#8220;Scoped&#8221; because the factory needs the ODataMediaType which is scoped.<\/p>\n<p>We also need to let <strong>ODataOutputFormatter<\/strong> to understand <strong>application\/cbor<\/strong> media type as:<\/p>\n<pre class=\"lang:c# decode:true\">builder.Services.AddControllers(opt =&gt;\r\n{\r\n    var odataFormatter = opt.OutputFormatters.OfType&lt;ODataOutputFormatter&gt;().First();\r\n    odataFormatter.SupportedMediaTypes.Add(\"application\/cbor\");\r\n});\r\n<\/pre>\n<h2>CBOR writing test<\/h2>\n<p>Now, let\u2019s run our sample to test\/verify the OData CBOR serialization.<\/p>\n<p>By default, we send &#8220;GET <a href=\"http:\/\/localhost:5015\/odata\/books\/1\">http:\/\/localhost:5015\/odata\/books\/1<\/a>&#8221; without request header setting, we can get plain text JSON response payload as:<\/p>\n<p><img decoding=\"async\" width=\"961\" height=\"585\" class=\"wp-image-5245\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-email-6.png\" alt=\"Graphical user interface, text, application, email Description automatically generated\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-email-6.png 961w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-email-6-300x183.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-email-6-768x468.png 768w\" sizes=\"(max-width: 961px) 100vw, 961px\" \/><\/p>\n<p>Let&#8217;s resend &#8220;GET <a href=\"http:\/\/localhost:5015\/odata\/books\/1\">http:\/\/localhost:5015\/odata\/books\/1<\/a>&#8221; with request header \u201c<strong>Accept=application\/cbor<\/strong>\u201d, we can get:<\/p>\n<p><img decoding=\"async\" class=\"wp-image-5246\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/cbor_book_1.png\" \/><\/p>\n<p>The response body is unreadable since it&#8217;s binary data. You can use any other technique to parse the binary data from the response body.<\/p>\n<p>Here&#8217;s a trick just for your reference. We can change the <strong><em>Flush<\/em><\/strong>() method in <strong>CborODataWriter<\/strong> to output the binary data as base64 string as:<\/p>\n<pre class=\"lang:c# decode:true\">public void Flush()\r\n{\r\n    if (!_cborWriter.IsWriteCompleted)\r\n    {\r\n        return;\r\n    }\r\n\r\n    var encode = _cborWriter.Encode();\r\n\r\n    \/\/ if you want to get the Base64 string, use the following codes\r\n    TextWriter sw = new StreamWriter(_stream);\r\n    sw.Write(Convert.ToBase64String(encode));\r\n    sw.Flush();\r\n\r\n    \/\/ Be noted, if you write it as base64, please comment out the following codes.\r\n    \/\/ _stream.Write(encode, 0, encode.Length);\r\n    \/\/ _stream.Flush();\r\n\r\n    _cborWriter.Reset();\r\n}\r\n<\/pre>\n<p>Now, send the request and we can get the following base64 string:<\/p>\n<pre class=\"lang:text decode:true\"><strong>v25Ab2RhdGEuY29udGV4dHgzaHR0cDovL2xvY2FsaG9zdDo1MDE1L29kYXRhLyRtZXRhZGF0YSNCb29rcy8kZW50aXR5YklkAWVUaXRsZWQxOTg0ZkF1dGhvcm1HZW9yZ2UgT3J3ZWxsZElTQk5xOTc4LTAtNDUxLTUyNDkzLTVlUGFnZXMZAQz\/<\/strong>\r\n<\/pre>\n<p>Decode the base64 string, we can get the byte array data as:<\/p>\n<pre class=\"lang:text decode:true\">bf 6e 40 6f 64 61 74 61 2e 63 6f 6e 74 65 78 74 78 33 68 74 74 70 3a 2f 2f 6c 6f 63 61 6c 68 6f 73 74 3a 35 30 31 35 2f 6f 64 61 74 61 2f 24 6d 65 74 61 64 61 74 61 23 42 6f 6f 6b 73 2f 24 65 6e 74 69 74 79 62 49 64 01 65 54 69 74 6c 65 64 31 39 38 34 66 41 75 74 68 6f 72 6d 47 65 6f 72 67 65 20 4f 72 77 65 6c 6c 64 49 53 42 4e 71 39 37 38 2d 30 2d 34 35 31 2d 35 32 34 39 33 2d 35 65 50 61 67 65 73 19 01 0c ff\r\n<\/pre>\n<p>Using any CBOR viewer tooling to decode the above byte array data, we can get the following plain text JSON, it&#8217;s the same as the default OData plain text JSON representation.<\/p>\n<pre class=\"lang:jsondecode:true\">\r\n{\r\n  \"@odata.context\": \"http:\/\/localhost:5015\/odata\/$metadata#Books\/$entity\",\r\n  \"Id\": 1,\r\n  \"Title\": \"1984\",\r\n  \"Author\": \"George Orwell\",\r\n  \"ISBN\": \"978-0-451-52493-5\",\r\n  \"Pages\": 268\r\n}\r\n<\/pre>\n<p>Here\u2019s the response data size comparison:<\/p>\n<p><img decoding=\"async\" class=\"wp-image-5245\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/cbor_mechmark_png.png\" alt=\"benchmark\" \/><\/p>\n<p>We can save &#8217;20B&#8217; even for such a simple entity, decreasing 12% the message size around.<\/p>\n<h2>CBOR deserialization<\/h2>\n<p>Same as CBOR writing, <strong>System.Formats.Cbor<\/strong> provides <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.formats.cbor.cborreader?view=dotnet-plat-ext-6.0\"><strong>CborReader<\/strong><\/a> to finish CBOR encoded JSON binary reading. To inject <strong>CborReader<\/strong> into OData JSON deserialization, we should customize an OData JSON reader to use it.<\/p>\n<h2>CBOR OData JSON reader<\/h2>\n<p>The OData JSON deserialization process relies on two interfaces:<\/p>\n<ul>\n<li>public interface IJsonReader {}<\/li>\n<li>public interface IJsonReaderAsync {}<\/li>\n<\/ul>\n<p>Let\u2019s create the following class named <strong>CborODataReader<\/strong> which implements the above two interfaces as below:<\/p>\n<pre class=\"lang:c# decode:true\">using System.Formats.Cbor;\r\npublic partial class CborODataReader : IJsonReader, IJsonReaderAsync\r\n{\r\n    private Stack&lt;JsonNodeType&gt; _scopes;\r\n    private CborReader _cborReader;\r\n    public CborODataReader(Stream stream)\r\n    {\r\n        byte[] data = ReadAllBytes(stream);\r\n        _cborReader = new CborReader(new ReadOnlyMemory&lt;byte&gt;(data));\r\n        _scopes = new Stack&lt;JsonNodeType&gt;();\r\n    }\r\n\r\n    public object Value { get; private set; }\r\n\r\n    public JsonNodeType NodeType { get; private set; } = JsonNodeType.None;\r\n\r\n    public bool Read()\r\n    {\r\n        \/\/ Omit here, see below\r\n    }\r\n\r\n    public Task&lt;object&gt; GetValueAsync()\r\n    {\r\n        return Task.FromResult(Value);\r\n    }\r\n\r\n    public Task&lt;bool&gt; ReadAsync()\r\n    {\r\n        bool result = Read();\r\n        return Task.FromResult(result);\r\n    }\r\n\r\n    private static byte[] ReadAllBytes(Stream instream)\r\n    {\r\n        \/\/ \u2026\u2026 omit codes\r\n    }\r\n}\r\n<\/pre>\n<p>Where, a <strong><em>stack<\/em><\/strong> of <strong>JsonNodeType<\/strong> is used to identify which node should be marked as &#8220;<strong>property<\/strong>&#8221; within a JSON object.<\/p>\n<p>OData JSON reader looks like a state machine. OData reading process calls <strong><em>Read<\/em><\/strong>() method every time when querying current <strong>JsonNodeType<\/strong> and its corresponding value, then move the reader to the next state until hit the end of input. For simplicity, we can implement the <strong><em>Read<\/em><\/strong>() method based on the <strong>CborReaderState<\/strong> as:<\/p>\n<pre class=\"lang:c# decode:true\">public bool Read()\r\n{\r\n    CborReaderState readerState = _cborReader.PeekState();\r\n    switch (readerState)\r\n    {\r\n        case CborReaderState.Finished:\r\n            NodeType = JsonNodeType.EndOfInput;\r\n            return false;\r\n\r\n        case CborReaderState.StartArray:\r\n            _cborReader.ReadStartArray();\r\n            NodeType = JsonNodeType.StartArray;\r\n            _scopes.Push(JsonNodeType.StartArray);\r\n            return true;\r\n\r\n        case CborReaderState.EndArray:\r\n            _cborReader.ReadEndArray();\r\n            NodeType = JsonNodeType.EndArray;\r\n            _scopes.Pop();\r\n            return true;\r\n\r\n        case CborReaderState.StartMap:\r\n            _cborReader.ReadStartMap();\r\n            NodeType = JsonNodeType.StartObject;\r\n            _scopes.Push(JsonNodeType.StartObject);\r\n            return true;\r\n\r\n        case CborReaderState.EndMap:\r\n            _cborReader.ReadEndMap();\r\n            NodeType = JsonNodeType.EndObject;\r\n            _scopes.Pop();\r\n            return true;\r\n\r\n        case CborReaderState.UnsignedInteger:\r\n            NodeType = ParsePrimitiveValueNodeType();\r\n            Value = _cborReader.ReadUInt32();\r\n            return true;\r\n\r\n        case CborReaderState.TextString:\r\n            NodeType = ParsePrimitiveValueNodeType();\r\n            Value = _cborReader.ReadTextString();\r\n            return true;\r\n\r\n        case CborReaderState.Boolean:\r\n            NodeType = ParsePrimitiveValueNodeType();\r\n            Value = _cborReader.ReadBoolean();\r\n            return true;\r\n\r\n        case CborReaderState.ByteString:\r\n            NodeType = ParsePrimitiveValueNodeType();\r\n            Value = _cborReader.ReadByteString();\r\n            return true;\r\n\r\n        default:\r\n            throw new NotImplementedException($\"Not implemented, please add more case to handle {readerState}\");\r\n    }\r\n}\r\n<\/pre>\n<p>Where, <strong><em>ParsePrimitiveValueNodeType<\/em><\/strong> is used to identify the <strong>JsonNodeType.Property<\/strong> if the primitive value is first within a JSON object, See the sample <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/ODataCborExample\/ODataCborExample\/Extensions\/CborODataReader.cs\" rel=\"noopener\" target=\"_blank\">here<\/a> for implementation.<\/p>\n<p>Here\u2019s the mapping between <strong>CborReaderStart<\/strong> and OData <strong>JsonNodeType<\/strong>:<\/p>\n<table style=\"width: 100%; text-align: center;\">\n<tbody>\n<tr>\n<td><strong>CborReaderState<\/strong><\/td>\n<td><strong>OData JSON Node Type<\/strong><\/td>\n<td><strong>Note<\/strong><\/td>\n<\/tr>\n<tr>\n<td>StartArray<\/td>\n<td>StartArray<\/td>\n<td>[<\/td>\n<\/tr>\n<tr>\n<td>EndArray<\/td>\n<td>EndArray<\/td>\n<td>]<\/td>\n<\/tr>\n<tr>\n<td>StartMap<\/td>\n<td>StartObject<\/td>\n<td>{<\/td>\n<\/tr>\n<tr>\n<td>EndMap<\/td>\n<td>EndObject<\/td>\n<td>}<\/td>\n<\/tr>\n<tr>\n<td rowspan=\"2\">UnsignedInteger<\/p>\n<p>ByteString<\/p>\n<p>TextString<\/p>\n<p>&#8230;&#8230;<\/td>\n<td>Property<\/td>\n<td>If it\u2019s first within JSON Object<\/td>\n<\/tr>\n<tr>\n<td>PrimitiveValue<\/td>\n<td>Others<\/td>\n<\/tr>\n<tr>\n<td>Finished<\/td>\n<td>EndOfInput<\/td>\n<td><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For the async methods, let\u2019s simply call the synchronous methods respectively.<\/p>\n<h2>CBOR OData JSON Reader factory<\/h2>\n<p>Same as the writer factory, the OData library also uses the <strong><em>Factory<\/em><\/strong> pattern to inject JSON reader. So far, the OData library has the following two factories to &#8220;<em>build<\/em>&#8221; JSON reader.<\/p>\n<ul>\n<li>public interface IJsonReaderFactory<\/li>\n<li>public interface IJsonReaderFactoryAsync<\/li>\n<\/ul>\n<p>I\u2019d use <strong>IJsonReaderFactory<\/strong> to build the CBOR OData JSON reader factory (The async version has not fully finished in the current OData library). Here it is.<\/p>\n<pre class=\"lang:c# decode:true\">public class CborODataJsonReaderFactory : IJsonReaderFactory\r\n{\r\n    private ODataMessageInfo _messageInfo;\r\n    private IJsonReaderFactory _innerFactory;\r\n\r\n    public CborODataJsonReaderFactory(ODataMessageInfo messageInfo, IJsonReaderFactory innerFactory)\r\n    {\r\n        _messageInfo = messageInfo;\r\n        _innerFactory = innerFactory;\r\n    }\r\n\r\n    public IJsonReader CreateJsonReader(TextReader textReader, bool isIeee754Compatible)\r\n    {\r\n        if (_messageInfo.MediaType.Type == \"application\" &amp;&amp; _messageInfo.MediaType.SubType == \"cbor\")\r\n        {\r\n            StreamReader reader = textReader as StreamReader;\r\n            return new CborODataReader(reader.BaseStream);\r\n        }\r\n\r\n        return _innerFactory.CreateJsonReader(textReader, isIeee754Compatible);\r\n    }\r\n}\r\n<\/pre>\n<p>Where,<\/p>\n<ol>\n<li>We only need to implement the synchronous interface method <strong><em>CreateJsonReader<\/em><\/strong>.<\/li>\n<li>An <strong>ODataMessageInfo<\/strong> is also injected into the constructor to provide the media type which is used to create plain text JSON reader or CBOR JSON reader.<\/li>\n<li>An <strong>IJsonReaderFactory<\/strong> is also injected into the constructor to create a default JSON reader if the media type is not <strong>application\/cbor<\/strong>.<\/li>\n<\/ol>\n<p>We can inject this factory into the service container at startup. However, as mentioned, we need <strong>IJsonReaderFactory<\/strong> in <strong>CborODataJsonReaderFactory<\/strong> to create the default JSON reader if the media type is not CBOR. So, we need more codes to inject the CBOR JSON reader factory into the service container as below:<\/p>\n<pre class=\"lang:c# decode:true\">builder.Services.AddControllers().\r\n    AddOData(opt =&gt;\r\n        opt.EnableQueryFeatures()\r\n           .AddRouteComponents(\"odata\", EdmModelBuilder.GetEdmModel(),\r\n              services =&gt;\r\n              {\r\n                  \/\/ for JSON Reader factory\r\n                  var selector = services.First(s =&gt; s.ServiceType == typeof(IJsonReaderFactory));\r\n                  services.Remove(selector);\r\n                  services.Add(new ServiceDescriptor(selector.ImplementationType, implementationType: selector.ImplementationType, lifetime: Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton));\r\n                  services.AddScoped&lt;IJsonReaderFactory&gt;(s =&gt;\r\n                  {\r\n                       return new CborODataJsonReaderFactory(\r\n                           s.GetRequiredService&lt;ODataMessageInfo&gt;(),\r\n                           (IJsonReaderFactory)s.GetRequiredService(selector.ImplementationType));\r\n                  });\r\n\r\n                \/\/ For JSON Writer factory\r\n                services\r\n                    .AddScoped&lt;IStreamBasedJsonWriterFactory, CborODataJsonWriterFactory&gt;()\r\n                    .AddSingleton&lt;ODataMediaTypeResolver&gt;(sp =&gt; new CborMediaTypeResolver());\r\n            }\r\n    ));\r\n<\/pre>\n<h2>CBOR reader test<\/h2>\n<p>That\u2019s all for the CBOR OData reading implementation. Let\u2019s run and test it.<\/p>\n<p>I want to send a POST request to the &#8220;<strong>books<\/strong>&#8221; endpoint to create a new book. By default, we can use <strong>application\/json<\/strong> to send the following POST request with plain text JSON.<\/p>\n<pre class=\"lang:c# decode:true\">POST http:\/\/localhost:5015\/odata\/books\r\n\r\nBODY (application\/json)\r\n\r\n{\r\n  \"Title\":\u00a0\"My Story\",\r\n  \"Author\":\u00a0\"Sam Xu\",\r\n  \"ISBN\":\u00a0\"978-0-111-52493-5\",\r\n  \"Pages\":\u00a09527\r\n}\r\n<\/pre>\n<p>To test the CBOR JSON reader, we need the CBOR binary data representation for the above plain text JSON. Let&#8217;s covert the plain text JSON to CBOR binary array and save it into a file and name it <strong>newbook.dat<\/strong>. Here\u2019s the file viewer:<\/p>\n<p><img decoding=\"async\" width=\"962\" height=\"237\" class=\"wp-image-5247\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-font-screenshot-numbe-2.png\" alt=\"binary file viewer\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-font-screenshot-numbe-2.png 962w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-font-screenshot-numbe-2-300x74.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/a-picture-containing-text-font-screenshot-numbe-2-768x189.png 768w\" sizes=\"(max-width: 962px) 100vw, 962px\" \/><\/p>\n<p>List the byte data as text below:<\/p>\n<pre class=\"lang:c# decode:true\">A4 65 54 69 74 6C 65 68 4D 79 20 53 74 6F 72 79 66 41 75 74 68 6F 72 66 53 61 6D 20 58 75 64 49 53 42 4E 71 39 37 38 2D 30 2D 31 31 31 2D 35 32 34 39 33 2D 35 65 50 61 67 65 73 19 25 37\r\n<\/pre>\n<p>Now, we can use Postman to send this binary file to <a href=\"http:\/\/localhost:5015\/odata\/books\">http:\/\/localhost:5015\/odata\/books<\/a> as below:<\/p>\n<p><img decoding=\"async\" width=\"909\" height=\"272\" class=\"wp-image-5248\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-descr-2.png\" alt=\"VS debug\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-descr-2.png 909w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-descr-2-300x90.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/graphical-user-interface-text-application-descr-2-768x230.png 768w\" sizes=\"(max-width: 909px) 100vw, 909px\" \/><\/p>\n<p>Where:<\/p>\n<ol>\n<li>Specify <strong>binary<\/strong> as the request body media type and use the &#8220;newbook.dat&#8221; created above.<\/li>\n<li>Set &#8220;Content-Type=<strong>application\/cbor<\/strong>&#8221; in the request header.<\/li>\n<\/ol>\n<p>Let\u2019s debug and click send button in the Postman, we can hit the following breaking point with correct data in the new &#8220;book&#8221; object as below:<\/p>\n<p><img decoding=\"async\" width=\"1676\" height=\"281\" class=\"wp-image-5249\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-7-1.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-7-1.png 1676w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-7-1-300x50.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-7-1-1024x172.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-7-1-768x129.png 768w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2023\/03\/word-image-5215-7-1-1536x258.png 1536w\" sizes=\"(max-width: 1676px) 100vw, 1676px\" \/><\/p>\n<p>That\u2019s all.<\/p>\n<h2>Summary<\/h2>\n<p>This post went through the steps to customize CBOR writing and reading within ASP.NET Core OData 8.x. CBOR, as the binary representation of JSON, can provide a small code size without losing any JSON benefits. Hope the contents and implementations in this post can help. Again, please do not hesitate to leave your comments below or let me know your thoughts through\u00a0<a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>. Thanks.<\/p>\n<p>I uploaded the whole project to\u00a0<a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/ODataCborExample\" target=\"_blank\" rel=\"noopener\">this<\/a>\u00a0repository. If you find any bug or issue, please file them <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/issues\/new\">here<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction CBOR, which stands for Concise Binary Object Representation, is\u00a0a data format whose design goals include the possibility of extremely small code size, small message size, and extensibility without the need for version negotiation (from cbor.io). CBOR is based on the wildly successful JSON (aka, JavaScript Object Notation) data model, and is a binary data\u00a0representation [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1472,1],"tags":[1474,1481,1482],"class_list":["post-5215","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-asp-net-core","category-odata","tag-asp-net-core-odata","tag-cbor","tag-json"],"acf":[],"blog_post_summary":"<p>Introduction CBOR, which stands for Concise Binary Object Representation, is\u00a0a data format whose design goals include the possibility of extremely small code size, small message size, and extensibility without the need for version negotiation (from cbor.io). CBOR is based on the wildly successful JSON (aka, JavaScript Object Notation) data model, and is a binary data\u00a0representation [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5215","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5215"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5215\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3253"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=5215"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5215"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5215"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}