<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pavel Osadchuk</title>
    <description>The latest articles on DEV Community by Pavel Osadchuk (@xakpc).</description>
    <link>https://dev.to/xakpc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F406964%2Fffde08aa-f25e-475c-ba98-1e240e376dbb.jpg</url>
      <title>DEV Community: Pavel Osadchuk</title>
      <link>https://dev.to/xakpc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xakpc"/>
    <language>en</language>
    <item>
      <title>Microsoft Has Killed Widgets Six Times. Here's Why They Keep Coming Back.</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Tue, 10 Feb 2026 03:50:26 +0000</pubDate>
      <link>https://dev.to/xakpc/microsoft-has-killed-widgets-six-timesheres-why-they-keep-coming-back-394g</link>
      <guid>https://dev.to/xakpc/microsoft-has-killed-widgets-six-timesheres-why-they-keep-coming-back-394g</guid>
      <description>&lt;p&gt;&lt;em&gt;The full history of Windows widgets, from 1997 to today.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every constraint you'll hit today exists because of a specific past disaster.&lt;/p&gt;




&lt;p&gt;Microsoft has been trying to solve the same UX problem since 1997: how to surface live information without making you launch an app. They've shipped six different implementations across nearly 30 years. Each one died from a different fundamental flaw — performance, security, screen space, privacy, engagement. And each death triggered the same reflex: containment.&lt;/p&gt;

&lt;p&gt;The pattern goes like this. Microsoft releases widgets into the wild. Users or attackers find the breaking point. Microsoft panics, kills the feature, and ships the next version locked inside a tighter box. Active Desktop splashed HTML across the wallpaper — performance collapsed, so Vista contained widgets in a rigid sidebar. Users hated losing screen space, so Windows 7 freed gadgets to float anywhere — then a security exploit blew the whole platform apart. Windows 8 responded by locking widgets into a full-screen Start Screen nobody wanted. The cycle repeats: build, ship, backlash, kill, rebuild with more walls.&lt;/p&gt;

&lt;p&gt;Understanding why each containment failed tells you exactly what constraints shape the platform you'd build on today. Every design decision in the current architecture — the declarative Adaptive Cards format, the native WinUI 3 renderer, the overlay-instead-of-dock layout — exists because of a specific past disaster. The constraints you'll hit when building widgets today aren't arbitrary. They're scar tissue.&lt;/p&gt;




&lt;h2&gt;
  
  
  01 — The Push Era (1997–2001)
&lt;/h2&gt;

&lt;p&gt;Internet Explorer 4.0 shipped in 1997 with a component called the Windows Desktop Update. It fused the Trident rendering engine into &lt;code&gt;explorer.exe&lt;/code&gt;, turning the Windows wallpaper from a static bitmap into a live HTML surface. Microsoft called it Active Desktop.&lt;/p&gt;

&lt;p&gt;The most visible piece was the Active Channel Bar — a vertical strip docked to the right edge of the screen, populated with branded buttons for Disney, MSNBC, Warner Bros, and CNN. Each button represented a "Channel," a website that pushed content to your machine for offline reading. The backbone was Channel Definition Format (CDF), an XML standard that told your PC which pages to download and when.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tech note:&lt;/strong&gt; CDF was an XML format — your modem dialed up at 2 AM, synced content, and cached it locally so you could browse "live" content without an active phone line.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A small red sparkle called the "Gleam" appeared on a channel's icon when new content arrived. It was an early attempt at the notification patterns we now take for granted. The handpicked channel lineup drew antitrust complaints, but the real killer was more mundane.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l4sd4vkennt3qp3g7yi.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1l4sd4vkennt3qp3g7yi.webp" alt="Windows 98 Active Channel Bar — branded buttons docked to the right edge of the desktop"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Active Channel Bar. Courtesy of Wikipedia.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkp4ovau6h19tuz91ehqw.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkp4ovau6h19tuz91ehqw.webp" alt="Windows 98 Active Desktop — HTML content rendered directly on the desktop wallpaper"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Active Desktop in action. Courtesy of &lt;a href="http://toastytech.com" rel="noopener noreferrer"&gt;toastytech.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Pentium I/II processors with 16–32 MB of RAM buckled under a constantly running HTML renderer. Because Trident was fused into the shell, a crash in Active Desktop crashed all of &lt;code&gt;explorer.exe&lt;/code&gt;. Users saw a white HTML recovery screen where their wallpaper should have been. Disk I/O from background syncs ground hard drives to a halt, degrading everything else running on the machine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Disable Active Desktop" became the top tuning tip for Windows 98.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft had discovered its first widget lesson: ambient information cannot cost performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;☠ Cause of Death: PERFORMANCE&lt;/strong&gt;&lt;br&gt;
Ambient info cannot cost performance. A live surface that degrades the system it runs on will always be disabled.&lt;/p&gt;




&lt;h2&gt;
  
  
  02 — The Glass Era (2007–2009)
&lt;/h2&gt;

&lt;p&gt;The widget concept went dormant during the XP years, then resurfaced inside the troubled "Longhorn" project. The original 2002 vision was ambitious — a translucent sidebar meant to replace the system tray entirely.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gi1lgcv41r4gj4m500r.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gi1lgcv41r4gj4m500r.webp" alt="Windows Longhorn desktop with sidebar widgets docked on the right edge"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Longhorn sidebar prototype — the original vision before Vista scaled it back. Courtesy of &lt;a href="https://longhorn.fandom.com" rel="noopener noreferrer"&gt;Longhorn Wiki&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fme4wyukwdmiqji1zel6z.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fme4wyukwdmiqji1zel6z.webp" alt="Windows Longhorn alarm clock widget with configuration dialog"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Widget configuration in Longhorn — alarm clock settings alongside the sidebar. Courtesy of &lt;a href="https://longhorn.fandom.com" rel="noopener noreferrer"&gt;Longhorn Wiki&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After Longhorn collapsed and was reset in 2004, the scope shrank. What shipped with Windows Vista in January 2007 was a supplementary pane: a dark, semi-transparent strip running the full height of the right edge of the screen.&lt;/p&gt;

&lt;p&gt;The sidebar was a showcase for Aero Glass. The default clock gadget had a sweeping red second hand behind a reflective glass face. The CPU meter mimicked luxury car dashboard dials — metallic bezels, needles that jittered physically as load spiked, turning red as they approached 100%. Eleven gadgets shipped in total, and thousands more followed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ibgaoqntly2ofd281il.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ibgaoqntly2ofd281il.webp" alt="Windows Vista Sidebar with Aero Glass gadgets and the Gadget Gallery open"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Vista's sidebar with the Gadget Gallery — clock, stocks, weather, and the 150-pixel tax on your workspace.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tech note:&lt;/strong&gt; A &lt;code&gt;.gadget&lt;/code&gt; file was a renamed ZIP containing HTML, CSS, JScript, and an XML manifest. Building one took an afternoon.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The problem was physical. Most monitors in 2007 were 4:3 or 5:4, and the sidebar permanently consumed around 150 pixels of horizontal space. On a 1280×1024 display, that's over 10% of your workspace. Power users disabled it on day one. The &lt;code&gt;sidebar.exe&lt;/code&gt; process compounded the issue — it leaked memory steadily, consuming 50–100 MB on systems where 1 GB of RAM was the minimum spec.&lt;/p&gt;

&lt;p&gt;Microsoft had attempted containment: give widgets a fixed home, keep things tidy. But rigid docking doesn't work when screen space is scarce.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;☠ Cause of Death: SCREEN SPACE&lt;/strong&gt;&lt;br&gt;
Rigid docking doesn't work when screen space is scarce. Widgets that steal workspace will be closed.&lt;/p&gt;




&lt;h2&gt;
  
  
  03 — The Free Era (2009–2012)
&lt;/h2&gt;

&lt;p&gt;Windows 7 freed the gadgets. Microsoft dropped the sidebar container entirely, renamed the feature Desktop Gadgets, and let users drag widgets anywhere on screen. Gadgets snapped magnetically to screen edges and to each other. A new "Aero Peek" button in the taskbar made all windows transparent on hover, so you could glance at your gadgets without minimizing your work.&lt;/p&gt;

&lt;p&gt;The third-party ecosystem exploded. DeviantArt and WinCustomize communities produced GPU temperature monitors, Winamp remote controls, elaborate calendar suites. It was the golden age of desktop customization.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff79z3z7gluf9m2pfwd0l.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff79z3z7gluf9m2pfwd0l.webp" alt="Windows 7 Desktop Gadgets — CPU monitor"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5xftlssmctfz4xqiijy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb5xftlssmctfz4xqiijy.webp" alt="Windows 7 Desktop Gadgets — CPU usage graph"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5zbvdsb6402zpwee5xz.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn5zbvdsb6402zpwee5xz.webp" alt="Windows 7 Desktop Gadgets — WiFi monitor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It was also a security catastrophe hiding in plain sight.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Desktop gadgets ran as HTML Applications with full local machine trust. No sandbox. A gadget could read files, write to the registry, and execute system commands.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In July 2012, at Black Hat in Las Vegas, researchers Mickey Shkatov and Toby Kohlenberg presented "We Have You By The Gadgets." The attack chain was elegant and devastating: intercept the HTTP traffic of a weather gadget on a shared network, inject JavaScript into the XML data stream, use that JavaScript to instantiate an ActiveX control like &lt;code&gt;WScript.Shell&lt;/code&gt;, then execute arbitrary code on the target machine. A weather widget became a backdoor.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/7uK8t0haGmU"&gt;
  &lt;/iframe&gt;


&lt;br&gt;
&lt;em&gt;Black Hat 2012: "We Have You By The Gadgets" — the presentation that killed Desktop Gadgets.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The researchers also demonstrated a distribution vector — since &lt;code&gt;.gadget&lt;/code&gt; files were just renamed ZIPs, an attacker could package a trojan inside a seemingly innocent clock skin. Users would install it without triggering antivirus warnings, because the code executed within the trusted &lt;code&gt;sidebar.exe&lt;/code&gt; process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tech note:&lt;/strong&gt; Security Advisory 2719662 told all Vista/7 users to disable the sidebar. A "Fix It" tool permanently killed sidebar.exe via the registry. The online gadget gallery was taken offline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft's response was nuclear. By the time Windows 8 launched in October 2012, the entire gadget engine was ripped from the codebase. Freedom without a sandbox was a death sentence. Everything Microsoft built after this moment was shaped by the security lesson. The move to declarative Adaptive Cards — JSON that describes a UI, never executes code — exists because of Black Hat 2012.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;☠ Cause of Death: SECURITY&lt;/strong&gt;&lt;br&gt;
Freedom without a sandbox is a death sentence. Declarative UI (Adaptive Cards) exists because of this failure.&lt;/p&gt;




&lt;h2&gt;
  
  
  04 — The Metro Era (2012–2021)
&lt;/h2&gt;

&lt;p&gt;Windows 8 made a radical bet: tiles ARE the launcher. The Start Menu disappeared, replaced by a full-screen Start Screen — a grid of rectangular tiles that doubled as widgets. When "live," a tile flipped or slid to reveal content: unread email counts, weather forecasts, news headlines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakuycnpwwzcjyk8q7vqe.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fakuycnpwwzcjyk8q7vqe.webp" alt="Windows 8.1 Start Screen — a full-screen grid of colorful Live Tiles"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Windows 8.1 Start Screen — Live Tiles as the primary launcher. Bold, polarizing, and ultimately rejected by desktop users.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The technology was elegant. Apps pushed tiny XML payloads through Windows Notification Services (WNS) to update their tiles without running a background process. Battery impact was near zero. Developers chose from a predefined catalog of XML templates — &lt;code&gt;TileWideSmallImageAndText03&lt;/code&gt; and friends — which enforced visual consistency but prevented any custom layout.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tech note:&lt;/strong&gt; The key security insight carried forward: the tile system rendered data, never executed code. That principle survives in today's Adaptive Cards.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The user experience was a disaster. On a desktop PC, checking your weather tile meant leaving your work, entering the full-screen Start Screen, then navigating back. That context switch enraged productivity users. Enterprise customers called it a dealbreaker.&lt;/p&gt;

&lt;p&gt;There was a deeper problem. Tiles were read-only. You couldn't check off a to-do, pause a song, or reply to a message from the tile. Clicking always launched the full app. Compared to the interactive gadgets of Windows 7 — where you could type notes, adjust sliders, and control media players directly on the desktop — this felt like a clear regression.&lt;/p&gt;

&lt;p&gt;Windows 10 tried a compromise — tiles went back into a traditional Start Menu. But third-party developers never invested in WNS backends. Building a live tile meant setting up a cloud service to format XML payloads and push them through Microsoft's notification infrastructure — a nontrivial cost for a feature most users barely noticed. By 2020, Start Menus were filled with static, lifeless squares. When Windows 11 launched in 2021, Live Tiles were gone.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7vdq2a9iij4eeq4w8sy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7vdq2a9iij4eeq4w8sy.webp" alt="Windows 10 Start Menu with Live Tiles"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The Windows 10 compromise — Live Tiles back in a traditional Start Menu. Courtesy of &lt;a href="https://www.windowslatest.com" rel="noopener noreferrer"&gt;windowslatest.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Containment attempt number two, even more extreme than Vista's sidebar. Widgets must live alongside your work, not in a separate universe.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;☠ Cause of Death: ENGAGEMENT&lt;/strong&gt;&lt;br&gt;
Read-only tiles in a walled garden killed developer investment. Widgets must be interactive and coexist with your workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  05 — The Interlude (2015–2021)
&lt;/h2&gt;

&lt;p&gt;Before the current Widget Board, Microsoft tried two half-measures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cortana Cards (2015–2020)&lt;/strong&gt; were the most capable widgets Microsoft ever shipped. The assistant scraped your email for flight confirmations and showed gate info. It read your calendar and estimated commute times. It tracked packages automatically. It surfaced sports scores for teams you followed, restaurant suggestions based on your location, and weather forecasts tuned to your daily routine. Users managed these through a settings panel called the "Notebook," where they explicitly declared their interests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjsds2e19xam75e40bmy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjsds2e19xam75e40bmy.webp" alt="Cortana Cards showing weather forecasts, travel info, currency conversion, and contextual suggestions"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Cortana Cards — weather, travel, currency, all powered by deep personal data access. Courtesy of &lt;a href="https://www.nytimes.com" rel="noopener noreferrer"&gt;The New York Times&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;All of this required deep access to your email, location, and calendar data. Users saw a company reading their emails. Most disabled Cortana entirely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The catch: disabling Cortana left the dashboard empty — a blank panel with nothing to show. In May 2020, Microsoft pivoted Cortana to a productivity chatbot and removed the cards.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;News and Interests (April 2021)&lt;/strong&gt; took a simpler approach — a weather icon on the Windows 10 taskbar that expanded into a news flyout on hover. The hover trigger was the problem. Users moving their mouse toward the system tray accidentally opened a panel of headlines. IT administrators scrambled to disable it via Group Policy. The feature was labeled bloatware within weeks of launch.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/fGtBLD-ORqQ"&gt;
  &lt;/iframe&gt;


&lt;br&gt;
&lt;em&gt;News and Interests — the accidental hover trigger that got it labeled bloatware within weeks.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Both attempts proved a consistent pattern: users want glanceable information but reject anything that feels invasive or disruptive. These two failures directly shaped what came next.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;☠ Cause of Death: PRIVACY &amp;amp; DISRUPTION&lt;/strong&gt;&lt;br&gt;
Users want glanceable info but reject anything invasive. Widgets must be intentional, never accidental.&lt;/p&gt;




&lt;h2&gt;
  
  
  06 — The Board Era (2021–2024)
&lt;/h2&gt;

&lt;p&gt;Windows 11 shipped in October 2021 with a dedicated Widgets Board — a panel that slides out from the left edge of the screen when you press &lt;code&gt;Win + W&lt;/code&gt; or click the weather icon on the taskbar. The board is an overlay, not a docked sidebar, so it claims zero permanent screen space.&lt;/p&gt;

&lt;p&gt;The Widget Board rendered everything through Edge WebView2, the Chromium-based web control shipped as part of the Windows Web Experience Pack. Widget providers implemented the &lt;code&gt;IWidgetProvider&lt;/code&gt; COM interface and defined their UI using Adaptive Cards — a JSON format that describes layout declaratively. No executable code, no scripts. The host parses data, never runs it. This eliminated the entire class of remote code execution attacks that killed Desktop Gadgets in 2012. But the rendering was still web-based. Users could see &lt;code&gt;msedgewebview2.exe&lt;/code&gt; processes in Task Manager when the board opened, often consuming hundreds of megabytes of RAM.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qti3174c95qic3sfnqd.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qti3174c95qic3sfnqd.webp" alt="The original Windows 11 Widget Board — widgets and news feed rendered through Edge WebView2"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The original Widget Board at launch — everything rendered through Edge WebView2, news feed and widgets mixed together.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Microsoft opened the board to third-party developers in 2023. Spotify added playback controls. Facebook Messenger shows recent conversations. Phone Link surfaces mobile notifications. Xbox Game Pass highlights new releases. Unlike Live Tiles, these widgets are interactive — you can pause a track, check a box, or reply to a message without leaving the board. For web developers, the PWA widget path is the most accessible entry point. Define widgets in your web app manifest, and they appear natively on the board. No Win32 code, no UWP packaging — your existing web stack works. That low barrier to entry is a deliberate correction from the Live Tiles era, where standing up a WNS backend cost more effort than most developers were willing to spend.&lt;/p&gt;

&lt;p&gt;The architecture was secure — sandboxed web content can't touch your file system. But the WebView2 rendering layer, the forced MSN news feed, and the lack of user control over the board's content drew persistent criticism. Something had to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;☠ Cause of Death: WEBVIEW2 &amp;amp; FORCED FEED&lt;/strong&gt;&lt;br&gt;
Secure but heavy. The WebView2 renderer and mandatory MSN feed drew persistent criticism until the EU forced a rebuild.&lt;/p&gt;




&lt;h2&gt;
  
  
  07 — The Start Experiences Era (2024–Present)
&lt;/h2&gt;

&lt;p&gt;In mid-2024, Microsoft began rebuilding the board. The forcing function was the EU's Digital Markets Act, which required Microsoft to open the Widget Board to third-party feed providers in the European Economic Area. To comply, Microsoft separated the MSN content pipeline into a replaceable Store-updatable component — the &lt;strong&gt;Microsoft Start Experiences app&lt;/strong&gt; — making it one feed provider among many rather than a hardwired default. The compliance overhaul gave Microsoft the opportunity to rebuild the rendering layer too: widget rendering moved from WebView2 to native WinUI 3.&lt;/p&gt;

&lt;p&gt;Adaptive Card JSON now maps directly to WinUI 3 XAML controls: a &lt;code&gt;TextBlock&lt;/code&gt; in your card becomes an actual &lt;code&gt;Microsoft.UI.Xaml.Controls.TextBlock&lt;/code&gt; on screen. Widgets inherit Fluent Design theming, high-contrast modes, and accessibility automatically. The memory footprint dropped — a native WinUI 3 control is far lighter than a DOM element inside a Chromium instance.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tech note:&lt;/strong&gt; The transition rolled out first to EEA users through KB5040546 in the Beta channel (July 2024). By late 2025, the WinUI 3 board, Widgets/Discover split, and optional MSN feed had expanded to all Windows 11 users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc30yd8ny34fqbji126ug.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc30yd8ny34fqbji126ug.webp" alt="Windows 11 Widget Board showing the Widgets/Feed split layout"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The rebuilt Widget Board — native WinUI 3 rendering, Widgets/Discover split, optional feed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The latest evolution arrived in early 2026: Lock Screen widgets. Weather, sports scores, and traffic now appear before you log in. Third-party apps can participate too. It's a full conceptual circle back to the "glance and go" vision that started this story in 1997 — except now the hardware can handle it, the sandbox keeps it safe, and the user stays in control. Microsoft appears to have realized that widgets shouldn't compete with applications for desktop space. They belong in the liminal moments of computing — the glance before you unlock, the pause between tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00hktutcqyxcvvigt09u.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F00hktutcqyxcvvigt09u.webp" alt="Windows 11 Lock Screen with widgets showing weather, sports scores, and traffic before login"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Lock Screen widgets — the "glance and go" vision from 1997, finally realized 30 years later.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;With 250+ million monthly active Windows users, the potential reach for widget developers is enormous. Whether the platform delivers depends on how Microsoft navigates the tensions that killed every previous attempt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;✅ Status: STILL ALIVE&lt;/strong&gt;&lt;br&gt;
Declarative JSON. Native WinUI 3 rendering. Overlay layout. Interactive widgets. Opt-in data access. Optional feed. Every past lesson encoded in architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Cycle Produced
&lt;/h2&gt;

&lt;p&gt;The current Widget Board is the scar tissue made manifest. Declarative JSON with no executable code (security). Native WinUI 3 rendering, migrated off the original WebView2 pipeline (performance). An overlay, not a dock (screen space). Interactive widgets (engagement). Opt-in data access (privacy). Every lesson encoded in architecture.&lt;/p&gt;

&lt;p&gt;But it's not perfect. The developer API has constraints that limit what you can build. The discoverability problem — most Windows users have never pressed &lt;code&gt;Win + W&lt;/code&gt; — remains unsolved. And while the news feed is finally optional, the tension between Microsoft's desire to monetize the Discover surface and users' desire for a clean utility board is far from resolved.&lt;/p&gt;

&lt;p&gt;Those constraints, and how to work within them to ship something useful, are what the rest of this series will cover.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Six Eras at a Glance
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Era&lt;/th&gt;
&lt;th&gt;Years&lt;/th&gt;
&lt;th&gt;Killed By&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The Push Era&lt;/td&gt;
&lt;td&gt;1997–2001&lt;/td&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Glass Era&lt;/td&gt;
&lt;td&gt;2007–2009&lt;/td&gt;
&lt;td&gt;Screen Space&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Free Era&lt;/td&gt;
&lt;td&gt;2009–2012&lt;/td&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Metro Era&lt;/td&gt;
&lt;td&gt;2012–2021&lt;/td&gt;
&lt;td&gt;Engagement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Interlude&lt;/td&gt;
&lt;td&gt;2015–2021&lt;/td&gt;
&lt;td&gt;Privacy &amp;amp; Disruption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The Board Era&lt;/td&gt;
&lt;td&gt;2021–Present&lt;/td&gt;
&lt;td&gt;Still Alive ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;This is Part 1 of my &lt;a href="https://xakpc.dev/windows-widgets/" rel="noopener noreferrer"&gt;Windows Widgets Development Series&lt;/a&gt;.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Building a widget for your product? I offer &lt;a href="https://xakpc.dev/widgets-service/" rel="noopener noreferrer"&gt;fixed-price widget development for online products&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>microsoft</category>
      <category>learning</category>
      <category>software</category>
      <category>history</category>
    </item>
    <item>
      <title>Exploring htmx with Razor Pages: An Old New Web Development Approach?</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Tue, 29 Aug 2023 22:10:38 +0000</pubDate>
      <link>https://dev.to/xakpc/exploring-htmx-with-razor-pages-an-old-new-web-development-approach-45b2</link>
      <guid>https://dev.to/xakpc/exploring-htmx-with-razor-pages-an-old-new-web-development-approach-45b2</guid>
      <description>&lt;p&gt;In the realm of web development, it's crucial to stay updated with the latest tools and methodologies. One such tool creating a buzz is &lt;strong&gt;htmx&lt;/strong&gt;. I am usually not swayed by the 'shiny object syndrome', but I worked a lot with &lt;strong&gt;Razor Pages&lt;/strong&gt; recently, and touched a lot of jQuery, which started to annoy me a bit.&lt;/p&gt;

&lt;p&gt;However, after watching a &lt;a href="https://youtu.be/NA5Fcgs_viU?si=0B0Ws7TMAZVveFCM" rel="noopener noreferrer"&gt;video from @t3dotgg&lt;/a&gt; I was convinced that &lt;strong&gt;htmx&lt;/strong&gt; might be helpful here and that it could fit me nicely. So, I decided to go through some &lt;a href="https://htmx.org/examples/" rel="noopener noreferrer"&gt;use cases&lt;/a&gt; for it and do some tests to check if it is any good.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 The results of these tests are available in &lt;a href="https://github.com/xakpc/RazorHtmx" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;. &lt;br&gt;
The demo application is hosted on Azure free plan and is available &lt;a href="https://razorhtmx.azurewebsites.net/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;br&gt;
To get maximum from this article you need to be familiar with ASP.NET MVC or RazorPages&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;First, let me briefly introduce what &lt;strong&gt;htmx&lt;/strong&gt; and &lt;strong&gt;Razor Pages&lt;/strong&gt; are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;htmx&lt;/strong&gt; is a js library that gives access to AJAX, CSS Transitions, WebSockets, and Server Sent Events directly in HTML, using attributes. The library is built around "Hypermedia-Driven Application Architecture," which, in simple terms, could be described as "return HTML instead of JSON."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Razor Pages&lt;/strong&gt; extends ASP.NET Core MVC, making coding page-focused scenarios easier and more productive than controllers and views. &lt;strong&gt;Razor Pages&lt;/strong&gt; group the action (called a &lt;em&gt;handler&lt;/em&gt;) and the viewmodel (called a &lt;em&gt;PageModel&lt;/em&gt;) in one class and link it to a view. It uses a routing convention based on location and name in a Pages folder. This tends to keep &lt;strong&gt;Razor Pages&lt;/strong&gt; and its handlers smaller and more focused while at the same time making it easier to find and work with.&lt;/p&gt;

&lt;p&gt;I also want to mention &lt;a href="https://picocss.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;pico.css&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt; This is a minimal CSS framework for semantic HTML. It focuses on using simple native HTML tags as much as possible and provides consistent adaptive spacings and typography on all devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Background
&lt;/h2&gt;

&lt;p&gt;Before I dive into integrating &lt;strong&gt;htmx&lt;/strong&gt; and &lt;strong&gt;Razor Pages&lt;/strong&gt;, let me briefly describe each technology. Both are praised for simplifying web development, but what are they?&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1. htmx
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;htmx&lt;/strong&gt; started as an &lt;code&gt;intercooler.js&lt;/code&gt; - a small helper library to make AJAX calls through HTML tag attributes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="c"&gt;&amp;lt;!-- This anchor tag posts to '/click' when it is clicked --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;ic-post-to=&lt;/span&gt;&lt;span class="s"&gt;"/click"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Click Me!
  &lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asynchronous JavaScript And XML (AJAX)To retrieve data from a web server, AJAX utilizes a blend of a browser's built-in XMLHttpRequest object and JavaScript and HTML DOM to display or utilize the data. However, newer browsers can use the Fetch API instead of the XMLHttpRequest Object. XML in the name is obsolete and misleading, in fact, any data could be transported: from JSON to HTML.&lt;/p&gt;

&lt;p&gt;Around three years ago, intercooler 2.0 (renamed to &lt;strong&gt;htmx 1.0&lt;/strong&gt;) has been released. It was smaller, more expressive, and no longer depended on jQuery.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;  &lt;span class="c"&gt;&amp;lt;!-- have a button POST a click via AJAX --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/clicked"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Click Me
  &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;htmx&lt;/strong&gt; intend to help web developers to create dynamic, interactive web content without the overhead of complex JavaScript frameworks.&lt;/p&gt;

&lt;p&gt;Leveraging HTML attributes allows seamless partial page updates, delivering the power of &lt;strong&gt;AJAX&lt;/strong&gt;, &lt;strong&gt;CSS Transitions&lt;/strong&gt;, &lt;strong&gt;WebSockets&lt;/strong&gt;, and &lt;strong&gt;Server-Sent Events&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I would not talk about WebSockets and SSE or try them, though, because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They have experimental support&lt;/li&gt;
&lt;li&gt;If I want WebSockets and SSE, I probably would use Blazor and SignalR&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2.1.1. AJAX
&lt;/h4&gt;

&lt;p&gt;The core of &lt;strong&gt;htmx&lt;/strong&gt; is a set of attributes that allow you to issue AJAX requests directly from HTML:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hx-get&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issues a &lt;code&gt;GET&lt;/code&gt; request to the given URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hx-post&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issues a &lt;code&gt;POST&lt;/code&gt; request to the given URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hx-put&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issues a &lt;code&gt;PUT&lt;/code&gt; request to the given URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hx-patch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issues a &lt;code&gt;PATCH&lt;/code&gt; request to the given URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hx-delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Issues a &lt;code&gt;DELETE&lt;/code&gt; request to the given URL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each of these attributes takes a URL to issue an AJAX request to. The element will issue a request of the specified type to the given URL when the element is triggered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;hx-put=&lt;/span&gt;&lt;span class="s"&gt;"/messages"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Put To Messages
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the browser:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When a user clicks on this div, issue a PUT request to the URL /messages and load the response into the div&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default, AJAX requests are triggered by the “natural” event of an element:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;input&lt;/code&gt;, &lt;code&gt;textarea&lt;/code&gt; &amp;amp; &lt;code&gt;select&lt;/code&gt; are triggered on the &lt;code&gt;change&lt;/code&gt; event&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;form&lt;/code&gt; is triggered on the &lt;code&gt;submit&lt;/code&gt; event&lt;/li&gt;
&lt;li&gt;everything else is triggered by the &lt;code&gt;click&lt;/code&gt; event&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;hx-trigger&lt;/code&gt; attribute could change this to specify which event will cause the request. Here is a &lt;code&gt;div&lt;/code&gt; that posts to &lt;code&gt;/mouse_entered&lt;/code&gt; when a mouse enters it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"/mouse_entered"&lt;/span&gt; &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"mouseenter"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    [Here Mouse, Mouse!]
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2.1.2. CSS Transitions
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;htmx&lt;/strong&gt; makes it easy to use CSS Transitions without javascript. Imagine that we need to replace content by &lt;strong&gt;htmx&lt;/strong&gt; via an AJAX request with this new content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"div1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Original Content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- replaced --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"div1"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"red"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;New Content&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The div has the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;id&lt;/code&gt; in the original and the new content&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;red&lt;/code&gt; class has been added to the new content&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given this situation, we can write a CSS transition from the old state to the new state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.red&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;ease-in&lt;/span&gt; &lt;span class="m"&gt;1s&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;strong&gt;htmx&lt;/strong&gt; swaps in this new content, it will do so in such a way that the CSS transition will apply to the new content, giving you a smooth transition to the new state. It happened because we keep its &lt;code&gt;id&lt;/code&gt; stable across requests.&lt;/p&gt;

&lt;h4&gt;
  
  
  2.1.3. The philosophy behind "Hypermedia-Driven Applications"
&lt;/h4&gt;

&lt;p&gt;I would not delve into details here, but it is worth mentioning because it's a pretty interesting concept. They wrote a &lt;a href="https://hypermedia.systems/" rel="noopener noreferrer"&gt;book&lt;/a&gt; on it, read it sometime.&lt;/p&gt;

&lt;p&gt;In short, it's all about the central role of HTML (or hypermedia) in driving and representing the state of web applications. Instead of relying heavily on intricate client-side scripting or full-page reloads to reflect changes in application state, this principle endorses the use of hypermedia itself.&lt;/p&gt;

&lt;p&gt;The application state evolves fluidly by allowing individual page pieces to update in response to user actions or events, reducing the need for heavy JavaScript while promoting a more resilient and accessible web experience.&lt;/p&gt;

&lt;p&gt;We did this with early ASP.NET MVC applications, utilizing jQuery to render partials, and now the proposal is to revert to this method for the sake of simplicity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F033d3iz6hr4oydm8pej7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F033d3iz6hr4oydm8pej7.png" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2. Razor Pages
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Razor Pages&lt;/strong&gt;, introduced in ASP.NET Core 2.0, offer a streamlined alternative to the MVC UI pattern, which Microsoft has supported since 2009.&lt;/p&gt;

&lt;p&gt;While MVC promotes separation of concerns, it often creates a sprawling structure of controllers, views, and viewmodels across multiple folders. In contrast, &lt;strong&gt;Razor Pages&lt;/strong&gt; encapsulate the action (referred to as a &lt;em&gt;handler&lt;/em&gt;) and the viewmodel (now the &lt;em&gt;PageModel&lt;/em&gt;) in a single class, directly tied to a corresponding Razor Page.&lt;/p&gt;

&lt;p&gt;All these pages reside in a central &lt;code&gt;Pages&lt;/code&gt; folder, following a naming-based routing convention. Handlers, prefixed with HTTP verbs like &lt;code&gt;OnGet&lt;/code&gt;, simplify actions, typically returning the associated page by default. This structure enables concise, focused pages, making an application's navigation and updates more intuitive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Razor Pages&lt;/strong&gt; overall a vast topic, but I want to point out two essential for the htmx parts of it: &lt;strong&gt;handlers&lt;/strong&gt; and &lt;strong&gt;partials&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2.2.1. Razor Pages Handlers
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Handler methods&lt;/strong&gt; are the public methods in &lt;strong&gt;Razor Pages&lt;/strong&gt; &lt;code&gt;PageModel&lt;/code&gt; and they are automatically executed on a request, implicitly returning a result for the current page.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Razor Pages&lt;/strong&gt; framework uses a naming convention to select the appropriate handler method to execute. The default convention works by matching the HTTP method used for the request to the name of the method, which is prefixed with "On": &lt;code&gt;OnGet(), OnPost(), OnPut()&lt;/code&gt; or async versions &lt;code&gt;OnPostAsync(), OnGetAsync()&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;Handler methods can return &lt;code&gt;void&lt;/code&gt;, &lt;code&gt;Task&lt;/code&gt; if asynchronous, or an &lt;code&gt;IActionResult&lt;/code&gt; (or &lt;code&gt;Task&amp;lt;IActionResult&amp;gt;&lt;/code&gt;).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;IActionResult&lt;/em&gt;: The IActionResult return type is appropriate when multiple ActionResult return types are possible in an action. The ActionResult types represent various HTTP status codes. Any non-abstract class deriving from ActionResult qualifies as a valid return type. Some common return types in this category are BadRequestResult (400), NotFoundResult (404), and OkObjectResult (200).&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ValidationModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnGet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnPost&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Page&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Razor Pages&lt;/strong&gt; include a feature called "&lt;strong&gt;named handler methods&lt;/strong&gt;". This feature enables you to specify multiple methods that can be executed for a single verb.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnGetItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_ItemPartial"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnPostItem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_ItemPartial"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could use tag helpers to generate links like &lt;code&gt;/validation?handler=Item&lt;/code&gt; , where the handler would be set as a query parameter. There is also an option to generate path links like &lt;code&gt;/validation/Item&lt;/code&gt; by setting a page route &lt;code&gt;@page "{handler?}"&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Tag Helpers&lt;/em&gt;: Tag Helpers in Razor files allow server-side code to create and render HTML elements. They include built-in helpers for common tasks like creating forms, links, and loading assets. Custom Tag Helpers can also be created in C# to target elements by name, attribute, or parent tag.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Razor code --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;asp-page=&lt;/span&gt;&lt;span class="s"&gt;"Validation"&lt;/span&gt; &lt;span class="na"&gt;asp-page-handler=&lt;/span&gt;&lt;span class="s"&gt;"Item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- HTML code --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/validation?handler=Item"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Link&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, these Tag Helpers exist only for &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; tags. One of the questions &lt;strong&gt;htmx&lt;/strong&gt; trying to solve is &lt;code&gt;Why should only &amp;lt;a&amp;gt; and &amp;lt;form&amp;gt; be able to make HTTP requests?&lt;/code&gt; so we could call handlers from any tag. MVC provides a way to generate a handler with a helper function: &lt;code&gt;@Url.Page("BulkUpdate", "Activate")&lt;/code&gt; - would generate &lt;code&gt;/BulkUpdate?handler=Activate&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;But in &lt;strong&gt;htmx&lt;/strong&gt; context, handlers usually belong to the same page, so to make it easier, I wrote a simple URL helper method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UrlHandlerExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IUrlHelper&lt;/span&gt; &lt;span class="n"&gt;urlHelper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Convert the values object to a dictionary&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;routeValues&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RouteValueDictionary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"handler"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="c1"&gt;// Add the handler to the dictionary&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RouteUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;UrlRouteContext&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;routeValues&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It could be used with any HTML tag to set the handler URL to a htmx attribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;outline&lt;/span&gt;&lt;span class="s"&gt;" hx-delete="&lt;/span&gt;&lt;span class="n"&gt;@Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s"&gt;" antiforgery="&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;
&lt;/span&gt;    &lt;span class="n"&gt;Delete&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2.2.2. Razor Pages Partials
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Partial Views&lt;/strong&gt; in &lt;strong&gt;Razor Pages&lt;/strong&gt; contain reusable HTML and code snippets that simplify complex pages by breaking them into smaller units.&lt;/p&gt;

&lt;p&gt;Partial Views in ASP.NET MVC were introduced with the initial release of ASP.NET MVC itself. ASP.NET MVC 1.0 was officially released in March 2009, and from that first version, developers could use partial views to encapsulate and reuse parts of their views.&lt;/p&gt;

&lt;p&gt;Partial Views are set up as &lt;em&gt;cshtml&lt;/em&gt; files that do not take part in routing, so they do not have &lt;code&gt;@page&lt;/code&gt; directive. Usually, partials are used to reuse some code on razor pages with the use of &lt;code&gt;partial&lt;/code&gt; tag helper.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"_ItemPartial"&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Model.UserInfo"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On top of that &lt;code&gt;PageModel&lt;/code&gt; provides a set of methods that implement &lt;code&gt;IActionResult&lt;/code&gt; , one of them: &lt;code&gt;Partial&lt;/code&gt; - would apply the model, render a partial HTML view to the response, and return it as a response body.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Page()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.razorpages.pageresult?view=aspnetcore-7.0" rel="noopener noreferrer"&gt;PageResult&lt;/a&gt; object that renders the page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Partial(String)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.partialviewresult?view=aspnetcore-7.0" rel="noopener noreferrer"&gt;PartialViewResult&lt;/a&gt; by specifying the name of a partial to render.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Partial(String, Object)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Creates a &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.partialviewresult?view=aspnetcore-7.0" rel="noopener noreferrer"&gt;PartialViewResult&lt;/a&gt; by specifying the name of a partial to render and the model object.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That gives us the ability to define razor partial view&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@model Xakpc.RazorHtmx.Data.UserInfoViewModel

&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@Model.FirstName&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@Model.Email&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;EditItem",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id =&lt;/span&gt; &lt;span class="s"&gt;Model.Id&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Edit
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and return it as generated HTML in a response&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnGetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_TableRow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;Krystal&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;krystal.heaney@bergnaummetz.us&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"/edit-row?id=2&amp;amp;amp;handler=EditItem"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Edit
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we could also re-use the exact partial for the initial page generation&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;        &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"closest tr"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        @foreach (var userInfo in Model.Users)
        {
            &lt;span class="nt"&gt;&amp;lt;partial&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_TableRow"&lt;/span&gt; &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"userInfo"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        }
        &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my opinion this feature is a single reason why &lt;strong&gt;Razor Pages&lt;/strong&gt; could even work with &lt;strong&gt;htmx&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Setting the Stage: Prerequisites and Initial Setup
&lt;/h2&gt;

&lt;p&gt;To start, you don't need much - an installed &lt;a href="https://dotnet.microsoft.com/en-us/download" rel="noopener noreferrer"&gt;dotnet core SDK&lt;/a&gt; and a favorite editor. I use Visual Studio because it's been my default editor since version VS 2013. But VS Code, or VS on Mac, or anything else, is fine. Check &lt;a href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start?view=aspnetcore-7.0&amp;amp;tabs=visual-studio-code" rel="noopener noreferrer"&gt;official tutorial&lt;/a&gt; if you feel stuck.&lt;/p&gt;

&lt;p&gt;Run the following command to create a new &lt;strong&gt;Razor Pages&lt;/strong&gt; project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;webapp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RazorHtmx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would create a default project with Bootstrap and jQuery applied by default. I prefer to use pico.css in my projects, so I have a reduced version (you can check it &lt;a href="https://github.com/xakpc/RazorHtmx/releases/tag/boilerplate" rel="noopener noreferrer"&gt;here&lt;/a&gt;), but unfortunately, it's not a template yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1. Integrating htmx into Razor Pages
&lt;/h3&gt;

&lt;p&gt;To integrate &lt;strong&gt;htmx&lt;/strong&gt;, you should include the js file into &lt;code&gt;_Layout&lt;/code&gt;. Choose whatever option you prefer from &lt;a href="https://htmx.org/docs/#installing" rel="noopener noreferrer"&gt;docs#installing&lt;/a&gt;. I downloaded and included &lt;code&gt;htmx.min.js&lt;/code&gt; to a head tag of &lt;code&gt;_Layout.cshtml&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;_&lt;em&gt;Layout.cshtml&lt;/em&gt;: Web apps use a common layout for consistency. This includes elements like headers, navigation, and footers. Layout files reduce duplicate code and the default layout for ASP.NET Core is named _Layout.cshtml.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- rest of the code --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"~/js/htmx.min.js"&lt;/span&gt; &lt;span class="na"&gt;asp-append-version=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Making First htmx Call in Razor
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, my aim was to explore each example from the &lt;strong&gt;htmx&lt;/strong&gt; examples page to understand what is possible and how that could be helpful. The first example is click-to-edit, which is my perfect use case.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The click-to-edit pattern provides a way to offer inline editing of all or part of a record without a page refresh.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The example ended with several files&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;_Partial.cshtml&lt;/code&gt; to present record&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_Edit.cshtml&lt;/code&gt; to present form for editing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ClickToEdit.cshtml&lt;/code&gt; to present the page&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ClickToEdit.cshtml.cs&lt;/code&gt; for PageModel&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UserInfoViewModel.cs&lt;/code&gt; for model&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me describe each of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UserInfoViewModel.cs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's a pretty generic model class. Each property has data annotation to render labels in forms.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserInfoViewModel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"First Name"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Last Name"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Email"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Status"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;RowId&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ClickToEdit.cshtml.cs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PageModel&lt;/code&gt; for this page consists of all required handler methods, basic &lt;code&gt;OnGet&lt;/code&gt; for initial loading, a couple named &lt;code&gt;GET&lt;/code&gt; handlers to show the edit form and view form, and &lt;code&gt;PUT&lt;/code&gt; method to update form data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ClickToEditModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnGet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnGetPartial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_Partial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnGetEdit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetUserInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnPutEdit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="n"&gt;userInfoViewModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;userInfoViewModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_Partial"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;ClickToEdit.cshtml&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The page itself is nothing other than a placeholder for partials. Instead of using &lt;code&gt;&amp;lt;partial&amp;gt;&lt;/code&gt; tag helper, I'm using &lt;code&gt;Html.PartialAsync&lt;/code&gt; helper method, but the outcome would be the same.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@page "/click-to-edit"
@model ClickToEditModel

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    @await Html.PartialAsync("_Partial", Model.UserInfoViewModel)
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, about partials. We have two of them - one renders a form, and another renders a view. Note that none of the HTML tags have classes because I use the semantic HTML CSS library pico.css. This approach significantly reduces the size of the partial. Consider the bulk that would be added if you were to use a class loaded with Tailwind CSS attributes.&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;_Partial.cshtml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@model Xakpc.RazorHtmx.Data.UserInfoViewModel

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.FirstName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
        @Model.FirstName
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.LastName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
        @Model.LastName
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
        @Model.Email
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;ClickToEdit",&lt;/span&gt; &lt;span class="err"&gt;"Edit",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id =&lt;/span&gt; &lt;span class="s"&gt;Model.Id&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Click To Edit
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-target="this"&lt;/code&gt; makes a div that updates itself when changed. It's also inherited, so it works when placed on the parent element.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-swap="outerHTML"&lt;/code&gt; instructs to replace the entire target element with the response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-get="@Url.Page("ClickToEdit", "Edit", new { id = Model.Id })"&lt;/code&gt; would generate a URL to &lt;code&gt;ClickToEdit.OnGetEdit&lt;/code&gt; handler method and would pass &lt;code&gt;id&lt;/code&gt; query parameter.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"/click-to-edit?id=1&amp;amp;handler=Edit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Click To Edit
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;_Edit.cshtml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@model Xakpc.RazorHtmx.Data.UserInfoViewModel

&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;hx-put=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;ClickToEdit",&lt;/span&gt; &lt;span class="err"&gt;"Edit")"&lt;/span&gt;
      &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    @Html.AntiForgeryToken()
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"Id"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.FirstName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.FirstName"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="err"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.LastName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.LastName"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="err"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Email"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="err"&gt;required&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;ClickToEdit",&lt;/span&gt; &lt;span class="err"&gt;"Partial",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id =&lt;/span&gt; &lt;span class="s"&gt;Model.Id&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"secondary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@Html.AntiForgeryToken()&lt;/code&gt; will generate a hidden field with an anti-forgery token to prevent CSRF vulnerabilities. More on this later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-put="@Url.Page("ClickToEdit", "Edit")"&lt;/code&gt; will perform &lt;code&gt;PUT&lt;/code&gt; a request to &lt;code&gt;OnPutEdit&lt;/code&gt; handler because updates should be &lt;code&gt;PUT&lt;/code&gt; according to RESTful best practices.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-target="this"&lt;/code&gt; makes a div that updates itself when changed. It's also inherited and works when placed on the parent element.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-swap="outerHTML"&lt;/code&gt; instructs to replace the entire target element with the response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;hx-get="@Url.Page("ClickToEdit", "Partial", new { id = Model.Id })"&lt;/code&gt; will perform &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;OnGetPartial&lt;/code&gt; handler and set query parameter &lt;code&gt;id&lt;/code&gt; to model id. This query would return rendered HTML of the record view:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"FirstName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;First Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
            Kennedy
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"LastName"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Last Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
            Heaney
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;strong&amp;gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;
            kennedy_heaney@botsford.uk
        &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"/click-to-edit?id=1&amp;amp;amp;handler=Edit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Click To Edit
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, a fully functional click-to-edit form in &lt;strong&gt;Razor Pages&lt;/strong&gt; and &lt;strong&gt;htmx&lt;/strong&gt; with 100-120 lines of code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dxcdz0nzmbm976413ts.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6dxcdz0nzmbm976413ts.gif" width="760" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Exploring Other htmx Use-Cases
&lt;/h2&gt;

&lt;p&gt;I surely could not even try to describe every feature &lt;strong&gt;htmx&lt;/strong&gt; has. I selected a couple of examples that might be useful references for any web application.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1. Progress Bar
&lt;/h3&gt;

&lt;p&gt;The progress bar is a very simple partial, just two lines. Having it as a separate file is almost a crime, but unfortunately, it's how it is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@model int

&lt;span class="nt"&gt;&amp;lt;progress&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"progress"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"@Model"&lt;/span&gt; &lt;span class="na"&gt;max=&lt;/span&gt;&lt;span class="s"&gt;"100"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/progress&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I want to show you here is a &lt;strong&gt;_ProgresPartial,&lt;/strong&gt; which hosts a progress bar.&lt;/p&gt;

&lt;p&gt;Based on the progress model (is progress done or not), we could alter how this partial is rendered and what &lt;code&gt;hx-trigger&lt;/code&gt; value is set. This is a primary function of Razor markup syntax.&lt;/p&gt;

&lt;p&gt;Therefore, we could decide on the application's state and what should be returned as a view on the backend, which is very convenient.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@model Progress

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"done"&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Progress")"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    @if (Model.Done)
    {
        &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="err"&gt;autofocus&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Complete&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    }
    else
    {
        &lt;span class="nt"&gt;&amp;lt;h3&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt; &lt;span class="na"&gt;tabindex=&lt;/span&gt;&lt;span class="s"&gt;"-1"&lt;/span&gt; &lt;span class="err"&gt;autofocus&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Running&lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;
    }

    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt;
        &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Progress")"&lt;/span&gt;
        &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"@(Model.Done ? "&lt;/span&gt;&lt;span class="err"&gt;none"&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="err"&gt;"every&lt;/span&gt; &lt;span class="err"&gt;600ms")"&lt;/span&gt;
        &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt;
        &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"innerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;partial&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_ProgressBarPartial"&lt;/span&gt; &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Value"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

    @if (Model.Done)
    {
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Start")"&lt;/span&gt; &lt;span class="na"&gt;antiforgery=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="na"&gt;classes=&lt;/span&gt;&lt;span class="s"&gt;"add show:600ms"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Restart Job
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    }
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dlhu913xf83q8lvqpjq.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1dlhu913xf83q8lvqpjq.gif" width="1204" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.2. Table Scroll, Edit, Delete
&lt;/h3&gt;

&lt;p&gt;Building interactive tables is a common task of any web application. With the help of &lt;strong&gt;htmx&lt;/strong&gt;, we could easily expand a static table with scroll loading in place of edit and deletion.&lt;/p&gt;

&lt;p&gt;I implemented several tests related to tables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infinite Scroll&lt;/li&gt;
&lt;li&gt;Table Row Edit&lt;/li&gt;
&lt;li&gt;Table Row Delete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;infinite scroll&lt;/strong&gt;, we have a partial in the &lt;code&gt;tbody&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;        &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tbody"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            @await Html.PartialAsync("_TableBody", Model.UsersTable)
        &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The partial would draw table rows, and the last row would add &lt;code&gt;hx-get&lt;/code&gt; attribute with &lt;code&gt;revelaed&lt;/code&gt; trigger.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@foreach (var userInfo in Model.Users)
{
    @if (userInfo == Model.Users.Last())
    {
        &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;InfiniteScroll",&lt;/span&gt; &lt;span class="err"&gt;"Page",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;page =&lt;/span&gt; &lt;span class="s"&gt;Model.Page&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt;
            &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"revealed"&lt;/span&gt;
            &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"afterend"&lt;/span&gt;
            &lt;span class="na"&gt;hx-indicator=&lt;/span&gt;&lt;span class="s"&gt;"#ind"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.FirstName @userInfo.LastName&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.Email&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.RowId.ToString("N").ToUpperInvariant()&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    }
    else
    {
        &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.FirstName @userInfo.LastName&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.Email&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.RowId.ToString("N").ToUpperInvariant()&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The handler method load and render the next page of rows. There is also &lt;code&gt;Task.Delay&lt;/code&gt; to simulate some loading.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IActionResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;OnGetPageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;PageSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PageSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_TableBody"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how it works:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipz0p083ao9pbuc51lak.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fipz0p083ao9pbuc51lak.gif" width="600" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;table editing&lt;/strong&gt;, I have two partials, for presenting and for editing, which are swapped on the button click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@Model.FirstName&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@Model.Email&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;EditItem",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id =&lt;/span&gt; &lt;span class="s"&gt;Model.Id&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Edit
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.FirstName"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt; &lt;span class="na"&gt;asp-for=&lt;/span&gt;&lt;span class="s"&gt;"@Model.Email"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Item",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id =&lt;/span&gt; &lt;span class="s"&gt;Model.Id&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"secondary"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"margin-bottom: 6px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Cancel
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-put=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Item",&lt;/span&gt; &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id =&lt;/span&gt; &lt;span class="s"&gt;Model.Id&lt;/span&gt; &lt;span class="err"&gt;})"&lt;/span&gt; &lt;span class="na"&gt;hx-include=&lt;/span&gt;&lt;span class="s"&gt;"closest tr"&lt;/span&gt; &lt;span class="na"&gt;antiforgery=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            Save
        &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handlers for this page are straightforward: get item, get item form, update (put) item&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnGetItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_TableRow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnGetEditItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_TableRowForm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;OnPutItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserInfoViewModel&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_TableRow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we can easily update table rows&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqt89sq46bqkye6140g8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqt89sq46bqkye6140g8.gif" width="720" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Delete Row has a similar table row partial, but with &lt;code&gt;hx-delete&lt;/code&gt; handler, because REST.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;@Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="n"&gt;@Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;@Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;@Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="err"&gt;="&lt;/span&gt;&lt;span class="nc"&gt;outline&lt;/span&gt;&lt;span class="s"&gt;" hx-delete="&lt;/span&gt;&lt;span class="n"&gt;@Url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Item"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="s"&gt;" antiforgery="&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="s"&gt;"&amp;gt;
&lt;/span&gt;            &lt;span class="n"&gt;Delete&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the delete call, the handler would return 200 OK with an empty body instructing &lt;strong&gt;htmx&lt;/strong&gt; to remove the row. We also use &lt;code&gt;hx-confirm&lt;/code&gt; tag to confirm deletion&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;tbody&lt;/span&gt; &lt;span class="n"&gt;hx&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;confirm&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Are you sure?"&lt;/span&gt; &lt;span class="n"&gt;hx&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"closest tr"&lt;/span&gt; &lt;span class="n"&gt;hx&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;swap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML swap:1s"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;@foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;userInfo&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;@await&lt;/span&gt; &lt;span class="n"&gt;Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PartialAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"_TableRow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also some transition animation&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqbunnu96rswr0wjamnh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqbunnu96rswr0wjamnh.gif" width="600" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at a couple of practical scenarios where &lt;strong&gt;htmx&lt;/strong&gt; and &lt;strong&gt;Razor Pages&lt;/strong&gt; integration shines.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.3. Dialogs and tabs
&lt;/h3&gt;

&lt;p&gt;A full code listing of how HTML5 dialogs could be created with &lt;strong&gt;htmx&lt;/strong&gt; and &lt;strong&gt;Razor Pages&lt;/strong&gt;. In this example, I used some JavaScript to show and hide the dialog.&lt;/p&gt;

&lt;p&gt;At the same time, when dialog is shown, &lt;strong&gt;htmx&lt;/strong&gt; would issue a get request to get data from the page handler and set the result as dialog content.&lt;/p&gt;

&lt;p&gt;Actual dialog content would be generated on the backend from Razor partial.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- DialogsHtml.cshtml --&amp;gt;&lt;/span&gt; 
@page "/dialogs-html"
@model DialogsHtmlModel

&lt;span class="c"&gt;&amp;lt;!-- The Dialog --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;dialog&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"myDialog"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;article&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"dialogContent"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dialog&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;&amp;lt;!-- Trigger buttons --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-target=&lt;/span&gt;&lt;span class="s"&gt;"myDialog"&lt;/span&gt;
            &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Modal")"&lt;/span&gt;
            &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#dialogContent"&lt;/span&gt;
            &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"click"&lt;/span&gt;
            &lt;span class="na"&gt;onClick=&lt;/span&gt;&lt;span class="s"&gt;"showDialog()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Show Dialog
    &lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

@section Scripts
{
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        function showDialog() {
            const dialog = document.getElementById('myDialog');
            dialog.showModal(); // Opens the dialog
        }

        function closeDialog() {
            const dialog = document.getElementById('myDialog');
            dialog.close(); // Closes the dialog
        }
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

}

&lt;span class="c"&gt;&amp;lt;!-- DialogsHtml.cshtml.cs --&amp;gt;&lt;/span&gt; 
public class DialogsHtmlModel : PageModel
{
    public void OnGet()
    {
    }

    public IActionResult OnGetModal()
    {
        return Partial("_ModalPartial", ("Model Content Header", """
                Nunc nec ligula a tortor sollicitudin dictum in vel enim.
                Quisque facilisis turpis vel eros dictum aliquam et nec turpis.
                Sed eleifend a dui nec ullamcorper.
                Praesent vehicula lacus ac justo accumsan ullamcorper.
                """));
    }
}

&lt;span class="c"&gt;&amp;lt;!-- _ModalPartial.cshtml --&amp;gt;&lt;/span&gt;
@model (string Header, string Text)

&lt;span class="nt"&gt;&amp;lt;header&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"#close"&lt;/span&gt; &lt;span class="na"&gt;aria-label=&lt;/span&gt;&lt;span class="s"&gt;"Close"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"close"&lt;/span&gt; &lt;span class="na"&gt;onclick=&lt;/span&gt;&lt;span class="s"&gt;"closeDialog(); return false;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    @Model.Header
&lt;span class="nt"&gt;&amp;lt;/header&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
        @Model.Text
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgudtqc4s390ng71ngpvx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgudtqc4s390ng71ngpvx.gif" width="760" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A full code listing of how tabs could be created with &lt;strong&gt;htmx&lt;/strong&gt; and &lt;strong&gt;Razor Pages&lt;/strong&gt;. Here, I have tabs div, which is replaced with tab partial.&lt;/p&gt;

&lt;p&gt;The interesting part is a &lt;code&gt;load&lt;/code&gt; trigger, which is fired on page load to get the initial tab. That could be done through a razor like I did previously, as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Tabs.chtmls --&amp;gt;&lt;/span&gt;
@page "/tabs-hateoas"
@model TabsModel

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"tabs"&lt;/span&gt;
     &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab1")"&lt;/span&gt;
     &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"load delay:100ms"&lt;/span&gt;
     &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#tabs"&lt;/span&gt;
     &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"innerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Tabs.chtmls.cs --&amp;gt;&lt;/span&gt;
public class TabsModel : PageModel
{
    public void OnGet()
    {
    }

    public IActionResult OnGetTab1()
    {
        return Partial("_Tab1Partial");
    }

    public IActionResult OnGetTab2()
    {
        return Partial("_Tab2Partial");
    }

    public IActionResult OnGetTab3()
    {
        return Partial("_Tab3Partial");
    }
}

&lt;span class="c"&gt;&amp;lt;!-- _Tab1Partial.cshtml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab1")"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 1&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab2")"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 2&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab3")"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 3&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        some text...
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- _Tab2Partial.cshtml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tablist"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab1")"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 1&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab2")"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 2&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab3")"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 3&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        some text 2...
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- _Tab3Partial.cshtml --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"grid"&lt;/span&gt; &lt;span class="na"&gt;role=&lt;/span&gt;&lt;span class="s"&gt;"tablist"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab1")"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 1&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab2")"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"outline"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 2&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Tab3")"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Tab 3&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
        some text 3...
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tabs, in my case, are buttons, but it still looks good&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftw2k6hcjql36ma9jbtg9.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftw2k6hcjql36ma9jbtg9.gif" width="720" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.4. Live search feature on a Razor Page
&lt;/h3&gt;

&lt;p&gt;Here we use &lt;code&gt;keyup&lt;/code&gt; trigger to send a search query to &lt;code&gt;OnPostSearch&lt;/code&gt; handler, which would render partial based on search results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- ActiveSearch.cshtml --&amp;gt;&lt;/span&gt;
@page "/active-search"
@model ActiveSearchModel

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h3&amp;gt;&lt;/span&gt;
        Search Contacts
        &lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"htmx-indicator"&lt;/span&gt; &lt;span class="na"&gt;aria-busy=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Searching...&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/h3&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt;
           &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Begin Typing To Search Users..."&lt;/span&gt;
           &lt;span class="na"&gt;hx-post=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;Search")"&lt;/span&gt;
           &lt;span class="na"&gt;hx-trigger=&lt;/span&gt;&lt;span class="s"&gt;"keyup changed delay:500ms, search"&lt;/span&gt;
           &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"#search-results"&lt;/span&gt;
           &lt;span class="na"&gt;hx-indicator=&lt;/span&gt;&lt;span class="s"&gt;".htmx-indicator"&lt;/span&gt;
           &lt;span class="na"&gt;antiforgery=&lt;/span&gt;&lt;span class="s"&gt;"true"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"table"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;First Name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Last Name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Email&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tbody&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"search-results"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- ActiveSearch.cshtml.cs --&amp;gt;&lt;/span&gt;
public class ActiveSearchModel : PageModel
{
    public void OnGet()
    {
    }

    public IActionResult OnPostSearch(string search)
    {
        if (search == null || string.IsNullOrEmpty(search))
        {
            return Partial("_TableBody", new List&lt;span class="nt"&gt;&amp;lt;UserInfoViewModel&amp;gt;&lt;/span&gt;());
        }

        var users = TestData.Users.Where(u =&amp;gt; u.Email.Contains(search.ToLowerInvariant())).ToList();

        if (users.Any())
        {
            return Partial("_TableBody", users);
        }

        return Partial("_TableBody", TestData.Users.Take(7).ToList());
    }
}

&lt;span class="c"&gt;&amp;lt;!-- _TableBody.cshtml --&amp;gt;&lt;/span&gt;
@model List&lt;span class="nt"&gt;&amp;lt;Xakpc.RazorHtmx.Data.UserInfoViewModel&amp;gt;&lt;/span&gt;

@foreach (var userInfo in Model)
{
    &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.FirstName&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.LastName&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;@userInfo.Email&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is how it works&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvroew11x41gg0gyjmb2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxvroew11x41gg0gyjmb2.gif" width="720" height="425"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.5 Animations
&lt;/h3&gt;

&lt;p&gt;While my primary focus wasn't on aesthetics during testing, it's worth noting that it could be done. &lt;strong&gt;htmx&lt;/strong&gt; supports &lt;code&gt;CSS transitions&lt;/code&gt; so we could apply some cool animations to our partials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@page "/animations"
@model AnimationsModel

&lt;span class="nt"&gt;&amp;lt;article&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Using the View Transition API&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"slide-it"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;partial&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"_SwapContentPartial"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="na"&gt;model=&lt;/span&gt;&lt;span class="s"&gt;"@("&lt;/span&gt;&lt;span class="err"&gt;Initial&lt;/span&gt; &lt;span class="err"&gt;Content",&lt;/span&gt; &lt;span class="err"&gt;"Swap&lt;/span&gt; &lt;span class="err"&gt;It")"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/article&amp;gt;&lt;/span&gt;

@section Styles
{
  &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
   @@keyframes fade-in {
     from { opacity: 0; }
   }

   @@keyframes fade-out {
     to { opacity: 0; }
   }

   @@keyframes slide-from-right {
     from { transform: translateX(90px); }
   }

   @@keyframes slide-to-left {
     to { transform: translateX(-90px); }
   }

   .slide-it {
     view-transition-name: slide-it;
   }

   ::view-transition-old(slide-it) {
     animation: 180ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
     600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
   }
   ::view-transition-new(slide-it) {
     animation: 420ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
     600ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
   }
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In partial, we instruct &lt;strong&gt;htmx&lt;/strong&gt; to use transition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;@model (string Title, string Button)

&lt;span class="nt"&gt;&amp;lt;h6&amp;gt;&lt;/span&gt;@Model.Title&lt;span class="nt"&gt;&amp;lt;/h6&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;hx-get=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Handler("&lt;/span&gt;&lt;span class="err"&gt;NewContent")"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"innerHTML transition:true"&lt;/span&gt; &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"closest div"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    @Model.Button
&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us nice animation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F640hdyrnc748b6vd6sjt.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F640hdyrnc748b6vd6sjt.gif" alt="animation" width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Benefits of the Integration
&lt;/h2&gt;

&lt;p&gt;Simplicity, simplicity, simplicity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4t3hu4m44ljdcovbo1hd.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4t3hu4m44ljdcovbo1hd.gif" width="335" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am a huge advocate of simplicity. I work with highly complex systems in my daily job, where dozens of microservices process gigabytes of data 24/7.&lt;/p&gt;

&lt;p&gt;Probably, that's why I wouldn't say I like to overcomplicate anything. And like &lt;strong&gt;Razor Pages&lt;/strong&gt; was invented to simplify MVC web development, &lt;strong&gt;pico.css&lt;/strong&gt; was invented to simplify web design, so as far as I see, &lt;strong&gt;htmx&lt;/strong&gt; could be the tool to simplify interactivity.&lt;/p&gt;

&lt;p&gt;But these are all personal preferences. Let's evaluate what actual benefits &lt;strong&gt;Razor Pages&lt;/strong&gt; together with &lt;strong&gt;htmx&lt;/strong&gt; might produce.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1. Speed and Efficiency: Faster page loads and enhanced user experience.
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;htmx&lt;/strong&gt; allows for seamless partial page updates, meaning web pages can reflect real-time data changes without requiring a complete page refresh. This leads to a more interactive and immersive user experience, particularly beneficial for applications where up-to-the-minute data display is crucial, like dashboards or monitoring systems.&lt;/p&gt;

&lt;p&gt;Due to the lightweight nature of &lt;strong&gt;htmx&lt;/strong&gt;, only necessary data is transferred between client and server. This minimizes the bandwidth usage and server processing power required for each user interaction, making it possible to serve more users without a proportional resource increase.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2. Maintainability: Separation of concerns with Razor's code-behind model and htmx's lightweight nature.
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Razor Pages&lt;/strong&gt; is MPA (Multi-Page Application) framework. It is designed for creating web applications where each user interaction or action corresponds to a different web page loaded from the server.&lt;/p&gt;

&lt;p&gt;Historically, dotnet developers relied on JavaScript and jQuery to add interactivity. I believe there was never a "best-way" of how to organize JavaScript: there was a custom colocation option &lt;code&gt;cshtml.js&lt;/code&gt;, section &lt;code&gt;@Scripts&lt;/code&gt; , setup &lt;code&gt;grunt&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt; aside dotnet project, but most of the time, js was simply dumped into &lt;code&gt;wwwroot&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;htmx takes razor page concept of self-contained Page with its associated logic, data model, and view and adds interactivity there but keeps it &lt;strong&gt;clean and maintainable&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3. Simplified Development Workflow
&lt;/h3&gt;

&lt;p&gt;By merging &lt;strong&gt;Razor Pages&lt;/strong&gt; and &lt;strong&gt;htmx&lt;/strong&gt;, developers can enjoy a more streamlined development process. The inherent structure of &lt;strong&gt;Razor Pages&lt;/strong&gt;, which emphasizes a clear delineation between view and logic, combined with the straightforward integration of &lt;strong&gt;htmx&lt;/strong&gt;, reduces the learning curve for new team members.&lt;/p&gt;

&lt;p&gt;This synergy minimizes the need for extensive reliance on JavaScript for dynamic content, which can often introduce complexities. As a result, development teams can bring features to market faster and address bugs or changes with greater agility.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Potential Challenges and Solutions
&lt;/h2&gt;

&lt;p&gt;While the benefits of this integration are numerous, like all technologies, it comes with its set of challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1. Antiforgery Token
&lt;/h3&gt;

&lt;p&gt;Running the code below to update data will result in the error:  &lt;code&gt;Response Status Error Code 400 from /click-to-edit?handler=Edit&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;hx-put=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;ClickToEdit",&lt;/span&gt; &lt;span class="err"&gt;"Edit")"&lt;/span&gt; 
        &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; 
        &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt; 
   &lt;span class="c"&gt;&amp;lt;!-- form body --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because any POST or PUT request to the razor page requires AntiforgeryToken to prevent CSRF attacks.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Cross-Site Request Forgery (CSRF) is an attack where a malicious entity tricks a user's browser into performing undesired actions on a trusted site where the user is authenticated. This is possible because browsers automatically include cookies, including session cookies, with requests.&lt;/p&gt;

&lt;p&gt;To counteract this, many sites employ CSRF tokens, generated server-side. These tokens, which can be created per session or request, ensure that every action taken on a site is genuinely intended by the authenticated user.&lt;/p&gt;

&lt;p&gt;The server validates the token with every request; if they don't match, the request is denied, potentially flagging it as a CSRF attempt.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Antiforgery Token check could be disabled by applying &lt;code&gt;[IgnoreAntiforgeryToken(Order = 1001)]&lt;/code&gt; attribute on the page model, but that is a security risk.&lt;/p&gt;

&lt;p&gt;You could manually expand forms and add anti-forgery token with a helper method &lt;code&gt;@Html.AntiForgeryToken()&lt;/code&gt;. This would provide tokens as a hidden field and include it into form data.&lt;/p&gt;

&lt;p&gt;Tokens also could be provided as a header. It is considered a more secure option, and we could add a header to our htmx request using &lt;code&gt;hx-headers&lt;/code&gt; attribute.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;hx-put=&lt;/span&gt;&lt;span class="s"&gt;"@Url.Page("&lt;/span&gt;&lt;span class="err"&gt;ClickToEdit",&lt;/span&gt; &lt;span class="err"&gt;"Edit")"&lt;/span&gt; 
    &lt;span class="na"&gt;hx-headers=&lt;/span&gt;&lt;span class="s"&gt;'{"RequestVerificationToken": "@Model.AntiforgeryToken"}'&lt;/span&gt;
    &lt;span class="na"&gt;hx-target=&lt;/span&gt;&lt;span class="s"&gt;"this"&lt;/span&gt; &lt;span class="na"&gt;hx-swap=&lt;/span&gt;&lt;span class="s"&gt;"outerHTML"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For forms, it is better to use the helper method. But to add tokens to other HTML elements, such as &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt; I created a helper tag class. It would generate &lt;code&gt;hx-headers&lt;/code&gt; tag and set token value when I add an attribute &lt;code&gt;antiforgery="true"&lt;/code&gt; to element.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HtmlTargetElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Attributes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AntiForgeryAttributeName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HtmlTargetElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Attributes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AntiForgeryAttributeName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AntiForgeryHeaderTagHelper&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TagHelper&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HtmlAttributeNotBound&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ViewContext&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ViewContext&lt;/span&gt; &lt;span class="n"&gt;ViewContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;HtmlAttributeName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AntiForgeryAttributeName&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;AntiForgery&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;AntiForgeryAttributeName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"antiforgery"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IAntiforgery&lt;/span&gt; &lt;span class="n"&gt;_antiforgery&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AntiForgeryHeaderTagHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAntiforgery&lt;/span&gt; &lt;span class="n"&gt;antiforgery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_antiforgery&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;antiforgery&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TagHelperContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TagHelperOutput&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AntiForgery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_antiforgery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAndStoreTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ViewContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;RequestToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentHeaderValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"hx-headers"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;newHeaderValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"\"RequestVerificationToken\": \"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;\""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsNullOrEmpty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;currentHeaderValue&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hx-headers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newHeaderValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Append the anti-forgery token to existing hx-headers&lt;/span&gt;
                &lt;span class="n"&gt;newHeaderValue&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;currentHeaderValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;newHeaderValue&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hx-headers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newHeaderValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7.2. Validation
&lt;/h3&gt;

&lt;p&gt;MVC and DataAttributes provide a lot of helpers to generate validation. Unfortunately, most of it is designed for &lt;code&gt;jquery.validate.js&lt;/code&gt; and &lt;code&gt;jquery.validate.unobtrusive.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;strong&gt;htmx&lt;/strong&gt; is designed to utilize HTML5 validation.&lt;/p&gt;

&lt;p&gt;Of course, we could disable client-side validation and write everything by hand, but that sounds annoying.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddViewOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HtmlHelperOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientValidationEnabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the moment I didn't found a good solution for that. I could see a several options here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Link together HTML 5 and MVC-generated validation, by writing custom js code&lt;/li&gt;
&lt;li&gt;Link together MVC-generated validation and htmx, but keep using &lt;code&gt;jquery.validate&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you know a solution to that problem, feel free to ping me.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Conclusion
&lt;/h2&gt;

&lt;p&gt;To sum it up, I tried different use cases of &lt;strong&gt;htmx&lt;/strong&gt; and dotnet &lt;strong&gt;Razor Pages&lt;/strong&gt; during my tests. In this article, I presented the history of &lt;strong&gt;htmx&lt;/strong&gt; and &lt;strong&gt;Razor Pages&lt;/strong&gt;, and show you how they can be integrated seamlessly.&lt;/p&gt;

&lt;p&gt;I was impressed by how a technology from 2009, &lt;strong&gt;partials&lt;/strong&gt;, integrates so well with &lt;strong&gt;htmx&lt;/strong&gt;. It also fits well with &lt;strong&gt;Razor Pages&lt;/strong&gt; and &lt;strong&gt;pico.css&lt;/strong&gt;, which I often use for side projects. I only have positive impressions here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It works with razor and MVC partials perfectly&lt;/li&gt;
&lt;li&gt;It's a good fit with semantic HTML CSS libraries&lt;/li&gt;
&lt;li&gt;It's two times smaller than jQuery&lt;/li&gt;
&lt;li&gt;It's straightforward to use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regarding the negative aspects&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is no IntelliSense support of htmx tags in Visual Studio yet (there is for Visual Studio Code)&lt;/li&gt;
&lt;li&gt;Need to figure out validation to reuse all that helpers &lt;strong&gt;Razor Pages&lt;/strong&gt; provides&lt;/li&gt;
&lt;li&gt;Some kind of &lt;a href="https://htmx.org/essays/template-fragments/" rel="noopener noreferrer"&gt;fragments&lt;/a&gt; would be nice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;htmx&lt;/strong&gt; might not be accepted by enterprises where dotnet is most used&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That would be all. Once again, the source code of my tests is available on &lt;a href="https://github.com/xakpc/RazorHtmx" rel="noopener noreferrer"&gt;github&lt;/a&gt;, the demo project &lt;a href="https://razorhtmx.azurewebsites.net/" rel="noopener noreferrer"&gt;deployed on Azure&lt;/a&gt;, and if you have any questions, feel free to ping &lt;a href="https://x.com/xakpc" rel="noopener noreferrer"&gt;me&lt;/a&gt; on Twitter, sorry, on 𝕏.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Further Reading/References
&lt;/h2&gt;

&lt;p&gt;In the future, I might explore some more advanced scenarios, like validation, boosting, history, and maybe even touch hyperscript (but I doubt that; it does not bring me joy at first glance).&lt;/p&gt;

&lt;p&gt;Some useful resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-7.0&amp;amp;tabs=visual-studio" rel="noopener noreferrer"&gt;Microsoft Razor Pages documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.learnrazorpages.com/" rel="noopener noreferrer"&gt;Learn Razor Pages&lt;/a&gt; by Mike Brind&lt;/li&gt;
&lt;li&gt;&lt;a href="https://htmx.org/docs/" rel="noopener noreferrer"&gt;htmx Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>dotnet</category>
      <category>aspnet</category>
      <category>htmx</category>
    </item>
    <item>
      <title>How to create Sitemap.xml for ASP.net Core Razor Pages</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Wed, 16 Aug 2023 17:16:34 +0000</pubDate>
      <link>https://dev.to/xakpc/how-to-create-sitemapxml-for-aspnet-core-razor-pages-38fl</link>
      <guid>https://dev.to/xakpc/how-to-create-sitemapxml-for-aspnet-core-razor-pages-38fl</guid>
      <description>&lt;p&gt;Recently I wanted to create a sitemap for my Razor Page web application. Adding a sitemap to a website is a relatively straightforward process, but I found out that many examples over the web are a bit outdated. So I decided to document how I added it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The following information is related to Microsoft.AspNetCore.App version 7.0.7.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In general, creating a &lt;code&gt;sitemap.xml&lt;/code&gt; for web applications can significantly enhance their search engine visibility. A sitemap provides a roadmap for search engine bots, enabling them to index your website content more efficiently.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 1: Understand the Structure of&lt;/strong&gt; &lt;code&gt;sitemap.xml&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A sitemap XML file lists URLs for a site along with additional metadata about each URL (when it was last updated, how often it changes, and its importance relative to other URLs).&lt;/p&gt;

&lt;p&gt;Here is a very basic XML sitemap that includes the location of a single URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;urlset&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://www.example.com/foo.html&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;lastmod&amp;gt;&lt;/span&gt;2022-06-04&lt;span class="nt"&gt;&amp;lt;/lastmod&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/urlset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are more parameters defined in the &lt;a href="https://www.sitemaps.org/protocol.html" rel="noopener noreferrer"&gt;protocol specification&lt;/a&gt;, but Google claims to ignore them, so I think they could be omitted:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Google ignores &lt;code&gt;&amp;lt;priority&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;changefreq&amp;gt;&lt;/code&gt; values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google uses the &lt;code&gt;&amp;lt;lastmod&amp;gt;&lt;/code&gt; value if it's consistently and verifiably (for example by comparing to the last modification of the page) accurate.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step 2: Create a Model for the Sitemap&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;First, I created a model for the sitemap node. This model will represent individual URLs, their priority, and other metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapNode&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;SitemapFrequency&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Frequency&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;LastModified&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SitemapFrequency&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Yearly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Monthly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Weekly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Daily&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Hourly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Always&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is another approach if I wanted to use serialization, which would look a bit clearer, but it takes twice as many lines of code, so I skipped it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"urlset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Namespace&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapUrlSet&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SitemapNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SitemapNodes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SitemapNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapNode&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"loc"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lastmod"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;LastModified&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"changefreq"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;SitemapFrequency&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Frequency&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SitemapFrequency&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"never"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Never&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"yearly"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Yearly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"monthly"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Monthly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"weekly"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Weekly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"daily"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Daily&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hourly"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Hourly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;XmlEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;Always&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 3: Setting Up the Method to Generate Sitemap Nodes&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;I needed a service or method that will generate the sitemap nodes based on my website's content. To generate URLs for Razor Pages, you typically could use &lt;code&gt;PageLink&lt;/code&gt; or &lt;code&gt;LinkGenerator&lt;/code&gt;. I used the last one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;LinkGenerator&lt;/span&gt; &lt;span class="n"&gt;_linkGenerator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;SitemapModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinkGenerator&lt;/span&gt; &lt;span class="n"&gt;linkGenerator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_linkGenerator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;linkGenerator&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// ... rest of the code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creating a sitemap for static pages is easier by hardcoding them. Blog pages or other dynamic content are taken from a database or file system (in my case) based on where they are stored.&lt;/p&gt;

&lt;p&gt;Method &lt;code&gt;GetUriByPage&lt;/code&gt; from provides an absolute URL based on page name - handy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;   
        &lt;span class="c1"&gt;// ... rest of the code   &lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IReadOnlyCollection&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SitemapNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetSitemapNodes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SitemapNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_linkGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUriByPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Index"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_linkGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUriByPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Tools/CreateCode"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_linkGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUriByPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Legal/Privacy"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;Url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_linkGenerator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetUriByPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"/Legal/TermsOfService"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.6&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// fill nodes from blog index&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 4: Creating the Sitemap Page&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Then I added a new Razor Page &lt;code&gt;Sitemap.cshtml&lt;/code&gt; that will be responsible for generating the &lt;code&gt;sitemap.xml&lt;/code&gt;. When users or search engine bots access this page, it should return the sitemap in XML format. This is an elegant trick: we return the razor page &lt;code&gt;Sitemap.cshtml&lt;/code&gt; as an XML file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt;
&lt;span class="n"&gt;@model&lt;/span&gt; &lt;span class="n"&gt;Xakpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SitemapModel&lt;/span&gt;
&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Layout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text/xml"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;?&lt;/span&gt;&lt;span class="n"&gt;xml&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"1.0"&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="p"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;@Html&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawXmlData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another required step is to set up the app to return this file by &lt;code&gt;/sitemap.xml&lt;/code&gt; path. It is done in options of &lt;code&gt;AddRazorPages&lt;/code&gt; method like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPages&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRazorPagesOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Conventions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddPageRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/sitemap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Sitemap.xml"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 5: Formatting the XML&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;On the sitemap Razor Page model, I formatted the sitemap nodes into XML format. For that, I manually built XML file with &lt;code&gt;XElements&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;   
        &lt;span class="c1"&gt;// ... rest of the code  &lt;/span&gt;

        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Serializes to raw XML&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetSitemapDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SitemapNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sitemapNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;XNamespace&lt;/span&gt; &lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"urlset"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sitemapNode&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sitemapNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;urlElement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"loc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EscapeUriString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Url&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                    &lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastModified&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"lastmod"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LastModified&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToLocalTime&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"yyyy-MM-ddTHH:mm:sszzz"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                    &lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frequency&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"changefreq"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Frequency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;ToLowerInvariant&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                    &lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="n"&gt;xmlns&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;sitemapNode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"F1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CultureInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvariantCulture&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
                &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urlElement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other option is to use serialization (if you map classes with attributes - I skipped that part)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetSitemapDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SitemapNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sitemapNodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sitemapUrlSet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SitemapUrlSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;SitemapNodes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sitemapNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xmlSerializer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;XmlSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SitemapUrlSet&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stringWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringWriterUtf8&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;xmlTextWriter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;XmlWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stringWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;XmlWriterSettings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Indent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="n"&gt;xmlSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xmlTextWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sitemapUrlSet&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;stringWriter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And all that is left is to call methods in &lt;code&gt;OnGet&lt;/code&gt; method and set bound property&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SitemapModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PageModel&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;   
        &lt;span class="c1"&gt;// ... rest of the code  &lt;/span&gt;

        &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
        &lt;span class="c1"&gt;/// Gets the raw XML data&lt;/span&gt;
        &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;BindProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SupportsGet&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;RawXmlData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnGet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetSitemapNodes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;RawXmlData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GetSitemapDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;Step 6: Register the Sitemap with Search Engines&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;After the sitemap is successfully set up, it's a good idea to register it with major search engines like Google and Bing. This will ensure that they know your sitemap's existence and can crawl your website more effectively.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google&lt;/strong&gt;: Use Google Search Console to submit your sitemap.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bing&lt;/strong&gt;: Use Bing Webmaster Tools to submit your sitemap.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Final Sitemap.xml&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;urlset&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://www.sitemaps.org/schemas/sitemap/0.9"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://contoso.com/&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;1.0&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://contoso.com/make-code&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.9&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://contoso.com/legal/privacy&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.6&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;url&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;loc&amp;gt;&lt;/span&gt;https://contoso.com/legal/termsofservice&lt;span class="nt"&gt;&amp;lt;/loc&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;priority&amp;gt;&lt;/span&gt;0.6&lt;span class="nt"&gt;&amp;lt;/priority&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/url&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/urlset&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, setting up a &lt;code&gt;sitemap.xml&lt;/code&gt; in a Razor Pages application is straightforward. Following the steps above and adding the appropriate code ensures your website is more visible and accessible to search engines.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>webdev</category>
      <category>aspnet</category>
    </item>
    <item>
      <title>Write and Read Google Spreadsheet from Telegram bot with Google Cloud Functions</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Fri, 19 Feb 2021 22:31:16 +0000</pubDate>
      <link>https://dev.to/xakpc/write-and-read-google-spreadsheet-from-telegram-bot-with-google-cloud-functions-54p5</link>
      <guid>https://dev.to/xakpc/write-and-read-google-spreadsheet-from-telegram-bot-with-google-cloud-functions-54p5</guid>
      <description>&lt;p&gt;Recently I faced a request&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm tracking the number of days off each person in my company in google sheets, where I manually add/subtract their remaining days off. Can a telegram bot show them how many days off they have left once they search for their names?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is yes, and here is how to do that with Google Cloud Functions and API.chat chatbot platform in &lt;strong&gt;under 100 lines of code&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup Google Functions
&lt;/h2&gt;

&lt;p&gt;First, you need to set up cloud functions and give them access to our document. &lt;/p&gt;

&lt;p&gt;Overall you will need two functions: &lt;code&gt;gsheet-get&lt;/code&gt; to provide answers and &lt;code&gt;gsheet-add&lt;/code&gt; to register users. Let's create them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafemv5fk1hicwuh4wfhh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafemv5fk1hicwuh4wfhh.png" alt="image.png" width="664" height="828"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We do not care about the function code yet. When you set up the first function GCloud will probably ask you to enable Billing and, most importantly, will create a Service account - App Engine default service account. Usage of this account will allow you to perform &lt;a href="https://github.com/googleapis/google-api-nodejs-client#service-account-credentials" rel="noopener noreferrer"&gt;server to server, app-level authentication&lt;/a&gt;  later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsc3ee37x0utddz9ie1w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzsc3ee37x0utddz9ie1w.png" alt="image.png" width="800" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But you do need to give access rights to our document to this account.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu3zjq54rkm1p7ab64aa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnu3zjq54rkm1p7ab64aa.png" alt="image.png" width="588" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The document itself is quite simple &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3a25fydnv0a8fzotfgh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa3a25fydnv0a8fzotfgh.png" alt="image.png" width="593" height="696"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Function &lt;code&gt;google-sheet-add&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This function will store the user channel (telegram in the case), chat id, and a name to your table. Google Functions support different languages, including &lt;code&gt;.net core 3.1&lt;/code&gt;, but I found out that node.js gives more compact code, so let's stick to it, and sorry for &lt;em&gt;dotnetish&lt;/em&gt; code-style&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/spreadsheets&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;  
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;spreadsheets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;spreadsheetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT-SHEET-ID-HERE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Days!A:C&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;valueInputOption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RAW&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;insertDataOption&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INSERT_ROWS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;    
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code here will append provided values as a new row to the Days sheet. API.chat will provide user data such as channel, chat id, and the name of the user.&lt;/p&gt;

&lt;p&gt;Don't forget to add &lt;code&gt;googleapis&lt;/code&gt; dependency into &lt;code&gt;package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "dependencies": {
    "googleapis": "^42"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main need for this function is to match actual people with their telegram account. There could be more sophisticated ways to do this, but for now this simple way: ask user name - is enough.&lt;/p&gt;

&lt;p&gt;That will give you all the required information about the user and you could start calculating his day-offs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwiq5uremh50ggx4n9w5e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwiq5uremh50ggx4n9w5e.png" alt="image.png" width="521" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Function &lt;code&gt;google-sheet-get&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The second function is for getting data from Google Spreadsheet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { google } = require('googleapis');

exports.main = async (req, res) =&amp;gt; {
  const chatId = req.query.id;
  const auth = await google.auth.getClient({ scopes: ['https://www.googleapis.com/auth/spreadsheets'] });  
  const sheets = google.sheets({ version: 'v4', auth });
  try 
  {
    let sheet = await sheets.spreadsheets.values.get({
      spreadsheetId: 'INSERT-SHEET-ID-HERE',
      range: 'Days!A:D'
    });

    const rows = sheet.data.values;
    if (rows.length) {
      let finded = rows.find(el =&amp;gt; el[1] === chatId)      
      if (finded === undefined) {     
        res.status(200).send({ CorrelationId: req.query.request, Messages: [ "Information not availible" ] });
      } else {      
        res.status(200).send({ CorrelationId: req.query.request, Messages: [ `You have ${finded[3]} days off` ] });
      }
    } else {
      console.warn('No data found.');
      res.status(200).send({ CorrelationId: req.query.request, Messages: [ "No data found" ] });
    }
    res.status(200).send();
  }
  catch  (err)
  {
    console.error('The API returned an error: ' + err)
    res.status(500).send({ err })
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main thing I found out here is that google.sheets API filter does not allow you to filter on actual data - only on metadata. That's why you need to get the entire sheet and filter it in code. Like I said before, API.chat will provide your endpoint with chat-id for filtering. From there you can return a formatted string with the answer like that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "Messages": [
    "You have 7 days off"
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Chatbot Scenario
&lt;/h2&gt;

&lt;p&gt;Now when you have all endpoints in place and working you can create an actual chatbot. I will use my very own  &lt;a href="https://api.chat" rel="noopener noreferrer"&gt;chatbot builder API.chat&lt;/a&gt;. A scenario might be very simple, like that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;bot&amp;gt;
  &amp;lt;states&amp;gt;
    &amp;lt;state name="Start"&amp;gt;
      &amp;lt;transition input="register" next="Registration"&amp;gt;Please enter your name&amp;lt;/transition&amp;gt;
      &amp;lt;transition input="*" next="Start" pending_keyboard="Register Me"&amp;gt;Hello. Please Register first&amp;lt;/transition&amp;gt;      
    &amp;lt;/state&amp;gt;
    &amp;lt;state name="Registration"&amp;gt;      
      &amp;lt;transition input="*" next="Registered" morphology="msg" action="https://us-central1-REDACTED.cloudfunctions.net/gsheet-add?name={msg}" no_stop="true"&amp;gt;Welcome!&amp;lt;/transition&amp;gt;
    &amp;lt;/state&amp;gt;
    &amp;lt;state name="Registered"&amp;gt;
      &amp;lt;transition input="days" next="Registered" action="https://us-central1-REDACTED.net/gsheet-get" pending_keyboard="Days Off"/&amp;gt;
      &amp;lt;transition input="*" next="Registered" pending_keyboard="Days Off"&amp;gt;Press the button to get your Days off&amp;lt;/transition&amp;gt;
    &amp;lt;/state&amp;gt;
  &amp;lt;/states&amp;gt;
&amp;lt;/bot&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we do here is register the user, asking his or her name, and pass it into &lt;code&gt;gsheet-add&lt;/code&gt; action in query. After registration user stays in the &lt;code&gt;Registered&lt;/code&gt; state forever with the ability to request their days-off through &lt;code&gt;gsheet-get&lt;/code&gt; action at any time.&lt;/p&gt;

&lt;p&gt;All that's left is to PUT this scenario to the chatbot scenario endpoint and our bot is ready to deliver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -v -X PUT "https://bot.api.chat/v1/bots/botName/scenario"
-H "Content-Type: application/xml"
-H "Cache-Control: no-cache"
-H "Ocp-Apim-Subscription-Key: REDACTED"
--data-raw '&amp;lt;bot&amp;gt;
  &amp;lt;states&amp;gt;
    &amp;lt;state name="Start"&amp;gt;
      &amp;lt;transition input="register" next="Registration"&amp;gt;Please enter your name&amp;lt;/transition&amp;gt;
      &amp;lt;transition input="*" next="Start" pending_keyboard="Register Me"&amp;gt;Hello. Please Register first&amp;lt;/transition&amp;gt;      
    &amp;lt;/state&amp;gt;
    &amp;lt;state name="Registration"&amp;gt;      
      &amp;lt;transition input="*" next="Registered" morphology="msg" action="https://us-central1-REDACTED.cloudfunctions.net/gsheet-add?name={msg}" no_stop="true"&amp;gt;Welcome!&amp;lt;/transition&amp;gt;
    &amp;lt;/state&amp;gt;
    &amp;lt;state name="Registered"&amp;gt;
      &amp;lt;transition input="days" next="Registered" action="https://us-central1-REDACTED.cloudfunctions.net/gsheet-get" pending_keyboard="Days Off"/&amp;gt;
      &amp;lt;transition input="*" next="Registered" pending_keyboard="Days Off"&amp;gt;Press the button to get your Days off&amp;lt;/transition&amp;gt;
    &amp;lt;/state&amp;gt;
  &amp;lt;/states&amp;gt;
&amp;lt;/bot&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Out tiny chatbot is ready&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsosi5cgqt4dfuwps3mjx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsosi5cgqt4dfuwps3mjx.png" alt="image" width="472" height="457"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Let's count code lines&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google-sheet-add&lt;/code&gt;: 25&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google-sheet-get&lt;/code&gt;: 30&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scenario&lt;/code&gt;: 15&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;overall&lt;/strong&gt;: 70 lines of code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not bad&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>chatbots</category>
    </item>
    <item>
      <title>My jeans tear down today due to extensive SITTING, what should I replace them with?</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Mon, 14 Dec 2020 13:07:46 +0000</pubDate>
      <link>https://dev.to/xakpc/my-jeans-tear-down-today-due-to-extensive-sitting-what-should-i-replace-them-with-42n4</link>
      <guid>https://dev.to/xakpc/my-jeans-tear-down-today-due-to-extensive-sitting-what-should-i-replace-them-with-42n4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3pi4ul7yx5jv8831vbor.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F3pi4ul7yx5jv8831vbor.jpg" alt="Some web Photo" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hello, dev. I have a genuine question, but let me share a little background first. My nice pair of jeans was torn down today in the crotch area due to extensive sitting I presume. While I probably can fix it in the shop or replace it, I rather move to something more robust, because this was not the first pair that was torn down that way.&lt;/p&gt;

&lt;p&gt;And the question is: &lt;strong&gt;what pants should I buy next?&lt;/strong&gt; Which one combines practicality, robustness, comfort, and fit the best lifestyle of a developer (who mostly sits)?&lt;/p&gt;

&lt;p&gt;P.S. The only option out of choice is sweatpants, they are home wear, and for me, it's critical to separate work and home wear to have at least some stability during the day.&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Bootstrap+Blazor essentials</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Tue, 13 Oct 2020 15:09:24 +0000</pubDate>
      <link>https://dev.to/xakpc/bootstrap-blazor-essentials-1g0</link>
      <guid>https://dev.to/xakpc/bootstrap-blazor-essentials-1g0</guid>
      <description>&lt;p&gt;When you start a new Blazor project it will quickstart you with a default Bootstrap template. But when you spent several weeks or months on the project you starting to notice that there is a lot of duplicate HTML code that can be refactored and moved into components.&lt;/p&gt;

&lt;p&gt;In this post, I want to give you some advice on how and which Bootstrap components you can wrap into &lt;code&gt;.razor&lt;/code&gt; components to make your code simpler, easier to maintain, and expandable.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xakpc" rel="noopener noreferrer"&gt;
        xakpc
      &lt;/a&gt; / &lt;a href="https://github.com/xakpc/Xakpc.BlazorBits.Bootstrap" rel="noopener noreferrer"&gt;
        Xakpc.BlazorBits.Bootstrap
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Sample project for https://dev.to/xakpc/ article
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;All these components are alterations of what I use every day in my projects. Why not share the same? In real projects, there is usually some CSS template used that could alter the component significantly.&lt;/p&gt;

&lt;p&gt;These examples use clean Bootstrap and can be the foundation that could be altered for your CSS template. That's why I usually just copy-paste them across projects and tune to fit the current CSS template, instead of moving them to separate NuGet package;&lt;/p&gt;

&lt;p&gt;Bootstrap has more than 20 components, but for this article, I keep only the ones that used very often and make a great example for you where to go from hereby.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Button&lt;/li&gt;
&lt;li&gt;Button Group&lt;/li&gt;
&lt;li&gt;Card&lt;/li&gt;
&lt;li&gt;Progress&lt;/li&gt;
&lt;li&gt;Spinner&lt;/li&gt;
&lt;li&gt;Toast&lt;/li&gt;
&lt;li&gt;Modals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next time&lt;/strong&gt; - Form and Form Components in Blazor with MVVM&lt;/p&gt;

&lt;h1&gt;
  
  
  Button
&lt;/h1&gt;

&lt;p&gt;The most basic component is a button. Because I use &lt;a href="https://dev.to/xakpc/using-blazor-consider-mvvm-59dm"&gt;MVVM&lt;/a&gt; my button is based on the &lt;code&gt;ICommand&lt;/code&gt; interface&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;When you add action to button's ICommand &lt;code&gt;AddCommand = new Command&amp;lt;int&amp;gt;(DoAdd);&lt;/code&gt; take notice of the action&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private void DoAdd(int p)
{
    Value += p;
    StateHasChanged(); // &amp;lt;--- not gonna work without it
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I &lt;a href="https://twitter.com/xakpc/status/1314235866605379585" rel="noopener noreferrer"&gt;found out&lt;/a&gt; that &lt;code&gt;onaction&lt;/code&gt; will re-render only the component it belongs to. Even if actual action changes something from another (parent) component. But when you use ViewModel there is no need in &lt;code&gt;StateHasChanged&lt;/code&gt; call here because the View should be subscribed to &lt;code&gt;INotifyPropertyChanged&lt;/code&gt; anyway.&lt;/p&gt;
&lt;h1&gt;
  
  
  Button Group
&lt;/h1&gt;

&lt;p&gt;Button group wraps several command buttons in a single neat component. I usually use it for data table editing&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg8swcuac9pdm2xt54bup.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fg8swcuac9pdm2xt54bup.PNG" alt="Alt Text" width="742" height="288"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;The component is rather simple, you can use icons instead of text for a better look, but the main thing that it nicely fits Blazor way of building tables.&lt;br&gt;
You could pass commands and current item to ViewModel for processing. If you have a lot of tables in your app that saves tons of space also, you get consistency across tables and can easily adapt styles in the future.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;tbody&amp;gt;
    @foreach (var item in Items)
    {
        &amp;lt;tr&amp;gt;
            &amp;lt;td&amp;gt;@item&amp;lt;/td&amp;gt;
            &amp;lt;td&amp;gt;&amp;lt;RowButtonGroup 
                    EditCommand="EditItemCommand" 
                    DeleteCommand="DeleteItemCommand" 
                    Parameter="item"/&amp;gt;&amp;lt;/td&amp;gt;
        &amp;lt;/tr&amp;gt;
    }
&amp;lt;/tbody&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h1&gt;
  
  
  Card
&lt;/h1&gt;

&lt;p&gt;For me, the card is the primary component of any web app. This component wrap most of the basic HTML card into several optional &lt;code&gt;RenderFragments&lt;/code&gt;. You can expand it further to add an optional &lt;code&gt;card-image&lt;/code&gt; or &lt;code&gt;list-group&lt;/code&gt; block.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;I noticed that some folks missing the fact that you &lt;strong&gt;can use several RenderFragment&lt;/strong&gt; inside your component. Well, you can and it allows you to use your components like that&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Card Title="Special title treatment"&amp;gt;
    &amp;lt;Body&amp;gt;
        &amp;lt;p class="card-text"&amp;gt;With supporting text below as a natural lead-in to additional content.&amp;lt;/p&amp;gt;
        &amp;lt;a href="#" class="btn btn-primary"&amp;gt;Go somewhere&amp;lt;/a&amp;gt;
    &amp;lt;/Body&amp;gt;
    &amp;lt;Footer&amp;gt;
        2 days ago
    &amp;lt;/Footer&amp;gt;
&amp;lt;/Card&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Please note&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Parameter(CaptureUnmatchedValues = true)]
public Dictionary&amp;lt;string, object&amp;gt; InputAttributes { get; set; } 
    = new Dictionary&amp;lt;string, object&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This parameter is used on card to catch-all unmatched attributes that you want to assign to it &lt;code&gt;&amp;lt;div class="card @Class" @attributes="InputAttributes"&amp;gt;&lt;/code&gt;. It's usually a good practice to include such catch-all property to every custom component. Saves some time on unmatched exceptions later. But be aware where you put it, for some components, it might be wise to apply attributes to the child or not apply it at all.&lt;/p&gt;
&lt;h1&gt;
  
  
  Progress Bar
&lt;/h1&gt;

&lt;p&gt;Small component but I want to show it here as an example of the power of Blazor components and proper MVVM usage.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h2&gt;
  
  
  Progress Bar with MVVM Example
&lt;/h2&gt;

&lt;p&gt;Let's dig into the example of usage of ProgressBar with backing it ViewModel.&lt;/p&gt;

&lt;h3&gt;
  
  
  View
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;h2 class="mt-3"&amp;gt;Interactive (with VM)&amp;lt;/h2&amp;gt;
&amp;lt;ProgressBar Progress="@DataContext.Progress" Class="mt-2 mb-2"&amp;gt;@((DataContext.Progress/100f).ToString("P0"))&amp;lt;/ProgressBar&amp;gt;
&amp;lt;CommandButton class="btn-primary" Command="@DataContext.Add"&amp;gt;+&amp;lt;/CommandButton&amp;gt;
&amp;lt;CommandButton class="btn-secondary" Command="@DataContext.Subtract"&amp;gt;-&amp;lt;/CommandButton&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;As you can see View is quite simple: we have progress which is transformed like that &lt;code&gt;@((DataContext.Progress/100f).ToString("P0"))&lt;/code&gt; for display only and couple buttons with commands &lt;code&gt;Add&lt;/code&gt; and &lt;code&gt;Subtract&lt;/code&gt;. &lt;br&gt;
These actions are calculated in ViewModel. To ensure that ViewModel created, initialized, and linked I use &lt;a href="https://gist.github.com/xakpc/6d15b7b10ac9d01fcf9dbe30e89fcde8" rel="noopener noreferrer"&gt;ContextComponentBase&lt;/a&gt; as a base class for all my pages.&lt;/p&gt;

&lt;p&gt;Page code-behind looks like this&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    public partial class Progresses
    {
        [Inject]
        protected new ProgressViewModel DataContext
        {
            get =&amp;gt; base.DataContext as ProgressViewModel;
            set =&amp;gt; base.DataContext = value;
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Ensure that &lt;code&gt;.razor&lt;/code&gt; page itself is inherited from the proper class&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@page "/progress"
@inherits Xakpc.BlazorBits.Bootstrap.ViewModels.ContextComponentBase;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That's all we need for View, let's switch to ViewModel!&lt;/p&gt;
&lt;h3&gt;
  
  
  ViewModel
&lt;/h3&gt;

&lt;p&gt;First, check out the previous code snippet. Our ViewModel is Injected into our View - so let's not forget to add it in &lt;code&gt;Startup.cs&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;services.AddScoped&amp;lt;ProgressViewModel&amp;gt;();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I recommend read several times about &lt;a href="https://docs.microsoft.com/en-Us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetime/" rel="noopener noreferrer"&gt;Scopes&lt;/a&gt; in Blazor. Once I lost a day trying to fix a problem caused by misunderstanding Blazor DI Scopes.&lt;br&gt;
Finally a ViewModel itself&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public class ProgressViewModel : ViewModelBase
{
    private int _progress;

    public ProgressViewModel()
    {
        Add = new Command&amp;lt;int&amp;gt;(i =&amp;gt; Progress += 1, i =&amp;gt; Progress &amp;lt; 100);
        Subtract = new Command&amp;lt;int&amp;gt;(i =&amp;gt; Progress -= 1, i =&amp;gt; Progress &amp;gt; 0);
    }

    public int Progress
    {
        get =&amp;gt; _progress;
        set { _progress = value; OnPropertyChanged(nameof(Progress)); }
    }

    public override Task InitializeAsync()
    {
        Progress = 22;
        return base.InitializeAsync();
    }

    public ICommand Add { get; set; }
    public ICommand Subtract { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I will not go deep into VM, only point to main things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;InitializeAsync&lt;/code&gt; called once per scope on first page-load - that means once per connection for Blazor Server because we added it with &lt;code&gt;AddScope&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Commands set &lt;code&gt;Progress&lt;/code&gt; which fire &lt;code&gt;OnPropertyChanged&lt;/code&gt; which fire &lt;code&gt;StateHasChanged&lt;/code&gt; which fire page redraw;&lt;/li&gt;
&lt;li&gt;Commands have &lt;code&gt;CanExecute&lt;/code&gt; func to ensure that Progress in range 0-100;&lt;/li&gt;
&lt;li&gt;CanExecute also used to disable buttons when needed;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Spinner
&lt;/h1&gt;

&lt;p&gt;Spinner is another simple example of how easy to wrap into a Razor component.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsuoy0ho1b7yuz6vsc5ut.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fsuoy0ho1b7yuz6vsc5ut.PNG" alt="Alt Text" width="339" height="85"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;h1&gt;
  
  
  Toasts
&lt;/h1&gt;

&lt;p&gt;For toasts, I usually don't use a Bootstrap component but a nice Blazor port of very popular toastr.js &lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/sotsera" rel="noopener noreferrer"&gt;
        sotsera
      &lt;/a&gt; / &lt;a href="https://github.com/sotsera/sotsera.blazor.toaster" rel="noopener noreferrer"&gt;
        sotsera.blazor.toaster
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A Toastr.js port to Blazor in pure .Net.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Sotsera.Blazor.Toaster&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;A Blazor port of &lt;a href="https://github.com/CodeSeven/toastr/" rel="noopener noreferrer"&gt;Toastr.js&lt;/a&gt; to Server and Webassembly Blazor Apps.&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;The transitions are implemented using &lt;code&gt;System.Threading.Timer&lt;/code&gt; timers so the resource usage should be closely monitored when using the server-side hosting model.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Only for &lt;strong&gt;server-side&lt;/strong&gt; projects&lt;/h3&gt;
&lt;/div&gt;
&lt;p&gt;The static assets from Razor Component Libraries are available by default &lt;a href="https://github.com/aspnet/AspNetCore/issues/13190#issuecomment-522066404" rel="noopener noreferrer"&gt;only in &lt;strong&gt;Development&lt;/strong&gt; mode&lt;/a&gt;. They can be enabled on &lt;strong&gt;Production&lt;/strong&gt;  using the &lt;code&gt;UseStaticWebAssets()&lt;/code&gt; method in the &lt;code&gt;Program.cs&lt;/code&gt; file as in the following example:&lt;/p&gt;
&lt;div class="highlight highlight-source-cs notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;public&lt;/span&gt; &lt;span class="pl-k"&gt;&lt;span class="pl-k"&gt;static&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-smi"&gt;IHostBuilder&lt;/span&gt; &lt;span class="pl-en"&gt;CreateHostBuilder&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-smi"&gt;string&lt;/span&gt;&lt;span class="pl-kos"&gt;[&lt;/span&gt;&lt;span class="pl-kos"&gt;]&lt;/span&gt; &lt;span class="pl-s1"&gt;args&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="pl-s1"&gt;Host&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;CreateDefaultBuilder&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;args&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
        &lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;ConfigureWebHostDefaults&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;webBuilder &lt;span class="pl-c1"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="pl-kos"&gt;{&lt;/span&gt;
            &lt;span class="pl-s1"&gt;webBuilder&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;UseStaticWebAssets&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
            &lt;span class="pl-s1"&gt;webBuilder&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-smi"&gt;UseStartup&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-smi"&gt;Startup&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
        &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Sample&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;The client-side sample project has been published &lt;a href="https://blazor-toaster.sotsera.com/" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Changes&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;__version 3.0.0&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;new configuration options for styling the toast title (&lt;strong&gt;ToastTitleClass&lt;/strong&gt;) and message (&lt;strong&gt;ToastTitleClass&lt;/strong&gt;) and an example for aligning their text to…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/sotsera/sotsera.blazor.toaster" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
I should probably try Bootrstap toasts someday.
&lt;h1&gt;
  
  
  Modal
&lt;/h1&gt;

&lt;p&gt;Bootstrap has a Modal component but it is highly dependant on JavaScript. In my work with Blazor, I prefer to minimize the amount of JS code overall, so I use &lt;code&gt;Blazored.Modal&lt;/code&gt; - an amazing native component for Modals. You should check it out.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Blazored" rel="noopener noreferrer"&gt;
        Blazored
      &lt;/a&gt; / &lt;a href="https://github.com/Blazored/Modal" rel="noopener noreferrer"&gt;
        Modal
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A powerful and customizable modal implementation for Blazor applications.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Blazored Modal&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;A powerful and customizable modal implementation for &lt;a href="https://blazor.net" rel="nofollow noopener noreferrer"&gt;Blazor&lt;/a&gt; applications.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.nuget.org/packages/Blazored.Modal/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/d2bc87183ed58439d289c11619e022cfeec300790ed90d40f1ebd40e3cc3a4bc/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f762f426c617a6f7265642e4d6f64616c2e7376673f6c6f676f3d6e75676574" alt="Nuget version"&gt;&lt;/a&gt;
&lt;a href="https://www.nuget.org/packages/Blazored.Modal/" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/abe78a80916e40f09a407178e88001e0618b3b720c7ac775a5515e9f4566ee77/68747470733a2f2f696d672e736869656c64732e696f2f6e756765742f64742f426c617a6f7265642e4d6f64616c3f6c6f676f3d6e75676574" alt="Nuget downloads"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/Blazored/Modal/workflows/Build%20&amp;amp;%20Test%20Main/badge.svg"&gt;&lt;img src="https://github.com/Blazored/Modal/workflows/Build%20&amp;amp;%20Test%20Main/badge.svg" alt="Build &amp;amp; Test Main"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/6171719/205016381-79dd5c65-8090-4bac-91e9-b4aee6e246d6.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F6171719%2F205016381-79dd5c65-8090-4bac-91e9-b4aee6e246d6.png" alt="Screenshot of Blazored Modal"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Documentation&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;For information on getting Blazored Modal setup in your application, as well as the many customisation options, please checkout our &lt;a href="https://blazored.github.io/Modal/" rel="nofollow noopener noreferrer"&gt;docs&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Blazored/Modal" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;And that's all for now. I am not very good at writing such long articles, so will be appreciated for any feedback. &lt;/p&gt;

&lt;p&gt;And if you have any questions, feel free to mention me in &lt;a href="https://twitter.com/xakpc" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>blazor</category>
      <category>bootsrap</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Using Blazor? Consider MVVM</title>
      <dc:creator>Pavel Osadchuk</dc:creator>
      <pubDate>Mon, 05 Oct 2020 12:11:42 +0000</pubDate>
      <link>https://dev.to/xakpc/using-blazor-consider-mvvm-59dm</link>
      <guid>https://dev.to/xakpc/using-blazor-consider-mvvm-59dm</guid>
      <description>&lt;p&gt;With the first glance at &lt;a href="https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor" rel="noopener noreferrer"&gt;Blazor&lt;/a&gt; - a framework for building interactive client-side web UI with .NET, I have that idea - it's perfect for MVVM.&lt;/p&gt;

&lt;p&gt;MVVM states for Model-View-ViewModel pattern.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu34bpm9qjp9k791578mo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu34bpm9qjp9k791578mo.png" alt="General MVVM Idea" width="401" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I started with MVVM on WPF apps many years ago and spent a lot of time with MVVMLight and Xamarin, so I immediately saw an advantage of it for Blazor as well.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Like in WPF, Blazor has the most important feature - data binding. In short, you have some data, typically in a collection of some sort, and you want to display it to the user. You can 'bind' your view to the data.&lt;/li&gt;
&lt;li&gt;Like in WPF, Blazor has two parts, the &lt;code&gt;.razor&lt;/code&gt; page which describes your GUI in HTML, and the code-behind &lt;code&gt;.razor.cs&lt;/code&gt; that is tied to it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This makes Blazor MVVM default for me. So I picked some base components from old MVVM projects which I want to share.&lt;/p&gt;

&lt;p&gt;This is ComponentBase which I use for MVVM everywhere.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;DataContext&lt;/code&gt; is quite basic, we only should ensure to initialize it only once for &lt;code&gt;ServerPrerendering&lt;/code&gt; mode because it will call &lt;code&gt;OnInitializeAsync&lt;/code&gt; twice due to, well, pre-rendering.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;The only problem here is JavaScript. You cannot use js methods in &lt;code&gt;OnInitializeAsync&lt;/code&gt;, and data initialization in &lt;code&gt;OnAfterRender&lt;/code&gt; looks wrong to me (at least it's not what docs propose).&lt;/p&gt;

&lt;p&gt;And there are things like js-loading indicator which need to be started if the page loaded but the data not.&lt;/p&gt;

&lt;p&gt;Much confusing... For now, I simply move all JS initialization to &lt;code&gt;OnAfterRender&lt;/code&gt; permanently.&lt;/p&gt;

&lt;p&gt;And how you would solve the JS problem in the MVVM pattern?&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>blazor</category>
      <category>mvvm</category>
    </item>
  </channel>
</rss>
