{"id":78863,"date":"2018-05-22T10:53:35","date_gmt":"2018-05-22T10:53:35","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=78863"},"modified":"2021-06-03T16:46:56","modified_gmt":"2021-06-03T16:46:56","slug":"razor-redux","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/razor-redux\/","title":{"rendered":"Razor Redux"},"content":{"rendered":"<p>ASP.NET Core 2 reintroduced Razor. If this humble markup <a href=\"https:\/\/weblogs.asp.net\/scottgu\/introducing-razor\">first exposed in 2010<\/a> allowing developers to embed C# and VB.NET into web pages failed to impress, the redesigned version just might. Imagine all the things developers adored about <a href=\"https:\/\/www.asp.net\/web-forms\">Web Forms<\/a>, <a href=\"https:\/\/www.asp.net\/mvc\">Model-View-Controller (MVC)<\/a>, and data-bound templating rolled up into one technology.<\/p>\n<p>This article provides a brief Razor introduction that might convince skeptical .NET developers to consider it for their next web application.<\/p>\n<h2>MVC Backgrounder<\/h2>\n<p>Readers unfamiliar with ASP.NET\u2019s implementation of the Model-View-Controller (MVC) may find this brief overview helpful before continuing with this article. MVC is an architectural pattern splitting a web application page into three components: model, view, and controller. Models capture the application domain, such as, user name information. Views display the model, again, exposing the user name as an <a href=\"https:\/\/en.wikipedia.org\/wiki\/HTML\">HTML<\/a> label in the upper left corner. Controllers manage the interaction between view and model.<\/p>\n<p>This loose coupling of model, view, and controller facilitates their individual development. For example, the view\u2019s design does not necessarily impact its associated model. The separation also simplifies unit testing for each of the components.<\/p>\n<p>Implementing MVC-based applications is not without its challenges. Some find it unnecessarily more complex than the Web Forms-based predecessor. Nonetheless, coding an MVC view with Razor markup seems easier than a Web Forms page. In all likelihood, this ease inspired the new Razor \u2013 markup sans model and controller components.<\/p>\n<h2>Getting Started<\/h2>\n<p>An ASP.NET Core 2 Razor-based web application requires installing the <a href=\"https:\/\/github.com\/dotnet\/core\/blob\/master\/release-notes\/download-archives\/2.0.0-download.md\">.NET Core 2.0 SDK<\/a> and upgrading to <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore\">Microsoft.AspNetCore 2.0.0<\/a>. Rather than explicitly installing AspNetCore directly, Visual Studio 2017 users may find upgrading to version <a href=\"https:\/\/www.visualstudio.com\/en-us\/news\/releasenotes\/vs2017-relnotes\">15.4+<\/a> a preferable option after installing the SDK. Upgrading Visual Studio exposes several new web project templates.<\/p>\n<p>Once the prerequisites are in place, create a new ASP.NET Core 2.0 project.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"786\" height=\"513\" class=\"wp-image-78864\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/word-image-83.png\" \/><\/p>\n<p>After selecting the highlighted template option, Visual Studio creates the simple project which is entitled <em>RazorDemo<\/em> as shown in the Solution Explorer window.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"416\" height=\"587\" class=\"wp-image-78865\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/word-image-84.png\" \/><\/p>\n<p>Like most Visual Studio templated projects, building and executing <em>RazorDemo<\/em> requires minimum effort on the part of the developer. Perhaps the most fascinating aspect of this starter project resides in the <em>Startup.cs<\/em> method <strong>ConfigureServices<\/strong>. Razor pages rely on the MVC framework even if they don\u2019t explicitly demand its expected components, such as, models and controllers.<\/p>\n<pre class=\"lang:c# theme:vs2012\">public void ConfigureServices(IServiceCollection services)\r\n{\r\n\tservices.AddMvc();\r\n}<\/pre>\n<p>Razor by convention assumes a <em>Pages<\/em> folder exists to house its web pages. These <em>cshtml<\/em> (or <em>vbhtml<\/em> for the VB.NET persuasion) files allow for two variations: markup only and code + markup.<\/p>\n<h2>A Tale of Two Pages<\/h2>\n<p>Razor supports two fundamental implementations of a web page. The first type is somewhat simple, an example in your project is <em>Error.cshtml<\/em>, which includes only markup. The second involves code and markup, for example <em>Index.cshtml<\/em> and <em>Index.cshtml.cs<\/em>.<\/p>\n<h3>Markup Only<\/h3>\n<p>Adding a static Razor page via Visual Studio is simple. It begins by right clicking on the project folder of interest and clicking <em>Add<\/em> followed by <em>New Item<\/em> and clicking <em>Razor Page<\/em>. This sequence, which might vary some between Visual Studio versions, brings up the following dialog.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"586\" height=\"291\" class=\"wp-image-78866\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/word-image-85.png\" \/><\/p>\n<p>Inspecting this newly added page highlights the simplicity of a basic markup-only file. While it may look similar to the View found in an MVC implementation, the mark up differs. The Razor keyword <strong>page<\/strong> stands out as the salient element. This enables page requests without a supporting model or controller while exploiting the popular Razor constructs.<\/p>\n<pre class=\"lang:c# theme:vs2012\">@page\r\n@{\r\n    Layout = null;\r\n}\r\n&lt;!DOCTYPE html&gt;\r\n&lt;html&gt;\r\n&lt;head&gt;\r\n    &lt;meta name=\"viewport\" content=\"width=device-width\" \/&gt;\r\n    &lt;title&gt;MarkupOnly&lt;\/title&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n@section Scripts {\r\n    @{await Html.RenderPartialAsync(\"_ValidationScriptsPartial\");}\r\n}\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<div class=\"note\">\n<p><em>Note: This discussion does not dwell on <\/em><a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/mvc\/views\/razor\">markup syntax<\/a><em> for a good reason \u2013 it didn\u2019t dramatically change. The most impressive aspect of the new Razor is everything other than the markup!<\/em><\/p>\n<\/div>\n<p>Developers may also add Razor pages by right clicking <em>Add<\/em> and selecting <em>Add Razor Page\u2026<\/em> at the top of the presented item list. This option provides help for incorporating the Entity Framework.<\/p>\n<p>Lastly, if a developer selects the <em>Razor View<\/em> when selecting a <em>New Item,<\/em> the added <em>cshtml<\/em> page may disappoint. It will contain a warning that the project needs MVC enabled. Count that as a gentle reminder that <em>Razor View<\/em> and <em>Razor Page<\/em> differ.<\/p>\n<h3>Code + Markup<\/h3>\n<p>The second type of Razor page contains a markup file with a Web Forms like code-behind file. <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/microsoft.aspnetcore.mvc.razorpages.pagemodel?view=aspnetcore-2.0\"><strong>PageModel<\/strong><\/a>, which resembles a hybrid MVC controller, provides the glue linking the two. Creating such a page is as simple as the markup only version except that you must check Generate <strong>PageModel <\/strong>class.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"586\" height=\"291\" class=\"wp-image-78867\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/c-users-tom-fischer-appdata-local-microsoft-windo.png\" alt=\"C:\\Users\\tom.fischer\\AppData\\Local\\Microsoft\\Windows\\INetCache\\Content.Word\\Add CodeMarkup Page.png\" \/><\/p>\n<p>The markup file created when adding the Razor page, <em>CodeMarkup.cshtml<\/em>, closely resembles the markup only type with one critical difference. After <strong>page<\/strong>, another Razor keyword, <strong>model<\/strong>, follows declaring the markup\u2019s supporting class <strong>RazorDemo.Pages.CodeMarkupModel<\/strong> which inherits from <strong>PageModel<\/strong>.<\/p>\n<pre class=\"lang:c# theme:vs2012\">@page\r\n@model RazorDemo.Pages.CodeMarkupModel\r\n@{\r\n    Layout = null;\r\n}\r\n&lt;!DOCTYPE html&gt;\r\n&lt;html&gt;\r\n&lt;head&gt;\r\n    &lt;meta name=\"viewport\" content=\"width=device-width\" \/&gt;\r\n    &lt;title&gt;CodeMarkup&lt;\/title&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n@section Scripts {\r\n    @{await Html.RenderPartialAsync(\"_ValidationScriptsPartial\");}\r\n}\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>The Code-behind <strong>CodeMarkupModel<\/strong> class reveals both the simplicity and power of the new Razor. Exploiting it only requires learning a few concepts. Razor code-behind pages do not equate to the older Web Forms like sounding construct. <strong>PageModel<\/strong> more closely resembles an MVC controller.<\/p>\n<h2>PageModel Basics<\/h2>\n<p>Developers familiar with ASP.NET MVC projects will find fully functional Razor-based web pages easy to implement. This article explores these essentials via a sample order editing page.<\/p>\n<h3>Requirements<\/h3>\n<p>We satisfy our limited ambitions with a web page to edit an order as shown below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"824\" height=\"591\" class=\"wp-image-78868\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/word-image-86.png\" \/><\/p>\n<p>This simple page allows highlighting a few new and many old features in Razor and MVC.<\/p>\n<h3>Preliminaries<\/h3>\n<p>Implementing the page only requires a few steps before coding:<\/p>\n<ol>\n<li>Add a Razor page via Visual Studio entitled <em>OrderEdit<\/em> with\n<ul>\n<li><em>Generate PageModel class<\/em> checked<\/li>\n<li><em>Use a layout page:<\/em> checked with the <em>_Layout.cshtml<\/em> value<\/li>\n<\/ul>\n<\/li>\n<li>Add the following using statements to the new <strong>OrderEditModel<\/strong> (<em>OrderEdit.cshtml.cs<\/em>)\n<ul>\n<li><em>Microsoft.Extensions.Caching.Memory<\/em> for temporarily maintaining state<\/li>\n<li><em>System.ComponentModel.DataAnnotations<\/em> for validation support<\/li>\n<\/ul>\n<\/li>\n<li>Add <em>services.AddMemoryCache()<\/em> to <em>ConfigureServices<\/em> enabling the in-memory cache<\/li>\n<\/ol>\n<p>Time to code.<\/p>\n<h3>[BindProperty]<\/h3>\n<p>This attribute dramatically alters how user input reaches the server compared to most MVC applications. Instead of passing an object via an action for server-side processing, it leverages two-way data binding. Readers familiar with <a href=\"https:\/\/en.wikipedia.org\/wiki\/Model%E2%80%93view%E2%80%93viewmodel\">Model\u2013View\u2013ViewModel (MVVM)<\/a>, another architecture pattern combining the strengths of MVC with the supporting framework\u2019s data binding features, will be comfortable with <em>[BindProperty]<\/em>.<\/p>\n<div class=\"note\">\n<p><em>Note: For those still wishing to pass state the old, MVC way, don\u2019t fret. It\u2019s still possible, read Dino Esposito\u2019s <\/em><a href=\"https:\/\/www.red-gate.com\/simple-talk\/dotnet\/asp-net\/improved-model-binding-asp-net-core\/\">Improvements to Model Binding in ASP.NET Core<\/a><em> to get started.<\/em><\/p>\n<\/div>\n<p><strong>OrderEditModel <\/strong>begins with the definition of <strong>OrderInformation<\/strong>. This simple model-like class houses all the properties expected to be passed between client and server. Once defined, the markup page can naturally access order information via the <strong>OrderInformation<\/strong> typed <strong>Order<\/strong> property.<\/p>\n<pre class=\"lang:c# theme:vs2012\">using System;\r\nusing Microsoft.AspNetCore.Mvc;\r\nusing Microsoft.Extensions.Caching.Memory;\r\nusing System.ComponentModel.DataAnnotations; \r\nusing Microsoft.AspNetCore.Mvc.RazorPages;\r\nnamespace RazorDemo.Pages\r\n{\r\n    public class OrderEditModel : PageModel\r\n    {        \r\n        public class OrderInformation\r\n        {\r\n            [Required]\r\n            [StringLength(6)]\r\n            public string OrderId { get; set; }\r\n            [Required(ErrorMessage=\"Customer name is required.\")]\r\n            public string CustomerName { get; set; }\r\n            [Range(0, 1000.00, ErrorMessage=\"Refunds not allowed.\")]\r\n            public decimal Sale { get; set; }\r\n        }\r\n               \r\n        [BindProperty]\r\n        public OrderInformation Order { get; set; }\r\n        [BindProperty]\r\n        public DateTime DisplayDate { get; set; }<\/pre>\n<p>With all data-bound properties defined, accessing them on <em>OrderEdit.cshtml<\/em> view proves equally simple with a little help from <strong>asp-for<\/strong> and<strong> asp-validation-for<\/strong> as shown next.<\/p>\n<pre class=\"lang:c# theme:vs2012\">&lt;html&gt;\r\n&lt;head&gt;\r\n    &lt;meta name=\"viewport\" content=\"width=device-width\" \/&gt;\r\n    &lt;title&gt;Order Edit&lt;\/title&gt;\r\n&lt;\/head&gt;\r\n&lt;body&gt;\r\n    @section Scripts {\r\n        @{await Html.RenderPartialAsync(\"_ValidationScriptsPartial\");}\r\n    }\r\n    &lt;h1&gt;Order Edit&lt;\/h1&gt;\r\n    &lt;div class=\"panel-primary\"&gt;\r\n        &lt;<strong>form<\/strong> method=\"post\" class=\"form-group\"&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n                &lt;label class=\"control-label\"&gt;Order Id&lt;\/label&gt;\r\n                &lt;<strong>input<\/strong> readonly <strong>asp-for<\/strong>=\"Order.OrderId\" class=\"form-control\" \/&gt;\r\n            &lt;\/div&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n                &lt;label class=\"control-label\"&gt;Customer Name&lt;\/label&gt;\r\n                &lt;<strong>input<\/strong> <strong>asp-for<\/strong>=\"Order.CustomerName\" class=\"form-control\" \/&gt;\r\n                &lt;<strong>span<\/strong> <strong>asp-validation-for<\/strong>=\"Order.CustomerName\"&gt;&lt;\/<strong>span<\/strong>&gt;\r\n            &lt;\/div&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n                &lt;label class=\"control-label\"&gt;Sale Amount&lt;\/label&gt;\r\n                &lt;<strong>input<\/strong> <strong>asp-for<\/strong>=\"Order.Sale\" class=\"form-control\" \/&gt;\r\n                &lt;<strong>span<\/strong> <strong>asp-validation-for<\/strong>=\"Order.Sale\" class=\"has-error\"&gt;&lt;\/<strong>span<\/strong>&gt;\r\n            &lt;\/div&gt;\r\n            &lt;div class=\"form-group\"&gt;\r\n                &lt;button class=\"btn btn-primary\" name=\"update\" \t\t\t\t\t     \t\t\ttype=\"submit\"&gt;Update&lt;\/button&gt;               \r\n            &lt;\/div&gt;\r\n            &lt;div class=\"info\"&gt;Date: @Model.DisplayDate&lt;\/div&gt;\r\n        &lt;\/<strong>form<\/strong>&gt;\r\n    &lt;\/div&gt;\r\n&lt;\/body&gt;\r\n&lt;\/html&gt;<\/pre>\n<p>Also note the minimal effort required to expose <strong>DisplayDate<\/strong>. All it required was applying <strong>@<\/strong>, another Razor keyword.<\/p>\n<h3>Handlers<\/h3>\n<p>Razor handles (no pun intended) the expected HTTP verbs via default naming conventions:<\/p>\n<ul>\n<li>OnGet\/OnGetAsync<\/li>\n<li>OnPost\/OnPostAsync<\/li>\n<li>OnDelete\/OnDeleteAsync<\/li>\n<li>OnPut\/OnPutAsync<\/li>\n<li>OnPatch\/OnPatchAsync<\/li>\n<\/ul>\n<p>It also supports named handlers via <strong>On&lt;HTTP Verb&gt;&lt;Custom Handler Name&gt;<\/strong> syntax with or without an <strong>Async<\/strong> suffix. And if that\u2019s not enough, consider rolling your own with a <a href=\"https:\/\/github.com\/aspnet\/Mvc\/blob\/de389226011fb15140ee0a8d6a2429b2eb943f3a\/src\/Microsoft.AspNetCore.Mvc.RazorPages\/Internal\/DefaultPageApplicationModelProvider.cs\">DefaultPageApplicationModelProvider<\/a>.<\/p>\n<p>Before inspecting the two handlers, the code-behind class constructor loads an instance of the Microsoft in-memory cache via built-in dependency management. <strong>_cache<\/strong> courteously remembers your order edits as you click about.<\/p>\n<pre class=\"lang:c# theme:vs2012\">        private IMemoryCache _cache;\r\n        public OrderEditModel(IMemoryCache memoryCache)\r\n        {\r\n            _cache = memoryCache;\r\n            const string testDefaultOrderId = \"XYZ123\";\r\n            if (_cache.TryGetValue(testDefaultOrderId, out OrderInformation order))\r\n            {\r\n                Order = order;\r\n            }\r\n            else\r\n            {\r\n\t\t  \/\/ Here's where we\u2019d retrieve real data, but for now...\r\n                Order = new OrderInformation\r\n                {\r\n                    CustomerName = \"John Smith\",\r\n                    OrderId = testDefaultOrderId,\r\n                    Sale = new decimal(99.99)}\r\n                ;\r\n                _cache.Set(Order.OrderId, Order);\r\n            }            \r\n        }\r\n        public void OnGet()\r\n        {\r\n            DisplayDate = DateTime.Now;\r\n        }\r\n        public void OnPost()\r\n        {\r\n            if (ModelState.IsValid)\r\n            {\r\n                _cache.Set(Order.OrderId, Order);\r\n                DisplayDate = DateTime.Now;\r\n            }\r\n        }\r\n    }\r\n}<\/pre>\n<p>Thru the power of convention, Razor pages default any GET to <strong>OnGet<\/strong> or <strong>OnGetAsync<\/strong>, along with any POST <strong>OnPost <\/strong>or <strong>OnPostAsync<\/strong>. <em>OrderEdit.cshtml <\/em>neither contains nor requires explicit references to its supporting <strong>OnGet<\/strong> or <strong>OnPost<\/strong> methods.<\/p>\n<h2>Authorization<\/h2>\n<p>While authorization remains intuitive, Razor operates with its own API. Adding the following snippet to the Startup\u2019s <strong>ConfigureServices<\/strong> method demonstrates one such page authorization arrangement.<\/p>\n<pre class=\"lang:c# theme:vs2012\">    services.AddMvc()\r\n        .AddRazorPagesOptions(options =&gt;\r\n        {\r\n            options.Conventions.AuthorizePage(\"\/CustomerEdit\");\r\n            options.Conventions.AuthorizeFolder(\"\/OrderManagement\")\r\n                .AllowAnonymousToPage(\"\/OrderMangement\/Search\");\r\n            options.Conventions.AllowAnonymousToPage(\"\/Index\");\r\n        });<\/pre>\n<p>Page level authorization possibilities remain. Fortunately, the API leverages many of the expected names and behaviors.<\/p>\n<h2>Almost Like MVC<\/h2>\n<p>As most likely apparent by now, Razor lives within the MVC framework. Our sample page leveraged several of its features: <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/startup\">application startup<\/a>, <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/dependency-injection\">dependency injection<\/a>, <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/fundamentals\/middleware?tabs=aspnetcore2x\">middleware management<\/a>, <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/mvc\/views\/tag-helpers\/intro\">Tag Helpers<\/a> and <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/mvc\/models\/validation\">model validation<\/a>. Despite this commonality, some differences exist which may alter how developers approach Razor versus MVC controller pages.<\/p>\n<h3>API Support<\/h3>\n<p>Nothing explicitly precludes using <strong>PageModel<\/strong> for an API controller. Some might even argue it\u2019s a perfect option to expose a service with user interface for checking configuration or errors. Nonetheless, doesn\u2019t it seems potentially confusing to drag in the <strong>Microsoft.AspNetCore.Mvc.Razor<\/strong> namespace in a world expecting only <strong>Microsoft.AspNetCore.Mvc<\/strong>?<\/p>\n<h3>Folder Layout<\/h3>\n<p>The simplified code folder layout stands out as a shining star of the new Razor. First, the underlying magic avoids the many, many files and folders found in a typical MVC application as suggested below.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"262\" height=\"273\" class=\"wp-image-78869\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/word-image-87.png\" \/><\/p>\n<p>Adding a new page doesn\u2019t require adding a file to the Controllers, Models, and Views folders for just a single web page.<\/p>\n<p>Second, Razor avoids those pesky issues when employing hierarchical folder structures or an <a href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/mvc\/controllers\/areas\">Area<\/a> in MVC. For example, incorporation of the sample order management feature only requires the folders shown in the following screenshot and the usual routing tweaks.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"218\" height=\"127\" class=\"wp-image-78870\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2018\/05\/word-image-88.png\" \/><\/p>\n<h2>Conclusion<\/h2>\n<p>This introduction into the Razor technology released with ASP.NET Core 2.0 barely scratched the surface. It hopefully covered enough ground to convince developers acquainted with the older version to reconsider. Razor now combines some of the best ideas from different technologies introduced into ASP.NET over the years. Anticipate seeing it as a compelling alternative to MVC based pages.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Microsoft has introduced a new version of Razor with ASP.NET Core 2. Tom Fischer explains why it\u2019s worth taking another look at this methodology, especially those who discounted it before. He walks you through creating a simple application, highlighting the benefits or Razor.&hellip;<\/p>\n","protected":false},"author":200451,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[],"coauthors":[17935],"class_list":["post-78863","post","type-post","status-publish","format-standard","hentry","category-dotnet-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/78863","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/200451"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=78863"}],"version-history":[{"count":5,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/78863\/revisions"}],"predecessor-version":[{"id":85637,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/78863\/revisions\/85637"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=78863"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=78863"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=78863"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=78863"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}