{"id":5733,"date":"2024-08-30T02:00:01","date_gmt":"2024-08-30T09:00:01","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5733"},"modified":"2024-08-30T02:00:01","modified_gmt":"2024-08-30T09:00:01","slug":"announcing-asp-net-core-odata-9-official-release","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/announcing-asp-net-core-odata-9-official-release\/","title":{"rendered":"Announcing ASP.NET Core OData 9 Official Release"},"content":{"rendered":"<p>We&#8217;re happy to announce that ASP.NET Core OData 9 has been officially released and is available on NuGet:<\/p>\n<ul>\n<li><a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\/9.0.0\">Microsoft.AspNetCore.OData 9.0.0<\/a><\/li>\n<\/ul>\n<p>The major highlight of this release is the update of the OData .NET dependencies to the <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.OData.Core\/8.0.1\">8.x major version<\/a>.\nBy updating the dependencies, we&#8217;re able to take advantage of the improvements and new capabilities introduced in <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.OData.Core\/8.0.1\">Microsoft.OData.Core 8.x<\/a> and <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.OData.Core\/8.0.1\">Microsoft.OData.Edm 8.x<\/a> releases specifically.<\/p>\n<p>The ASP.NET Core OData 9 release will only support .NET 8 or later.<\/p>\n<p>The <a href=\"https:\/\/devblogs.microsoft.com\/odata\/announcing-odata-net-8-official-release\/\">OData .NET 8 official release announcement<\/a> addresses the major changes introduced in that release. It&#8217;s advisable to go through the post to familiarize yourself with the changes.<\/p>\n<p>In this post, we explore how some of those changes affect the ASP.NET Core OData library, and how to toggle the legacy behavior where possible.<\/p>\n<h2>Character encoding in request and response payloads<\/h2>\n<p>In OData .NET 8, we introduced a new JSON writer that uses the .NET <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.text.json.utf8jsonwriter\">Utf8JsonWriter<\/a> behind the scenes to write request and response payloads. The new JSON writer is significantly faster and is the default writer in ASP.NET Core OData 9.<\/p>\n<p>You might observe differences in character encoding on output payloads. We cover those differences in the sections below.<\/p>\n<h3>Subset of characters encoded<\/h3>\n<p>In its default configuration, the new JSON writer doesn&#8217;t encode a large subset of characters like the legacy (OData .NET 7) one did.<\/p>\n<p>The legacy JSON writer encodes all characters with integer values less than 32 and greater than 127 by default &#8211; basically all non-ascii characters. This evaluates to around 65440 characters! It is possible to override the default configuration for the legacy JSON writer by passing <code>ODataStringEscapeOption.EscapeOnlyControls<\/code> option to the <a href=\"https:\/\/github.com\/OData\/odata.net\/blob\/7d5c6c553f0229522e3520109604263691769525\/src\/Microsoft.OData.Core\/Json\/DefaultJsonWriterFactory.cs#L32\">default JSON writer factory constructor<\/a>. With this alternative configuration, a much-reduced subset of characters is encoded.<\/p>\n<p>The new JSON writer uses an underlying <code>Utf8JsonWriter<\/code> that is configured with the <code>JavaScriptEncoder.UnsafeRelaxedJsonEscaping<\/code> encoder option by default. The resulting set of characters encoded is much smaller.<\/p>\n<p>Below is an illustration &#8211; in form of code sample for an OData service, demonstrating how an output payload looks like when the new JSON writer is used:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">\/\/ Model\r\nnamespace Ver900Sample.Models\r\n{\r\n    public class Order\r\n    {\r\n        public int Id { get; set; }\r\n        public decimal Amount { get; set; }\r\n        public string Note { get; set; }\r\n    }\r\n}\r\n\r\n\/\/ Controller\r\nusing Microsoft.AspNetCore.OData.Query;\r\nusing Microsoft.AspNetCore.OData.Results;\r\nusing Microsoft.AspNetCore.OData.Routing.Controllers;\r\nusing Ver900Sample.Models;\r\n\r\nnamespace Ver900Sample.Controllers\r\n{\r\n    public class OrdersController : ODataController\r\n    {\r\n        private static readonly List&lt;Order&gt; orders = new List&lt;Order&gt;\r\n        {\r\n            new Order { Id = 1, Amount = 130m, Note = \"a - z, \u03b1 - \u03a9\" },\r\n            new Order { Id = 2, Amount = 170.50m, Note = \"\ud83d\ude00 \ud83d\udc02 \ud83d\udc15\" }\r\n        };\r\n\r\n        [EnableQuery]\r\n        public SingleResult&lt;Order&gt; Get(int key)\r\n        {\r\n            return SingleResult.Create(orders.AsQueryable().Where(d =&gt; d.Id == key));\r\n        }\r\n    }\r\n}\r\n\r\n\/\/ Service configuration\r\nusing Ver900Sample.Models;\r\nusing Microsoft.AspNetCore.OData;\r\nusing Microsoft.OData.ModelBuilder;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nvar modelBuilder = new ODataConventionModelBuilder();\r\nmodelBuilder.EntitySet&lt;Order&gt;(\"Orders\");\r\n\r\nbuilder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        model: modelBuilder.GetEdmModel()));\r\n\r\nvar app = builder.Build();\r\n\r\napp.UseRouting();\r\napp.MapControllers();\r\n\r\napp.Run();<\/code><\/pre>\n<p>You can query this service for order 1 using the <code>curl<\/code> tool &#8211; the browser may not be a good choice for this illustration since it will intelligently decode the encoded characters.<\/p>\n<pre class=\"prettyprint language-default\"><code class=\"language-default\">curl http:\/\/localhost:5090\/Orders(1)<\/code><\/pre>\n<p><strong>Note<\/strong>: 5090 is a random port assigned by machine and would differ from one computer to the next.<\/p>\n<p>Below is the result you would observe:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5090\/$metadata#Orders\/$entity\",\r\n  \"Id\": 1,\r\n  \"Amount\": 130,\r\n  \"Note\": \"a - z, \u03b1 - \u03a9\"\r\n}<\/code><\/pre>\n<p>In the above case, the new JSON writer doesn&#8217;t encode the <code class=\"language-default\">\u03b1<\/code> and <code class=\"language-default\">\u03a9<\/code> non-ascii characters.<\/p>\n<p>If it&#8217;s important in your scenario to retain the behaviour of the legacy JSON writer, you can do so by swapping out the new JSON writer with the legacy JSON writer using dependency injection. You can do this by modifying the <code>AddOData<\/code> method call from the above code snippet as follows:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddScoped&lt;Microsoft.OData.Json.IJsonWriterFactory&gt;(\r\n                sp =&gt; new Microsoft.OData.Json.ODataJsonWriterFactory());\r\n        }));<\/code><\/pre>\n<p><strong>Note<\/strong>: The <code>ODataJsonWriterFactory<\/code> class is what was previously named <code>DefaultJsonWriterFactory<\/code> in OData .NET 7. We felt that having &#8220;Default&#8221; in the name would perpetuate ambiguity since it&#8217;s not the default JSON writer factory in OData .NET 8.<\/p>\n<p>Here&#8217;s how the response payload would look like if you queried for order 1 after the above change:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5090\/$metadata#Orders\/$entity\",\r\n  \"Id\": 1,\r\n  \"Amount\": 130,\r\n  \"Note\": \"a - z, \\u03b1 - \\u03a9\"\r\n}<\/code><\/pre>\n<p>In the above case, the non-ascii characters are encoded.<\/p>\n<p>The <code>Utf8JsonWriter<\/code> also supports a stricter JavaScript encoder. You can configure your service to use that encoder as follows:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddScoped&lt;Microsoft.OData.Json.IJsonWriterFactory&gt;(\r\n                sp =&gt; new Microsoft.OData.Json.ODataUtf8JsonWriterFactory(\r\n                    System.Text.Encodings.Web.JavaScriptEncoder.Default));\r\n        }));<\/code><\/pre>\n<p>Here&#8217;s how the response payload would look like if you queried for order 1 after the above change:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5090\/$metadata#Orders\/$entity\",\r\n  \"Id\": 1,\r\n  \"Amount\": 130,\r\n  \"Note\": \"a - z, \\u03B1 - \\u03A9\"\r\n}<\/code><\/pre>\n<p>The above response payload looks similar to the output from the legacy JSON writer.<\/p>\n<p>However, it&#8217;s important to note that the <code>Utf8JsonWriter<\/code> configured with the stricter JavaScript encoder is not a mirror of the legacy JSON writer in respect to the characters that get encoded.<\/p>\n<p>To close the loop, here&#8217;s how you can configure your service to use the legacy JSON writer with the less strict encoding option:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddScoped&lt;Microsoft.OData.Json.IJsonWriterFactory&gt;(\r\n                sp =&gt; new Microsoft.OData.Json.ODataJsonWriterFactory(\r\n                    Microsoft.OData.Json.ODataStringEscapeOption.EscapeOnlyControls));\r\n        }));<\/code><\/pre>\n<p>The choice of <code>JavaScriptEncoder.UnsafeRelaxedJsonEscaping<\/code> configuration option was informed by the fact that it&#8217;s the default option configured in ASP.NET Core 8.0.<\/p>\n<h3>Uppercase letters for unicode code points<\/h3>\n<p>The new JSON writer uses uppercase letters in the encoded output while the legacy JSON writer uses lowercase letters. Both outputs are legal, and clients should not have a problem deserializing either format.<\/p>\n<p>Using the sample service from previous section with the new JSON writer configured, you can query for order 2 as follows:<\/p>\n<pre class=\"prettyprint language-default\"><code class=\"language-default\">curl http:\/\/localhost:5090\/Orders(2)<\/code><\/pre>\n<p>Below is the result you would observe:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5090\/$metadata#Orders\/$entity\",\r\n  \"Id\": 2,\r\n  \"Amount\": 170.50,\r\n  \"Note\": \"\\uD83D\\uDE00 \\uD83D\\uDC02 \\uD83D\\uDC15\"\r\n}<\/code><\/pre>\n<p>Note that the encoded values for the unicode characters are in uppercase.<\/p>\n<p>If you query the same order 2 with the legacy JSON writer configured, here is how the response payload looks like:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5090\/$metadata#Orders\/$entity\",\r\n  \"Id\": 2,\r\n  \"Amount\": 170.50,\r\n  \"Note\": \"\\ud83d\\ude00 \\ud83d\\udc02 \\ud83d\\udc15\"\r\n}<\/code><\/pre>\n<p>If the described changes to character encoding are a deal breaker in your scenario, swap out the new JSON writer with the legacy JSON writer as illustrated.<\/p>\n<h2>Changes to injection of dependencies<\/h2>\n<p>We made significant changes to accommodate dependency injection restructure in OData .NET 8. We got rid of non-standard dependency injection artifacts (<code>IContainerBuilder<\/code> for instance) in favour of the .NET framework dependency injection abstractions.<\/p>\n<p>In particular, the changes to the <code>AddODataDefaultServices<\/code> method in the OData core library makes it possible to configure <code>ODataReaderSettings<\/code>, <code>ODataMessageWriterSettings<\/code>, and <code>ODataUriParserSettings<\/code> as follows:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddDefaultODataServices(\r\n                odataVersion: Microsoft.OData.ODataVersion.V4,\r\n                configureReaderAction: (messageReaderSettings) =&gt;\r\n                {\r\n                    \/\/ Relevant changes to the ODataMessageReaderSettings instance here\r\n                },\r\n                configureWriterAction: (messageWriterSettings) =&gt;\r\n                {\r\n                    \/\/ Relevant changes to the ODataMessageWriterSettings instance here\r\n                },\r\n                configureUriParserAction: (uriParserSettings) =&gt;\r\n                {\r\n                    \/\/ \/\/ Relevant changes to the ODataUriParserSettings instance here\r\n                });\r\n        }));<\/code><\/pre>\n<p>This offers the developer greater flexibility when manipulating those specific settings.<\/p>\n<p>The <code>IServiceCollection<\/code> parameter also makes it straightforward to inject any desired dependencies:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddSingleton&lt;Microsoft.OData.ODataPayloadValueConverter&gt;(\r\n                sp =&gt; new CustomODataPayloadValueConverter());\r\n        }));\r\n\r\n\/\/ ...\r\n\r\npublic class CustomODataPayloadValueConverter : Microsoft.OData.ODataPayloadValueConverter\r\n{\r\n    public override object ConvertToPayloadValue(object value, IEdmTypeReference edmTypeReference)\r\n    {\r\n        if (edmTypeReference.PrimitiveKind() == EdmPrimitiveTypeKind.DateTimeOffset)\r\n        {\r\n            var dateTimeOffset = (DateTimeOffset)value;\r\n            return dateTimeOffset.ToString(\"yyyy-MM-dd'T'HH:mm:ss.fffffffzzz\");\r\n        }\r\n\r\n        return base.ConvertToPayloadValue(value, edmTypeReference);\r\n    }\r\n}<\/code><\/pre>\n<h2>Backward compatibility flags<\/h2>\n<p>A major goal for OData .NET 8 was to ensure that customers who migrated to the new version could output the same response payloads as they did in OData .NET 7. Where changes were made to better align with OData standard, a compatibility flag was added to make it possible to toggle the legacy behaviour.<\/p>\n<p>One example of a change that we made to align with the OData standard was in writing the <code>Scale<\/code> property for decimal properties in service metadata payloads, as well as <code>SRID<\/code> property for spatial properties.<\/p>\n<p>If you query the service metadata endpoint (<a href=\"http:\/\/localhost:5090\/$metadata\">http:\/\/localhost:5090\/$metadata<\/a>) for the sample service from the previous section, you&#8217;ll get the payload below:<\/p>\n<pre class=\"prettyprint language-xml\"><code class=\"language-xml\">&lt;?xml version=\"1.0\" encoding=\"utf-8\"?&gt;\r\n&lt;edmx:Edmx Version=\"4.0\" xmlns:edmx=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edmx\"&gt;\r\n    &lt;edmx:DataServices&gt;\r\n        &lt;Schema Namespace=\"Ver900Sample.Models\" xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n            &lt;EntityType Name=\"Order\"&gt;\r\n                &lt;Key&gt;\r\n                    &lt;PropertyRef Name=\"Id\" \/&gt;\r\n                &lt;\/Key&gt;\r\n                &lt;Property Name=\"Id\" Type=\"Edm.Int32\" Nullable=\"false\" \/&gt;\r\n                &lt;Property Name=\"Amount\" Type=\"Edm.Decimal\" Nullable=\"false\" Scale=\"variable\" \/&gt;\r\n                &lt;Property Name=\"Note\" Type=\"Edm.String\" Nullable=\"false\" \/&gt;\r\n            &lt;\/EntityType&gt;\r\n        &lt;\/Schema&gt;\r\n        &lt;Schema Namespace=\"Default\" xmlns=\"http:\/\/docs.oasis-open.org\/odata\/ns\/edm\"&gt;\r\n            &lt;EntityContainer Name=\"Container\"&gt;\r\n                &lt;EntitySet Name=\"Orders\" EntityType=\"Ver900Sample.Models.Order\" \/&gt;\r\n            &lt;\/EntityContainer&gt;\r\n        &lt;\/Schema&gt;\r\n    &lt;\/edmx:DataServices&gt;\r\n&lt;\/edmx:Edmx&gt;<\/code><\/pre>\n<p>In OData .NET 7, the <code>Scale<\/code> property would be written as <code class=\"language-xml\">Scale=\"Variable\"<\/code> &#8211; with an uppercase &#8220;V&#8221;. This deviation from the OData standard was fixed in OData .NET 8. The <code>Scale<\/code> property is now written as <code class=\"language-xml\"> Scale=\"variable\"<\/code>, like in the payload above.<\/p>\n<p>If it&#8217;s important to maintain the OData .NET 7 behaviour in your scenario, you can achieve that by toggling a feature flag as follows:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddDefaultODataServices(\r\n                odataVersion: Microsoft.OData.ODataVersion.V4,\r\n                configureReaderAction: null,\r\n                configureWriterAction: (messageWriterSettings) =&gt;\r\n                {\r\n                    messageWriterSettings.LibraryCompatibility |= Microsoft.OData.ODataLibraryCompatibility.UseLegacyVariableCasing;\r\n                },\r\n                configureUriParserAction: null);\r\n        }));<\/code><\/pre>\n<p>With the <code>ODataLibraryCompability.UseLegacyVariableCasing<\/code> flag toggled, the <code>Scale<\/code> property is written as <code class=\"language-xml\">Scale=\"Variable\"<\/code> for compatibility with OData .NET 7.<\/p>\n<p>You can combine as many compatibility flags as necessary to achieve the desired behaviour.<\/p>\n<p>To toggle all the compatibility flags to achieve OData .NET 7 compatibility, you can use the convinient <code>ODataLibraryCompatibility.Version7<\/code> flag as follows:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">builder.Services.AddControllers().AddOData(\r\n    options =&gt; options.EnableQueryFeatures().AddRouteComponents(\r\n        routePrefix: string.Empty,\r\n        model: modelBuilder.GetEdmModel(),\r\n        configureServices: (services) =&gt;\r\n        {\r\n            services.AddDefaultODataServices(\r\n                odataVersion: Microsoft.OData.ODataVersion.V4,\r\n                configureReaderAction: null,\r\n                configureWriterAction: (messageWriterSettings) =&gt;\r\n                {\r\n                    messageWriterSettings.LibraryCompatibility |= Microsoft.OData.ODataLibraryCompatibility.Version7;\r\n                },\r\n                configureUriParserAction: null);\r\n        }));<\/code><\/pre>\n<p>&nbsp;<\/p>\n<h2>Conclusion<\/h2>\n<p>We invite you to try out ASP.NET Core OData 9 and share your feedback with us. Thank you for your continued support and contributions to the OData ecosystem.<\/p>\n<p>If you have any questions, or comments, or if you run into any issues, feel free to reach out on this blog post or report on <a href=\"https:\/\/github.com\/OData\/AspNetCoreOData\/issues\" target=\"_blank\" rel=\"noopener\">GitHub repo<\/a> for ASP.NET Core OData.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>We&#8217;re happy to announce that ASP.NET Core OData 9 has been officially released and is available on NuGet: Microsoft.AspNetCore.OData 9.0.0 The major highlight of this release is the update of the OData .NET dependencies to the 8.x major version. By updating the dependencies, we&#8217;re able to take advantage of the improvements and new capabilities introduced [&hellip;]<\/p>\n","protected":false},"author":25333,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-5733","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>We&#8217;re happy to announce that ASP.NET Core OData 9 has been officially released and is available on NuGet: Microsoft.AspNetCore.OData 9.0.0 The major highlight of this release is the update of the OData .NET dependencies to the 8.x major version. By updating the dependencies, we&#8217;re able to take advantage of the improvements and new capabilities introduced [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5733","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\/25333"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5733"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5733\/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=5733"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5733"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5733"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}