{"id":5074,"date":"2022-12-28T12:26:54","date_gmt":"2022-12-28T19:26:54","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5074"},"modified":"2022-12-28T12:26:54","modified_gmt":"2022-12-28T19:26:54","slug":"bulk-operations-support-in-odata-web-api","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/bulk-operations-support-in-odata-web-api\/","title":{"rendered":"Bulk Operations Support in OData Web API"},"content":{"rendered":"<p>Good news! OData Web API 7.x now supports bulk operations.<\/p>\n<p>Install the most recent version of OData Web API 7.x(v7.6.3) to take advantage of bulk operations.<\/p>\n<p>A bulk operation is one that allows you to perform multiple operations (insert, update, delete) on multiple resources (of the same type) and their nested resources (to any level) with a single request.<\/p>\n<p><span data-contrast=\"none\">A bulk operation can either be:\u00a0<\/span><span data-ccp-props=\"{&quot;134233117&quot;:false,&quot;134233118&quot;:false,&quot;201341983&quot;:0,&quot;335559738&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/p>\n<ol>\n<li>A deep update<\/li>\n<li>A deep insert<\/li>\n<\/ol>\n<h5><strong>Deep Update<\/strong><\/h5>\n<p>A deep update is a patch request. The following is an example of a deep update request:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">PATCH http:\/\/localhost:6285\/odata\/Customers\r\nHost: host \r\nContent-Type: application\/json\r\nPrefer: odata.include-annotations=\"*\"\r\n\r\n{\r\n  \"@odata.context\": \"http:\/\/localhost:6285\/odata\/Customers\/$delta\",\r\n  \"value\": [\r\n    {\r\n      \"Id\": 1,\r\n      \"Name\": \"Customer1\",\r\n      \"Orders@odata.delta\": [\r\n        {\r\n          \"Id\": 1,\r\n          \"Price\": 10.24,\r\n          \"Quantity\": 10\r\n        },\r\n        {\r\n          \"Id\": 2,\r\n          \"Price\": 11.54,\r\n          \"Quantity\": 21\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"Id\": 2,\r\n      \"Name\": \"Customer2\",\r\n      \"Orders@odata.delta\": [\r\n        {\r\n          \"@odata.id\": \"Orders(1)\",\r\n          \"Price\": 14\r\n        },\r\n        {\r\n          \"@odata.removed\": {\r\n            \"reason\": \"changed\"\r\n          },\r\n          \"Id\": 2\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}<\/code><\/pre>\n<p>How the above request will be processed:<\/p>\n<ol>\n<li><span data-contrast=\"auto\">If the 2 customers exist in the server, they will be updated, otherwise, they will be created.\u202f\u202f<\/span><span data-ccp-props=\"{&quot;134233117&quot;:false,&quot;134233118&quot;:false,&quot;201341983&quot;:0,&quot;335559738&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<li><span data-contrast=\"auto\">The orders for the customers will also be created or updated depending on whether they exist or not.\u202f\u202f<\/span><span data-ccp-props=\"{&quot;134233117&quot;:false,&quot;134233118&quot;:false,&quot;201341983&quot;:0,&quot;335559738&quot;:0,&quot;335559739&quot;:0,&quot;335559740&quot;:240}\">\u00a0<\/span><\/li>\n<li>If Customer(1) has two orders (Orders(1) and Orders(2)), they will be updated; otherwise, they will be created.<\/li>\n<li>Orders(1) will be linked to Customer(2), and its Price will be updated (notice the use of <strong>@odata.id<\/strong>.).<\/li>\n<li>Orders(2) will be delinked from Customer(2) for Customer(2). (Take note of the use of <strong>@odata.removed<\/strong>.).<\/li>\n<li>@odata.delta informs OData Web API that the provided orders list is for updates (updates in this case refer to: an update, a create or a delete). If the @odata.delta annotation is not added to the navigation property, a replace operation will be performed. By replace, I mean that the payload will replace all related entities of the type with what is on the payload.<\/li>\n<\/ol>\n<p><strong>NOTE: <\/strong>The <strong>@odata.contex<\/strong>t annotation with the correct context URL must be included in the request body. The <strong>$delta<\/strong> segment must be present at the end of the context URL. Additionally, the request body must include an array-valued property named &#8216;value&#8217; that contains the objects to be created, updated, or deleted.<\/p>\n<h5><strong>Model Classes<\/strong><\/h5>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public class Customer\r\n{\r\n    [Key]\r\n    public int Id { get; set; }\r\n    public String Name { get; set; }\r\n    public int Age { get; set; }\r\n    public List&lt;Order&gt; Orders { get; set; }\r\n}\r\n\r\npublic class Order\r\n{\r\n    [Key]\r\n    public int Id { get; set; }\r\n    public int Price { get; set; }\r\n    public int Quantity { get; set; }\r\n    public Customer Customer { get; set; }\r\n}<\/code><\/pre>\n<p>How will the controller action method look like?<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">[HttpPatch]\r\n[ODataRoute(\"Customers\")]\r\npublic IActionResult Patch([FromBody] DeltaSet&lt;Customer&gt; customers)\r\n{\r\n    var retuncol = customers.Patch(new CustomerPatchHandler(this._db), new APIHandlerFactory(Request.GetModel(), this._db));\r\n    this._db.SaveChanges();\r\n    return Ok(retuncol);\r\n}<\/code><\/pre>\n<p>Note the use of <strong><code class=\"language-cs language-csharp\">DeltaSet, CustomerPatchHandler<\/code><\/strong> and <strong><code class=\"language-cs language-csharp\">APIHandlerFactory<\/code> <\/strong>in the above code segment.<\/p>\n<p>A DeltaSet&lt;T&gt; is a collection of Delta&lt;T&gt;.<\/p>\n<p>CustomerPatchHandler and APIHandlerFactory are OData API Handlers.<\/p>\n<p><strong>OData API Handlers.<\/strong><\/p>\n<p>OData API Handlers are a new construct introduced in OData Web API 7.x that allows the library to interact with the CRUD operations logic of developers.<\/p>\n<h5><strong>How will this happen?\u00a0<\/strong><\/h5>\n<p>In OData Web API 7.x, we added two abstract classes from which developers must derive if they want to benefit from the use of OData API Handlers in carrying out various update operations on their resources: These are the classes:<\/p>\n<h5><strong> <span class=\"TextRun SCXW71684174 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"auto\"><span class=\"NormalTextRun SpellingErrorV2Themed SCXW71684174 BCX8\" data-ccp-parastyle=\"paragraph\" data-ccp-parastyle-defn=\"{&quot;ObjectId&quot;:&quot;00000000-0000-0000-0000-000000000000|0&quot;,&quot;ClassId&quot;:1073872969,&quot;Properties&quot;:[469775450,&quot;paragraph&quot;,201340122,&quot;2&quot;,134233614,&quot;true&quot;,469778129,&quot;paragraph&quot;,335572020,&quot;1&quot;,469777841,&quot;Times New Roman&quot;,469777842,&quot;Times New Roman&quot;,469777843,&quot;Times New Roman&quot;,469777844,&quot;Times New Roman&quot;,469769226,&quot;Times New Roman&quot;,268442635,&quot;24&quot;,335559740,&quot;240&quot;,201341983,&quot;0&quot;,134233118,&quot;true&quot;,134233117,&quot;true&quot;,469778324,&quot;Normal&quot;]}\">ODataAPIHandlerFactory<\/span><\/span><\/strong><\/h5>\n<p>A sneak peek at how this class will look:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public abstract class ODataAPIHandlerFactory\r\n{\r\n    protected ODataAPIHandlerFactory(IEdmModel model)\r\n    {\r\n        Model = model;\r\n    }\r\n\r\n    public IEdmModel Model { get; }\r\n    public abstract IODataAPIHandler GetHandler(ODataPath odataPath);\r\n}<\/code><\/pre>\n<ol>\n<li>The <strong>GetHandler<\/strong> method accepts an <strong>ODataPath<\/strong> and returns an <strong>OData API Handler<\/strong> for patching the various resources for that specific type. The developer must return an <strong>OData API Handler<\/strong> based on the path segment in the <strong>ODataPath<\/strong> that is being processed.<\/li>\n<\/ol>\n<h5><strong><span class=\"TextRun Highlight SCXW20412006 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"auto\"><span class=\"NormalTextRun SpellingErrorV2Themed SCXW20412006 BCX8\" data-ccp-charstyle=\"pl-token\" data-ccp-charstyle-defn=\"{&quot;ObjectId&quot;:&quot;00000000-0000-0000-0000-000000000000|0&quot;,&quot;ClassId&quot;:1073872969,&quot;Properties&quot;:[469775450,&quot;pl-token&quot;,201340122,&quot;1&quot;,134233614,&quot;true&quot;,469778129,&quot;pl-token&quot;,335572020,&quot;1&quot;,469778324,&quot;Default Paragraph Font&quot;]}\">ODataAPIHandler<\/span><\/span><span class=\"TextRun Highlight SCXW20412006 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"none\"><span class=\"NormalTextRun SCXW20412006 BCX8\">&lt;<\/span><\/span><span class=\"TextRun Highlight SCXW20412006 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"auto\"><span class=\"NormalTextRun SpellingErrorV2Themed SCXW20412006 BCX8\" data-ccp-charstyle=\"pl-en\" data-ccp-charstyle-defn=\"{&quot;ObjectId&quot;:&quot;00000000-0000-0000-0000-000000000000|0&quot;,&quot;ClassId&quot;:1073872969,&quot;Properties&quot;:[469775450,&quot;pl-en&quot;,201340122,&quot;1&quot;,134233614,&quot;true&quot;,469778129,&quot;pl-en&quot;,335572020,&quot;1&quot;,469778324,&quot;Default Paragraph Font&quot;]}\">TStructuralType<\/span><\/span><span class=\"TextRun Highlight SCXW20412006 BCX8\" lang=\"EN-US\" xml:lang=\"EN-US\" data-contrast=\"none\"><span class=\"NormalTextRun SCXW20412006 BCX8\">&gt;<\/span><\/span><span class=\"EOP SCXW20412006 BCX8\" data-ccp-props=\"{}\">\u00a0<\/span><\/strong><\/h5>\n<p>A sneak peek at how this class will look:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public abstract class ODataAPIHandler&lt;TStructuralType&gt;: IODataAPIHandler where TStructuralType : class\r\n{\r\n    public abstract ODataAPIResponseStatus TryCreate(IDictionary&lt;string, object&gt; keyValues, out TStructuralType createdObject, out string errorMessage);\r\n\r\n    public abstract ODataAPIResponseStatus TryGet(IDictionary&lt;string, object&gt; keyValues, out TStructuralType originalObject, out string errorMessage);\r\n\r\n    public abstract ODataAPIResponseStatus TryDelete(IDictionary&lt;string, object&gt; keyValues, out string errorMessage);\r\n\r\n    public abstract ODataAPIResponseStatus TryAddRelatedObject(TStructuralType resource, out string errorMessage);\r\n\r\n    public abstract IODataAPIHandler GetNestedHandler(TStructuralType parent, string navigationPropertyName);\r\n}<\/code><\/pre>\n<p>Let&#8217;s take a look at some of the methods in this class and how developers may implement them:<\/p>\n<ul>\n<li><strong>TryCreate<\/strong> &#8211; This method accepts key-value pairs representing the key property values of the object to be created. If the key values are not included in the payload, then create an empty object and return it. The library will populate the object&#8217;s properties with payload values.<\/li>\n<\/ul>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">    public override ODataAPIResponseStatus TryCreate(\r\n         IDictionary&lt;string, object&gt; keyValues, \r\n         out Customer createdObject, \r\n         out string errorMessage)\r\n     {\r\n         createdObject = null;\r\n         errorMessage = string.Empty;\r\n\r\n         try\r\n         {\r\n              createdObject = new Employee();\r\n              dbContext.Customers.Add(createdObject);\r\n\r\n              return ODataAPIResponseStatus.Success;\r\n          }\r\n          catch (Exception ex)\r\n          {\r\n              errorMessage = ex.Message;\r\n\r\n              return ODataAPIResponseStatus.Failure;\r\n          }\r\n      }<\/code><\/pre>\n<ul>\n<li><strong>TryGet<\/strong> &#8211; This method accepts key-value pairs that represent the object&#8217;s key property values. Use these key values to retrieve and return the object from your data store. If the object is found, the returned object will be updated with any new values from the payload. If no object is returned, the TryCreate method is called by the library.<\/li>\n<\/ul>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">    public override ODataAPIResponseStatus TryGet(\r\n        IDictionary&lt;string, object&gt; keyValues,\r\n        out Customer originalObject,\r\n        out string errorMessage)\r\n    {\r\n        ODataAPIResponseStatus status = ODataAPIResponseStatus.Success;\r\n        errorMessage = string.Empty;\r\n        originalObject = null;\r\n\r\n        try\r\n        {\r\n            var id = keyValues[\"ID\"].ToString();\r\n            originalObject = dbContext.Customers.First(x =&gt; x.ID == Int32.Parse(id));\r\n\r\n            if (originalObject == null)\r\n            {\r\n                status = ODataAPIResponseStatus.NotFound;\r\n            }\r\n\r\n        }\r\n        catch (Exception ex)\r\n        {\r\n            status = ODataAPIResponseStatus.Failure;\r\n            errorMessage = ex.Message;\r\n        }\r\n\r\n        return status;\r\n    }<\/code><\/pre>\n<ul>\n<li><strong>TryDelete<\/strong> &#8211; This method accepts key-value pairs that represent the object&#8217;s key property values. The developer provides logic for deleting the object using the provided keys.<\/li>\n<\/ul>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">    public override ODataAPIResponseStatus TryDelete(\r\n        IDictionary&lt;string, object&gt; keyValues,\r\n        out string errorMessage)\r\n    {\r\n        errorMessage = string.Empty;\r\n\r\n        try\r\n        {\r\n            var id = keyValues.First().Value.ToString();\r\n            var customer = dbContext.Customers.First(x =&gt; x.ID == Int32.Parse(id));\r\n\r\n            dbContext.Customers.Remove(customer);\r\n\r\n            return ODataAPIResponseStatus.Success;\r\n        }\r\n        catch (Exception ex)\r\n        {\r\n            errorMessage = ex.Message;\r\n\r\n            return ODataAPIResponseStatus.Failure;\r\n        }\r\n    }<\/code><\/pre>\n<ul>\n<li><strong>TryAddRelatedObject &#8211;<\/strong>This method will include logic for linking one object to another. Assume we want to associate an order with a specific customer.<\/li>\n<\/ul>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">    public override ODataAPIResponseStatus TryAddRelatedObject(\r\n        Order resource, \r\n        out string errorMessage)\r\n    {\r\n        errorMessage = string.Empty;\r\n\r\n        try\r\n        {\r\n            parent.Orders.Add(resource);\r\n\r\n            return ODataAPIResponseStatus.Success;\r\n        }\r\n        catch (Exception ex)\r\n        {\r\n            errorMessage = ex.Message;\r\n\r\n            return ODataAPIResponseStatus.Failure;\r\n        }\r\n    }<\/code><\/pre>\n<ul>\n<li><strong>GetNestedHandler<\/strong> &#8211; This method will include logic for returning the appropriate handler for processing the nested resources.<\/li>\n<\/ul>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">    public override IODataAPIHandler GetNestedHandler(\r\n        Customer parent,\r\n        string navigationPropertyName)\r\n    {\r\n        switch (navigationPropertyName)\r\n        {\r\n            case \"Orders\":\r\n                return new OrdersHandler(parent);\r\n            default:\r\n                return null;\r\n        }\r\n    }<\/code><\/pre>\n<p><strong>NOTE:<\/strong> The code samples provided above show how to implement the various methods. You are free to provide your own logic for implementing these methods.<\/p>\n<p><strong>Important things to note:\u00a0<\/strong><\/p>\n<p>If one of the operations in a bulk-operation fails, the processing does not stop. It continues on error, but a <strong>DataModificationException<\/strong> is created for the failed operation, with a compensating action. In such a case, the returned response will contain the DataModificationExceptions for the failed operations, along with their compensating actions, and the created or updated objects for the successful operations.<\/p>\n<p>Compensating actions ensure that the server state is always in sync with the local state.<\/p>\n<ol>\n<li>A delete compensating action will be created if a create operation fails. This ensures that if the object was created locally, it must be deleted because it was not created on the server.<\/li>\n<li>A create compensating action is created if a delete operation fails.<\/li>\n<li>If an update operation fails, the response will contain the original object that was to be updated on the server.<\/li>\n<\/ol>\n<p><strong>NOTE: Prefer: odata.include-annotations=&#8221;*&#8221;<\/strong> must be included in the request headers for the DataModificationExceptions annotations to be included in the response.<\/p>\n<p>An example of a response to a bulk operation request:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n  \"@context\": \"http:\/\/localhost:6285\/odata\/$metadata#Customers\/$delta\",\r\n  \"value\": [\r\n    {\r\n      \"Id\": 1,\r\n      \"Name\": \"Customer1\",\r\n      \"Age\": 0,\r\n      \"Orders@delta\": [\r\n        {\r\n          \"Id\": 1005,\r\n          \"Price\": 40,\r\n          \"Quantity\": 1005\r\n        },\r\n        {\r\n          \"Id\": 2000,\r\n          \"Price\": 200,\r\n          \"Quantity\": 90\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"Id\": 2,\r\n      \"Name\": \"Customer2\",\r\n      \"Age\": 0,\r\n      \"Orders@delta\": [\r\n        {\r\n          \"@removed\": {\r\n            \"reason\": \"changed\"\r\n          },\r\n          \"@id\": \"http:\/\/localhost:6285\/odata\/Orders(10)\",\r\n          \"@Core.DataModificationException\": {\r\n            \"@type\": \"#Org.OData.Core.V1.DataModificationExceptionType\"\r\n          },\r\n          \"Id\": 10,\r\n          \"Price\": 0,\r\n          \"Quantity\": 0\r\n        },\r\n        {\r\n          \"@removed\": {\r\n            \"reason\": \"changed\"\r\n          },\r\n          \"@id\": \"http:\/\/localhost:6285\/odata\/Orders(1004)\",\r\n          \"Id\": 1004,\r\n          \"Price\": 0,\r\n          \"Quantity\": 0\r\n        },\r\n        {\r\n          \"Id\": 12,\r\n          \"Price\": 12,\r\n          \"Quantity\": 12\r\n        },\r\n        {\r\n          \"Id\": 13,\r\n          \"Price\": 13,\r\n          \"Quantity\": 13\r\n        },\r\n        {\r\n          \"Id\": 1003,\r\n          \"Price\": 999,\r\n          \"Quantity\": 99\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}<\/code><\/pre>\n<h5><strong>Deep Insert\u00a0<\/strong><\/h5>\n<p>A deep insert is a post request. Here&#8217;s an example of a deep insert request:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">POST http:\/\/localhost:6285\/odata\/Customers\r\nHost: host \r\nContent-Type: application\/json\r\n<\/code><code class=\"language-json\">{\r\n  \"Id\": 1,\r\n  \"Name\": \"Customer1\",\r\n  \"Orders\": [\r\n    {\r\n      \"@odata.id\": \"Customers(3)\/Orders(1005)\"\r\n    },\r\n    {\r\n      \"Id\": 2000,\r\n      \"Price\": 200,\r\n      \"Quantity\": 90\r\n    }\r\n  ]\r\n}<\/code><\/pre>\n<p>In the above request we want to:<\/p>\n<ol>\n<li>Create a new Customer with an id of 1.<\/li>\n<li>Link an existing Order(1005) to customer 1.<\/li>\n<li>Create a new Order with id 2000 and link it to customer with id 1.<\/li>\n<\/ol>\n<h5><strong>Important things to note:\u00a0<\/strong><\/h5>\n<ol>\n<li>In a deep insert, the <strong>@odata.id<\/strong> is only used to reference a related object. We cannot update the related object&#8217;s properties as in a deep update.<\/li>\n<li>In order to get the referenced object in the controller action method when using <strong>@odata.id<\/strong> in a deep insert, you must include an ODataIdContainer property in your model class. Example:<\/li>\n<\/ol>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public class Order\r\n{\r\n    [Key]\r\n    public int Id { get; set; }\r\n    public int Price { get; set; }\r\n    public int Quantity { get; set; }\r\n    public Customer Customers { get; set; }\r\n    public ODataIdContainer Container { get; set; }\r\n}<\/code><\/pre>\n<p>The <strong>Container<\/strong> property will hold the referenced object.<\/p>\n<p>How will the controller look like?<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">[HttpPost]\r\n[ODataRoute(\"Customers\")]\r\npublic IActionResult Post([FromBody] Customer customer)\r\n{\r\n    return Created(customer);\r\n}<\/code><\/pre>\n<h5><strong>Known Issues:\u00a0<\/strong><\/h5>\n<p>The nested navigation properties that were included in the request payload are not present in the response from a deep insert. There is still some work to be done in order to return the correct response for a deep insert. The response should include the top-level resource&#8217;s nested navigation properties up to the level of nesting specified in the request payload. In the preceding example, the response should include the newly created customer as well as the two orders that were either linked or created and linked to the newly created customer. This is not the case right now.<\/p>\n<p>Users should be able to use the OData API Handlers to perform insertion and linking of related objects operations in a deep insert, but that part is currently missing. It will be taken into account in our next release. In the meantime, developers can implement their own logic.<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\"><\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Good news! OData Web API 7.x now supports bulk operations. Install the most recent version of OData Web API 7.x(v7.6.3) to take advantage of bulk operations. A bulk operation is one that allows you to perform multiple operations (insert, update, delete) on multiple resources (of the same type) and their nested resources (to any level) [&hellip;]<\/p>\n","protected":false},"author":12390,"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-5074","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata"],"acf":[],"blog_post_summary":"<p>Good news! OData Web API 7.x now supports bulk operations. Install the most recent version of OData Web API 7.x(v7.6.3) to take advantage of bulk operations. A bulk operation is one that allows you to perform multiple operations (insert, update, delete) on multiple resources (of the same type) and their nested resources (to any level) [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5074","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\/12390"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5074"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5074\/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=5074"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5074"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5074"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}