{"id":1834,"date":"2013-02-06T16:21:24","date_gmt":"2013-02-06T16:21:24","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/webdev\/2013\/02\/06\/protect-your-queryable-api-with-the-validation-feature-in-asp-net-web-api-odata\/"},"modified":"2013-02-06T16:21:24","modified_gmt":"2013-02-06T16:21:24","slug":"protect-your-queryable-api-with-the-validation-feature-in-asp-net-web-api-odata","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/protect-your-queryable-api-with-the-validation-feature-in-asp-net-web-api-odata\/","title":{"rendered":"Protect your Queryable API with the validation feature in ASP.NET Web API OData"},"content":{"rendered":"<p>In the previous blog <a href=\"http:\/\/blogs.msdn.com\/b\/webdev\/archive\/2013\/01\/29\/getting-started-with-asp-net-webapi-odata-in-3-simple-steps.aspx\">post<\/a>, you can see how easy it is to enable OData query syntax for a particular action using Web API OData. Simply add a Queryable attribute to your action as follows, and you are done. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>It not only works for those actions using OData format, but also applies to any vanilla web api actions using other formats such as JSON.NET. It greatly reduces the need to write a lot of actions to perform operations like top, skip, orderby as well as filter. It is so powerful that it is almost hard to imagine building a real Web API application without it.<\/p>\n<h3>Now everything works beautifully, why validation?<\/h3>\n<p>As great as this may sound, adding Queryable to your action could expose your service to a DOS attack. A very complex query could take a long time to complete and burn many CPU cycles, with the unwanted side effect of blocking access to the entire service. For example, sorting a large set of records with a non indexed column could take a long time. So it makes sense to restrict the properties used in $orderby.<\/p>\n<p>Also keep in mind if your queryable action is only exposed within your organization, you are probably fine given all your requests come from trusted clients. However, as soon as you expose your queryable action outside your organization, you must take extra caution. One way to protect your service is to validate your query and reject the bad ones upfront.&#160; <\/p>\n<p>In short, if your action returns a large set of data, and your action can be accessed by untrusted clients, it is strongly recommended that you add a layer of defense to your service using the new validation feature. In this blog post, I will go through a few common user scenarios and show you how your queryable action can be protected. <\/p>\n<h3>Basic Scenarios<\/h3>\n<p>Starting with the <a href=\"http:\/\/www.asp.net\/vnext\">Web API OData RC release<\/a>, we have added some really convenient properties on QueryableAttribute to help with validating the incoming queries. Let us go through them one by one. <\/p>\n<p><strong>Scenario 1: Only allow $top and $skip<\/strong><\/p>\n<p>A lot of time, all you want to support is client driven paging. In that case, you can limit your action to only allow query that contains $top and $skip. None of the advanced features in $orderby or $filter matter to you. This can be easily done using an simple property called AllowedQueryOptions. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(AllowedQueryOptions = AllowedQueryOptions.Skip | AllowedQueryOptions.Top)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now with this, a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$top=2&amp;$skip=3\">http:\/\/localhost\/api\/workitems\/?$top=2&amp;$skip=3<\/a> works fine, but a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$orderby=Description\">http:\/\/localhost\/api\/workitems\/?$orderby=Description<\/a> or <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=Id eq 123\">http:\/\/localhost\/api\/workitems\/?$filter=Id eq 123<\/a> will receive a response of 400 Bad Request. <\/p>\n<p><strong>Scenario 2: Limit the value for $top to 100<\/strong><\/p>\n<p>In the client driven paging scenario, sometimes the server might want to limit the maximum number of records that the client wants to request using $top. You can use MaxTop property in this case. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(MaxTop = 100)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$top=120\">http:\/\/localhost\/api\/workitems\/?$top=120<\/a> will fail since it exceeds the limit of 100. <\/p>\n<p><strong>Scenario 3: Limit the value for $skip to 200<\/strong>&#160;<\/p>\n<p>Similar to the previous scenario, server wants to limit the maximum number of records to be skipped. You can use MaxSkip property to achieve that.<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(MaxSkip = 200)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p> Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$skip=220\">http:\/\/localhost\/api\/workitems\/?$skip=220<\/a> will fail since it exceeds the limit of 200.<\/p>\n<p><strong>Scenario 4:<\/strong> <b>Only want to order the results by Id property, nothing else<\/b><\/p>\n<p>A lot of times people only want to order the results with a fixed number of properties. Order by any arbitrary properties could be slow and unwanted. Now you have a simple way to do that using AllowedOrderbyProperties.<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(AllowedOrderByProperties = <span>&quot;Id&quot;<\/span>)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$orderby=Description\">http:\/\/localhost\/api\/workitems\/?$orderby=Description<\/a> will fail since it can\u2019t be ordered by Description property. <\/p>\n<p><strong>Scenario 5: Only need eq comparison in $filter<\/strong><\/p>\n<p>$filter is one of the most powerful query options you can apply to the results. If you know that your trusted clients only uses equal comparison inside the $filter. You should validate that as well using AllowedLogicalOperators. Here is how you can do it. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(AllowedLogicalOperators = AllowedLogicalOperators.Equal)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=Id&nbsp;eq 100\">http:\/\/localhost\/api\/workitems\/?$filter=Id eq 100<\/a> will succeed, while a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=Id gt 100\">http:\/\/localhost\/api\/workitems\/?$filter=Id gt 100<\/a> will fail since it does not allow gt. <\/p>\n<p><strong>Scenario 6: Do not need any of the arithmetic operations in $filter<\/strong><\/p>\n<p>OData URL convention supports a lot of convenient arithmetic operations. However, it is possible your scenario don\u2019t need to use any of those. Now you can turn that off by setting AllowedArithmeticOperators to None. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(AllowedArithmeticOperators = AllowedArithmeticOperators.None)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=Votes\">http:\/\/localhost\/api\/workitems\/?$filter=Votes<\/a> gt 20 will succeed, while a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=Votes\">http:\/\/localhost\/api\/workitems\/?$filter=Votes<\/a> mul 2 gt 20 will fail since this action does not allow multiplication, which is one of the arithmetic operations. <\/p>\n<p><strong>Scenario 7: Only need to use StartsWith function in $filter<\/strong><\/p>\n<p>One of the reasons $filter is powerful is that it supports a lot of useful functions. You can read the whole list at the <a href=\"http:\/\/www.odata.org\/documentation\">official odata spec<\/a>. Now oftentimes you only use very few functions, so again you can limit that as well using AllowedFunctions property.<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [Queryable(AllowedFunctions = AllowedFunctions.StartsWith)]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=startswith(Name\">http:\/\/localhost\/api\/workitems\/?$filter=startswith(Name<\/a>, \u2018A\u2019) will succeed, while a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter\">http:\/\/localhost\/api\/workitems\/?$filter<\/a>=length(Name) lt 10 will fail because this action does not allow length function.&#160; <\/p>\n<h3>Advanced Scenarios<\/h3>\n<p>If you can\u2019t restrict the queries via those out of box properties described in the previous section, then you are NOT out of luck. You can use our extensibility point to add your own validation logic easily.<\/p>\n<p><strong>Scenario 8: How to customize default validation logic for $skip, $top, $orderby, $filter<\/strong><\/p>\n<p>The actual validation logic for those four query options can be extended by overriding their validator classes respectively. That includes TopQueryValidator, SkipQueryValidator, OrderbyQueryValidator and FilterQueryValidator. <\/p>\n<p>The FilterQueryValidator in particular has a lot of virtual methods to help you walk through the Abstract Syntax Tree ( AST ) that is being generated by processing the incoming request. <\/p>\n<p>Here is an example of how you can restrict properties used inside $filter. First you override the existing FilterQueryValidator and override the ValidateSingleValuePropertyAccessNode method so you can examine the PropertyAccessNode, and throw if its Name property has something you don\u2019t like to see.<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span>         <span>public <\/span><span>class<\/span> RestrictiveFilterByQueryValidator : FilterQueryValidator<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span>         {   <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>             <span>public<\/span> <span>override<\/span> <span>void<\/span> ValidateSingleValuePropertyAccessNode(SingleValuePropertyAccessNode propertyAccessNode, ODataValidationSettings settings)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>             {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>                 <span>\/\/ Validate if we are accessing some sensitive property of WorkItem, such as Votes<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>                 <span>if<\/span> (propertyAccessNode.Property.Name == <span>&quot;Votes&quot;<\/span>)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>                 {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>                     <span>throw<\/span> <span>new<\/span> ODataException(<span>&quot;Filter with Votes is not allowed.&quot;<\/span>);<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>                 }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum11\">  11:<\/span>                 <span>base<\/span>.ValidateSingleValuePropertyAccessNode(propertyAccessNode, settings);<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum12\">  12:<\/span>             }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum13\">  13:<\/span>         }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Then you can wire your custom validator up via a custom QueryableAttribute. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> <span>public<\/span> <span>class<\/span> MyQueryableAttribute : QueryableAttribute<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     <span>public<\/span> <span>override<\/span> <span>void<\/span> ValidateQuery(System.Net.Http.HttpRequestMessage request, ODataQueryOptions queryOptions)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>         <span>if<\/span> (queryOptions.Filter != <span>null<\/span>)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>         {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>             queryOptions.Filter.Validator = <span>new<\/span> RestrictiveFilterByQueryValidator();<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>         }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span>         <span>base<\/span>.ValidateQuery(request, queryOptions);<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum11\">  11:<\/span>     }<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum12\">  12:<\/span> }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>The last step is to use your custom QueryableAttribute to decorate your action.<\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> [MyQueryable]<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> <span>public<\/span> IQueryable&lt;WorkItem&gt; Get(<span>int<\/span> projectId)<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now a request to <a href=\"http:\/\/localhost\/api\/workitems\/?$filter=Votes\">http:\/\/localhost\/api\/workitems\/?$filter=Votes<\/a> eq 20 will fail as the custom validation does not like filtering using Votes property. <\/p>\n<p><strong>Scenario 9: Use ODataQueryOptions to validate the query only <\/strong><\/p>\n<p>One of the hidden jewels in Web API OData queryable support is this low level API called ODataQueryOptions. If you want to have your own fine control over when the query option is validated or when the query is applied. You can use this instead of QueryableAttribute. Here is how your action will look like with ODataQueryOptions manually applied and your action will return the actual count of the queryable result. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> <span>public<\/span> <span>int<\/span> GetCount(ODataQueryOptions&lt;WorkItem&gt; queryOptions)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     <span>\/\/ Validate<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     queryOptions.Validate(<span>new<\/span> ODataValidationSettings() { AllowedQueryOptions = AllowedQueryOptions.Top | AllowedQueryOptions.Skip });<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>     <span>\/\/ you can apply your query without using queryOptions at all <\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>     IQueryable&lt;WorkItem&gt; result = queryOptions.ApplyTo(originalResult) <span>as<\/span> IQueryable&lt;WorkItem&gt;;<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>     <span>return<\/span> result.Count();<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span> }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>Now you can see one can easily call Validate without calling ApplyTo in the above example. And you can retrieve the raw query value as string and implement your own ApplyTo logic. <\/p>\n<p>In this world, if you have a custom validator, you can also set it on queryOptions directly. Here is how that could look like. <\/p>\n<div id=\"codeSnippetWrapper\">\n<div id=\"codeSnippet\">\n<pre><span id=\"lnum1\">   1:<\/span> <span>public<\/span> <span>int<\/span> Get(ODataQueryOptions&lt;WorkItem&gt; queryOptions)<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum2\">   2:<\/span> {<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum3\">   3:<\/span>     <span>\/\/ Validate using custom validator<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum4\">   4:<\/span>     queryOptions.Filter.Validator = <span>new<\/span> RestrictiveFilterByQueryValidator();<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum5\">   5:<\/span>     queryOptions.Validate(<span>new<\/span> ODataValidationSettings());<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum6\">   6:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum7\">   7:<\/span>     <span>\/\/ you can apply your query without using queryOptions<\/span><\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum8\">   8:<\/span>     IQueryable&lt;WorkItem&gt; result = queryOptions.ApplyTo(originalResult) <span>as<\/span> IQueryable&lt;WorkItem&gt;;<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum9\">   9:<\/span>&#160; <\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum10\">  10:<\/span>     <span>return<\/span> result.Count();<\/pre>\n<p><!--CRLF--><\/p>\n<pre><span id=\"lnum11\">  11:<\/span> }<\/pre>\n<p><!--CRLF--><\/div>\n<\/div>\n<p>To conclude, ASP.NET Web API OData has not only made it super easy to enable OData query syntax on your actions, it also provides you with tools to help protect your queryable actions with upfront validation so that undesired and untrusted queries will be rejected. For more information on ASP.NET Web API OData security, please visit <a href=\"http:\/\/go.microsoft.com\/fwlink\/?LinkId=279712\">here<\/a>. Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In the previous blog post, you can see how easy it is to enable OData query syntax for a particular action using Web API OData. Simply add a Queryable attribute to your action as follows, and you are done. 1: [Queryable] 2: public IQueryable&lt;WorkItem&gt; Get(int projectId) It not only works for those actions using OData [&hellip;]<\/p>\n","protected":false},"author":447,"featured_media":58792,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[197],"tags":[],"class_list":["post-1834","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-aspnet"],"acf":[],"blog_post_summary":"<p>In the previous blog post, you can see how easy it is to enable OData query syntax for a particular action using Web API OData. Simply add a Queryable attribute to your action as follows, and you are done. 1: [Queryable] 2: public IQueryable&lt;WorkItem&gt; Get(int projectId) It not only works for those actions using OData [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1834","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/users\/447"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=1834"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/1834\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/58792"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=1834"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=1834"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=1834"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}