{"id":32031,"date":"2021-03-03T08:00:15","date_gmt":"2021-03-03T15:00:15","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/dotnet\/?p=32031"},"modified":"2021-03-03T14:43:18","modified_gmt":"2021-03-03T21:43:18","slug":"f-and-f-tools-update-for-visual-studio-16-9","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/dotnet\/f-and-f-tools-update-for-visual-studio-16-9\/","title":{"rendered":"F# and F# tools update for Visual Studio 16.9"},"content":{"rendered":"<p>We&#8217;re excited to announce updates to the F# tools for Visual Studio 16.9. Since the <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/announcing-f-5\/\">F# 5 release last November<\/a>, we&#8217;ve been hard at work to improve the F# tools experience in Visual Studio. I&#8217;ll cover the major improvements made by category:<\/p>\n<ul>\n<li>.NET 5 scripting for Visual Studio<\/li>\n<li>New productivity features for Visual Studio<\/li>\n<li>Tooling performance and responsiveness improvements<\/li>\n<li>Core compiler improvements<\/li>\n<\/ul>\n<p>There&#8217;s a lot to cover, so strap in!<\/p>\n<h2>But first, we need your feedback!<\/h2>\n<p>Are you using F# and Visual Studio today? If so, <strong>we want your feedback!<\/strong><\/p>\n<p>\u00bb <a href=\"https:\/\/www.surveymonkey.com\/r\/JXXG96D\" rel=\"noopener\" target=\"_blank\"><strong>Take the F# and Visual Studio tools survey<\/strong><\/a><\/p>\n<h2>.NET 5 scripting for Visual Studio<\/h2>\n<p>Visual Studio 16.9 introduces .NET 5 scripting and a .NET 5-based FSI. To turn it on, visit <strong>tools &gt; options &gt; F# Tools &gt; F# Interactive &gt; Use .NET Core Scripting<\/strong>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/fs-tools.png\" alt=\"F# tools options to enable .NET 5 scripting\" \/><\/p>\n<p>If you already had the F# Interactive window open, you&#8217;ll need to close and re-open it. Now you can use Visual Studio for modern .NET scripting tasks and pull in package dependencies that aren&#8217;t compatible with .NET Framework.<\/p>\n<p>With this setting turned on, F# Interactive will use <code>dotnet fsi<\/code> to process F# code, and all F# scripts will behave as if they will run on .NET Core. This means a few things:<\/p>\n<ul>\n<li>Packages that require <code>netstandard2.1<\/code>, <code>netcoreapp3.1<\/code>, or <code>net5.0<\/code> will now work with <code>#r \"nuget:...\"<\/code> in F# Interactive from Visual Studio<\/li>\n<li>The default set of libraries available to your scripts will be .NET 5-based, not .NET Framework-based<\/li>\n<li>Any scripts that require .NET Framework to run (e.g., depend on AppDomains) may not work when this is used<\/li>\n<\/ul>\n<p>For now, you&#8217;ll need to turn this on to get the behavior. In a future update, we will make this the default and require anyone who does .NET Framework-based scripting to flip the setting.<\/p>\n<h2>New F# productivity features<\/h2>\n<p>Visual Studio 16.9 comes with a new kind of signature\/parameter help mode and 14 new code fixes.<\/p>\n<h3>Signature Help for F# function calls<\/h3>\n<p>IntelliSense has a feature called Signature Help (sometimes called Parameter Help) for when you&#8217;re calling something and passing in a parameter. For F#, this has always been available for method calls whenever pressing the <code>(<\/code> key.<\/p>\n<p>Now, Signature Help can trigger when you&#8217;re calling an F# function! This is a long-requested feature by F# developers. F# function paramters are separated by spaces, so to trigger Signature Help, press the <code>space<\/code> key. Then you&#8217;ll see a similar tooltip, but this time for the F# function signature:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/sighelp-2.png\" alt=\"F# function application signature help on List.map\" \/><\/p>\n<p>The feature is also aware of how the number of already-applied arguments can change depending on if you&#8217;re using pipelines like <code>|&gt;<\/code> or <code>||&gt;<\/code>. Here&#8217;s the same function in Signature Help, except it&#8217;s being used in a pipeline where the last argument (the list) is already being applied in the pipeline:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/sighelp-3.png\" alt=\"F# function application signature help on List.map but pipelined\" \/><\/p>\n<p>As you can see, the second argument to <code>List.map<\/code> is no longer in the tooltip. That&#8217;s because it&#8217;s already applied by the pipeline! Signature Help is pipeline-aware.<\/p>\n<h3>14 new code fixes<\/h3>\n<p>We identified several common scenarios where errors or warnings in routine F# coding could occur where those errors or warnings could actually be resolved by Visual Studio. Many of these are motivated by mistakes that newcomers to F# can make, but some are common even for F# experts. Let&#8217;s go through them all!<\/p>\n<h4>Add missing <code>rec<\/code> keyword<\/h4>\n<p>If you&#8217;re missing a <code>rec<\/code> keyword in a recursively-defined function, Visual Studio will offer to add it:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-2.png\" alt=\"F# codefix for missing rec keyword\" \/><\/p>\n<p>If you&#8217;re defining a mutually recursive definition and missing the <code>rec<\/code> keyword, Visual Studio will offer to add it:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-1.png\" alt=\"F# codefix for missing rec keyword in mutually recursive definition\" \/><\/p>\n<h4>Convert C# lambda to F# lambda<\/h4>\n<p>If you&#8217;re familiar with C# and accidentaly type in an C# lambda expression in F#, Visual Studio can detect it and rewrite it to work:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-3.png\" alt=\"F# codefix when using C# lambda syntax\" \/><\/p>\n<p>Thanks to <a href=\"https:\/\/github.com\/baronfel\">Chet Husk<\/a> for the original implementation.<\/p>\n<h4>Add missing <code>fun<\/code> keyword in F# lambda<\/h4>\n<p>Another lambda fix, if you forget to type the <code>fun<\/code> keyword, Visual Studio can detect that and add it for you:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-4.png\" alt=\"F# codefix for forgetting the fun keyword\" \/><\/p>\n<h4>Remove incorrect use of <code>return<\/code> keyword<\/h4>\n<p>The <code>return<\/code> keyword has special meaning when used in <a href=\"https:\/\/docs.microsoft.com\/dotnet\/fsharp\/language-reference\/computation-expressions\">Computation Expressions<\/a> but is otherwise not used in F#. For beginners coming from other languages where you need to use the <code>return<\/code> keyword to indicate you&#8217;re returning something from a function or method, this might be confusing at first. If you use it when you don&#8217;t need to, Visual Studio will suggest to remove it:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-5.png\" alt=\"F# codefix for removing unecessary return keyword\" \/><\/p>\n<h4>Convert record expression to anonymous record<\/h4>\n<p>Anonymous records use the <code>{|<\/code> and <code>|}<\/code> brace pair when you construct an instance. Named records don&#8217;t have the <code>|<\/code> characters. In the case where you construct a record but it&#8217;s not known, Visual Studio will suggest to use Anonymous Record syntax, since you might have forgotted the <code>|<\/code> parts of the syntax:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-6.png\" alt=\"F# codefix for converting bad record construction to anonymous record call\" \/><\/p>\n<h4>Use mutation syntax when a value is mutable<\/h4>\n<p>F# uses the <code>&lt;-<\/code> symbol to mutate a value, but if you&#8217;re coming from a language where <code>=<\/code> is used for assignment and mutation, you might have muscle memory to use <code>=<\/code>. In various circumstances this will emit a warning, and so Visual Studio can offer a code fix to use <code>&lt;-<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-7.png\" alt=\"F# codefix for using mutation syntax when a value is mutable\" \/><\/p>\n<h4>Make a declaration mutable<\/h4>\n<p>If you&#8217;re using <code>&lt;-<\/code> to mutate a value but it&#8217;s not already declared as <code>mutable<\/code>, Visual Studio will offer to change its declaration:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-8.png\" alt=\"F# codefix for changing a value declaration to be mutable when using mutation syntax\" \/><\/p>\n<h4>Use upcast instead of downcast<\/h4>\n<p>If you&#8217;re downcasting a value that can&#8217;t be downcast (either via the <code>:?&gt;<\/code> operator or the <code>downcast<\/code> keyword), Visual Studio will suggest an upcast:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-9.png\" alt=\"F# codefix for changing an incorrect downcast into an upcast\" \/><\/p>\n<h4>Add missing <code>=<\/code> to record, union, or type alias definition<\/h4>\n<p>If you&#8217;re defining a record type, union type, or type alias and forget to type the <code>=<\/code> keyword, Visual Studio will suggest to add one:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-10.png\" alt=\"F# codefix for adding a missing equals to a type definition\" \/><\/p>\n<h4>Use single <code>=<\/code> for equality check<\/h4>\n<p>F# uses a single <code>=<\/code> to perform an equality check. Many other languages use <code>==<\/code>. When you&#8217;re using one for an equality check, Visual Studio will suggest to switch to <code>=<\/code> so your code can compile:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-11.png\" alt=\"F# codefix for using single equals instead of double equals in an equality check\" \/><\/p>\n<h4>Use <code>not<\/code> to negate an expression<\/h4>\n<p>F# uses the <code>not<\/code> operator to negate a boolean expression. Many other languages use <code>!<\/code>. If you&#8217;re using <code>!<\/code>, which has a different meaning in F#, Visual Studio will suggest to switch to <code>not<\/code>:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-12.png\" alt=\"F# codefix for using the 'not' keyword instead of '!' to negate an expression\" \/><\/p>\n<h4>Wrap expression in parentheses<\/h4>\n<p>There are several scenarios where you need to use parentheses to disambiguate your code. Visual Studio can detect several of these and offer to wrap an expression in parentheses so that your code will compile:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-13.png\" alt=\"F# codefix for wrapping an expression in parentheses\" \/><\/p>\n<p>Not every possible case is covered yet. There are a surprisingly large amount of tricky ways that the error this is triggered off of can manifest. We hope to increase the scope of this code fix over time.<\/p>\n<h4>Fix ambiguity between subtraction and negation<\/h4>\n<p>In F#, the code <code>values.Length -1<\/code> and <code>values.Length - 1<\/code> are considered ambiguous. This is because the compiler isn&#8217;t sure if you&#8217;re trying to pass <code>-1<\/code> to <code>values.Length<\/code> or if you&#8217;re subtracting <code>1<\/code> from <code>values.Length<\/code>. We found that people who start with F# can struggle with this issue, so Visual Studio will offer a code fix to change the code to subtraction:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/codefix-14.png\" alt=\"F# codefix for wrapping an expression in parentheses\" \/><\/p>\n<h3>Each of the 14 code fixes are also in Visual Studio Code<\/h3>\n<p>Thanks to <a href=\"https:\/\/github.com\/baronfel\">Chet Husk<\/a>, the <a href=\"https:\/\/marketplace.visualstudio.com\/items?itemName=Ionide.Ionide-fsharp\">Ionide plugin for Visual Studio Code<\/a> (the best way to write F# code in Visual Studio Code) has every one of these code fixes.<\/p>\n<h2>Tooling performance and responsiveness improvements<\/h2>\n<p>For the past several Visual Studio releases, we&#8217;ve been chipping away at F# tooling performance for large solutions. This release is no different!<\/p>\n<h3>Improved responsiveness for most IDE features<\/h3>\n<p>This release features an adjustment to the F# language service. To summarize, the F# language service distinguishes between two kinds of requests:<\/p>\n<ol>\n<li>A request for data about the structure (syntax) of F# code<\/li>\n<li>A request for data about the meaning (semantics) of F# code<\/li>\n<\/ol>\n<p>The first kind of request doesn&#8217;t rely on the F# typechecker, and for over two years now these requests have been free-threaded. That is, if a background thread was busy typechecking user code, it can just use a different background thread to get a parse tree and calculate some information based on that data.<\/p>\n<p>The second kind of request is serial because it relies on the F# typechecker. Because F# uses type inference, changes to your source code could impact the types of anything else that comes after it throughout your entire project or solution. For example, adding a new case to a Union Type or changing the output type of a highly-used function can have downstream effects across your entire codebase. A consequence of this behavior is that F# tooling features that work with typechecked data are impacted by the F# compiler needing to typecheck things.<\/p>\n<p>However, in the large majority of cases, changing the types of things doesn&#8217;t actually change everything downstream! So-called &#8220;stale&#8221; data is almost always correct, and the F# language service has a caching system that can determine if things are already up-to-date or not. In light of this, we changed how these requests for some tooling features are processed by the F# language service:<\/p>\n<ol>\n<li>If there are up to date caches of typecheck info to use, just use the data there and don&#8217;t wait for a background typecheck operation<\/li>\n<li>If there are no up to date caches available, or a cache is deemed out of date, perform a typecheck operation first<\/li>\n<li>If the request comes from a type provider requesting to update its types, this remains serialized because they must always be up to date<\/li>\n<\/ol>\n<p>The result is increased responsiveness for tooltips, IntelliSense, and other features when working in a large codebase. For smaller codebases, things were likely already fairly responsive and so you might not notice a whole lot. If you are using Type Providers, then things will still behave as before.<\/p>\n<h3>Big performance gains for codebases with F# signature files<\/h3>\n<p><a href=\"https:\/\/docs.microsoft.com\/dotnet\/fsharp\/language-reference\/signature-files\">F# signature files<\/a> are files that define a public API surface area that a backing implementation file implements. They can be helpful in &#8220;locking down&#8221; an API so that consumers can only see additional things if they are added to the signature file.<\/p>\n<p>If you&#8217;re unfamiliar with them, here&#8217;s an example of what a signature file and implementation file pair can look like:<\/p>\n<p><strong>mymath.fsi:<\/strong><\/p>\n<pre><code class=\"fsharp\">module MyMath\r\n\r\n\/\/\/ Adds two numbers, x and y.\r\nval add2: x: int -&gt; y: int -&gt; int\r\n<\/code><\/pre>\n<p><strong>mymath.fs:<\/strong><\/p>\n<pre><code class=\"fsharp\">module MyMath\r\n\r\nlet add2 x y = x + y\r\n<\/code><\/pre>\n<p>This is a highly simplified example, but it demonstrates how the <em>signature<\/em> of the <code>add2<\/code> function is explicitly represented in a file.<\/p>\n<p>If you are working in a large codebase, we <strong>highly recommend<\/strong> creating signature files (<code>.fsi<\/code>) for each of your implementation files (<code>.fs<\/code>). This will ensure that APIs exposed throughout your codebase have the shape you intend them to have. And starting with Visual Studio 16.9, there are two major performance improvements when using them.<\/p>\n<p>First, we apply an optimization that goes &#8220;up&#8221; a dependency hierarchy. Consider the following project order:<\/p>\n<pre><code class=\"text\">Project1\r\n|__file1.fsi\r\n|__file1.fs\r\n|__file2.fsi\r\n|__file3.fs\r\n<\/code><\/pre>\n<p>If you are working in <code>file3.fs<\/code>, so long as the signature files <code>file1.fsi<\/code> and <code>file2.fsi<\/code> are unchanged, the F# compiler will re-use the typecheck information it has about them. This is because the constructs they make available to <code>file3.fs<\/code> via their signatures are unchanged, so there is no need to typecheck the implementation files that back them. As a result, the F# language service will spend a lot less time typechecking things, making any IDE features that rely on typecheck tools faster.<\/p>\n<p>Secondly, now consider a scenario where you are working in <code>file1.fs<\/code>. If you edit code there but you don&#8217;t make any updates to <code>file1.fsi<\/code>, then the only file that the compiler will typecheck is <code>file1.fs<\/code>. This is because the signature hasn&#8217;t changed. The rest of the project will still be considered up to date. As a result, the IDE will spend a lot less time typechecking things &#8220;down&#8221; the dependency heirarchy. In other words, it&#8217;s less work typechecking and more work processing typecheck data for IDE features that use it!<\/p>\n<p>The <a href=\"https:\/\/github.com\/dotnet\/fsharp\">F# codebase<\/a> makes use of signature files mostly in the <code>FSharp.Compiler.Service<\/code> project, which is an enormous project with over 100k lines of F# code and the difference with these changes is significant for us. We hope you&#8217;ll notice improvements too!<\/p>\n<p>In the future, we&#8217;re going to look into ways to make it easier to generate signature files from existing implementation files within Visual Studio.<\/p>\n<h2>Core compiler improvements<\/h2>\n<p>Finally, the Visual Studio 16.9 release comes with several compiler improvements that can help your code editing experience.<\/p>\n<h3>Warnings for incorrect XML documentation files are turned on by default<\/h3>\n<p>In F# 5, we introduced a new warning that validated XML documentation:<\/p>\n<ul>\n<li>Checks for any malformed tags<\/li>\n<li>Checks that parameter names in the XML doc match the parameter names in code<\/li>\n<li>Checks that XML docs don&#8217;t refer to a parameter that is missing in a function or method signature<\/li>\n<\/ul>\n<p>For example, here&#8217;s a malformed XMl documentation comment with a closing tag that&#8217;s missing the <code>\/<\/code> character:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/xml-1.png\" alt=\"F# xml doc with wrong summary tag\" \/><\/p>\n<p>And here&#8217;s an example of emitting warnings for incorrect parameter documentation:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/dotnet\/wp-content\/uploads\/sites\/10\/2021\/02\/xml-2.png\" alt=\"F# xml doc with wrong parameter tag\" \/><\/p>\n<p>We&#8217;ve gotten feedback from library authors that this warning was very helpful to them, so we turned it on by default.<\/p>\n<h3>Warnings for mismatched parameter names between signature and implementation files<\/h3>\n<p>We also turned on another warning to ensure that parameter names in signature files and implementation files are correct. Consider the following signature:<\/p>\n<pre><code class=\"fsharp\">val myFunction: x: int -&gt; y: int -&gt; int\r\n<\/code><\/pre>\n<p>If the backing implementation file has an implementation for <code>myFunction<\/code> doesn&#8217;t define <code>x<\/code> and <code>y<\/code> as its parameter names, the F# compiler will emit a warning.<\/p>\n<h3>Improved runtime performance for code making heavy use of closures<\/h3>\n<p>Consider the following F# code:<\/p>\n<pre><code class=\"fsharp\">let mutable a = 0\r\nlet mutable res = 0\r\n\r\nlet f () = a &lt;- a + 1; (fun x -&gt; x + 1)\r\n\r\nlet g() = \r\n   for i in 0 .. 10000 do\r\n      for j in 0 .. i do\r\n        res &lt;- f() res\r\n\r\ng()\r\n<\/code><\/pre>\n<p>This code will now run approximately twice as fast as before. The reason is because the F# compiler used to reallocate certain kinds of closures at runtime each time they were invoked. The compiler will now avoid doing that in most cases.<\/p>\n<h2>Looking forward<\/h2>\n<p>There&#8217;s more tooling and compiler improvements we&#8217;re looking to invest in. Some concrete tooling improvements we&#8217;re already looking into include:<\/p>\n<ul>\n<li>Showing decompiled sources when you invoke Go to Definition on a construct not in your codebase<\/li>\n<li>Support for Inline Type Hints when pressing a key command<\/li>\n<li>Support for Inline Parameter Name Hints when pressing a key command<\/li>\n<li>Improved F# Signature File generation from within Visual Studio<\/li>\n<li>Support loading packages that depend on framework references in F# Interactive<\/li>\n<\/ul>\n<p>We&#8217;re also planning out what the next version of the F# language will include to coincide with the .NET 6 release. We hope to have some exciting things to share in the coming months.<\/p>\n<p>Stay tuned, and happy F# coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Visual Studio 16.9 comes with significant updates to the F<\/p>\n","protected":false},"author":678,"featured_media":31655,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[685],"tags":[4,73,7227],"class_list":["post-32031","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-dotnet","tag-net","tag-f","tag-f-tools"],"acf":[],"blog_post_summary":"<p>Visual Studio 16.9 comes with significant updates to the F<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/32031","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\/678"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/comments?post=32031"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/posts\/32031\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media\/31655"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/media?parent=32031"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/categories?post=32031"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/dotnet\/wp-json\/wp\/v2\/tags?post=32031"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}