{"id":4407,"date":"2021-04-07T11:47:35","date_gmt":"2021-04-07T18:47:35","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=4407"},"modified":"2021-09-29T13:48:28","modified_gmt":"2021-09-29T20:48:28","slug":"attribute-routing-in-asp-net-core-odata-8-0-rc","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/attribute-routing-in-asp-net-core-odata-8-0-rc\/","title":{"rendered":"Attribute Routing in ASP.NET Core OData 8.0 RC"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Attribute routing is how Web API matches the incoming HTTP requests to an action based on route template attributes decorated on controller or action. ASP.NET Core defines a set of route template attributes to enable attribute routing, such as <strong>RouteAttribute<\/strong>, <strong>HttpGetAttribute<\/strong> etc. ASP.NET Core OData 8.0 RC supports these attributes to enable you to define OData attribute routing endpoints.<\/p>\n<p>In this post, I would like to share details about the changes and usages of the attribute routing in OData ASP.NET Core OData 8.0 RC. The code\nsnippets in this post are from <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/AttributeRouting80Rc\">this<\/a> sample project. Please try and let me know your thoughts.<\/p>\n<h2>No ODataRouteAttribute and ODataRoutePrefixAttribute<\/h2>\n<p>In the \u201chistory\u201d of ASP.NET Core OData, such as 6.x and 7.x version, OData <strong>attribute routing\u00a0<\/strong>uses two\u00a0attributes\u00a0to find\u00a0controller\u00a0and\u00a0action. One is <strong>ODataRoutePrefixAttribute<\/strong>, the other is\u00a0<strong>ODataRouteAttribute<\/strong>. Here&#8217;s a basic usage to define an OData attribute routing:<\/p>\n<pre class=\"lang:c# decode:true\">[ODataRoute(\"Books({key})\")]\r\npublic IActionResult Get(int key)\r\n{\r\n    \u2026\r\n}\r\n<\/pre>\n<p>In ASP.NET Core OData 8.0 RC, <strong>these two attributes are gone<\/strong>.<\/p>\n<p>Instead, OData attribute routing is changed to use ASP.NET Core route template attribute classes. ASP.NET Core has the following route template attributes:<\/p>\n<ul>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.routeattribute\">[Route]<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.httpgetattribute\">[HttpGet]<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.httppostattribute\">[HttpPost]<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.httpputattribute\">[HttpPut]<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.httpdeleteattribute\">[HttpDelete]<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.httpheadattribute\">[HttpHead]<\/a><\/li>\n<li><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.httppatchattribute\">[HttpPatch]<\/a><\/li>\n<\/ul>\n<p>Switch to use ASP.NET Core route attributes is straightforward. Here\u2019s the same OData route template using <strong>HttpGetAttribute<\/strong>:<\/p>\n<pre class=\"lang:c# decode:true\"><s>[ODataRoute(\"Books({key})\")]<\/s>\r\n[HttpGet(\"Books({key})\")]\r\npublic IActionResult Get(int key)\r\n{\r\n    \u2026\r\n}\r\n<\/pre>\n<p>Be noted, Head HTTP method is not supported yet in RC.<\/p>\n<h2>ODataRoutingAttribute<\/h2>\n<p>To enable OData attribute routing to work, either controller or action should decorate an attribute named <strong>ODataRoutingAttribute<\/strong>. [It&#8217;s renamed as <strong>ODataAttributeRoutingAttribute<\/strong> in 8.0.]<\/p>\n<p><strong>ODataRoutingAttribute<\/strong> is introduced in RC version to avoid polluting other ASP.NET Core route templates, since OData attribute routing is enabled by default if call <code>AddOData()<\/code>.<\/p>\n<p><strong>ODataRoutingAttribute<\/strong> can be used on controller and action. If we decorate it on the controller, all actions in this controller are considered as OData actions. That means the attribute routing convention will parse all routing templates as OData attribute routing. For example:<\/p>\n<pre class=\"lang:c# decode:true\">[ODataRouting]\r\npublic class HandleCustomerController : Controller\r\n{\r\n\u2026\r\n}\r\n<\/pre>\n<p>We can decorate a specific action using <strong>ODataRoutingAttribute<\/strong> as:<\/p>\n<pre class=\"lang:c# decode:true\">public class HandleOthersController : Controller\r\n{\r\n    [ODataRouting]\r\n    [HttpGet(\"odata\/Orders\/{key}\")]\r\n    public IActionResult Get(int key)\r\n    {\r\n        return Ok($\"Orders{key} from OData\");\r\n    }\r\n\r\n    [HttpGet(\"odata\/Orders({key})\")]\r\n    public IActionResult GetOrder(int key)\r\n    {\r\n        return Ok($\"Orders{key} from non-OData\");\r\n    }\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ol>\n<li><strong>Get(int key)<\/strong> is built as OData endpoint, because it\u2019s decorated using <code>[ODataRouting]<\/code> and the route template \u201cOrders\/{key}\u201d is a valid OData template.<\/li>\n<li><strong>GetOrder(int key)<\/strong> is not built as OData endpoint, it will go to normal ASP.NET Core routing.<\/li>\n<\/ol>\n<p>If you run and test using following requests:<\/p>\n<p>1) <em>GET http:\/\/localhost:5000\/odata\/Orders\/2<\/em><\/p>\n<p>The response is OData payload:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5000\/odata\/$metadata#Edm.String\",\r\n  \"value\": \"Orders2 from OData\"\r\n}\r\n<\/pre>\n<p>2) <em>GET http:\/\/localhost:5000\/odata\/Orders(3)<\/em><\/p>\n<p>The response is a plain text string:<\/p>\n<pre class=\"lang:json decode:true\">Orders3 from non-OData<\/pre>\n<h2>ODataController<\/h2>\n<p><strong>ODataController<\/strong> has <strong>ODataRoutingAttribute<\/strong> decorated as:<\/p>\n<pre class=\"lang:c# decode:true\">[ODataRouting]\r\npublic abstract class ODataController : ControllerBase\r\n{}\r\n<\/pre>\n<p>So, to create your own controller and derived it from <strong>ODataController<\/strong> is a common way to use OData attribute routing. Here is an example:<\/p>\n<pre class=\"lang:c# decode:true\">public class HandleBookController : ODataController\r\n{}\r\n<\/pre>\n<p>Starting from next section, let&#8217;s review some OData attribute routing scenarios.<\/p>\n<h2>Attribute routing using Http Verb attributes<\/h2>\n<p>The basic attribute routing scenario is to use HTTP Verb attributes directly.<\/p>\n<p>Let us have the following controller as example (<strong>Be noted<\/strong>, we use <code>ODataController<\/code> directly):<\/p>\n<pre class=\"lang:c# decode:true\">public class HandleBookController : ODataController\r\n{\r\n    [EnableQuery(PageSize = 1)]\r\n    [HttpGet(\"odata\/Books\")]\r\n    [HttpGet(\"odata\/Books\/$count\")]\r\n    public IActionResult Get()\r\n    {\r\n       return Ok(_db.Books);\r\n    }\r\n\r\n    [EnableQuery]\r\n    [HttpGet(\"odata\/Books({id})\")]\r\n    [HttpGet(\"odata\/Books\/{id}\")]\r\n    public IActionResult Get(int id)\r\n    {\r\n        return Ok(_db.Books.FirstOrDefault(c =&gt; c.Id == id));\r\n    }\r\n}\r\n<\/pre>\n<p>In the above codes, where:<\/p>\n<ul>\n<li>Each <strong>Get<\/strong> action contains two\u00a0<strong>[HttpGet]<\/strong>\u00a0attributes with route templates. Each <strong>[HttpGet]<\/strong> matches GET HTTP requests only based on the route template.<\/li>\n<li>Each route template on the first <strong>Get()<\/strong> action includes string literal only, such as, &#8220;odata&#8221;, &#8220;Books&#8221; and &#8220;$count&#8221;. Particularly, &#8220;odata&#8221; is the route prefix defined in startup.cs. &#8220;Books&#8221; is OData entity set name, and &#8220;$count&#8221; is OData count segment.<\/li>\n<li>Each route template on the\u00a0second <strong>Get(int id)<\/strong>\u00a0action includes <strong>{id}<\/strong>\u00a0route template parameter. Therefore,\u00a0the &#8220;id&#8221; value in the request is binded to <code>int id<\/code> parameter.<\/li>\n<\/ul>\n<p>Based on the preceding route templates,<\/p>\n<ul style=\"list-style-type: square;\">\n<li><em>GET ~\/odata\/Books<\/em> matches the first <strong>Get<\/strong> action.<\/li>\n<li><em>GET ~\/odata\/Books(3)<\/em> matches the second <strong>Get<\/strong> action and binds the key value 3 to the <strong>id<\/strong> parameter.<\/li>\n<\/ul>\n<p><span style=\"color: #ff0000;\"><strong>Be noted<\/strong><\/span>, don&#8217;t forget to append the route prefix when you construct the route template.<\/p>\n<h2>Attribute routing using Http Verb and Route attributes<\/h2>\n<p>We can decorate <strong>RouteAttribute<\/strong> on action and combine it with Http Verb attributes.<\/p>\n<p>Let us have the following controller as example:<\/p>\n<pre class=\"lang:c# decode:true\">public class HandleBookController : ODataController\r\n{\r\n    [Route(\"odata\/Books({key})\")]\r\n    [HttpPatch]\r\n    [HttpDelete]\r\n    public IActionResult OperationBook(int key)\r\n    {\r\n       \/\/ the return is just for test.\r\n       return Ok(_db.Books.FirstOrDefault(c =&gt; c.Id == key));\r\n    }\r\n}\r\n<\/pre>\n<p>In this controller, <strong>OperationBook(int key)<\/strong> has two HTTP Verb attributes, <strong>[HttpPatch]<\/strong> and <strong>[HttpDelete]<\/strong>. Both have <strong>null<\/strong> route template. It also has a <strong>RouteAttribute<\/strong> with route template string. Therefore, <strong>[Route(&#8220;odata\/Books({key})&#8221;)]<\/strong> is combined with patch and delete verb attributes to construct the following route template (From the <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/AttributeRouting80Rc\">sample<\/a>, you can send \u201c~\/$odata\u201d to get the following debug information):<\/p>\n<p><img decoding=\"async\" width=\"1406\" height=\"138\" class=\"wp-image-4415\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/03\/word-image.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/03\/word-image.png 1406w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/03\/word-image-300x29.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/03\/word-image-1024x101.png 1024w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2021\/03\/word-image-768x75.png 768w\" sizes=\"(max-width: 1406px) 100vw, 1406px\" \/><\/p>\n<p>Based on the route template, The Uri path <em>Patch ~\/odata\/Books(2)<\/em> can match this action and bind the key value 2 to the <strong>key<\/strong> parameter.<\/p>\n<h2>Attribute routing using RouteAttribute on controller<\/h2>\n<p>We can decorate <strong>RouteAttribute<\/strong> on the controller. The route template in [Route] attribute is combined with route template on the individual action in that controller. The\nroute template of <strong>RouteAttribute<\/strong> is prepended before route template on the action to form the final route template for that action.<\/p>\n<p>Let us have the following controller as example (be noted, I use <strong>ODataRouting<\/strong> on the controller):<\/p>\n<pre class=\"lang:c# decode:true\">[ODataRouting]\r\n[Route(\"v{version}\")]\r\npublic class HandleCustomerController : Controller\r\n{\r\n    [HttpGet(\"Customers\")]\r\n    [HttpGet(\"Customers\/$count\")]\r\n    public IActionResult Get(string version)\r\n    {\r\n        return Ok(_db.Customers);\r\n    }\r\n\r\n    [HttpGet(\"Customers\/{key}\/Default.PlayPiano(kind={kind},name={name})\")]\r\n    [HttpGet(\"Customers\/{key}\/PlayPiano(kind={kind},name={name})\")]\r\n    public string LetUsPlayPiano(string version, int key, int kind, string name)\r\n    {\r\n        return $\"[{data}], Customer {key} is playing Piano (kind={kind},name={name}\";\r\n    }\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ol>\n<li><strong>HandleCustomerController<\/strong> has <code>RouteAttribute<\/code>, its route template string \u201cv{version}\u201d is prepended to route template on each individual action.<\/li>\n<li><strong>Get(string version)<\/strong> has two <code>[HttpGet]<\/code> attributes, the route template in each [HttpGet] combines with the route template on the controller to build the following attribute routing templates:\n<ul style=\"list-style-type: square;\">\n<li>~\/v{version}\/Customers<\/li>\n<li>~\/v{version}\/Customers\/$count<\/li>\n<\/ul>\n<\/li>\n<li><strong>LetUsPlayPiano(\u2026)<\/strong> has two <code>[HttpGet]<\/code> attributes, the route template in each [HttpGet] combines with the route template on the controller to build the following attribute routing templates:\n<ul style=\"list-style-type: square;\">\n<li>~\/v{version}\/Customers\/{key}\/Default.PlayPiano(kind={kind},name={name})<\/li>\n<li>~\/v{version}\/Customers\/{key}\/PlayPiano(kind={kind},name={name})<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Based on the attribute routing templates:<\/p>\n<ul style=\"list-style-type: disc;\">\n<li>The URL path <em>&#8220;GET\u00a0~\/v2\/Customers&#8221;<\/em>\u00a0matches <code>Get(string version)<\/code>, where the value of <strong>version<\/strong> parameter is &#8220;2&#8221;.<\/li>\n<li>The URL path\u00a0<em>&#8220;GET ~\/v2\/Customers\/3\/PlayPiano(kind=4,name=&#8217;Yep&#8217;)&#8221;<\/em> matches <code>LetUsPlayPiano(version, key, kind, name)<\/code>, where <strong>version<\/strong>=&#8221;2&#8243;, <strong>key<\/strong>=3, <strong>kind<\/strong>=4 and <strong>name<\/strong>=&#8221;Yep&#8221;.<\/li>\n<\/ul>\n<h2>Multiple RouteAttribute routes<\/h2>\n<p>We can also decorate multiple <strong>RouteAttribute<\/strong> on the controller. It means that route template of each [Route] combines with each of the route template of attributes on the action methods:<\/p>\n<p>Let us have the following controller as example (be noted, I use <strong>ODataRouting<\/strong> on the action):<\/p>\n<pre class=\"lang:c# decode:true\">[Route(\"odata\")]\r\n[Route(\"v{version}\")]\r\npublic class HandleMultipleController: Controller\r\n{\r\n    [ODataRouting]\r\n    [HttpGet(\"orders\")]\r\n    public string Get(string version)\r\n    {\r\n        if (version != null)\r\n        {\r\n           return $\"Orders from version = {version}\";\r\n        }\r\n        else\r\n        {\r\n            return \"Orders from odata\";\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>So, <strong>Get(string version)<\/strong> has two attribute routing templates:<\/p>\n<ul>\n<li>GET ~\/odata\/orders<\/li>\n<li>GET ~\/v{version}\/orders<\/li>\n<\/ul>\n<p>Based on the implementation of <code>Get(string version)<\/code>, we can test it using following requests:<\/p>\n<p>1) <em>GET http:\/\/localhost:5000\/odata\/Orders<\/em><\/p>\n<p>The response is:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5000\/odata\/$metadata#Edm.String\",\r\n  \"value\":\u00a0\"Orders\u00a0from\u00a0odata\"\r\n}\r\n<\/pre>\n<p>2) <em>GET http:\/\/localhost:5000\/v2001\/Orders<\/em><\/p>\n<p>The response is:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5000\/v2001\/$metadata#Edm.String\",\r\n  \"value\":\u00a0\"Orders\u00a0from\u00a0version\u00a0=\u00a02001\"\r\n}\r\n<\/pre>\n<h2>Suppress RouteAttribute on controller<\/h2>\n<p>We can use &#8220;\/&#8221; to suppress prepending the <strong>RouteAttribute<\/strong> on controller to individual action.<\/p>\n<p>Let us have the following controller as example:<\/p>\n<pre class=\"lang:c# decode:true\">[Route(\"v{version}\")]\r\npublic class HandAbolusteController: Controller\r\n{\r\n    [ODataRouting]\r\n    [HttpGet(\"\/odata\/orders({key})\/SendTo(lat={lat},lon={lon})\")]\r\n    public string SendTo(int key, double lat, double lon)\r\n    {\r\n        return $\"Send Order({key}) to location at ({lat},{lon})\";\r\n    }\r\n}\r\n<\/pre>\n<p>Where, <strong>SendTo(\u2026)<\/strong> action has one route template as:<\/p>\n<p><strong>~\/odata\/orders({key})\/SendTo(lat={lat},lon={lon})<\/strong><\/p>\n<p>Clearly, &#8220;v{version}&#8221; in <code>[Route(\"v{version}\")]<\/code> doesn&#8217;t prepend to [HttpGet] attribute template.<\/p>\n<p><strong>Known issue<\/strong>: If we use two [Route(\u2026)] on HandAbolusteController, <strong>SendTo<\/strong> will have two selector models associated and ASP.NET Core throws ambiguous selector exception. It\u2019s a known issue and will fix in the next version.<\/p>\n<h2>Other Attributes<\/h2>\n<p>We can use <strong>[NonODataController]<\/strong> and <strong>[NonODataAction]<\/strong> to exclude certain controller or action out of attribute routing. [Both are replaced by <strong>ODataIgnoredAttribute<\/strong> in 8.0.]<\/p>\n<p>Besides, <strong>[ODataModelAttribute] (<\/strong>renamed as <strong>ODataRouteComponentAttribute<\/strong> in 8.0<strong>)<\/strong>\u00a0has no effect to attribute routing, it&#8217;s only for the conventional routing to specify the route prefix.\nIn attribute routing, we put the route prefix in the route template directly, either using [Route] attribute or prepend the route prefix before the route template, such as &#8220;odata&#8221; prefix in route template &#8220;odata\/Books&#8221;.<\/p>\n<p>We can also disable the attribute routing globally using <strong>EnableAttributeRouting<\/strong> property on <strong>ODataOptions<\/strong>.<\/p>\n<pre class=\"lang:c# decode:true\">services.AddOData(opt =&gt; opt.EnableAttributeRouting = false);\r\n<\/pre>\n<h2>Route template parser<\/h2>\n<p>As mentioned in <a href=\"https:\/\/devblogs.microsoft.com\/odata\/routing-in-asp-net-core-8-0-preview\/\">Routing in ASP.NET Core OData 8.0 Preview<\/a>, OData attribute routing is also a &#8220;conventional routing&#8221;, because the template string in the attribute should follow up the <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html\">OData URL convention<\/a>. Here&#8217;s the definition of <strong>AttributeRoutingConvention<\/strong>:<\/p>\n<pre class=\"lang:c# decode:true\">public class AttributeRoutingConvention : IODataControllerActionConvention\r\n{\r\n    public AttributeRoutingConvention(ILogger&lt;AttributeRoutingConvention&gt; logger,\r\n       IODataPathTemplateParser parser)\r\n    { ... }\r\n\r\n    public virtual int Order =&gt; -100;\r\n    \/\/ \u2026 \r\n}<\/pre>\n<p>Where,\n<code>IODataPathTemplateParser<\/code> interface is a route template parser which is how OData parses and understands the route template string.<\/p>\n<pre class=\"lang:c# decode:true\">public interface IODataPathTemplateParser\r\n{\r\n    ODataPathTemplate Parse(IEdmModel model, string odataPath, IServiceProvider requestProvider);\r\n}\r\n<\/pre>\n<p><strong>IODataPathTemplateParser<\/strong> is registered in the service provider. It can inject into the constructor of <strong>AttributeRoutingConvention<\/strong>. The default route template parser uses the built-in OData Uri parser to parse the route template path. If it can&#8217;t meet your requirement, you can create your own template parser to overwrite the default one.<\/p>\n<h2>Summary<\/h2>\n<p>Attribute routing enables you to achieve more routings by constructing basic and advanced OData routing templates. Moreover, you can mix it with conventional routing to achieve more. Again, to improve OData Web API routing is never stopped. We are still looking forward to feedbacks, requirements and concerns to improve the routing design and implementation. Please do not hesitate to try and let me know your thoughts through\u00a0<a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>. Thanks.<\/p>\n<p>Great thanks for <a href=\"mailto:jacalvar@microsoft.com\">Javier Calvarro Nelson<\/a> and <a href=\"mailto:david.fowler@microsoft.com\">David Fowler<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction Attribute routing is how Web API matches the incoming HTTP requests to an action based on route template attributes decorated on controller or action. ASP.NET Core defines a set of route template attributes to enable attribute routing, such as RouteAttribute, HttpGetAttribute etc. ASP.NET Core OData 8.0 RC supports these attributes to enable you to [&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,117],"tags":[502,1471,48],"class_list":["post-4407","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-asp-net-core","category-odata","category-webapi","tag-asp-net-core","tag-attribute-routing","tag-odata"],"acf":[],"blog_post_summary":"<p>Introduction Attribute routing is how Web API matches the incoming HTTP requests to an action based on route template attributes decorated on controller or action. ASP.NET Core defines a set of route template attributes to enable attribute routing, such as RouteAttribute, HttpGetAttribute etc. ASP.NET Core OData 8.0 RC supports these attributes to enable you to [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4407","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=4407"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4407\/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=4407"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=4407"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=4407"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}