<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[.NET Bits and Bytes: dotnet, azure, web dev, htmx, chatbots]]></title><description><![CDATA[Random stories from dotnet fields. I wrote most about azure, dotnet mvc, razor pages, blazor and chatbots.]]></description><link>https://xakpc.info</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1695146587917/RNLym6axB.png</url><title>.NET Bits and Bytes: dotnet, azure, web dev, htmx, chatbots</title><link>https://xakpc.info</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 21:40:31 GMT</lastBuildDate><atom:link href="https://xakpc.info/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Building a Powerful MCP Server in .NET with Just 20 Lines of Code]]></title><description><![CDATA[The Model Context Protocol (MCP) enables AI assistants to connect with external tools and data sources. While traditional approaches require complex setups, .NET’s 10 (Preview 4) modern features let you create a fully functional MCP server in remarka...]]></description><link>https://xakpc.info/building-a-powerful-mcp-server-in-net-with-just-20-lines-of-code</link><guid isPermaLink="true">https://xakpc.info/building-a-powerful-mcp-server-in-net-with-just-20-lines-of-code</guid><category><![CDATA[mcp]]></category><category><![CDATA[mcp server]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Thu, 07 Aug 2025 04:14:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754540030389/f6412f7e-c8ed-4943-a1c2-0a198ff586e0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The Model Context Protocol (MCP) enables AI assistants to connect with external tools and data sources. While traditional approaches require complex setups, .NET’s 10 (Preview 4) modern features let you create a fully functional MCP server in remarkably few lines of code.</p>
<h2 id="heading-the-20-line-mcp-server">The 20-Line MCP Server</h2>
<p>Here's a complete, production-ready MCP server that could provide multiple useful tools:</p>
<pre><code class="lang-csharp"><span class="hljs-meta">#:package Microsoft.Extensions.Hosting@9.0.8</span>
<span class="hljs-meta">#:package ModelContextProtocol@0.3.0-preview.3</span>
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection;
<span class="hljs-keyword">using</span> Microsoft.Extensions.Hosting;
<span class="hljs-keyword">using</span> Microsoft.Extensions.Logging;
<span class="hljs-keyword">using</span> ModelContextProtocol.Server;
<span class="hljs-keyword">using</span> System.ComponentModel;

<span class="hljs-keyword">var</span> builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =&gt;
{
    <span class="hljs-comment">// Configure all logs to go to stderr</span>
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

<span class="hljs-comment">// Register the MCP server</span>
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

<span class="hljs-comment">// Build and run the MCP Server Application</span>
<span class="hljs-keyword">await</span> builder.Build().RunAsync();

<span class="hljs-comment">//====== TOOLS ======</span>

[<span class="hljs-meta">McpServerToolType</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TextTools</span>
{
    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Generates a password with specified length and complexity"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GeneratePassword</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> length = <span class="hljs-number">12</span>, <span class="hljs-keyword">bool</span> includeSymbols = <span class="hljs-literal">true</span>, <span class="hljs-keyword">bool</span> includeNumbers = <span class="hljs-literal">true</span></span>)</span>
    {
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> letters = <span class="hljs-string">"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> numbers = <span class="hljs-string">"0123456789"</span>;
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> symbols = <span class="hljs-string">"!@#$%^&amp;*()_+-=[]{}|;:,.&lt;&gt;?"</span>;

        <span class="hljs-keyword">var</span> chars = letters;
        <span class="hljs-keyword">if</span> (includeNumbers) chars += numbers;
        <span class="hljs-keyword">if</span> (includeSymbols) chars += symbols;

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">string</span>(Enumerable.Repeat(chars, length)
            .Select(s =&gt; s[Random.Shared.Next(s.Length)]).ToArray());
    }
}
</code></pre>
<p>That's it! Save this as <code>mcp-server.cs</code> and run with <code>dotnet run mcp-server.cs</code>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The code uses new <code>dotnet run app.cs</code> feature, that allows to run cs file directly without any projects, so you would need at least .NET 10 Preview 4 installed to make it work.</div>
</div>

<p>To explore the benefits of single-file MCPs further, I created an open-source solution: <a target="_blank" href="https://anymcp.io/">https://anymcp.io/</a>.</p>
<h2 id="heading-why-this-approach-is-revolutionary">Why This Approach is Revolutionary</h2>
<h3 id="heading-1-zero-project-setup">1. <strong>Zero Project Setup</strong></h3>
<p>Traditional .NET development requires creating projects, managing dependencies, and complex build configurations. The <code>#:package</code> directive eliminates all that overhead.</p>
<h3 id="heading-2-automatic-tool-discovery">2. <strong>Automatic Tool Discovery</strong></h3>
<p>The <code>WithToolsFromAssembly()</code> method automatically discovers any class marked with <code>[McpServerToolType]</code> and exposes its methods as MCP tools. No manual registration required.</p>
<h3 id="heading-3-type-safe-by-default">3. <strong>Type-Safe by Default</strong></h3>
<p>Unlike JSON-based tool definitions, your tools are strongly typed. IntelliSense works, refactoring is safe, and runtime errors are minimized.</p>
<h3 id="heading-4-production-ready">4. <strong>Production Ready</strong></h3>
<p>This isn't a toy example. The server includes proper logging, error handling, and follows MCP protocol specifications.</p>
<h2 id="heading-expanding-your-server">Expanding Your Server</h2>
<p>Adding new tools is trivial. Here's how to add mathematical capabilities:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerToolType</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MathTools</span>
{
    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Evaluates mathematical expressions safely"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Calculate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> expression</span>)</span>
    {
        <span class="hljs-keyword">var</span> table = <span class="hljs-keyword">new</span> System.Data.DataTable();
        <span class="hljs-keyword">return</span> Convert.ToDouble(table.Compute(expression.Replace(<span class="hljs-string">"^"</span>, <span class="hljs-string">"**"</span>), <span class="hljs-string">""</span>));
    }

    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Converts temperatures between units (C, F, K)"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">ConvertTemp</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> <span class="hljs-keyword">value</span>, <span class="hljs-keyword">string</span> <span class="hljs-keyword">from</span>, <span class="hljs-keyword">string</span> to</span>)</span> =&gt;
        (<span class="hljs-keyword">from</span>.ToLower(), to.ToLower()) <span class="hljs-keyword">switch</span>
        {
            (<span class="hljs-string">"c"</span>, <span class="hljs-string">"f"</span>) =&gt; <span class="hljs-keyword">value</span> * <span class="hljs-number">9</span>/<span class="hljs-number">5</span> + <span class="hljs-number">32</span>,
            (<span class="hljs-string">"f"</span>, <span class="hljs-string">"c"</span>) =&gt; (<span class="hljs-keyword">value</span> - <span class="hljs-number">32</span>) * <span class="hljs-number">5</span>/<span class="hljs-number">9</span>,
            (<span class="hljs-string">"c"</span>, <span class="hljs-string">"k"</span>) =&gt; <span class="hljs-keyword">value</span> + <span class="hljs-number">273.15</span>,
            (<span class="hljs-string">"k"</span>, <span class="hljs-string">"c"</span>) =&gt; <span class="hljs-keyword">value</span> - <span class="hljs-number">273.15</span>,
            _ =&gt; <span class="hljs-keyword">value</span>
        };
}
</code></pre>
<h2 id="heading-advanced-features-made-simple">Advanced Features Made Simple</h2>
<h3 id="heading-complex-return-types">Complex Return Types</h3>
<p>Return tuples, objects, or collections naturally:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Analyzes text content"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">static</span> (<span class="hljs-params"><span class="hljs-keyword">int</span> words, <span class="hljs-keyword">int</span> chars, <span class="hljs-keyword">int</span> lines, <span class="hljs-keyword">double</span> readability</span>) <span class="hljs-title">AnalyzeText</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> text</span>)</span>
{
    <span class="hljs-keyword">var</span> words = text.Split(<span class="hljs-string">' '</span>, StringSplitOptions.RemoveEmptyEntries).Length;
    <span class="hljs-keyword">var</span> sentences = text.Split(<span class="hljs-string">'.'</span>, <span class="hljs-string">'!'</span>, <span class="hljs-string">'?'</span>).Length - <span class="hljs-number">1</span>;
    <span class="hljs-keyword">var</span> readability = Math.Max(<span class="hljs-number">0</span>, <span class="hljs-number">206.835</span> - <span class="hljs-number">1.015</span> * (words / Math.Max(sentences, <span class="hljs-number">1</span>)));

    <span class="hljs-keyword">return</span> (words, text.Length, text.Split(<span class="hljs-string">'\n'</span>).Length, readability);
}
</code></pre>
<h3 id="heading-error-handling">Error Handling</h3>
<p>Exceptions are automatically converted to proper MCP error responses:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Fetches and parses web content"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">FetchUrl</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> url</span>)</span>
{
    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> HttpClient();
    <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> client.GetStringAsync(url); <span class="hljs-comment">// Throws on error - automatically handled</span>
    <span class="hljs-keyword">return</span> response;
}
</code></pre>
<h2 id="heading-deployment-and-integration">Deployment and Integration</h2>
<h3 id="heading-configuration-for-mcp-clients">Configuration for MCP Clients</h3>
<p>Create <code>.mcp.json</code> in your project root:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"servers"</span>: {
    <span class="hljs-attr">"MyUtilityServer"</span>: {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"stdio"</span>,
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"dotnet"</span>,
      <span class="hljs-attr">"args"</span>: [<span class="hljs-string">"run"</span>, <span class="hljs-string">"C:\\path\\to\\your\\mcp-server.cs"</span>]
    }
  }
}
</code></pre>
<h3 id="heading-docker-deployment">Docker Deployment</h3>
<p>For containerized environments:</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> mcr.microsoft.com/dotnet/sdk:<span class="hljs-number">10.0</span>-preview-alpine
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">COPY</span><span class="bash"> mcp-server.cs .</span>
<span class="hljs-keyword">ENTRYPOINT</span><span class="bash"> [<span class="hljs-string">"dotnet"</span>, <span class="hljs-string">"run"</span>, <span class="hljs-string">"mcp-server.cs"</span>]</span>
</code></pre>
<h2 id="heading-environment-variables">Environment variables</h2>
<p>Sometimes you might need to pass environment variables into your code, such as tokens or authentication keys. This can be done very easily with this approach.</p>
<p>First, add the input and set up the variable in your <code>.mcp.json</code> file.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"inputs"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-string">"user_email"</span>,
      <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Your email"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"promptString"</span>,
      <span class="hljs-attr">"password"</span>: <span class="hljs-literal">true</span>
    }
  ],
  <span class="hljs-attr">"servers"</span>: {
    <span class="hljs-attr">"DotnetMcpFile"</span>: {
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"stdio"</span>,
      <span class="hljs-attr">"command"</span>: <span class="hljs-string">"dotnet"</span>,
      <span class="hljs-attr">"args"</span>: [<span class="hljs-string">"run"</span>, <span class="hljs-string">"C:\\path\\to\\your\\mcp-server.cs"</span>]
      <span class="hljs-string">"env"</span>: {
        <span class="hljs-attr">"USER_EMAIL"</span>: <span class="hljs-string">"${input:user_email}"</span>
      }
    }
  }
}
</code></pre>
<p>Visual Studio will display a prompt where you can enter the data.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754539707534/b3714b83-ce2e-4f8d-b166-5b257140b74b.png" alt class="image--center mx-auto" /></p>
<p>To use the data, apply C# <code>Environment</code> methods.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerToolType</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">EnvironmentExamples</span>
{
    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Gets environment variable value with optional default"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetEnvironmentVariable</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> variableName, <span class="hljs-keyword">string</span>? defaultValue = <span class="hljs-literal">null</span></span>)</span>
    {
        <span class="hljs-keyword">var</span> <span class="hljs-keyword">value</span> = Environment.GetEnvironmentVariable(variableName);

        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(<span class="hljs-keyword">value</span>))
        {
            <span class="hljs-keyword">if</span> (defaultValue != <span class="hljs-literal">null</span>)
                <span class="hljs-keyword">return</span> <span class="hljs-string">$"Environment variable '<span class="hljs-subst">{variableName}</span>' not found, using default: <span class="hljs-subst">{defaultValue}</span>"</span>;
            <span class="hljs-keyword">else</span>
                <span class="hljs-keyword">return</span> <span class="hljs-string">$"Environment variable '<span class="hljs-subst">{variableName}</span>' not found"</span>;
        }

        <span class="hljs-keyword">return</span> <span class="hljs-string">$"<span class="hljs-subst">{variableName}</span> = <span class="hljs-subst">{<span class="hljs-keyword">value</span>}</span>"</span>;
    }
}
</code></pre>
<p>And the result is shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754539812226/4f39b63f-cb2e-4339-8c16-fcba287e36d9.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-real-world-example-file-system-tools">Real-World Example: File System Tools</h2>
<p>Here's a practical server that provides file system operations:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerToolType</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">FileTools</span>
{
    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Lists files in directory with optional pattern"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span>[] <span class="hljs-title">ListFiles</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> path, <span class="hljs-keyword">string</span> pattern = <span class="hljs-string">"*"</span></span>)</span> =&gt;
        Directory.GetFiles(path, pattern).Take(<span class="hljs-number">100</span>).ToArray();

    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Gets file information"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">object</span> <span class="hljs-title">GetFileInfo</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> path</span>)</span>
    {
        <span class="hljs-keyword">var</span> info = <span class="hljs-keyword">new</span> FileInfo(path);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> { 
            Name = info.Name, 
            Size = info.Length, 
            Modified = info.LastWriteTime,
            IsReadOnly = info.IsReadOnly 
        };
    }

    [<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Safely reads text file with size limits"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ReadTextFile</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> path, <span class="hljs-keyword">int</span> maxLines = <span class="hljs-number">1000</span></span>)</span> =&gt;
        <span class="hljs-keyword">string</span>.Join(<span class="hljs-string">"\n"</span>, File.ReadLines(path).Take(maxLines));
}
</code></pre>
<h2 id="heading-best-practices">Best Practices</h2>
<h3 id="heading-1-security-first">1. <strong>Security First</strong></h3>
<p>Always validate inputs and limit resource usage:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerTool</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ProcessFile</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> path</span>)</span>
{
    <span class="hljs-keyword">if</span> (!Path.IsPathFullyQualified(path)) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Use absolute paths"</span>);
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">new</span> FileInfo(path).Length &gt; <span class="hljs-number">10</span>_000_000) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"File too large"</span>);
    <span class="hljs-comment">// ... process safely</span>
}
</code></pre>
<h3 id="heading-2-clear-descriptions">2. <strong>Clear Descriptions</strong></h3>
<p>Make your tools discoverable with detailed descriptions:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Compresses images while maintaining quality. Supports JPEG (quality 85), PNG (lossless), WebP (quality 90). Max input: 50MB"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">byte</span>[] <span class="hljs-title">CompressImage</span>(<span class="hljs-params"><span class="hljs-keyword">byte</span>[] imageData, <span class="hljs-keyword">string</span> format = <span class="hljs-string">"jpeg"</span></span>)</span> { <span class="hljs-comment">/* ... */</span> }
</code></pre>
<h3 id="heading-3-async-when-needed">3. <strong>Async When Needed</strong></h3>
<p>Use async for I/O operations:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">McpServerTool, Description(<span class="hljs-meta-string">"Downloads and processes remote content"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">ProcessRemoteData</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> url</span>)</span>
{
    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> HttpClient();
    <span class="hljs-keyword">var</span> data = <span class="hljs-keyword">await</span> client.GetStringAsync(url);
    <span class="hljs-keyword">return</span> ProcessData(data);
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This 20-line approach to MCP servers represents a paradigm shift in how we build AI tool integrations. By leveraging .NET's modern features - top-level programs, package directives, and attribute-based configuration - we've eliminated the traditional complexity barrier.</p>
<p>The result is a development experience that's:</p>
<ul>
<li><p><strong>Fast</strong>: From idea to working server in minutes</p>
</li>
<li><p><strong>Maintainable</strong>: Clear, typed code that's easy to modify</p>
</li>
<li><p><strong>Powerful</strong>: Full access to .NET ecosystem and NuGet packages</p>
</li>
<li><p><strong>Professional</strong>: Production-ready with proper error handling and logging</p>
</li>
</ul>
<p>Whether you're building personal productivity tools or enterprise AI integrations, this pattern provides the perfect balance of simplicity and capability. Start with the 20-line foundation, then grow your server organically as your needs evolve.</p>
<p>The future of AI tool development is here, and it fits in 20 lines of code.</p>
]]></content:encoded></item><item><title><![CDATA[WinUI 3: How to Set Minimum Window Size (Desktop)]]></title><description><![CDATA[Setting a minimum window size in WinUI 3 desktop applications isn't as straightforward as you might expect. Unlike WPF or Windows Forms, there's no simple MinWidth/MinHeight property on the Window class. However, we can achieve this by intercepting W...]]></description><link>https://xakpc.info/winui-3-how-to-set-minimum-window-size-desktop</link><guid isPermaLink="true">https://xakpc.info/winui-3-how-to-set-minimum-window-size-desktop</guid><category><![CDATA[winui3]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 28 Jan 2025 16:54:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738083135347/c83b58a7-e371-4eae-a83d-af37fcd1de03.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Setting a minimum window size in WinUI 3 desktop applications isn't as straightforward as you might expect. Unlike WPF or Windows Forms, there's no simple MinWidth/MinHeight property on the Window class. However, we can achieve this by intercepting Windows Messages using window subclassing.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Windows applications use a messaging system for events like resizing. "Window subclassing" lets you intercept these messages to modify behavior. In WinUI 3, we use this to override the default minimum size logic handled by the OS.</div>
</div>

<h2 id="heading-setup">Setup</h2>
<p>First, you'll need to add the <a target="_blank" href="https://www.nuget.org/packages/Microsoft.Windows.CsWin32">CsWin32 NuGet</a> package to your project:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PackageReference</span> <span class="hljs-attr">Include</span>=<span class="hljs-string">"Microsoft.Windows.CsWin32"</span> <span class="hljs-attr">Version</span>=<span class="hljs-string">"0.3.49-beta"</span> /&gt;</span>
</code></pre>
<p>Then create a file called <code>NativeMethods.txt</code> in your project root and add these required Win32 APIs:</p>
<pre><code class="lang-bash">SetWindowSubclass
DefSubclassProc
GetDpiForWindow
MINMAXINFO
</code></pre>
<p>After creating <code>NativeMethods.txt</code>, ensure its build action is set to <strong>"C# analyzer additional file"</strong>:</p>
<ol>
<li>Right-click the file &gt; Properties &gt; Build Action &gt; Select "C# analyzer additional file".</li>
</ol>
<p>This file tells the CsWin32 source generator which Win32 APIs to generate code for.</p>
<h2 id="heading-the-solution">The Solution</h2>
<p>Here is a helper class that will handle the window size constraints:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">WindowsSubclass</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> WM_GETMINMAXINFO = <span class="hljs-number">0x0024</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> Windows.Win32.UI.Shell.SUBCLASSPROC _subclassProc;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> Windows.Win32.Foundation.HWND _hwnd;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">uint</span> _id;

    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> Initializes a new instance of the <span class="hljs-doctag">&lt;see cref="WindowMinSizeSubclass"/&gt;</span> class.</span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="hwnd"&gt;</span>The handle to the window.<span class="hljs-doctag">&lt;/param&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="id"&gt;</span>The subclass ID, could be any number.<span class="hljs-doctag">&lt;/param&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="minSize"&gt;</span>The minimum size of the window.<span class="hljs-doctag">&lt;/param&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;exception cref="InvalidOperationException"&gt;</span>Thrown when setting the window subclass fails.<span class="hljs-doctag">&lt;/exception&gt;</span></span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">WindowsSubclass</span>(<span class="hljs-params">IntPtr hwnd, <span class="hljs-keyword">uint</span> id, Size minSize</span>)</span>
    {
        _hwnd = (Windows.Win32.Foundation.HWND)hwnd;
        _id = id;
        MinSize = minSize;
        _subclassProc = <span class="hljs-keyword">new</span> Windows.Win32.UI.Shell.SUBCLASSPROC(WindowSubclassProc);

        <span class="hljs-keyword">var</span> result = Windows.Win32.PInvoke.SetWindowSubclass(_hwnd, _subclassProc, _id, <span class="hljs-number">0</span>);
        <span class="hljs-keyword">if</span> (result.Value == <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Failed to set window subclass"</span>);
        }
    }

    <span class="hljs-keyword">public</span> Size MinSize { <span class="hljs-keyword">get</span>; }

    <span class="hljs-keyword">private</span> Windows.Win32.Foundation.<span class="hljs-function">LRESULT <span class="hljs-title">WindowSubclassProc</span>(<span class="hljs-params">Windows.Win32.Foundation.HWND hWnd, <span class="hljs-keyword">uint</span> uMsg,
        Windows.Win32.Foundation.WPARAM wParam, Windows.Win32.Foundation.LPARAM lParam,
        nuint uIdSubclass, nuint dwRefData</span>)</span>
    {
        <span class="hljs-keyword">switch</span> (uMsg)
        {
            <span class="hljs-keyword">case</span> WM_GETMINMAXINFO:
                <span class="hljs-comment">// Windows sends this message to query size constraints. </span>
                <span class="hljs-comment">// ptMinTrackSize defines the smallest draggable window size.</span>
                <span class="hljs-comment">// We adjust it based on DPI scaling.</span>
                <span class="hljs-keyword">var</span> dpi = Windows.Win32.PInvoke.GetDpiForWindow(hWnd);
                <span class="hljs-keyword">float</span> scalingFactor = (<span class="hljs-keyword">float</span>)dpi / <span class="hljs-number">96</span>;
                <span class="hljs-keyword">var</span> minMaxInfo = Marshal.PtrToStructure&lt;Windows.Win32.UI.WindowsAndMessaging.MINMAXINFO&gt;(lParam);
                minMaxInfo.ptMinTrackSize.X = (<span class="hljs-keyword">int</span>)(MinSize.Width * scalingFactor);
                minMaxInfo.ptMinTrackSize.Y = (<span class="hljs-keyword">int</span>)(MinSize.Height * scalingFactor);
                Marshal.StructureToPtr(minMaxInfo, lParam, <span class="hljs-literal">true</span>);
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> Windows.Win32.Foundation.LRESULT(<span class="hljs-number">0</span>);            
        }
        <span class="hljs-keyword">return</span> Windows.Win32.PInvoke.DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
}
</code></pre>
<h2 id="heading-how-to-use-it">How to Use It</h2>
<p>To use this in your WinUI 3 window, add this code to your window's constructor:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MainWindow</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">this</span>.InitializeComponent();

    <span class="hljs-keyword">var</span> hwnd = WinRT.Interop.WindowNative.GetWindowHandle(<span class="hljs-keyword">this</span>);
    _ = <span class="hljs-keyword">new</span> WindowsSubclass(hwnd, <span class="hljs-number">69</span>, <span class="hljs-keyword">new</span> Size(<span class="hljs-number">500</span>, <span class="hljs-number">300</span>)); <span class="hljs-comment">// Sets minimum size to 500x300</span>
}
</code></pre>
<h2 id="heading-how-it-works">How It Works</h2>
<p>The solution works by intercepting the WM_GETMINMAXINFO Windows Message, which Windows sends to a window when it needs to know its size constraints. We use window subclassing to add our own procedure that handles this message.</p>
<p>Key points about the implementation:</p>
<ol>
<li><p>CsWin32 generates strongly-typed wrappers for the Win32 APIs we specified in NativeMethods.txt</p>
</li>
<li><p>The class keeps a reference to the subclass procedure delegate to prevent it from being garbage collected</p>
</li>
<li><p>It properly handles DPI scaling to ensure consistent sizing across different display scales</p>
</li>
<li><p>All other window messages are passed through to the default handler</p>
</li>
<li><p>The subclass is automatically cleaned up when the window is destroyed</p>
</li>
</ol>
<h2 id="heading-important-notes">Important Notes</h2>
<ul>
<li><p>The minimum size is specified in logical pixels and will be properly scaled based on the display's DPI</p>
</li>
<li><p>The window subclassing is automatically cleaned up when the window is destroyed</p>
</li>
<li><p>This solution works only for desktop applications, not for UWP or other platforms</p>
</li>
<li><p>Make sure NativeMethods.txt is included in your project and the file properties are set to "C# analyzer additional file"</p>
</li>
</ul>
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<p>Some problems you could encounter and how to fix them:</p>
<ul>
<li><p><strong>Subclass ID Conflicts</strong>: Ensure unique IDs if subclassing multiple windows.</p>
</li>
<li><p><strong>DPI Scaling Issues</strong>: Verify <code>GetDpiForWindow</code> returns valid values (e.g., not zero).</p>
</li>
<li><p><strong>Garbage Collection</strong>: The <code>SUBCLASSPROC</code> delegate must remain referenced (already handled in the helper class).</p>
</li>
</ul>
<p>Now you can set minimum window sizes in your WinUI 3 desktop applications while maintaining proper Windows integration and DPI awareness.</p>
]]></content:encoded></item><item><title><![CDATA[Debugging .NET in Docker: Mount Points and Cross-platform Symbols]]></title><description><![CDATA[Today I want to share a tricky debugging issue I encountered with Visual Studio's Docker tools and how I fixed it. The problem arose when trying to debug a .NET application that uses a custom base image with pre-installed Python.
The Problem
My appli...]]></description><link>https://xakpc.info/fix-debugging-net-in-docker</link><guid isPermaLink="true">https://xakpc.info/fix-debugging-net-in-docker</guid><category><![CDATA[dotnet]]></category><category><![CDATA[Docker]]></category><category><![CDATA[visual studio]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 14 Jan 2025 17:37:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736876144012/ac62558b-2be3-4607-b980-59122d4950e1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today I want to share a tricky debugging issue I encountered with Visual Studio's Docker tools and how I fixed it. The problem arose when trying to debug a .NET application that uses a custom base image with pre-installed Python.</p>
<h2 id="heading-the-problem">The Problem</h2>
<p>My application Docker is based on a custom Docker image that comes with Python pre-installed at <code>/app/python</code>. This is a requirement I cannot change, and Python must be available at this specific location for the application to work.</p>
<pre><code class="lang-dockerfile"><span class="hljs-comment"># This stage is used when running from VS in fast mode (Default for Debug configuration)</span>
<span class="hljs-keyword">FROM</span> myregistry.azurecr.io/base-python:latest AS base
<span class="hljs-keyword">USER</span> $APP_UID
<span class="hljs-keyword">ENV</span> Python3_ROOT_DIR=<span class="hljs-string">"/app/python"</span>
<span class="hljs-keyword">WORKDIR</span><span class="bash"> /app</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8080</span>
<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">8081</span>
</code></pre>
<p>When trying to debug the application from Visual Studio using Fast Mode (default for Debug configuration), I ran into a problem.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Visual Studio's Fast Mode is designed to speed up the development cycle by building on host system and mounting the build output directly into the container instead of copying files during image build.</div>
</div>

<p>The issue was that Fast Mode was mounting my application files to <code>/app</code> (the WORKDIR), which effectively overlaid and hid the Python installation that was already there. As a result, Python became unavailable and the application couldn't work properly.</p>
<h2 id="heading-issue-1-fixing-the-mount-points">Issue 1: Fixing the Mount Points</h2>
<p>After some research, I found that Visual Studio's mounting behavior can be controlled through the project file. The solution was to configure Fast Mode to mount files into a different directory:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DockerDebuggeeWorkingDirectory</span>&gt;</span>/app/dotnet<span class="hljs-tag">&lt;/<span class="hljs-name">DockerDebuggeeWorkingDirectory</span>&gt;</span> 
    <span class="hljs-tag">&lt;<span class="hljs-name">DockerFastModeProjectMountDirectory</span>&gt;</span>/app/dotnet<span class="hljs-tag">&lt;/<span class="hljs-name">DockerFastModeProjectMountDirectory</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<p>This configuration tells Fast Mode to:</p>
<ol>
<li><p>Mount application files to <code>app/dotnet</code> instead of <code>/app</code></p>
</li>
<li><p>Set the working directory for the debugger</p>
</li>
<li><p>Leave the Python installation at <code>/app/python</code> intact and accessible</p>
</li>
</ol>
<h2 id="heading-issue-2-windows-pdbs-not-working">Issue 2: Windows PDBs Not Working</h2>
<p>After fixing the mount point, I encountered another issue. When trying to hit breakpoints, the debugger was complaining about symbol files:</p>
<pre><code class="lang-bash">WARNING: Could not load symbols <span class="hljs-keyword">for</span> <span class="hljs-string">'MyCompany.Service.dll'</span>. 
<span class="hljs-string">'/src/build/bin/.../MyCompany.Service.pdb'</span> is a Windows PDB. 
These are not supported by the cross-platform .NET Core debugger.
</code></pre>
<p>The problem comes from the difference between debugging environments. While developing on Windows, Visual Studio by default generates Windows PDB files (Program Database) which contain debugging information. However, when debugging in a Linux container with the cross-platform .NET debugger, these Windows-specific PDBs aren't supported.</p>
<p>Microsoft introduced Portable PDBs exactly for this cross-platform scenario - they contain the same debugging information but in a format that works across different operating systems. The solution was to tell the compiler to generate portable PDBs instead of Windows ones:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">DebugType</span>&gt;</span>portable<span class="hljs-tag">&lt;/<span class="hljs-name">DebugType</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span>
</code></pre>
<p>After this change and rebuilding the application, the debugger successfully loaded the symbols and I could debug my application running in the container.</p>
<h2 id="heading-references">References</h2>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/containers/container-msbuild-properties">Visual Studio Container Tools Build Properties</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/debug-compiler-option">Debug Type Property (C#)</a></p>
</li>
</ul>
<p>That's it. Both issues fixed and debugging now works as expected.</p>
]]></content:encoded></item><item><title><![CDATA[Harmonizing Line Endings Across Windows and WSL]]></title><description><![CDATA[When using a Git repository across Windows and Windows Subsystem for Linux (WSL), you may encounter unexpected issues with line endings. This article explains why these issues occur and how to resolve them effectively.
The Challenge
The core issue st...]]></description><link>https://xakpc.info/harmonizing-line-endings-across-windows-and-wsl</link><guid isPermaLink="true">https://xakpc.info/harmonizing-line-endings-across-windows-and-wsl</guid><category><![CDATA[WSL]]></category><category><![CDATA[Git]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Sat, 30 Nov 2024 16:32:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732984090188/d31e8d8d-f2b8-484e-ab6a-41e8ce267d9b.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When using a Git repository across Windows and Windows Subsystem for Linux (WSL), you may encounter unexpected issues with line endings. This article explains why these issues occur and how to resolve them effectively.</p>
<h2 id="heading-the-challenge">The Challenge</h2>
<p>The core issue stems from different operating systems using different line-ending conventions:</p>
<ul>
<li><p>Windows uses CRLF (Carriage Return + Line Feed, <code>\r\n</code>)</p>
</li>
<li><p>Linux systems, including WSL, use LF (Line Feed, <code>\n</code>)</p>
</li>
</ul>
<p>When working with Git repositories created in Windows and accessed through WSL, these differences can cause Git to flag files as modified even when no actual content changes have occurred. This happens because Git may interpret the line-ending differences as file modifications.</p>
<h2 id="heading-understanding-the-environment">Understanding the Environment</h2>
<p>A key insight is that WSL Git and Windows Git maintain separate global environments, which might not be immediately obvious to developers new to this setup. This means configurations need to be managed separately for each environment.</p>
<h2 id="heading-solutions">Solutions</h2>
<p>To ensure consistent line-ending handling, you have two main configuration options:</p>
<ol>
<li><p><strong>Windows Configuration</strong></p>
<pre><code class="lang-powershell"> git config -<span class="hljs-literal">-global</span> core.autocrlf true
</code></pre>
<p> This setting converts line endings to CRLF during checkout. Note that this is often the default setting in Windows Git installations.</p>
</li>
<li><p><strong>WSL Configuration</strong></p>
<pre><code class="lang-powershell"> git config -<span class="hljs-literal">-global</span> core.autocrlf input
</code></pre>
<p> This setting prevents line ending conversion in the Linux environment, accepting whatever line endings are provided.</p>
</li>
</ol>
<h2 id="heading-understanding-coreautocrlf">Understanding core.autocrlf</h2>
<p>The <code>core.autocrlf</code> setting controls how Git handles line ending conversions:</p>
<ul>
<li><p>When set to <code>true</code>, Git converts LF to CRLF in your working directory while maintaining LF in the repository</p>
</li>
<li><p>When set to <code>input</code>, Git performs no output conversion, accepting files as-is</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Understanding and properly configuring line-ending behavior is crucial when sharing Git repositories between Windows and WSL environments. With the appropriate settings, you can prevent unnecessary file modifications and maintain a smooth development workflow across both platforms.</p>
]]></content:encoded></item><item><title><![CDATA[Extending Visual Studio 2022]]></title><description><![CDATA[HTMX is becoming extremely noisy in the web development world. It seems that more and more personal projects adopt it to simplify things. But recently, I noticed increasing adoption in corporate environments. Internal websites and even some public pr...]]></description><link>https://xakpc.info/extending-visual-studio-2022</link><guid isPermaLink="true">https://xakpc.info/extending-visual-studio-2022</guid><category><![CDATA[vsix]]></category><category><![CDATA[htmx]]></category><category><![CDATA[.NET]]></category><category><![CDATA[visual studio]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Mon, 02 Sep 2024 23:48:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1725320715427/bf742405-039e-4611-91ac-6952e6795a24.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>HTMX is becoming extremely noisy in the web development world. It seems that more and more personal projects adopt it to simplify things. But recently, I noticed increasing adoption in corporate environments. Internal websites and even some public products are being built with htmx using different backend languages as we speak. Visual Studio and .NET are some of these languages.</p>
<p>Just look at this amazing growth of websites using the htmx over the year.</p>
<p><img src="https://w3techs.com/diagram/history_technology/js-htmx" alt="Historical trends in the usage of Htmx" class="image--center mx-auto" /></p>
<p>Unfortunately, currently, Visual Studio's support for htmx is limited. While there are some NuGet packages providing tag helpers, developers lack crucial features like code completion and IntelliSense for HTMX. This gap in tooling can lead to inefficiencies, with developers constantly switching between Visual Studio and HTMX documentation.</p>
<p>Because I <a target="_blank" href="https://xakpc.info/htmx-dotnet">work with dotnet and htmx</a> a lot, I decided to create a Visual Studio extension to fill the gap. I had no prior experience building extensions, so here are my discoveries and frustrations along the way. Despite the challenges, I managed to complete it, and now it's <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=xakpc.htmx-pal">available</a> in the VS marketplace.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725302720988/3a80ac56-dbe5-4b19-b35d-d90978daeb19.gif" alt class="image--center mx-auto" /></p>
<p>This is what we want to achieve.</p>
<h1 id="heading-building-the-htmx-code-completion-extension">Building the HTMX Code Completion Extension</h1>
<p>To build a Visual Studio extension, you need to install specific extension development packages through the Visual Studio Installer. It's important to note that Visual Studio extensions are built on the .NET framework. And Visual Studio works only on Windows now - they <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/releases/2022/what-happened-to-vs-for-mac">discontinued</a> it literally a couple of days ago.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725293391858/7dc3fc18-00a3-4ae2-8ed1-28414395b519.png" alt class="image--center mx-auto" /></p>
<p>So the first thing we need is to install the required tooling. If you have Visual Studio, you have Visual Studio Installer. This is the app to install VSIX tooling</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725294812069/17eb354d-7022-43ee-b8aa-d92a81e9e5c9.png" alt class="image--center mx-auto" /></p>
<p>Additionally, it's recommended to install <strong>Extensibility Essentials</strong>. I didn't use it, but it looks like it has a ton of useful pieces. KnownMonikers for example is something I need but found on the web - it shows what kind of icons are available in VS.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725294991236/91d09304-b0a7-441d-9afa-fb12b47b238f.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-extension-architecture">Extension Architecture</h1>
<p>A VSIX package is a <code>.vsix</code> archive that contains one or more Visual Studio extensions, together with the metadata Visual Studio uses to classify and install the extensions. The first thing in the extension is a <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/extensibility/vsix-extension-schema-2-0-reference?view=vs-2022">manifest</a>, VSIX manifest contains information about the extension to be installed.</p>
<p>Any VSIX extension can provide several exported Managed Extensibility Framework (MEF) classes. MEF is used to export classes and interfaces that Visual Studio uses to integrate new functionality. Recent versions of Visual Studio (2019 and 2022) have introduced significant improvements in code completion and IntelliSense features, particularly through asynchronous interfaces.</p>
<p>Here is an example of an exported source provider.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Export(typeof(IAsyncQuickInfoSourceProvider))</span>]
[<span class="hljs-meta">Name(<span class="hljs-meta-string">"htmx tooltip quickInfo source"</span>)</span>]
[<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"razor"</span>)</span>]
<span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxQuickInfoSourceProvider</span> : <span class="hljs-title">IAsyncQuickInfoSourceProvider</span>
{
    [<span class="hljs-meta">Import</span>]
    <span class="hljs-keyword">internal</span> ITextStructureNavigatorSelectorService NavigatorService { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IAsyncQuickInfoSource <span class="hljs-title">TryCreateQuickInfoSource</span>(<span class="hljs-params">ITextBuffer textBuffer</span>)</span>
    {            
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HtmxQuickInfoSource(NavigatorService, textBuffer);
    }
}
</code></pre>
<p>This code exports our <code>HtmxQuickInfoSourceProvider</code> as an <code>IAsyncQuickInfoSourceProvider</code>, specifically for HTML content types. The <code>[Import]</code> attribute allows MEF to inject the required <code>ITextStructureNavigatorSelectorService</code>.</p>
<h1 id="heading-key-components-of-the-extension">Key Components of the Extension</h1>
<ol>
<li><p><strong>Completion Source</strong>: Implements <code>IAsyncCompletionSource</code> interface to provide HTMX-specific code completion.</p>
</li>
<li><p><strong>Provider Interface</strong>: Exported as a MEF interface, called by Visual Studio under specific conditions (e.g., when an HTML file is opened). We particularly interested in the next providers</p>
<ol>
<li><p><code>IAsyncCompletionSourceProvider</code>: provides instances of <code>IAsyncCompletionSource</code> which provides <code>CompletionItems</code></p>
</li>
<li><p><code>IAsyncCompletionCommitManagerProvider</code>: provides instances of <code>IAsyncCompletionCommitManager</code> which provides means to adjust the commit behavior, including which typed characters commit</p>
</li>
<li><p><code>IAsyncQuickInfoSourceProvider</code>: creates an <code>IAsyncQuickInfoSource</code> specified <code>ITextBuffer</code>.</p>
</li>
<li><p><code>IIntellisenseControllerProvider</code>: creates IntelliSense controllers for individual <code>ITextView</code> instances.</p>
</li>
</ol>
</li>
<li><p><strong>QuickInfo Source</strong>: Provides additional information when hovering over HTMX attributes with <code>IAsyncQuickInfoSource</code>.</p>
</li>
</ol>
<p>Overall, the class overview of the extension would look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725296948465/d6711653-fad8-4d10-b6f2-84e43640b61f.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-code-completion-implementation">Code Completion Implementation</h1>
<p>The extension implements two completion sources: one for HTMX attributes and another for attribute values. The main challenge was detecting when a user starts typing an HTMX attribute (which always begins with "hx-").</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725307786172/36dc5f98-73d1-42d5-8173-5ec3159b95de.gif" alt class="image--center mx-auto" /></p>
<p>The full completion process looks like this:</p>
<ul>
<li><p><code>IAsyncCompletionSource</code> - Produces the completion list when needed.</p>
</li>
<li><p><code>IAsyncCompletionBroker</code> - Triggers completion and provides the completion session.</p>
</li>
<li><p><code>IAsyncCompletionItemManager</code> - Filters and sorts completion items.</p>
</li>
<li><p><code>IAsyncCompletionCommitManager</code> - Adjust the commit behavior and perform a completion commit</p>
</li>
</ul>
<p>For attribute code completion we would need to implement two interfaces: <code>IAsyncCompletionSourceProvider</code> and <code>IAsyncCompletionSource</code></p>
<p>To handle completion Visual Studio sends an event when the completion trigger is fired, which should be handled by <code>IAsyncCompletionSource</code>. In it, we should implement three methods:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxCompletionSource</span> : <span class="hljs-title">IAsyncCompletionSource</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;CompletionContext&gt; <span class="hljs-title">GetCompletionContextAsync</span>(<span class="hljs-params">IAsyncCompletionSession session, CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Provide completion context which has a list of items to show</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;<span class="hljs-keyword">object</span>&gt; <span class="hljs-title">GetDescriptionAsync</span>(<span class="hljs-params">IAsyncCompletionSession session, CompletionItem item, CancellationToken token</span>)</span>
    {
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Provide description for currently selected item</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> CompletionStartData <span class="hljs-title">InitializeCompletion</span>(<span class="hljs-params">CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken cancellationToken</span>)</span>
    {
       <span class="hljs-comment"><span class="hljs-doctag">///</span> Perform checks if completion needs to be shown</span>
    }
</code></pre>
<p>Method <code>InitializeCompletion</code> provides the trigger and a snapshot, which is essentially the location in the text view where the cursor was at the moment the trigger occurred.</p>
<p>This allows us to evaluate the context around the cursor to decide whether to trigger completion.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> CompletionStartData <span class="hljs-title">InitializeCompletion</span>(<span class="hljs-params">CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken cancellationToken</span>)</span>
{
    Output.WriteInfo(<span class="hljs-string">$"HtmxCompletionSource:InitializeCompletion: triggered by <span class="hljs-subst">{trigger.Reason}</span>:<span class="hljs-subst">{trigger.Character}</span>."</span>);
    <span class="hljs-keyword">if</span> (!triggerLocation.IsInsideHtmlTag())
    {
        <span class="hljs-keyword">return</span> CompletionStartData.DoesNotParticipateInCompletion;
    }

    <span class="hljs-keyword">var</span> tokenSpan = triggerLocation.GetContainingToken();
    <span class="hljs-keyword">var</span> currentToken = tokenSpan.GetText();
    Output.WriteInfo(<span class="hljs-string">$"HtmxCompletionSource:InitializeCompletion: <span class="hljs-subst">{currentToken}</span>"</span>);

    <span class="hljs-keyword">if</span> (currentToken.StartsWith(<span class="hljs-string">"hx"</span>, StringComparison.OrdinalIgnoreCase) ||
        currentToken.StartsWith(<span class="hljs-string">"hx-"</span>, StringComparison.OrdinalIgnoreCase) ||
        (trigger.Character == <span class="hljs-string">'-'</span> &amp;&amp; tokenSpan.GetPreviousToken().GetText().Equals(<span class="hljs-string">"hx"</span>, StringComparison.OrdinalIgnoreCase)))
    {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CompletionStartData(CompletionParticipation.ProvidesItems, tokenSpan);
    }

    <span class="hljs-keyword">return</span> CompletionStartData.DoesNotParticipateInCompletion;
}
</code></pre>
<p>First, we check if our cursor is inside an HTML tag. Then, we check if it is inside an htmx attribute. These checks are implemented as extension methods.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">IsInsideHtmlTag</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> SnapshotPoint point</span>)</span>
{
    <span class="hljs-keyword">var</span> snapshot = point.Snapshot;
    <span class="hljs-keyword">var</span> position = point.Position;

    <span class="hljs-comment">// Check if we're already at the start of the snapshot</span>
    <span class="hljs-keyword">if</span> (position == <span class="hljs-number">0</span>)
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    <span class="hljs-keyword">int</span> openBracketPos = <span class="hljs-number">-1</span>;

    <span class="hljs-comment">// Look backwards for opening bracket</span>
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = position - <span class="hljs-number">1</span>; i &gt;= <span class="hljs-number">0</span>; i--)
    {
        <span class="hljs-keyword">if</span> (snapshot[i] == <span class="hljs-string">'&gt;'</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// Found closing bracket first, so we're not inside a tag</span>
        <span class="hljs-keyword">if</span> (snapshot[i] == <span class="hljs-string">'&lt;'</span>)
        {
            openBracketPos = i;
            <span class="hljs-keyword">break</span>;
        }
    }

    <span class="hljs-keyword">return</span> openBracketPos != <span class="hljs-number">-1</span>;
}
</code></pre>
<p>A Snapshot is an <code>ITextSnapshot</code>, which provides read access to an unchangeable snapshot of an <code>ITextBuffer</code> containing a sequence of Unicode characters. The algorithm is simple — we move left until we confirm that we are inside an HTML tag.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> SnapshotSpan <span class="hljs-title">GetContainingToken</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> SnapshotPoint point</span>)</span>
{
    <span class="hljs-keyword">var</span> line = point.GetContainingLine();

    <span class="hljs-keyword">var</span> linePosition = point.Position;
    <span class="hljs-keyword">var</span> snapshot = line.Snapshot;

    <span class="hljs-keyword">int</span> tokenStart = linePosition;
    <span class="hljs-keyword">while</span> (tokenStart &gt; line.Start.Position &amp;&amp; IsValidTokenChar(snapshot[tokenStart - <span class="hljs-number">1</span>]))
    {
        tokenStart--;
    }

    <span class="hljs-keyword">int</span> tokenEnd = linePosition;
    <span class="hljs-keyword">while</span> (tokenEnd &lt; line.End.Position &amp;&amp; IsValidTokenChar(snapshot[tokenEnd]))
    {
        tokenEnd++;
    }

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> SnapshotSpan(line.Snapshot, tokenStart, tokenEnd - tokenStart);
}
</code></pre>
<p>A line here is <code>ITextSnapshotLine</code> - an immutable information about a line of text from an <code>ITextSnapshot</code>. We do not need to go outside the line, because attributes could not be beyond the single line. The algorithm is pretty much the same, but we go to both sides of the line to extract the entire attribute name.</p>
<p>If we successfully confirm that the cursor is inside an attribute, the CompletionContext with a list of items will be requested.</p>
<h3 id="heading-providing-a-list-of-completion-items">Providing a List of Completion Items</h3>
<p>List of completion items provided from <code>CompletionContext</code></p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;CompletionContext&gt; <span class="hljs-title">GetCompletionContextAsync</span>(<span class="hljs-params">IAsyncCompletionSession session, CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken cancellationToken</span>)</span>
{
    <span class="hljs-keyword">var</span> arr = ToolTipsProvider.Instance.Keywords.Select(ConvertToItem).ToImmutableArray();

    Output.WriteInfo(<span class="hljs-string">$"HtmxCompletionSource:GetCompletionContextAsync: got a request for a context. Returning <span class="hljs-subst">{arr.Length}</span> items."</span>);

    <span class="hljs-keyword">return</span> Task.FromResult(<span class="hljs-keyword">new</span> CompletionContext(arr));
}
</code></pre>
<p>The item itself has several parameters, and to be honest, I don't know what half of them do. However, I do know that we have <code>text</code>, <code>insertText</code> which will be inserted, and <code>icon</code> which adds a small icon to the left of the completion item.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ImageElement CompletionItemIcon = <span class="hljs-keyword">new</span> ImageElement(KnownMonikers.HTMLEndTag.ToImageId(), <span class="hljs-string">"htmx"</span>);

<span class="hljs-function"><span class="hljs-keyword">private</span> CompletionItem <span class="hljs-title">ConvertToItem</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> text</span>)</span>
{
    <span class="hljs-keyword">string</span> insertText = text == <span class="hljs-string">"hx-on"</span> ? <span class="hljs-string">$"hx-on:"</span> : <span class="hljs-string">$"<span class="hljs-subst">{text}</span>=\"\""</span>;
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> CompletionItem(
                    text,
                    insertText: insertText,
                    sortText: text,
                    filterText: text,
                    automationText: text,
                    source: <span class="hljs-keyword">this</span>,
                    filters: ImmutableArray&lt;CompletionFilter&gt;.Empty,
                    icon: CompletionItemIcon,
                    suffix: <span class="hljs-keyword">default</span>,
                    attributeIcons: ImmutableArray&lt;ImageElement&gt;.Empty);
}
</code></pre>
<p>Here is a screenshot to better understand what each part does:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725308836134/eb108900-30bf-491c-9eb6-c99dc3d301c0.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-customizing-completion-behavior">Customizing Completion Behavior</h3>
<p>By default, the completion feature places the cursor after the string. So, when <code>hx-get</code> is completed and <code>hx-get=""</code> is inserted, the cursor is set at the end. However, this isn't very convenient. We want the cursor inside the quotation marks, like this: <code>hx-get="|"</code>.</p>
<p>This can be achieved by using <code>IAsyncCompletionCommitManager</code>.</p>
<pre><code class="lang-csharp"> <span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxCompletionCommitManager</span> : <span class="hljs-title">IAsyncCompletionCommitManager</span>
 {
     <span class="hljs-comment">// ...</span>
     <span class="hljs-function"><span class="hljs-keyword">public</span> CommitResult <span class="hljs-title">TryCommit</span>(<span class="hljs-params">IAsyncCompletionSession session, ITextBuffer buffer, CompletionItem item, <span class="hljs-keyword">char</span> typedChar, CancellationToken token</span>)</span>
     {
         <span class="hljs-comment">// Check if this is one of your custom completions</span>
         <span class="hljs-keyword">if</span> (item.InsertText.StartsWith(<span class="hljs-string">"hx"</span>) &amp;&amp; item.InsertText.EndsWith(<span class="hljs-string">"=\"\""</span>))
         {
             <span class="hljs-keyword">var</span> span = session.ApplicableToSpan;

             <span class="hljs-comment">// Commit the completion</span>
             <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> edit = buffer.CreateEdit())
             {
                 edit.Replace(span.GetSpan(buffer.CurrentSnapshot), item.InsertText);
                 edit.Apply();
             }

             <span class="hljs-comment">// Move the caret inside the quotes</span>
             <span class="hljs-keyword">var</span> newPosition = span.GetStartPoint(buffer.CurrentSnapshot).Position + item.InsertText.Length - <span class="hljs-number">1</span>;
             session.TextView.Caret.MoveTo(<span class="hljs-keyword">new</span> SnapshotPoint(buffer.CurrentSnapshot, newPosition));

             Output.WriteInfo(<span class="hljs-string">"HtmxCompletionCommitManager:TryCommit: committed completion."</span>);
             <span class="hljs-keyword">return</span> CommitResult.Handled;
         }

         <span class="hljs-keyword">return</span> CommitResult.Unhandled;
     }
 }
</code></pre>
<p>The implementation here is straightforward. If we insert an <code>hx-</code> attribute, we move the caret inside the quotation marks.</p>
<h3 id="heading-providing-selected-item-description">Providing Selected Item Description</h3>
<p>Method <code>GetDescriptionAsync</code> is responsible for obtaining an object to be rendered as a popup window with a description.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725312796259/16949674-def7-4aa9-861f-7daf55537e99.png" alt class="image--center mx-auto" /></p>
<p>This popup window is different from the one rendered by IntelliSense. I believe It is intended to show less information, like text-only help. However, due to convenience, I implemented a single <code>ContainerElement</code> for both cases.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;<span class="hljs-keyword">object</span>&gt; <span class="hljs-title">GetDescriptionAsync</span>(<span class="hljs-params">IAsyncCompletionSession session, CompletionItem item, CancellationToken token</span>)</span>
{
    <span class="hljs-keyword">if</span> (ToolTipsProvider.Instance.TryGetValue(item.DisplayText, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> element))
    {
        <span class="hljs-comment">// add some spacing between text paragraphs (should really cache that as well)</span>
        <span class="hljs-keyword">var</span> elements = element.Elements.ToList();
        <span class="hljs-keyword">var</span> newElements = <span class="hljs-keyword">new</span> <span class="hljs-keyword">object</span>[elements.Count * <span class="hljs-number">2</span> - <span class="hljs-number">1</span>];
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i &lt; elements.Count; i++)
        {
            newElements[i * <span class="hljs-number">2</span>] = elements[i];
            <span class="hljs-keyword">if</span> (i &lt; elements.Count - <span class="hljs-number">1</span>)
            {
                newElements[i * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>] = <span class="hljs-keyword">new</span> ClassifiedTextElement(<span class="hljs-keyword">new</span> ClassifiedTextRun(PredefinedClassificationTypeNames.WhiteSpace, <span class="hljs-string">" "</span>));
            }
        }
        element = <span class="hljs-keyword">new</span> ContainerElement(element.Style | ContainerElementStyle.Wrapped, newElements);
        <span class="hljs-keyword">return</span> Task.FromResult((<span class="hljs-keyword">object</span>)element);
    }

    <span class="hljs-keyword">return</span> Task.FromResult&lt;<span class="hljs-keyword">object</span>&gt;(<span class="hljs-literal">null</span>);
}
</code></pre>
<p>You can see that I had to add an empty line between paragraphs in the popup window because the renderer ignores <code>ContainerElementStyle.VerticalPadding</code>. Also link element is not supported in the code-completion window.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725313838722/84d34863-e59b-45ad-ba35-349f87020765.png" alt class="image--center mx-auto" /></p>
<p>I will explore how to build <code>ContainerElement</code> later, as I initially built it for IntelliSense.</p>
<h2 id="heading-source-provider">Source Provider</h2>
<p>Interface <code>IAsyncCompletionSourceProvider</code> is responsible for providing the completion source for code completion. This is a MEF component and should be exported with the <code>[ContentType]</code> and <code>[Name]</code> attributes, and optionally the <code>[TextViewRoles]</code> attribute.</p>
<p>The completion feature will request data from all exported <code>IAsyncCompletionSources</code> whose ContentType matches the content type of any buffer at the completion's trigger location.</p>
<p>These content types are a bit tricky, so I started with <code>[ContentType("html")]</code> and <code>[ContentType("razor")]</code> and was very surprised that they do not work for <code>.html</code> files. I checked all available documentation and did not find out why. The problem is that in VS2022, <code>ContentType("html-delegation")</code> is used. You will not find this information in the documentation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725314490521/22bd6ea1-c44e-427e-ba13-6ee9f6013a74.png" alt class="image--center mx-auto" /></p>
<p>To figure this out, I was forced to set <code>[ContentType("text")]</code>, which is the basic type for all text files, and then catch <code>textView.TextBuffer.ContentType</code> in the <code>GetOrCreate</code> method.</p>
<p>The final list of content types becomes quite extensive.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Export(typeof(IAsyncCompletionSourceProvider))</span>]
[<span class="hljs-meta">Name(<span class="hljs-meta-string">"htmx completion source"</span>)</span>]
[<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"html"</span>)</span>]
[<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"htmlx"</span>)</span>]
[<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"html-delegation"</span>)</span>] <span class="hljs-comment">// VS 2022</span>
[<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"razor"</span>)</span>]
[<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"LegacyRazorCSharp"</span>)</span>] <span class="hljs-comment">// VS 2022</span>
<span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxCompletionSourceProvider</span> : <span class="hljs-title">IAsyncCompletionSourceProvider</span>
{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-title">Lazy</span>&lt;<span class="hljs-title">HtmxCompletionSource</span>&gt; Source</span> = <span class="hljs-keyword">new</span> Lazy&lt;HtmxCompletionSource&gt;(() =&gt; <span class="hljs-keyword">new</span> HtmxCompletionSource());

    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> Gets or creates an instance of <span class="hljs-doctag">&lt;see cref="HtmxCompletionSource"/&gt;</span>.</span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="textView"&gt;</span>The text view for which the completion source is requested.<span class="hljs-doctag">&lt;/param&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;returns&gt;</span>An instance of <span class="hljs-doctag">&lt;see cref="HtmxCompletionSource"/&gt;</span>.<span class="hljs-doctag">&lt;/returns&gt;</span></span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> IAsyncCompletionSource <span class="hljs-title">GetOrCreate</span>(<span class="hljs-params">ITextView textView</span>)</span>
    {
        Output.WriteInfo(<span class="hljs-string">"HtmxCompletionSourceProvider:GetOrCreate: got a request for a source."</span>);
        <span class="hljs-keyword">return</span> Source.Value;
    }
}
</code></pre>
<p>Also, notice that I use <code>Lazy</code> here to save memory and avoid creating anything unless it's requested. As I understand it, the lifecycle provider is created once, but <code>GetOrCreate</code> could be requested for each completion.</p>
<p>With our code completion feature in place, let's turn our attention to another critical aspect of developer assistance: IntelliSense. This feature works hand-in-hand with code completion to provide a comprehensive development experience.</p>
<h1 id="heading-intellisense-implementation">IntelliSense Implementation</h1>
<p>The IntelliSense feature displays additional information when hovering over an HTMX attribute. It checks if the attribute is within an HTML element and if it's an HTMX attribute before displaying the corresponding description.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725320812887/02b7c7a1-d0c9-4fb2-97dc-8fa19613c459.gif" alt class="image--center mx-auto" /></p>
<p>The info popup itself provided through <code>IAsyncQuickInfoSource</code></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxQuickInfoSource</span> : <span class="hljs-title">IAsyncQuickInfoSource</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;QuickInfoItem&gt; <span class="hljs-title">GetQuickInfoItemAsync</span>(<span class="hljs-params">IAsyncQuickInfoSession session, CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-keyword">var</span> (containerElement, applicableToSpan) = <span class="hljs-keyword">await</span> BuildQuickInfoElementsAsync(session, cancellationToken);

        <span class="hljs-keyword">if</span> (containerElement != <span class="hljs-literal">null</span> &amp;&amp; applicableToSpan != <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> QuickInfoItem(applicableToSpan, containerElement);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }

    <span class="hljs-comment">// ... BuildQuickInfoElementsAsync</span>
}
</code></pre>
<h3 id="heading-building-container-element">Building Container Element</h3>
<p>A <code>ContainerElement</code> class represents a container of one or more elements for display in an <code>IToolTipPresenter</code>. It can contain other containers or <code>ClassifiedTextRun</code> elements, which represent parts of the text with applied styles.</p>
<p>Here is an example of how a typical tooltip is built. We have a <code>ContainerElement</code> with <code>ClassifiedTextRun</code> elements inside, each with different types.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725316734169/7dd96eeb-01bd-43f2-a591-720c924e929d.png" alt class="image--center mx-auto" /></p>
<p>This tooltip is created by parsing a markdown file into a set of containers. The code is straightforward, and if you want to check it out, feel free to look at the <a target="_blank" href="https://github.com/xakpc/HtmxPal/blob/master/src/Xakpc.VisualStudio.Extensions.HtmxPal/Services/MarkdownConverter.cs">GitHub repo</a>.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">There are plenty of built-in styles in <code>PredefinedClassificationTypeNames</code>, making it easy to present tooltips properly.</div>
</div>

<p>The link container is built a bit differently. We have a couple of factory methods in <code>ClassifiedTextElement</code> to create plain text and hyperlinks. So we could use it here:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ContainerElement <span class="hljs-title">CreateRichContent</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> description, <span class="hljs-keyword">string</span> link</span>)</span>
{
    <span class="hljs-keyword">var</span> converter = <span class="hljs-keyword">new</span> MarkdownConverter();

    <span class="hljs-keyword">var</span> elements = converter.Convert(description);
    elements.Add(<span class="hljs-keyword">new</span> ContainerElement(ContainerElementStyle.Wrapped,
        CreateHyperlink(link)));
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ContainerElement(ContainerElementStyle.Stacked | ContainerElementStyle.VerticalPadding,
        elements.ToArray());
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ClassifiedTextElement <span class="hljs-title">CreateHyperlink</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> url</span>)</span>
{
    <span class="hljs-keyword">return</span> ClassifiedTextElement.CreateHyperlink(<span class="hljs-string">"HTMX Reference"</span>, <span class="hljs-string">"Click here to see more details in official documentation"</span>, () =&gt;
    {
        System.Diagnostics.Process.Start(url);
    });
}
</code></pre>
<p>Inside <code>ClassifiedTextElement.CreateHyperlink</code> is a simple text element with a navigation action set. This action will launch the default app to open the URL and navigate there.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ClassifiedTextElement(<span class="hljs-keyword">new</span> ClassifiedTextRun(<span class="hljs-string">"text"</span>, text, navigationAction, tooltip));
</code></pre>
<h2 id="heading-intellisense-controller">IntelliSense Controller</h2>
<p>Interface <code>IAsyncQuickInfoSource</code> itself would not show quick info. To present a quick info tooltip we need to implement <code>IIntellisenseController</code> and handle the mouse event by ourselves.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxQuickInfoController</span> : <span class="hljs-title">IIntellisenseController</span>
{
    <span class="hljs-comment">// ...</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HtmxQuickInfoController</span>(<span class="hljs-params">ITextView textView, IList&lt;ITextBuffer&gt; subjectBuffers, IAsyncQuickInfoBroker broker</span>)</span>
    {
        _textView = textView;
        _subjectBuffers = subjectBuffers;
        _broker = broker;

        _textView.MouseHover += <span class="hljs-keyword">this</span>.OnTextViewMouseHover;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnTextViewMouseHover</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> sender, MouseHoverEventArgs e</span>)</span>
    {
        <span class="hljs-comment">// Debounce (not sure if needed)</span>
        <span class="hljs-keyword">if</span> ((DateTime.Now - _lastCheckTime).TotalMilliseconds &lt; DebounceMs)
        {
            <span class="hljs-keyword">return</span>;
        }

        _lastCheckTime = DateTime.Now;

        <span class="hljs-comment">// find the mouse position by mapping down to the subject buffer</span>
        <span class="hljs-keyword">var</span> point = _textView.BufferGraph.MapDownToFirstMatch(<span class="hljs-keyword">new</span> SnapshotPoint(_textView.TextSnapshot, e.Position),
                PointTrackingMode.Positive,
                snapshot =&gt; _subjectBuffers.Contains(snapshot.TextBuffer),
                PositionAffinity.Predecessor);

        <span class="hljs-keyword">if</span> (point != <span class="hljs-literal">null</span>)
        {
            ITrackingPoint triggerPoint = point.Value.Snapshot.CreateTrackingPoint(point.Value.Position,
            PointTrackingMode.Positive);

            <span class="hljs-keyword">if</span> (!_broker.IsQuickInfoActive(_textView))
            {
                _ = <span class="hljs-keyword">await</span> _broker.
                    TriggerQuickInfoAsync(_textView, triggerPoint, QuickInfoSessionOptions.TrackMouse);
            }
        }
    }
}
</code></pre>
<p>The controller handles mouse movement and then triggers quick info through the broker. The appropriate quick info is shown based on whichever source returns the first quick info.</p>
<p>You might also notice a debounce logic there. I'm not entirely sure it's needed, but it seems to improve the experience, so I included it.</p>
<h2 id="heading-source-provider-1">Source Provider</h2>
<p>Obviously, we need to export our <code>IIntellisenseController</code> and <code>IAsyncQuickInfoSource</code>. This is done through source providers, similar to how completion sources work. The only difference is that they call TryCreate once, so there's no need for lazy initialization.</p>
<pre><code class="lang-csharp">    [<span class="hljs-meta">Export(typeof(IAsyncQuickInfoSourceProvider))</span>]
    [<span class="hljs-meta">Name(<span class="hljs-meta-string">"htmx tooltip quickInfo source"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"html"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"htmlx"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"html-delegation"</span>)</span>] <span class="hljs-comment">// VS 2022</span>
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"razor"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"LegacyRazorCSharp"</span>)</span>] <span class="hljs-comment">// VS 2022</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxQuickInfoSourceProvider</span> : <span class="hljs-title">IAsyncQuickInfoSourceProvider</span>
    {
        [<span class="hljs-meta">Import</span>]
        <span class="hljs-keyword">internal</span> ITextStructureNavigatorSelectorService NavigatorService { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> IAsyncQuickInfoSource <span class="hljs-title">TryCreateQuickInfoSource</span>(<span class="hljs-params">ITextBuffer textBuffer</span>)</span>
        {            
            Output.WriteInfo(<span class="hljs-string">"HtmxQuickInfoSourceProvider:TryCreateQuickInfoSource: got a request for a source."</span>);
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HtmxQuickInfoSource(NavigatorService, textBuffer);
        }
    }

    [<span class="hljs-meta">Export(typeof(IIntellisenseControllerProvider))</span>]
    [<span class="hljs-meta">Name(<span class="hljs-meta-string">"htmx tooltip quickinfo controller"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"html"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"htmlx"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"html-delegation"</span>)</span>] <span class="hljs-comment">// VS 2022</span>
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"razor"</span>)</span>]
    [<span class="hljs-meta">ContentType(<span class="hljs-meta-string">"LegacyRazorCSharp"</span>)</span>] <span class="hljs-comment">// VS 2022</span>
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">HtmxQuickInfoControllerProvider</span> : <span class="hljs-title">IIntellisenseControllerProvider</span>
    {
        [<span class="hljs-meta">Import</span>]
        <span class="hljs-keyword">internal</span> IAsyncQuickInfoBroker QuickInfoBroker { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> IIntellisenseController <span class="hljs-title">TryCreateIntellisenseController</span>(<span class="hljs-params">ITextView textView, IList&lt;ITextBuffer&gt; subjectBuffers</span>)</span>
        {
            Output.WriteInfo(<span class="hljs-string">"HtmxQuickInfoControllerProvider:TryCreateIntellisenseController: got a request for a controller."</span>);

            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> HtmxQuickInfoController(textView, subjectBuffers, QuickInfoBroker);
        }
}
</code></pre>
<h1 id="heading-cross-version-support-challenges">Cross-Version Support Challenges</h1>
<p>Initially, my goal was to support both Visual Studio 2019 and 2022. However, due to architectural differences (x86 vs. x64) and API changes, the focus shifted to Visual Studio 2022 only.</p>
<p>When I set manifest, I could add different install targets, based on the Visual Studio version and architecture.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725318556732/cf79eacf-c449-407d-a64d-2dc0980c626c.png" alt class="image--center mx-auto" /></p>
<p>Targeting multiple versions of Visual Studio requires setting up different targets, using different SDKs, and sharing projects. There is a comprehensive guide <a target="_blank" href="https://learn.microsoft.com/en-us/visualstudio/extensibility/migration/update-visual-studio-extension?view=vs-2022">available</a> that I might explore later.</p>
<h1 id="heading-packaging-and-publishing">Packaging and Publishing</h1>
<p>The extension is packaged as a <code>.vsix</code> file and published on the Visual Studio Marketplace. This process is very simple. When you build as release, Visual Studio packs your <code>.vsix</code> file. I recommend installing it locally first to ensure it works correctly.</p>
<p>The next step is to create an account on <a target="_blank" href="https://marketplace.visualstudio.com/manage/">Visual Studio Marketplace</a> and upload the <code>.vsix</code> file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725318897646/f73b9bb3-b15e-4a10-9dec-12832ca38ea5.png" alt class="image--center mx-auto" /></p>
<p>Most of the information will be taken from the manifest, but you can write a README and select categories and tags.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725319122421/2e2ebc08-1b01-41ad-9411-2fbaa453ab70.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">I discovered that you cannot update the readme page without updating the VSIX version (hence v.1.0.1), so double-check your markdown before publishing.</div>
</div>

<p>The marketplace also provides useful analytics, so you can monitor the growth of your user base.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1725319251936/0e760fa5-d72d-4bc6-b295-9db36ecec811.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-lessons-learned-and-future-plans">Lessons Learned and Future Plans</h1>
<p>Writing an extension was a very rewarding experience. I got to explore how the internals of Visual Studio work, reviewed documentation and examples and significantly improved my htmx developer experience.</p>
<p>Here are some key lessons I want to share:</p>
<ol>
<li><p><strong>API Documentation Gap</strong>: Current documentation, especially the manuals and examples, focuses on older methods, making it difficult to work with newer APIs.</p>
</li>
<li><p><strong>Community Resources</strong>: Existing GitHub projects and issues often provide valuable insights into new APIs, so study them thoroughly.</p>
</li>
<li><p><strong>Version Focus</strong>: Concentrating on a single Visual Studio version simplifies development. If I ever need to support the 2019 version, I would approach it with a fresh perspective.</p>
</li>
<li><p><strong>Experimentation</strong>: Understanding the completion subsystem required hands-on experimentation. The debugger works great, so use it to explore all the details.</p>
</li>
</ol>
<p>In the future, I might add some improvements to the extension:</p>
<ol>
<li><p>Syntax highlighting to make hx-attributes more noticeable</p>
</li>
<li><p>More completion options for complex attributes like <code>hx-on:</code> and others</p>
</li>
<li><p>Additional features based on user feedback</p>
</li>
</ol>
<h1 id="heading-useful-links">Useful Links</h1>
<ul>
<li><p>AsyncCompletion <a target="_blank" href="https://github.com/KirillOsenkov/vs-editor-api/tree/0209a13c58194d4f2a7d03a2615ef03e857547e7/src/Editor/Language/Def/Language/AsyncCompletion">source code</a> (with examples)</p>
</li>
<li><p>AsyncCompletion <a target="_blank" href="https://github.com/microsoft/VSSDK-Extensibility-Samples/tree/master/AsyncCompletion">sample project</a></p>
</li>
<li><p>QuickInfo <a target="_blank" href="https://github.com/microsoft/VSSDK-Extensibility-Samples/tree/master/AsyncQuickInfo">sample project</a></p>
</li>
<li><p><code>Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion</code> <a target="_blank" href="http://Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion">documentation</a></p>
</li>
<li><p><code>Microsoft.VisualStudio.Language.Intellisense.IIntellisenseController</code> <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.language.intellisense.iintellisensecontroller?view=visualstudiosdk-2022">documentation</a></p>
</li>
<li><p>htmx-pal <a target="_blank" href="https://github.com/xakpc/HtmxPal/tree/master">source code</a></p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>The htmx-pal extension enhances Visual Studio's capabilities for HTMX development, offering code completion and quick info features. I spent around 12 hours building and publishing it, and I'm extremely delighted with how smooth the experience was.</p>
<p>There were moments when I almost gave up, such as when adding <code>hx-...</code> to the default code completion. However, these issues don't significantly affect the developer experience, so it's okay.</p>
<p>The most important part is that I now have a better way to write htmx apps, and so do you.</p>
<p>The extension is open source, and the code is available on <a target="_blank" href="https://github.com/xakpc/HtmxPal">GitHub</a>. The extension itself is published on the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=xakpc.htmx-pal">marketplace</a>. If you find it useful, I would be grateful if you rate it.</p>
]]></content:encoded></item><item><title><![CDATA[ASP.NET forms validation and htmx]]></title><description><![CDATA[Htmx works great with ASP.NET Razor Pages. Let's explore how we can solve the most annoying part of any form post: client-side and server-side validations.
We will explore built-in validation options, how we can adapt ASPNET's built-in validation to ...]]></description><link>https://xakpc.info/aspnet-forms-validation-and-htmx</link><guid isPermaLink="true">https://xakpc.info/aspnet-forms-validation-and-htmx</guid><category><![CDATA[htmx]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Sat, 15 Jun 2024 01:47:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718411824948/17e585f1-cf48-4f2d-85db-7d3d862cd819.webp" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Htmx works great with ASP.NET Razor Pages. Let's explore how we can solve the most annoying part of any form post: client-side and server-side validations.</p>
<p>We will explore built-in validation options, how we can adapt ASPNET's built-in validation to work efficiently with htmx, or how we can skip it completely and use HTML5 for validation.</p>
<p>To understand this article, you should already be familiar with Razor Pages and htmx. If not, I recommend reading a more introductory <a target="_blank" href="https://xakpc.info/htmx-dotnet">article</a> in the <a target="_blank" href="https://xakpc.info/series/htmx-dotnet">series</a>.</p>
<h1 id="heading-history-of-validation-in-aspnet">History of validation in ASPNET</h1>
<p>In ASP-NET applications, validation <strong>has been built right into the framework</strong> using Data Annotations and unobtrusive JavaScript since early versions. You can define validation rules in your model classes with attributes like <code>[Required]</code>, <code>[StringLength]</code>, or <code>[Range]</code>. These rules would be used on both the server and client sides.</p>
<p><strong>For client-side validation</strong>, the <code>jquery.validate.unobtrusive</code> library steps in. It applies validation rules using data attributes in the HTML elements. The Razor Pages engine uses tag helpers (like <code>asp-for</code>) to generate <code>input</code> tags with all the necessary attributes.</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- generated input with validation for use with jquery.validate.unobtrusive --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"FirstName"</span>&gt;</span>First Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">data-val</span>=<span class="hljs-string">"true"</span> 
    <span class="hljs-attr">data-val-length</span>=<span class="hljs-string">"First Name cannot be longer than 50 characters"</span> 
    <span class="hljs-attr">data-val-length-max</span>=<span class="hljs-string">"50"</span> 
    <span class="hljs-attr">data-val-required</span>=<span class="hljs-string">"First Name is required"</span> 
    <span class="hljs-attr">id</span>=<span class="hljs-string">"FirstName"</span> 
    <span class="hljs-attr">maxlength</span>=<span class="hljs-string">"50"</span> 
    <span class="hljs-attr">name</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span>&gt;</span>
</code></pre>
<p>The <code>jquery.validate.unobtrusive</code> library was introduced with the ASP.NET MVC 3 release on January 13, 2011. In ASP.NET MVC 2, released in March 2010, server-side validation was more common. While client-side validation was possible, it required more manual setup. Developers had to use the jQuery Validation plugin and write custom JavaScript code for the validation logic.</p>
<p><strong>For server-side validation</strong>, user-submitted data must follow certain rules before it can be processed. The rules are defined using <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations?view=net-8.0">Data Annotations</a> in model classes. During form submission, the model binding process maps the form data to the model properties and runs the validation framework.</p>
<pre><code class="lang-csharp">
<span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">UserRegistrationForm</span>
{
    <span class="hljs-comment">// annotatied property</span>
    [<span class="hljs-meta">Required(ErrorMessage = <span class="hljs-meta-string">"First Name is required"</span>)</span>]
    [<span class="hljs-meta">StringLength(50, ErrorMessage = <span class="hljs-meta-string">"First Name cannot be longer than 50 characters"</span>)</span>]
    [<span class="hljs-meta">Display(Name = <span class="hljs-meta-string">"First Name"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">init</span>; }
}
</code></pre>
<p>If any data doesn't meet the defined rules, the framework sets the <code>ModelState</code> to invalidate and record the validation errors. This information could be used to build a response page and display server-side validation errors on the form.</p>
<h1 id="heading-project-set-up">Project set-up</h1>
<p>To test different types of validation, I <a target="_blank" href="https://github.com/xakpc/RazorHtmx/tree/master/Xakpc.RazorHtmx.Validation">created</a> a simple Razor Pages project using <a target="_blank" href="https://picocss.com/">pico CSS</a> as the default design library. I did not use Bootstrap or any third-party JavaScript libraries except for <code>jquery</code>, <code>jquery.validation</code>, and <code>htmx</code>.</p>
<p>Each case has a separate page with the same partial <code>_FormContent</code> for the form's internals. We can reuse the same partial and almost the same page models for any type of validation: <code>htmx</code>, <code>non-htmx</code>, <code>HTML5</code>, and <code>jquery.validation</code>.</p>
<pre><code class="lang-xml">@page
@model Xakpc.RazorHtmx.Validation.Pages.RazorPageFormModel

@{
    ViewData["Title"] = "User Registration";
}

<span class="hljs-tag">&lt;<span class="hljs-name">hgroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>User Registration - Unobtrusive<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Regular POST of the form with <span class="hljs-tag">&lt;<span class="hljs-name">mark</span>&gt;</span>jquery.validate.unobtrusive<span class="hljs-tag">&lt;/<span class="hljs-name">mark</span>&gt;</span> validation<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">hgroup</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_FormContent"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"@Model.UserRegistration"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

@section Scripts {
    <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_ValidationScriptsPartial"</span> /&gt;</span>
}
</code></pre>
<p>The page model would be also almost the same for each validation: a bound property and a single <code>OnPost</code> handler.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RazorPageFormModel</span> : <span class="hljs-title">PageModel</span>
    {
        [<span class="hljs-meta">BindProperty</span>]
        <span class="hljs-keyword">public</span> UserRegistrationForm UserRegistration { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
        {
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPost</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">if</span> (User.PhoneNumber.Length &lt; <span class="hljs-number">4</span>) {
                ModelState.AddModelError(<span class="hljs-string">"PhoneNumber"</span>, <span class="hljs-string">"Phone format is wrong"</span>);   
            }

            <span class="hljs-keyword">if</span> (!ModelState.IsValid)
            {
                <span class="hljs-keyword">return</span> Page();
            }

            <span class="hljs-comment">// Store the user data in TempData</span>
            TempData[<span class="hljs-string">"User"</span>] = JsonSerializer.Serialize(UserRegistration);
            <span class="hljs-keyword">return</span> RedirectToPage(<span class="hljs-string">"Success"</span>);
        }
    }
</code></pre>
<h3 id="heading-fancy-validation-states">Fancy Validation States</h3>
<p>Pico CSS supports some fancy validation states using the <code>aria-invalid</code> attribute. All pages include custom code to set this attribute during server-side validation and also on the client side for HTML5 validation, as it does not do this by default.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718415332272/446baaf4-61b7-4928-a984-681f484c0f29.png" alt="aria-invalid=true" class="image--center mx-auto" /></p>
<p>Managing validation state on the server side is done using <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-8.0">TagHelpers</a>, which are convenient and powerful tools for generating HTML. I use a set of tag helpers in my Razor Pages projects, both with and without htmx.</p>
<p>Specifically, the <code>ValidationStateInputTagHelper</code> is applied to all input tags where the <code>asp-for</code> attribute is present. It adds the <code>aria-invalid</code> attribute based on the value and error state.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">HtmlTargetElement(<span class="hljs-meta-string">"input"</span>, Attributes = <span class="hljs-meta-string">"asp-for"</span>)</span>]
[<span class="hljs-meta">HtmlTargetElement(<span class="hljs-meta-string">"select"</span>, Attributes = <span class="hljs-meta-string">"asp-for"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ValidationStateInputTagHelper</span> : <span class="hljs-title">TagHelper</span>
{
    [<span class="hljs-meta">HtmlAttributeNotBound</span>]
    [<span class="hljs-meta">ViewContext</span>]
    <span class="hljs-keyword">public</span> ViewContext ViewContext { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">HtmlAttributeName(<span class="hljs-meta-string">"asp-for"</span>)</span>]
    <span class="hljs-keyword">public</span> ModelExpression For { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Process</span>(<span class="hljs-params">TagHelperContext context, TagHelperOutput output</span>)</span>
    {
        <span class="hljs-keyword">var</span> model = For.Model;

        <span class="hljs-comment">// Check if the field has a value</span>
        <span class="hljs-keyword">bool</span> hasValue = model != <span class="hljs-literal">null</span> &amp;&amp; !<span class="hljs-keyword">string</span>.IsNullOrEmpty(model.ToString());

        <span class="hljs-comment">// Check if there are any validation errors for this field</span>
        <span class="hljs-keyword">var</span> modelState = ViewContext;
        <span class="hljs-keyword">bool</span> hasError = modelState != <span class="hljs-literal">null</span> &amp;&amp;
                        modelState.ViewData.ModelState.TryGetValue(For.Name, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> entry) &amp;&amp;
                        entry.Errors.Count &gt; <span class="hljs-number">0</span>;

        <span class="hljs-comment">// Set the aria-invalid attribute based on the value and error</span>
        <span class="hljs-keyword">if</span> (hasValue)
        {
            output.Attributes.SetAttribute(<span class="hljs-string">"aria-invalid"</span>, hasError ? <span class="hljs-string">"true"</span> : <span class="hljs-string">"false"</span>);
        }
    }
}
</code></pre>
<p>The full code for this article is available on <a target="_blank" href="https://github.com/xakpc/RazorHtmx">GitHub</a>, feel free to explore it.</p>
<h1 id="heading-razor-pages-default-validation">Razor Pages "default" Validation</h1>
<p>As I mentioned earlier, In ASP.NET web applications, including Razor Pages, default validation is built upon validation attributes and the <code>jquery.validate.unobtrusive</code> library. This setup ensures seamless integration of client-side and server-side validation.</p>
<p>When data is submitted via a POST request, the validation process kicks in before the data reaches the <code>OnPost</code> handler. This means that any data entered by the user is checked against the specified validation rules defined by attributes such as <code>[Required]</code>, <code>[StringLength]</code>, <code>[Range]</code>, and custom validation attributes.</p>
<h2 id="heading-client-side">Client-side</h2>
<p>For example, if we define a typical input field with label and span for validation message:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"FirstName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"FirstName"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">asp-validation-for</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>And we have property with attributes like that</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Required(ErrorMessage = <span class="hljs-meta-string">"First Name is required"</span>)</span>]
[<span class="hljs-meta">StringLength(50, ErrorMessage = <span class="hljs-meta-string">"First Name cannot be longer than 50 characters"</span>)</span>]
[<span class="hljs-meta">Display(Name = <span class="hljs-meta-string">"First Name"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
</code></pre>
<p>It would be translated into the next code:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"FirstName"</span>&gt;</span>First Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> 
    <span class="hljs-attr">data-val</span>=<span class="hljs-string">"true"</span> 
    <span class="hljs-attr">data-val-length</span>=<span class="hljs-string">"First Name cannot be longer than 50 characters"</span> 
    <span class="hljs-attr">data-val-length-max</span>=<span class="hljs-string">"50"</span> <span class="hljs-attr">data-val-required</span>=<span class="hljs-string">"First Name is required"</span> 
    <span class="hljs-attr">id</span>=<span class="hljs-string">"FirstName"</span> 
    <span class="hljs-attr">maxlength</span>=<span class="hljs-string">"50"</span> 
    <span class="hljs-attr">name</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger field-validation-valid"</span> <span class="hljs-attr">data-valmsg-for</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">data-valmsg-replace</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>The rendered page transfers all model's data contract validations to HTML and utilizing <code>data-*</code> attributes to perform validation on change and submission. When something is wrong, an error message is injected into the span and <code>aria-invalid</code> attribute is set automatically.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718214688631/e6ac33ea-82be-4e7b-965d-1abc41b63739.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-server-side-validation">Server-side Validation</h2>
<p>By default, in Razor Pages, submitting a form triggers a POST request that results in a full page reload. In the case of POST in Razor Pages, we usually add an <code>OnPost</code> handler that might look like this:</p>
<pre><code class="lang-csharp"> <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPost</span>(<span class="hljs-params"></span>)</span>
 {
    <span class="hljs-keyword">if</span> (!ModelState.IsValid)
    {
        <span class="hljs-keyword">return</span> Page();
    }

    <span class="hljs-comment">// Store the user data in TempData</span>
    TempData[<span class="hljs-string">"User"</span>] = JsonSerializer.Serialize(UserRegistration);
    <span class="hljs-keyword">return</span> RedirectToPage(<span class="hljs-string">"Success"</span>, UserRegistration);
 }
</code></pre>
<p><code>ModelState</code> is a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelbinding.modelstatedictionary?view=aspnetcore-8.0">ModelStateDictionary</a>. After the POST request is accepted but before the <code>OnPost</code> handler called, the framework would perform validation and fill it with validation results:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718219417740/37ece71b-0d3a-47fa-8a1e-d79be52d42df.png" alt class="image--center mx-auto" /></p>
<p>If there are no validation errors, <code>ModelState.IsValid</code><strong>will be true</strong>, and we will continue processing with a redirect to the success page.</p>
<p>But if we introduce any validation error</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">if</span> (UserRegistration.PhoneNumber.Length &lt; <span class="hljs-number">4</span>) {
    ModelState.AddModelError(<span class="hljs-string">"User.PhoneNumber"</span>, <span class="hljs-string">"Phone format is wrong"</span>);   
}
</code></pre>
<p>We will have this information in the <code>ModelState</code> for the related property.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718219642220/4697afa6-42b8-4620-bbf5-189f6c402362.png" alt class="image--center mx-auto" /></p>
<p><code>ModelState.IsValid</code> <strong>will be false</strong>, and Razor Page will render the page with this error information put in place of the related tag helper.</p>
<p>Since we have a <code>[BindProperty]</code> attribute on the <code>User</code> model, returning the <code>Page()</code> will populate this model with data from the request. Therefore, all the data we input (except passwords) will be automatically filled back in.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718219780316/f6a79909-659e-4519-91ac-85b11d8b590d.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- template tag helper --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"PhoneNumber"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"PhoneNumber"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">asp-validation-for</span>=<span class="hljs-string">"PhoneNumber"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

<span class="hljs-comment">&lt;!-- rendered tag --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger field-validation-error"</span> 
    <span class="hljs-attr">data-valmsg-for</span>=<span class="hljs-string">"PhoneNumber"</span> 
    <span class="hljs-attr">data-valmsg-replace</span>=<span class="hljs-string">"true"</span>&gt;</span>Phone format is wrong<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>This is how default Razor Pages validation works, and ASP.NET validation in general. If you want you could explore it in detail in official <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/validation?view=aspnetcore-8.0">Microsoft documentation</a>.</p>
<p><strong>But how could we adapt this validation to use it with htmx?</strong></p>
<h1 id="heading-unobtrusive-validation-with-htmx">Unobtrusive Validation with htmx</h1>
<p>Htmx integrates with the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation">HTML5 Validation API</a> and will not issue a request for a form if a validatable input is invalid. However, since we do not use HTML5 when we use <code>jquery.validate.unobtrusive</code> we need to hook into the submit process to prevent submission if any client-side validation error is found.</p>
<p>Server-side we would need to render and return <code>Partial</code> - our server-side validated form instead of the whole page.</p>
<h2 id="heading-client-side-1">Client-side</h2>
<p>First, let's set up the form as a separate partial <code>_Form</code> . We would require to get the user model and ViewData with some additional properties like handler route or trigger.</p>
<pre><code class="lang-xml">@model Xakpc.RazorHtmx.Validation.UserRegistrationForm

@{
    var handler = (string)ViewData["Handler"];
    var trigger = (ViewData["Trigger"] as string) ?? "submit";
}

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"@handler"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHtml"</span> <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"@trigger"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_FormContent"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"@Model"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>We would use <code>_Form</code> partial like this</p>
<pre><code class="lang-xml">@page
@model Xakpc.RazorHtmx.Validation.Pages.UnobtrusiveHtmxModel
@{
    ViewData["Title"] = "User Registration";
    ViewData["Handler"] = Url.Page("UnobtrusiveHtmx");
    ViewData["Trigger"] = "valid-form";
}

...

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_Form"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"Model.UserRegistration"</span> <span class="hljs-attr">view-data</span>=<span class="hljs-string">"ViewData"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><code>@Url.Page("pageName")</code> is a convenient way to get a URL to the page, but I usually use custom-made <code>@Url.Handler("handlerName")</code> for it.</div>
</div>

<p>Note <code>ViewData["Trigger"] = "valid-form"</code> that would be rendered as <code>hx-trigger="valid-form"</code>. By default, AJAX requests are triggered by the “natural” event of an element. For a <code>form</code>, this is the <code>submit</code> event.</p>
<p>However, this won't work because the submit event is emitted even if there are validation errors. The solution here is to intercept the event and emit our own event if the form is valid.</p>
<pre><code class="lang-javascript">$(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    $(<span class="hljs-string">'form'</span>).on(<span class="hljs-string">'submit'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">e</span>) </span>{
        <span class="hljs-keyword">var</span> form = $(<span class="hljs-built_in">this</span>);
        <span class="hljs-keyword">if</span> (form.valid()) {
            <span class="hljs-comment">// Form is valid, allow htmx to handle the submission</span>
            htmx.trigger(<span class="hljs-string">"form"</span>, <span class="hljs-string">"valid-form"</span>);
            e.preventDefault();                    
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
            } <span class="hljs-keyword">else</span> {
            e.preventDefault();
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
        }
    });
});
</code></pre>
<p>When the form is valid the event <code>valid-form</code> would trigger <code>hx-post</code> and submit the form.</p>
<h2 id="heading-server-side">Server-side</h2>
<p>Server-side validation works the same way in this case, but the way we produce the response changes. Razor Pages are smart enough to handle binding and validation even for AJAX/htmx requests (same thing).</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPost</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (UserRegistration.PhoneNumber.Length &lt; <span class="hljs-number">4</span>)
    {
        ModelState.AddModelError(<span class="hljs-string">"PhoneNumber"</span>, <span class="hljs-string">"Phone format is wrong"</span>);
    }

    <span class="hljs-keyword">if</span> (!ModelState.IsValid)
    {
        Response.StatusCode = <span class="hljs-number">422</span>;
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_FormContent"</span>, UserRegistration);
    }

    <span class="hljs-comment">// no need to use temp data to pass user object to another page</span>
    <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_SuccessUser"</span>, UserRegistration);
}
</code></pre>
<p>Note the return code <code>422 Unprocessable Entity</code> - it is a <em>smart</em> way to tell the client that the form data has an error and cannot be processed. Unfortunately, htmx would not process requests with such a code - we need to set it up.</p>
<pre><code class="lang-javascript">$(<span class="hljs-built_in">document</span>).on(<span class="hljs-string">'htmx:beforeSwap'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">event</span>) </span>{
    <span class="hljs-keyword">var</span> evt = event.originalEvent; <span class="hljs-comment">// Get the original event details</span>
    <span class="hljs-keyword">if</span> (evt.detail.xhr.status === <span class="hljs-number">422</span>) {
        <span class="hljs-comment">// Allow 422 responses to swap as we are using this as a signal that</span>
        <span class="hljs-comment">// a form was submitted with bad data and want to rerender with the errors</span>
        <span class="hljs-comment">//</span>
        <span class="hljs-comment">// Set isError to false to avoid error logging in console</span>
        evt.detail.shouldSwap = <span class="hljs-literal">true</span>;
        evt.detail.isError = <span class="hljs-literal">false</span>;
    }
});
</code></pre>
<p>Unobtrusive validation is great and very powerful, but it requires using jQuery. While this is absolutely fine, it does add some weight to our page: <em>jquery-3.3.2.min.js + jquery.validate.min.js + jquery.validate.unobtrusive.min.js = 112 KB.</em></p>
<p><strong>Could we omit that?</strong></p>
<hr />
<h1 id="heading-html5-validation">HTML5 Validation</h1>
<p>Now let's remove <code>jquery.validation</code> and jQuery, and disable built-in validation with the setting <code>ViewContext.ClientValidationEnabled = false</code>. With this option disabled, Razor Pages will not convert Data Annotation attributes to HTML attributes.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">It's actually not necessary to disable <code>ClientValidationEnabled</code>. If you keep it enabled and just remove the jQuery libraries, you will still get all the attributes generated. You can then use them in your JavaScript validation however you like, including integrating them with HTML5 validation.</div>
</div>

<p>On the server side, not much will change because it does not rely on client validation.</p>
<h2 id="heading-client-side-2">Client-side</h2>
<p>We would lose quite a lot of generated code. Remember the input snippet?</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"FirstName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"FirstName"</span>/&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">asp-validation-for</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre>
<p>Here is how it would look without client validation enabled - all data attributes and validation messages are gone.</p>
<pre><code class="lang-csharp">&lt;label <span class="hljs-keyword">for</span>=<span class="hljs-string">"FirstName"</span>&gt;First Name&lt;/label&gt;
&lt;input type=<span class="hljs-string">"text"</span> id=<span class="hljs-string">"FirstName"</span> maxlength=<span class="hljs-string">"50"</span> name=<span class="hljs-string">"FirstName"</span> <span class="hljs-keyword">value</span>=<span class="hljs-string">""</span>&gt;
&lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">"text-danger"</span>&gt;&lt;/span&gt;
</code></pre>
<p>Razor Pages has <strong>very limited</strong> support for HTML5 validation, and Microsoft has somewhat <a target="_blank" href="https://github.com/dotnet/aspnetcore/issues/8573">given up</a> on improving it or replacing jQuery. From what I found, it only supports <code>type</code> and <code>maxlength</code>, not even <code>required</code>, which <strong>is a shame</strong> because supporting other built-in attributes could easily be done with tag helpers.</p>
<p>Here is an example of a tag helper that adds the <code>required</code> attribute to inputs, as well as a custom <code>data-err-required</code> attribute with an error message.</p>
<pre><code class="lang-csharp">
[<span class="hljs-meta">HtmlTargetElement(<span class="hljs-meta-string">"input"</span>, Attributes = <span class="hljs-meta-string">"asp-for"</span>)</span>]
[<span class="hljs-meta">HtmlTargetElement(<span class="hljs-meta-string">"textarea"</span>, Attributes = <span class="hljs-meta-string">"asp-for"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RequiredInputTagHelper</span> : <span class="hljs-title">TagHelper</span>
{
    [<span class="hljs-meta">HtmlAttributeName(<span class="hljs-meta-string">"asp-for"</span>)</span>]
    <span class="hljs-keyword">public</span> ModelExpression For { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Process</span>(<span class="hljs-params">TagHelperContext context, TagHelperOutput output</span>)</span>
    {
        <span class="hljs-keyword">var</span> attribute = For.ModelExplorer.Metadata.ValidatorMetadata.FirstOrDefault(
            attr =&gt; attr <span class="hljs-keyword">is</span> RequiredAttribute) <span class="hljs-keyword">as</span> RequiredAttribute;

        <span class="hljs-keyword">if</span> (attribute != <span class="hljs-literal">null</span>)
        {
            output.Attributes.SetAttribute(<span class="hljs-string">"required"</span>, <span class="hljs-literal">null</span>);

            <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">string</span>.IsNullOrEmpty(attribute.ErrorMessage))
            {
                output.Attributes.SetAttribute(<span class="hljs-string">"data-err-required"</span>, attribute.ErrorMessage);    
            }
        }
    }
}
</code></pre>
<p>I collected some <a target="_blank" href="https://github.com/xakpc/RazorHtmx/tree/master/Xakpc.RazorHtmx.Validation/TagHelpers">more tag helpers</a> for my personal use.</p>
<p>As you can see, these tag helpers not only set up built-in HTML5 validation but also add a <code>data-err-*</code> attribute that can be used to customize the validation message.</p>
<p>To achieve this, we need to write some JavaScript and attach event listeners to handle the <code>invalid</code> event. Then, we can use <code>setCustomValidity</code> for custom validation messages.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> inputs = <span class="hljs-built_in">document</span>.querySelectorAll(<span class="hljs-string">'input'</span>); 

    inputs.forEach(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">input</span>) </span>{
        <span class="hljs-comment">// Check if the input has any data-err-* attributes</span>
        <span class="hljs-keyword">if</span> (input.dataset.errRequired ||
            input.dataset.errLength ||
            input.dataset.errMinmax) {

            input.addEventListener(<span class="hljs-string">'invalid'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">event</span>) </span>{
                <span class="hljs-keyword">const</span> target = event.target;
                <span class="hljs-keyword">let</span> customMessage = <span class="hljs-string">''</span>;

                <span class="hljs-keyword">if</span> (target.validity.valueMissing &amp;&amp; target.dataset.errRequired) {
                    customMessage = target.dataset.errRequired;
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((target.validity.tooLong || target.validity.tooShort) &amp;&amp; target.dataset.errLength) {
                    customMessage = target.dataset.errLength;
                } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((target.validity.rangeUnderflow || target.validity.rangeOverflow) &amp;&amp; target.dataset.errMinmax) {
                    customMessage = target.dataset.errMinmax;
                }

                target.setCustomValidity(customMessage);
            });

            input.addEventListener(<span class="hljs-string">'input'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">event</span>) </span>{
                event.target.setCustomValidity(<span class="hljs-string">''</span>); <span class="hljs-comment">// Clear custom message on input</span>
            });
        }
    });
});
</code></pre>
<p>Example of HTML5 validation with a custom message</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718413814126/24a2fa6d-d899-4bc6-bc54-842b4c32cb87.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-server-side-validation-1">Server-side Validation</h2>
<p>Server-side validation would work the same as in the default way in this case, no changes needed.</p>
<p>In the end, we did lose some client-side features, like the <code>Password and Confirmation Password do not match</code> validation, but this would be handled server-side luckily. The main benefit is that we got rid of heavy jQuery libraries, simplified our HTML, and set a foundation for htmx to take over.</p>
<p>If built-in validation is not enough (<strong>many such cases</strong>), you can use any validation library you wish. Some are <a target="_blank" href="https://github.com/haacked/aspnet-client-validation">more convenient</a> for use with Razor Pages, while others are less so.</p>
<h1 id="heading-html5-validation-with-htmx">HTML5 Validation with htmx</h1>
<p>For me, this is usually a preferred setup for validation. I like to keep it simple, so built-in client validation is enough for me in 90% of cases.</p>
<p>It is not slightly different from the HTML5 and htmx we explored before.</p>
<h2 id="heading-client-side-3">Client-side</h2>
<p>We need to make some minor changes on the client side to make it work. Since htmx is integrated with the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation">HTML5 Validation API</a> and will not send a request for a form if a validatable input is invalid, we can remove the custom trigger <code>hx-trigger="valid-form"</code>.</p>
<p>The default trigger for the form is <code>submit</code> so no <code>hx-trigger</code> attribute needed.</p>
<pre><code class="lang-xml">@page
@model Xakpc.RazorHtmx.Validation.Pages.Html5HtmxModel
@{
    ViewData["Title"] = "User Registration";
    ViewData["Handler"] = Url.Page("Html5Htmx");
    ViewContext.ClientValidationEnabled = false;    
}

<span class="hljs-tag">&lt;<span class="hljs-name">hgroup</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>User Registration - HTML5 with htmx<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>htmx form submit with <span class="hljs-tag">&lt;<span class="hljs-name">mark</span>&gt;</span>HTML5<span class="hljs-tag">&lt;/<span class="hljs-name">mark</span>&gt;</span> validation<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">hgroup</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_Form"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"@Model.UserRegistration"</span> <span class="hljs-attr">view-data</span>=<span class="hljs-string">"ViewData"</span>/&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>We also would not need to prevent form submission anymore, but we would need to reattach events on the form after the swap is done (DOM is settled).</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'DOMContentLoaded'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    setupValidation();

    <span class="hljs-built_in">document</span>.body.addEventListener(<span class="hljs-string">'htmx:afterSettle'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">evt</span>) </span>{
        <span class="hljs-keyword">if</span> (evt.detail.xhr.status === <span class="hljs-number">422</span>) {
            setupValidation();
        }
    });
});
</code></pre>
<h2 id="heading-server-side-validation-2">Server-side Validation</h2>
<p>Server-side, nothing has changed. The server does not care about how we validate on the client side.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Html5HtmxModel</span> : <span class="hljs-title">PageModel</span>
{
    [<span class="hljs-meta">BindProperty</span>]
    <span class="hljs-keyword">public</span> UserRegistrationForm UserRegistration { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
    {
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPost</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">if</span> (UserRegistration.PhoneNumber.Length &lt; <span class="hljs-number">4</span>)
        {
            ModelState.AddModelError(<span class="hljs-string">"PhoneNumber"</span>, <span class="hljs-string">"Phone format is wrong"</span>);
        }

        <span class="hljs-keyword">if</span> (!ModelState.IsValid)
        {
            Response.StatusCode = <span class="hljs-number">422</span>;
            <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_FormContent"</span>, UserRegistration);
        }

        <span class="hljs-comment">// no need to use temp data to pass user object to another page</span>
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_SuccessUser"</span>, UserRegistration);
    }
}
</code></pre>
<p>As expected we have HTML5 validation with a custom message, submitted by htmx</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718414236913/7b8ba646-c107-4bbd-9501-1d92ebf30868.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-hypermedia-friendly-way"><strong>Hypermedia-Friendly</strong> way</h2>
<p>One of the somewhat controversial, but clever, ideas behind the hypermedia-friendly way to use htmx is inline scripting: writing your scripts directly within hypermedia, rather than placing them in an external file.</p>
<p>This can be done by using the <code>hx-on</code> attribute and incorporating functions or even inline code snippets to execute our JavaScript. For example, if we need to set the <code>aria-invalid</code> attribute based on the input's validity, instead of using <code>input.addEventListener('input', function (event) {...}</code>, we could use something like this:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"@handler"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHtml"</span>
      <span class="hljs-attr">hx-on:focusout</span>=<span class="hljs-string">"event.target.setAttribute('aria-invalid', 
        event.target.validity.valid &amp;&amp; event.target.value !== '' ? 'false' : 'true')"</span>&gt;</span>

      ...
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>This would set the attribute when we leave focus from any input on the form.</p>
<p>If we need to run a lot of code, we can put it into a function in the partial or, better yet, on the page where the partial is used.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"@handler"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHtml"</span>/&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"FirstName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">hx-on:invalid</span>=<span class="hljs-string">"setCustomValidity(event.target)"</span> /&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">asp-validation-for</span>=<span class="hljs-string">"FirstName"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

     <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"LastName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"LastName"</span> <span class="hljs-attr">hx-on:invalid</span>=<span class="hljs-string">"setCustomValidity(event.target)"</span> /&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">asp-validation-for</span>=<span class="hljs-string">"LastName"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

     <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"Email"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"Email"</span> <span class="hljs-attr">hx-on:invalid</span>=<span class="hljs-string">"setCustomValidity(event.target)"</span> /&gt;</span>
     <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">asp-validation-for</span>=<span class="hljs-string">"Email"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-danger"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
     ...
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setCustomValidity</span>(<span class="hljs-params">target</span>) </span>{
        <span class="hljs-keyword">let</span> customMessage = <span class="hljs-string">''</span>;

        <span class="hljs-keyword">if</span> (target.validity.valueMissing &amp;&amp; target.dataset.errRequired) {
            customMessage = target.dataset.errRequired;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((target.validity.tooLong || target.validity.tooShort) &amp;&amp; target.dataset.errLength) {
            customMessage = target.dataset.errLength;
        } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> ((target.validity.rangeUnderflow || target.validity.rangeOverflow) &amp;&amp; target.dataset.errMinmax) {
            customMessage = target.dataset.errMinmax;
        }

        target.setCustomValidity(customMessage);
    }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<hr />
<p>Finally, when everything is validated on both the client and server, we would see the success screen.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1718414476275/78e82a52-242d-4013-bcac-a41356416d0f.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Integrating htmx with .NET Razor Pages for form validation offers a streamlined and efficient approach to both client-side and server-side validation. By leveraging built-in validation mechanisms, customizing them with tag helpers, and utilizing HTML5 validation APIs, we can create robust and user-friendly forms.</p>
<p>It still has some rough edges and missing parts, but nothing that can't be figured out with a few hours of work (or by using AI), and it provides a lot of room for exploring different ways to handle validation:</p>
<ul>
<li><p>Built-in Razor Pages validation (is extremely powerful but somewhat outdated),</p>
</li>
<li><p>More modern variations like <a target="_blank" href="https://github.com/haacked/aspnet-client-validation">aspnet-client-validation</a>,</p>
</li>
<li><p>HTML5 validation with vanilla JS,</p>
</li>
<li><p>or any third-party validation library.</p>
</li>
</ul>
<p>You can choose whichever you prefer, and with minor code adjustments in tag helpers, you can achieve both client-side and server-side validation while fully supporting the DRY principle.</p>
<p>Feel free to explore the full code on <a target="_blank" href="https://github.com/xakpc/RazorHtmx/tree/master/Xakpc.RazorHtmx.Validation">GitHub</a> to see these concepts in action and improve your form validation workflows, also feel free to subscribe <a target="_blank" href="https://xakpc.info/series/htmx-dotnet">to the series</a> where I explore more htmx + dotnet crap, or ping me <a target="_blank" href="https://x.com/xakpc">on X</a> if you have any questions.</p>
]]></content:encoded></item><item><title><![CDATA[Hashnode sitemap.xml is broken. 
Here is how I fixed it]]></title><description><![CDATA[Hashnode is a great place to host a blog. It even gives you some options to optimize the SEO of your blog. For example, it generates a sitemap.xml file based on your blog.


💡
Sitemaps are XML files that tell search engines which URLs on a website t...]]></description><link>https://xakpc.info/hashnode-sitemapxml-is-broken-here-is-how-i-fixed-it</link><guid isPermaLink="true">https://xakpc.info/hashnode-sitemapxml-is-broken-here-is-how-i-fixed-it</guid><category><![CDATA[Hashnode]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 19 Sep 2023 23:03:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1695929902698/505bdf1f-629f-464e-876a-f780b7b6b471.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hashnode is a great place to host a blog. It even gives you some options to optimize the SEO of your blog. For example, it generates a <code>sitemap.xml</code> file based on your blog.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695159838454/4eed6acc-b30c-4a50-bdcd-0e713a6f3c58.png" alt class="image--center mx-auto" /></p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Sitemaps are XML files that tell search engines which URLs on a website they can crawl.</div>
</div>

<h1 id="heading-but-this-sitemap-file-is-broken"><strong>But this sitemap file is broken!</strong></h1>
<p>It's not a problem for most users, but if you want to help Google index your blog better, you could provide a sitemap to Google Search Console.</p>
<p>When I tried to do this, I got an error: <code>Sitemap could not be read</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695161601802/5ed6a707-5558-4167-a15d-1ef7e3d302f7.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695161628638/7d0be6cb-1c42-4816-a53d-42af2946a7b6.png" alt class="image--center mx-auto" /></p>
<p>I checked several times that a sitemap <a target="_blank" href="https://xakpc.info/sitemap.xml">is available</a>. I put it into an XML validator to validate it. But it simply looks like the generated sitemap is incompatible with Google Search Console.</p>
<p>So, what is wrong with the sitemap here?</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695162198995/0bd4859d-fe95-4ea6-83f4-8292655fe9ad.png" alt class="image--center mx-auto" /></p>
<p>Here is the diff between what was not working and the fixed version. It looks like the sitemap built by Hasnode is incorrect enough to confuse Google Search Console. All these spaces make me think it's generated "manually" by a concatenating string.</p>
<h1 id="heading-my-fix">My fix</h1>
<p>As a dotnet developer, I fixed this problem with code. I built a <strong>simple Azure Function</strong> that consumes the original sitemap and does a minor cleanup to create a valid sitemap.xml.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695162569844/67304abf-5e1b-437f-9c50-cdfe760edf5f.png" alt class="image--center mx-auto" /></p>
<p>Google Search Console would not allow adding any link as a source for a sitemap file, only the link from the blog site.</p>
<p>Luckily, Hashnode provides a way to handle it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695162638330/6afa1fae-2412-4e28-a4d7-90b14f93b1cc.png" alt class="image--center mx-auto" /></p>
<p>Where you could map any path to any link for redirect.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695162745825/cd25f744-5fee-448a-a6db-bc9fa44ad557.png" alt class="image--center mx-auto" /></p>
<p>That would satisfy Google, and it would accept your sitemap.xml</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1695162878011/ef09ab78-9c2e-4d86-b579-639b96341dca.png" alt class="image--center mx-auto" /></p>
<p>A complete code of a function is pretty simple:</p>
<ul>
<li><p>get a current sitemap</p>
</li>
<li><p>deserialze invalid structure</p>
</li>
<li><p>serialize without errors</p>
</li>
<li><p>return as a result</p>
</li>
</ul>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="c4ab9c023ad031ed9a992c8ca8199212"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/xakpc/c4ab9c023ad031ed9a992c8ca8199212" class="embed-card">https://gist.github.com/xakpc/c4ab9c023ad031ed9a992c8ca8199212</a></div><p> </p>
<p>If you want to reproduce it yourself, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-your-first-function-visual-studio">create a new Azure Function</a> Project, add the file, and then deploy it to Azure.</p>
<p>If you have any questions, feel free to ping me at <a target="_blank" href="https://x.com/xakpc">@xakpc</a></p>
]]></content:encoded></item><item><title><![CDATA[htmx 🤝 dotnet]]></title><description><![CDATA[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 htmx. I am usually not swayed by the 'shiny object syndrome', but I worked a lot with Razor Pages recently, and to...]]></description><link>https://xakpc.info/htmx-dotnet</link><guid isPermaLink="true">https://xakpc.info/htmx-dotnet</guid><category><![CDATA[dotnet]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[htmx]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 29 Aug 2023 21:51:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1693345917561/8579c4cb-c1d2-4d44-b4e8-49979ecd3e89.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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 <strong>htmx</strong>. I am usually not swayed by the 'shiny object syndrome', but I worked a lot with <strong>Razor Pages</strong> recently, and touched a lot of jQuery, which started to annoy me a bit.</p>
<p>However, after watching a <a target="_blank" href="https://youtu.be/NA5Fcgs_viU?si=0B0Ws7TMAZVveFCM">video from @t3dotgg</a> I was convinced that <strong>htmx</strong> might be helpful here and that it could fit me nicely. So, I decided to go through some <a target="_blank" href="https://htmx.org/examples/">use cases</a> for it and do some tests to check if it is any good.</p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">The results of these tests are available in <a target="_blank" href="https://github.com/xakpc/RazorHtmx">the repo</a>. The demo application is hosted on Azure free plan and is available <a target="_blank" href="https://razorhtmx.azurewebsites.net/">here</a>. To get maximum from this article you need to be familiar with ASP.NET MVC or RazorPages</div>
</div>

<h1 id="heading-1-introduction">1. Introduction</h1>
<p>First, let me briefly introduce what <strong>htmx</strong> and <strong>Razor Pages</strong> are:</p>
<p><strong>htmx</strong> 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."</p>
<p><strong>Razor Pages</strong> extends ASP.NET Core MVC, making coding page-focused scenarios easier and more productive than controllers and views. <strong>Razor Pages</strong> group the action (called a <em>handler</em>) and the viewmodel (called a <em>PageModel</em>) 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 <strong>Razor Pages</strong> and its handlers smaller and more focused while at the same time making it easier to find and work with.</p>
<p>I also want to mention <a target="_blank" href="https://picocss.com/"><strong>pico.css</strong></a><strong>.</strong> 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.</p>
<h1 id="heading-2-background">2. Background</h1>
<p>Before I dive into integrating <strong>htmx</strong> and <strong>Razor Pages</strong>, let me briefly describe each technology. Both are praised for simplifying web development, but what are they?</p>
<h2 id="heading-21-htmx">2.1. htmx</h2>
<p><strong>htmx</strong> started as an <code>intercooler.js</code> - a small helper library to make AJAX calls through HTML tag attributes.</p>
<pre><code class="lang-xml">  <span class="hljs-comment">&lt;!-- This anchor tag posts to '/click' when it is clicked --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">ic-post-to</span>=<span class="hljs-string">"/click"</span>&gt;</span>
    Click Me!
  <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
</code></pre>
<details><summary>Asynchronous JavaScript And XML (AJAX)</summary><div data-type="detailsContent">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.</div></details>

<p>Around three years ago, intercooler 2.0 (renamed to <strong>htmx 1.0</strong>) has been released. It was smaller, more expressive, and no longer depended on jQuery.</p>
<pre><code class="lang-xml">  <span class="hljs-comment">&lt;!-- have a button POST a click via AJAX --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"/clicked"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>&gt;</span>
    Click Me
  <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p><strong>htmx</strong> intend to help web developers to create dynamic, interactive web content without the overhead of complex JavaScript frameworks.</p>
<p>Leveraging HTML attributes allows seamless partial page updates, delivering the power of <strong>AJAX</strong>, <strong>CSS Transitions</strong>, <strong>WebSockets</strong>, and <strong>Server-Sent Events</strong>.</p>
<p>I would not talk about WebSockets and SSE or try them, though, because:</p>
<ul>
<li><p>They have experimental support</p>
</li>
<li><p>If I want WebSockets and SSE, I probably would use Blazor and SignalR</p>
</li>
</ul>
<h3 id="heading-211-ajax">2.1.1. AJAX</h3>
<p>The core of <strong>htmx</strong> is a set of attributes that allow you to issue AJAX requests directly from HTML:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Attribute</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>hx-get</code></td><td>Issues a <code>GET</code> request to the given URL</td></tr>
<tr>
<td><code>hx-post</code></td><td>Issues a <code>POST</code> request to the given URL</td></tr>
<tr>
<td><code>hx-put</code></td><td>Issues a <code>PUT</code> request to the given URL</td></tr>
<tr>
<td><code>hx-patch</code></td><td>Issues a <code>PATCH</code> request to the given URL</td></tr>
<tr>
<td><code>hx-delete</code></td><td>Issues a <code>DELETE</code> request to the given URL</td></tr>
</tbody>
</table>
</div><p>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:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">"/messages"</span>&gt;</span>
    Put To Messages
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>This tells the browser:</p>
<blockquote>
<p>When a user clicks on this div, issue a PUT request to the URL /messages and load the response into the div</p>
</blockquote>
<p>By default, AJAX requests are triggered by the “natural” event of an element:</p>
<ul>
<li><p><code>input</code>, <code>textarea</code> &amp; <code>select</code> are triggered on the <code>change</code> event</p>
</li>
<li><p><code>form</code> is triggered on the <code>submit</code> event</p>
</li>
<li><p>everything else is triggered by the <code>click</code> event</p>
</li>
</ul>
<p>The <code>hx-trigger</code> attribute could change this to specify which event will cause the request. Here is a <code>div</code> that posts to <code>/mouse_entered</code> when a mouse enters it:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"/mouse_entered"</span> <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"mouseenter"</span>&gt;</span>
    [Here Mouse, Mouse!]
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h3 id="heading-212-css-transitions">2.1.2. CSS Transitions</h3>
<p><strong>htmx</strong> makes it easy to use CSS Transitions without javascript. Imagine that we need to replace content by <strong>htmx</strong> via an AJAX request with this new content:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"div1"</span>&gt;</span>Original Content<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-comment">&lt;!-- replaced --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"div1"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"red"</span>&gt;</span>New Content<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Note two things:</p>
<ul>
<li><p>The div has the <em>same</em> <code>id</code> in the original and the new content</p>
</li>
<li><p>The <code>red</code> class has been added to the new content</p>
</li>
</ul>
<p>Given this situation, we can write a CSS transition from the old state to the new state:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.red</span> {
    <span class="hljs-attribute">color</span>: red;
    <span class="hljs-attribute">transition</span>: all ease-in <span class="hljs-number">1s</span> ;
}
</code></pre>
<p>When <strong>htmx</strong> 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 <code>id</code> stable across requests.</p>
<h3 id="heading-213-the-philosophy-behind-hypermedia-driven-applications">2.1.3. The philosophy behind "Hypermedia-Driven Applications"</h3>
<p>I would not delve into details here, but it is worth mentioning because it's a pretty interesting concept. They wrote a <a target="_blank" href="https://hypermedia.systems/">book</a> on it, read it sometime.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p><img src="https://htmx.org/img/memes/original.png" alt="meme from htmx.org" /></p>
<h2 id="heading-22-razor-pages">2.2. Razor Pages</h2>
<p><strong>Razor Pages</strong>, introduced in ASP.NET Core 2.0, offer a streamlined alternative to the MVC UI pattern, which Microsoft has supported since 2009.</p>
<p>While MVC promotes separation of concerns, it often creates a sprawling structure of controllers, views, and viewmodels across multiple folders. In contrast, <strong>Razor Pages</strong> encapsulate the action (referred to as a <em>handler</em>) and the viewmodel (now the <em>PageModel</em>) in a single class, directly tied to a corresponding Razor Page.</p>
<p>All these pages reside in a central <code>Pages</code> folder, following a naming-based routing convention. Handlers, prefixed with HTTP verbs like <code>OnGet</code>, simplify actions, typically returning the associated page by default. This structure enables concise, focused pages, making an application's navigation and updates more intuitive.</p>
<p><strong>Razor Pages</strong> overall a vast topic, but I want to point out two essential for the htmx parts of it: <strong>handlers</strong> and <strong>partials</strong></p>
<h3 id="heading-221-razor-pages-handlers">2.2.1. Razor Pages Handlers</h3>
<p><strong>Handler methods</strong> are the public methods in <strong>Razor Pages</strong> <code>PageModel</code> and they are automatically executed on a request, implicitly returning a result for the current page.</p>
<p>The <strong>Razor Pages</strong> 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": <code>OnGet(), OnPost(), OnPut()</code> or async versions <code>OnPostAsync(), OnGetAsync()</code>, etc.</p>
<p>Handler methods can return <code>void</code>, <code>Task</code> if asynchronous, or an <code>IActionResult</code> (or <code>Task&lt;IActionResult&gt;</code>).</p>
<details><summary>IActionResult</summary><div data-type="detailsContent">The <a target="_blank" class="no-loc" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.iactionresult">IActionResult</a> return type is appropriate when multiple <code>ActionResult</code> return types are possible in an action. The <code>ActionResult</code> types represent various HTTP status codes. Any non-abstract class deriving from <code>ActionResult</code> qualifies as a valid return type. Some common return types in this category are BadRequestResult (400), NotFoundResult (404), and OkObjectResult (200).</div></details>

<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ValidationModel</span> : <span class="hljs-title">PageModel</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
    {
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPost</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> Page();
    }
}
</code></pre>
<p><strong>Razor Pages</strong> include a feature called "<strong>named handler methods</strong>". This feature enables you to specify multiple methods that can be executed for a single verb.</p>
<pre><code class="lang-csharp">    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGetItem</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_ItemPartial"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPostItem</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_ItemPartial"</span>);
    }
</code></pre>
<p>We could use tag helpers to generate links like <code>/validation?handler=Item</code> , where the handler would be set as a query parameter. There is also an option to generate path links like <code>/validation/Item</code> by setting a page route <code>@page "{handler?}"</code></p>
<details><summary>Tag Helpers</summary><div data-type="detailsContent">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.</div></details>

<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Razor code --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">asp-page</span>=<span class="hljs-string">"Validation"</span> <span class="hljs-attr">asp-page-handler</span>=<span class="hljs-string">"Item"</span>&gt;</span>Link<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

<span class="hljs-comment">&lt;!-- HTML code --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/validation?handler=Item"</span>&gt;</span>Link<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>Unfortunately, these Tag Helpers exist only for <code>&lt;a&gt;</code> and <code>&lt;form&gt;</code> tags. One of the questions <strong>htmx</strong> trying to solve is <code>Why should only &lt;a&gt; and &lt;form&gt; be able to make HTTP requests?</code> so we could call handlers from any tag. MVC provides a way to generate a handler with a helper function: <code>@Url.Page("BulkUpdate", "Activate")</code> - would generate <code>/BulkUpdate?handler=Activate</code> .</p>
<p>But in <strong>htmx</strong> context, handlers usually belong to the same page, so to make it easier, I wrote a simple URL helper method:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UrlHandlerExtensions</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Handler</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IUrlHelper urlHelper, <span class="hljs-keyword">string</span> handler, <span class="hljs-keyword">object</span>? values = <span class="hljs-literal">null</span></span>)</span>
    {
        <span class="hljs-comment">// Convert the values object to a dictionary</span>
        <span class="hljs-keyword">var</span> routeValues = <span class="hljs-keyword">new</span> RouteValueDictionary(values)
        {
            [<span class="hljs-meta"><span class="hljs-meta-string">"handler"</span></span>] = handler <span class="hljs-comment">// Add the handler to the dictionary</span>
        };

        <span class="hljs-keyword">var</span> url = urlHelper.RouteUrl(<span class="hljs-keyword">new</span> UrlRouteContext
        {
            Values = routeValues
        });

        <span class="hljs-keyword">return</span> url;
    }
}
</code></pre>
<p>It could be used with any HTML tag to set the handler URL to a htmx attribute:</p>
<pre><code class="lang-csharp">&lt;button <span class="hljs-keyword">class</span>=<span class="hljs-string">"outline"</span> hx-delete=<span class="hljs-string">"@Url.Handler("</span>Item<span class="hljs-string">", new { id = Model.Id })"</span> antiforgery=<span class="hljs-string">"true"</span>&gt;
    Delete
&lt;/button&gt;
</code></pre>
<h3 id="heading-222-razor-pages-partials">2.2.2. Razor Pages Partials</h3>
<p><strong>Partial Views</strong> in <strong>Razor Pages</strong> contain reusable HTML and code snippets that simplify complex pages by breaking them into smaller units.</p>
<p>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.</p>
<p>Partial Views are set up as <em>cshtml</em> files that do not take part in routing, so they do not have <code>@page</code> directive. Usually, partials are used to reuse some code on razor pages with the use of <code>partial</code> tag helper.</p>
<pre><code class="lang-csharp">&lt;<span class="hljs-keyword">partial</span> name=<span class="hljs-string">"_ItemPartial"</span> model=<span class="hljs-string">"Model.UserInfo"</span> /&gt;
</code></pre>
<p>On top of that <code>PageModel</code> provides a set of methods that implement <code>IActionResult</code> , one of them: <code>Partial</code> - would apply the model, render a partial HTML view to the response, and return it as a response body.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Method</td><td>Description</td></tr>
</thead>
<tbody>
<tr>
<td><code>Page()</code></td><td>Creates a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.razorpages.pageresult?view=aspnetcore-7.0">PageResult</a> object that renders the page.</td></tr>
<tr>
<td><code>Partial(String)</code></td><td>Creates a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.partialviewresult?view=aspnetcore-7.0">PartialViewResult</a> by specifying the name of a partial to render.</td></tr>
<tr>
<td><code>Partial(String, Object)</code></td><td>Creates a <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.partialviewresult?view=aspnetcore-7.0">PartialViewResult</a> by specifying the name of a partial to render and the model object.</td></tr>
</tbody>
</table>
</div><p>That gives us the ability to define razor partial view</p>
<pre><code class="lang-xml">@model Xakpc.RazorHtmx.Data.UserInfoViewModel

<span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@Model.FirstName<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@Model.Email<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">EditItem</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">id</span> = <span class="hljs-string">Model.Id</span> })"&gt;</span>
            Edit
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
</code></pre>
<p>and return it as generated HTML in a response</p>
<pre><code class="lang-csharp">    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGetItem</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span>
    {
        <span class="hljs-keyword">var</span> item = TestData.Users.First(x =&gt; x.Id == id);

        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_TableRow"</span>, item);
    }
</code></pre>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>Krystal<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>krystal.heaney@bergnaummetz.us<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"/edit-row?id=2<span class="hljs-symbol">&amp;amp;</span>handler=EditItem"</span>&gt;</span>
            Edit
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
</code></pre>
<p>And we could also re-use the exact partial for the initial page generation</p>
<pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span> <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"closest tr"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>&gt;</span>
        @foreach (var userInfo in Model.Users)
        {
            <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_TableRow"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"userInfo"</span>/&gt;</span>
        }
        <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
</code></pre>
<p>In my opinion this feature is a single reason why <strong>Razor Pages</strong> could even work with <strong>htmx</strong>.</p>
<h1 id="heading-3-setting-the-stage-prerequisites-and-initial-setup">3. Setting the Stage: Prerequisites and Initial Setup</h1>
<p>To start, you don't need much - an installed <a target="_blank" href="https://dotnet.microsoft.com/en-us/download">dotnet core SDK</a> 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 <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start?view=aspnetcore-7.0&amp;tabs=visual-studio-code">official tutorial</a> if you feel stuck.</p>
<p>Run the following command to create a new <strong>Razor Pages</strong> project:</p>
<pre><code class="lang-powershell">dotnet new webapp <span class="hljs-literal">-o</span> RazorHtmx
</code></pre>
<p>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 <a target="_blank" href="https://github.com/xakpc/RazorHtmx/releases/tag/boilerplate">here</a>), but unfortunately, it's not a template yet.</p>
<h2 id="heading-31-integrating-htmx-into-razor-pages">3.1. Integrating htmx into Razor Pages</h2>
<p>To integrate <strong>htmx</strong>, you should include the js file into <code>_Layout</code>. Choose whatever option you prefer from <a target="_blank" href="https://htmx.org/docs/#installing">docs#installing</a>. I downloaded and included <code>htmx.min.js</code> to a head tag of <code>_Layout.cshtml</code></p>
<details><summary>_Layout.cshtml</summary><div data-type="detailsContent">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 <code>_Layout.cshtml</code>.</div></details>

<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- rest of the code --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"~/js/htmx.min.js"</span> <span class="hljs-attr">asp-append-version</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
</code></pre>
<h1 id="heading-4-making-first-htmx-call-in-razor">4. Making First htmx Call in Razor</h1>
<p>As I mentioned earlier, my aim was to explore each example from the <strong>htmx</strong> 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.</p>
<blockquote>
<p>The click-to-edit pattern provides a way to offer inline editing of all or part of a record without a page refresh.</p>
</blockquote>
<p>The example ended with several files</p>
<ul>
<li><p><code>_Partial.cshtml</code> to present record</p>
</li>
<li><p><code>_Edit.cshtml</code> to present form for editing</p>
</li>
<li><p><code>ClickToEdit.cshtml</code> to present the page</p>
</li>
<li><p><code>ClickToEdit.cshtml.cs</code> for PageModel</p>
</li>
<li><p><code>UserInfoViewModel.cs</code> for model</p>
</li>
</ul>
<p>Let me describe each of them.</p>
<p><strong>UserInfoViewModel.cs</strong></p>
<p>It's a pretty generic model class. Each property has data annotation to render labels in forms.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserInfoViewModel</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        [<span class="hljs-meta">Display(Name = <span class="hljs-meta-string">"First Name"</span>)</span>]
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FirstName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        [<span class="hljs-meta">Display(Name = <span class="hljs-meta-string">"Last Name"</span>)</span>]
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> LastName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        [<span class="hljs-meta">Display(Name = <span class="hljs-meta-string">"Email"</span>)</span>]
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        [<span class="hljs-meta">Display(Name = <span class="hljs-meta-string">"Status"</span>)</span>]
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? Status { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-keyword">public</span> Guid RowId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = Guid.Empty;
    }
</code></pre>
<p><strong>ClickToEdit.cshtml.cs</strong></p>
<p><code>PageModel</code> for this page consists of all required handler methods, basic <code>OnGet</code> for initial loading, a couple named <code>GET</code> handlers to show the edit form and view form, and <code>PUT</code> method to update form data.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ClickToEditModel</span> : <span class="hljs-title">PageModel</span>
{
    <span class="hljs-keyword">public</span> UserInfoViewModel UserInfoViewModel { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
    {
        UserInfoViewModel = GetUserInfo(<span class="hljs-number">1</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGetPartial</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span>
    {
        UserInfoViewModel = GetUserInfo(id);
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_Partial"</span>, UserInfoViewModel);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGetEdit</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span>
    {
        UserInfoViewModel = GetUserInfo(id);
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_Edit"</span>, UserInfoViewModel);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPutEdit</span>(<span class="hljs-params">UserInfoViewModel userInfoViewModel</span>)</span>
    {
        UserInfoViewModel = userInfoViewModel;
        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_Partial"</span>, UserInfoViewModel);
    }
}
</code></pre>
<p><strong>ClickToEdit.cshtml</strong></p>
<p>The page itself is nothing other than a placeholder for partials. Instead of using <code>&lt;partial&gt;</code> tag helper, I'm using <code>Html.PartialAsync</code> helper method, but the outcome would be the same.</p>
<pre><code class="lang-xml">@page "/click-to-edit"
@model ClickToEditModel

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    @await Html.PartialAsync("_Partial", Model.UserInfoViewModel)
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>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.</p>
<p>First, <strong>_Partial.cshtml</strong></p>
<pre><code class="lang-xml">@model Xakpc.RazorHtmx.Data.UserInfoViewModel

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.FirstName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
        @Model.FirstName
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.LastName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
        @Model.LastName
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.Email"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
        @Model.Email
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Page("</span><span class="hljs-attr">ClickToEdit</span>", "<span class="hljs-attr">Edit</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">id</span> = <span class="hljs-string">Model.Id</span> })"&gt;</span>
        Click To Edit
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<ul>
<li><p><code>hx-target="this"</code> makes a div that updates itself when changed. It's also inherited, so it works when placed on the parent element.</p>
</li>
<li><p><code>hx-swap="outerHTML"</code> instructs to replace the entire target element with the response.</p>
</li>
<li><p><code>hx-get="@Url.Page("ClickToEdit", "Edit", new { id = Model.Id })"</code> would generate a URL to <code>ClickToEdit.OnGetEdit</code> handler method and would pass <code>id</code> query parameter.</p>
</li>
</ul>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"/click-to-edit?id=1&amp;handler=Edit"</span>&gt;</span>
        Click To Edit
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p><strong>_Edit.cshtml</strong></p>
<pre><code class="lang-xml">@model Xakpc.RazorHtmx.Data.UserInfoViewModel

<span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">"@Url.Page("</span><span class="hljs-attr">ClickToEdit</span>", "<span class="hljs-attr">Edit</span>")"
      <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>&gt;</span>

    @Html.AntiForgeryToken()
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"Id"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.FirstName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.FirstName"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">required</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.LastName"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.LastName"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">required</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.Email"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.Email"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">required</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"grid"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Page("</span><span class="hljs-attr">ClickToEdit</span>", "<span class="hljs-attr">Partial</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">id</span> = <span class="hljs-string">Model.Id</span> })" <span class="hljs-attr">class</span>=<span class="hljs-string">"secondary"</span>&gt;</span>Cancel<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<ul>
<li><p><code>@Html.AntiForgeryToken()</code> will generate a hidden field with an anti-forgery token to prevent CSRF vulnerabilities. More on this later.</p>
</li>
<li><p><code>hx-put="@Url.Page("ClickToEdit", "Edit")"</code> will perform <code>PUT</code> a request to <code>OnPutEdit</code> handler because updates should be <code>PUT</code> according to RESTful best practices.</p>
</li>
<li><p><code>hx-target="this"</code> makes a div that updates itself when changed. It's also inherited and works when placed on the parent element.</p>
</li>
<li><p><code>hx-swap="outerHTML"</code> instructs to replace the entire target element with the response.</p>
</li>
<li><p><code>hx-get="@Url.Page("ClickToEdit", "Partial", new { id = Model.Id })"</code> will perform <code>GET</code> request to <code>OnGetPartial</code> handler and set query parameter <code>id</code> to model id. This query would return rendered HTML of the record view:</p>
</li>
</ul>
<pre><code class="lang-xml">    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"FirstName"</span>&gt;</span>First Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
            Kennedy
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"LastName"</span>&gt;</span>Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
            Heaney
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"Email"</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
            kennedy_heaney@botsford.uk
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"/click-to-edit?id=1<span class="hljs-symbol">&amp;amp;</span>handler=Edit"</span>&gt;</span>
            Click To Edit
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>As a result, a fully functional click-to-edit form in <strong>Razor Pages</strong> and <strong>htmx</strong> with 100-120 lines of code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693338867659/4eae9486-ee6b-4fc8-aa95-6d7acdda16b0.gif" alt class="image--center mx-auto" /></p>
<h1 id="heading-5-exploring-other-htmx-use-cases">5. Exploring Other htmx Use-Cases</h1>
<p>I surely could not even try to describe every feature <strong>htmx</strong> has. I selected a couple of examples that might be useful references for any web application.</p>
<h2 id="heading-51-progress-bar">5.1. Progress Bar</h2>
<p>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.</p>
<pre><code class="lang-xml">@model int

<span class="hljs-tag">&lt;<span class="hljs-name">progress</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"progress"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"@Model"</span> <span class="hljs-attr">max</span>=<span class="hljs-string">"100"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">progress</span>&gt;</span>
</code></pre>
<p>But I want to show you here is a <strong>_ProgresPartial,</strong> which hosts a progress bar.</p>
<p>Based on the progress model (is progress done or not), we could alter how this partial is rendered and what <code>hx-trigger</code> value is set. This is a primary function of Razor markup syntax.</p>
<p>Therefore, we could decide on the application's state and what should be returned as a view on the backend, which is very convenient.</p>
<pre><code class="lang-xml">@model Progress

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"done"</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Progress</span>")" <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span> <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span>&gt;</span>
    @if (Model.Done)
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"status"</span> <span class="hljs-attr">tabindex</span>=<span class="hljs-string">"-1"</span> <span class="hljs-attr">autofocus</span>&gt;</span>Complete<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    }
    else
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"status"</span> <span class="hljs-attr">tabindex</span>=<span class="hljs-string">"-1"</span> <span class="hljs-attr">autofocus</span>&gt;</span>Running<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    }

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
        <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Progress</span>")"
        <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"@(Model.Done ? "</span><span class="hljs-attr">none</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">every</span> <span class="hljs-attr">600ms</span>")"
        <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span>
        <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"innerHTML"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_ProgressBarPartial"</span> <span class="hljs-attr">model</span>=<span class="hljs-string">"@Model.Value"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    @if (Model.Done)
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Start</span>")" <span class="hljs-attr">antiforgery</span>=<span class="hljs-string">"true"</span> <span class="hljs-attr">classes</span>=<span class="hljs-string">"add show:600ms"</span>&gt;</span>
            Restart Job
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    }
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693338966920/c3935bda-0082-40b8-8345-5da664df2475.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-52-table-scroll-edit-delete">5.2. Table Scroll, Edit, Delete</h2>
<p>Building interactive tables is a common task of any web application. With the help of <strong>htmx</strong>, we could easily expand a static table with scroll loading in place of edit and deletion.</p>
<p>I implemented several tests related to tables:</p>
<ul>
<li><p>Infinite Scroll</p>
</li>
<li><p>Table Row Edit</p>
</li>
<li><p>Table Row Delete</p>
</li>
</ul>
<p>For <strong>infinite scroll</strong>, we have a partial in the <code>tbody</code></p>
<pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"tbody"</span>&gt;</span>
            @await Html.PartialAsync("_TableBody", Model.UsersTable)
        <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
</code></pre>
<p>The partial would draw table rows, and the last row would add <code>hx-get</code> attribute with <code>revelaed</code> trigger.</p>
<pre><code class="lang-xml">@foreach (var userInfo in Model.Users)
{
    @if (userInfo == Model.Users.Last())
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Page("</span><span class="hljs-attr">InfiniteScroll</span>", "<span class="hljs-attr">Page</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">page</span> = <span class="hljs-string">Model.Page</span> + <span class="hljs-attr">1</span> })"
            <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"revealed"</span>
            <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"afterend"</span>
            <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">"#ind"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.FirstName @userInfo.LastName<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.Email<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.RowId.ToString("N").ToUpperInvariant()<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    }
    else
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.FirstName @userInfo.LastName<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.Email<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.RowId.ToString("N").ToUpperInvariant()<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    }
}
</code></pre>
<p>The handler method load and render the next page of rows. There is also <code>Task.Delay</code> to simulate some loading.</p>
<pre><code class="lang-csharp">    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnGetPageAsync</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> page</span>)</span>
    {
        <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">1000</span>);

        <span class="hljs-keyword">var</span> data = TestData.Users.GetRange(page * PageSize, PageSize);

        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_TableBody"</span>, data);
    }
</code></pre>
<p>Here is how it works:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693339110448/db6e0f8d-87f2-41c9-a8de-2876e2e56c15.gif" alt class="image--center mx-auto" /></p>
<p>For <strong>table editing</strong>, I have two partials, for presenting and for editing, which are swapped on the button click.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@Model.FirstName<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@Model.Email<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">EditItem</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">id</span> = <span class="hljs-string">Model.Id</span> })"&gt;</span>
            Edit
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
</code></pre>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.FirstName"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">asp-for</span>=<span class="hljs-string">"@Model.Email"</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Item</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">id</span> = <span class="hljs-string">Model.Id</span> })" <span class="hljs-attr">class</span>=<span class="hljs-string">"secondary"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"margin-bottom: 6px;"</span>&gt;</span>
            Cancel
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Item</span>", <span class="hljs-attr">new</span> { <span class="hljs-attr">id</span> = <span class="hljs-string">Model.Id</span> })" <span class="hljs-attr">hx-include</span>=<span class="hljs-string">"closest tr"</span> <span class="hljs-attr">antiforgery</span>=<span class="hljs-string">"true"</span>&gt;</span>
            Save
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
</code></pre>
<p>Handlers for this page are straightforward: get item, get item form, update (put) item</p>
<pre><code class="lang-csharp">    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGetItem</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span>
    {
        <span class="hljs-keyword">var</span> item = TestData.Users.First(x =&gt; x.Id == id);

        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_TableRow"</span>, item);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGetEditItem</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span>
    {
        <span class="hljs-keyword">var</span> item = TestData.Users.First(x =&gt; x.Id == id);

        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_TableRowForm"</span>, item);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnPutItem</span>(<span class="hljs-params">UserInfoViewModel user</span>)</span>
    {
        <span class="hljs-keyword">var</span> item = TestData.Users.First(x =&gt; x.Id == user.Id);
        item.FirstName = user.FirstName;
        item.Email = user.Email;

        <span class="hljs-keyword">return</span> Partial(<span class="hljs-string">"_TableRow"</span>, item);
    }
</code></pre>
<p>And now we can easily update table rows</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693339214699/639ed542-baeb-43cd-b773-cff738696974.gif" alt class="image--center mx-auto" /></p>
<p>Delete Row has a similar table row partial, but with <code>hx-delete</code> handler, because REST.</p>
<pre><code class="lang-csharp">&lt;tr&gt;
    &lt;td&gt;@Model.FirstName @Model.LastName&lt;/td&gt;
    &lt;td&gt;@Model.Email&lt;/td&gt;
    &lt;td&gt;@Model.Status&lt;/td&gt;
    &lt;td&gt;
        &lt;button <span class="hljs-keyword">class</span>=<span class="hljs-string">"outline"</span> hx-delete=<span class="hljs-string">"@Url.Handler("</span>Item<span class="hljs-string">", new { id = Model.Id })"</span> antiforgery=<span class="hljs-string">"true"</span>&gt;
            Delete
        &lt;/button&gt;
    &lt;/td&gt;
&lt;/tr&gt;
</code></pre>
<p>On the delete call, the handler would return 200 OK with an empty body instructing <strong>htmx</strong> to remove the row. We also use <code>hx-confirm</code> tag to confirm deletion</p>
<pre><code class="lang-csharp">        &lt;tbody hx-confirm=<span class="hljs-string">"Are you sure?"</span> hx-target=<span class="hljs-string">"closest tr"</span> hx-swap=<span class="hljs-string">"outerHTML swap:1s"</span>&gt;
        @foreach (<span class="hljs-keyword">var</span> userInfo <span class="hljs-keyword">in</span> Model.Users)
        {
            @await Html.PartialAsync(<span class="hljs-string">"_TableRow"</span>, userInfo)
        }
        &lt;/tbody&gt;
</code></pre>
<p>There is also some transition animation</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693339371099/f397a7b0-15ab-4130-b1c2-7ec891c7cbf4.gif" alt class="image--center mx-auto" /></p>
<p>Let's look at a couple of practical scenarios where <strong>htmx</strong> and <strong>Razor Pages</strong> integration shines.</p>
<h2 id="heading-53-dialogs-and-tabs">5.3. Dialogs and tabs</h2>
<p>A full code listing of how HTML5 dialogs could be created with <strong>htmx</strong> and <strong>Razor Pages</strong>. In this example, I used some JavaScript to show and hide the dialog.</p>
<p>At the same time, when dialog is shown, <strong>htmx</strong> would issue a get request to get data from the page handler and set the result as dialog content.</p>
<p>Actual dialog content would be generated on the backend from Razor partial.</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- DialogsHtml.cshtml --&gt;</span> 
@page "/dialogs-html"
@model DialogsHtmlModel

<span class="hljs-comment">&lt;!-- The Dialog --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">dialog</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"myDialog"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"dialogContent"</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">dialog</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Trigger buttons --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">data-target</span>=<span class="hljs-string">"myDialog"</span>
            <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Modal</span>")"
            <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"#dialogContent"</span>
            <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"click"</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">"showDialog()"</span>&gt;</span>
        Show Dialog
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

@section Scripts
{
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">showDialog</span>(<span class="hljs-params"></span>) </span>{
            <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myDialog'</span>);
            dialog.showModal(); <span class="hljs-comment">// Opens the dialog</span>
        }

        <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">closeDialog</span>(<span class="hljs-params"></span>) </span>{
            <span class="hljs-keyword">const</span> dialog = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'myDialog'</span>);
            dialog.close(); <span class="hljs-comment">// Closes the dialog</span>
        }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

}

<span class="hljs-comment">&lt;!-- DialogsHtml.cshtml.cs --&gt;</span> 
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.
                """));
    }
}

<span class="hljs-comment">&lt;!-- _ModalPartial.cshtml --&gt;</span>
@model (string Header, string Text)

<span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#close"</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Close"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"close"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"closeDialog(); return false;"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    @Model.Header
<span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
        @Model.Text
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693339517554/c1c1baf8-88bd-4e9e-ba96-1b41118ec74c.gif" alt class="image--center mx-auto" /></p>
<p>A full code listing of how tabs could be created with <strong>htmx</strong> and <strong>Razor Pages</strong>. Here, I have tabs div, which is replaced with tab partial.</p>
<p>The interesting part is a <code>load</code> 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.</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Tabs.chtmls --&gt;</span>
@page "/tabs-hateoas"
@model TabsModel

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"tabs"</span>
     <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab1</span>")"
     <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"load delay:100ms"</span>
     <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"#tabs"</span>
     <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"innerHTML"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-comment">&lt;!-- Tabs.chtmls.cs --&gt;</span>
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");
    }
}

<span class="hljs-comment">&lt;!-- _Tab1Partial.cshtml --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"grid"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab1</span>")"&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab2</span>")" <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span>&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab3</span>")" <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span>&gt;</span>Tab 3<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        some text...
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

<span class="hljs-comment">&lt;!-- _Tab2Partial.cshtml --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"grid"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"tablist"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab1</span>")" <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span>&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab2</span>")"&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab3</span>")" <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span>&gt;</span>Tab 3<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        some text 2...
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

<span class="hljs-comment">&lt;!-- _Tab3Partial.cshtml --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"grid"</span> <span class="hljs-attr">role</span>=<span class="hljs-string">"tablist"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab1</span>")" <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span>&gt;</span>Tab 1<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab2</span>")" <span class="hljs-attr">class</span>=<span class="hljs-string">"outline"</span>&gt;</span>Tab 2<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Tab3</span>")"&gt;</span>Tab 3<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        some text 3...
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
</code></pre>
<p>Tabs, in my case, are buttons, but it still looks good</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693339622933/d10ebe4e-b859-4839-92b9-d1916643fdde.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-54-live-search-feature-on-a-razor-page">5.4. Live search feature on a Razor Page</h2>
<p>Here we use <code>keyup</code> trigger to send a search query to <code>OnPostSearch</code> handler, which would render partial based on search results.</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- ActiveSearch.cshtml --&gt;</span>
@page "/active-search"
@model ActiveSearchModel

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>
        Search Contacts
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"htmx-indicator"</span> <span class="hljs-attr">aria-busy</span>=<span class="hljs-string">"true"</span>&gt;</span>Searching...<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"search"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"search"</span>
           <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Begin Typing To Search Users..."</span>
           <span class="hljs-attr">hx-post</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">Search</span>")"
           <span class="hljs-attr">hx-trigger</span>=<span class="hljs-string">"keyup changed delay:500ms, search"</span>
           <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"#search-results"</span>
           <span class="hljs-attr">hx-indicator</span>=<span class="hljs-string">".htmx-indicator"</span>
           <span class="hljs-attr">antiforgery</span>=<span class="hljs-string">"true"</span>/&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">table</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"table"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">thead</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>First Name<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Last Name<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">th</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">th</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">thead</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">tbody</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search-results"</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">table</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

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

    public IActionResult OnPostSearch(string search)
    {
        if (search == null || string.IsNullOrEmpty(search))
        {
            return Partial("_TableBody", new List<span class="hljs-tag">&lt;<span class="hljs-name">UserInfoViewModel</span>&gt;</span>());
        }

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

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

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

<span class="hljs-comment">&lt;!-- _TableBody.cshtml --&gt;</span>
@model List<span class="hljs-tag">&lt;<span class="hljs-name">Xakpc.RazorHtmx.Data.UserInfoViewModel</span>&gt;</span>

@foreach (var userInfo in Model)
{
    <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.FirstName<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.LastName<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@userInfo.Email<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
}
</code></pre>
<p>Here is how it works</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693339714303/a8f9c891-c913-4ffa-b1dc-9ae2f37aaf77.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-55-animations">5.5 Animations</h2>
<p>While my primary focus wasn't on aesthetics during testing, it's worth noting that it could be done. <strong>htmx</strong> supports <code>CSS transitions</code> so we could apply some cool animations to our partials.</p>
<pre><code class="lang-xml">@page "/animations"
@model AnimationsModel

<span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Using the View Transition API<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"slide-it"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">partial</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"_SwapContentPartial"</span>, <span class="hljs-attr">model</span>=<span class="hljs-string">"@("</span><span class="hljs-attr">Initial</span> <span class="hljs-attr">Content</span>", "<span class="hljs-attr">Swap</span> <span class="hljs-attr">It</span>")"/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

@section Styles
{
  <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
   @<span class="hljs-keyword">@keyframes</span> fade-in {
     <span class="hljs-selector-tag">from</span> { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>; }
   }

   @<span class="hljs-keyword">@keyframes</span> fade-out {
     <span class="hljs-selector-tag">to</span> { <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>; }
   }

   @<span class="hljs-keyword">@keyframes</span> slide-from-right {
     <span class="hljs-selector-tag">from</span> { <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(<span class="hljs-number">90px</span>); }
   }

   @<span class="hljs-keyword">@keyframes</span> slide-to-left {
     <span class="hljs-selector-tag">to</span> { <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translateX</span>(-<span class="hljs-number">90px</span>); }
   }

   <span class="hljs-selector-class">.slide-it</span> {
     <span class="hljs-attribute">view-transition-name</span>: slide-it;
   }

   <span class="hljs-selector-pseudo">::view-transition-old(slide-it)</span> {
     <span class="hljs-attribute">animation</span>: <span class="hljs-number">180ms</span> <span class="hljs-built_in">cubic-bezier</span>(<span class="hljs-number">0.4</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>) both fade-out,
     <span class="hljs-number">600ms</span> <span class="hljs-built_in">cubic-bezier</span>(<span class="hljs-number">0.4</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>, <span class="hljs-number">1</span>) both slide-to-left;
   }
   <span class="hljs-selector-pseudo">::view-transition-new(slide-it)</span> {
     <span class="hljs-attribute">animation</span>: <span class="hljs-number">420ms</span> <span class="hljs-built_in">cubic-bezier</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>, <span class="hljs-number">1</span>) <span class="hljs-number">90ms</span> both fade-in,
     <span class="hljs-number">600ms</span> <span class="hljs-built_in">cubic-bezier</span>(<span class="hljs-number">0.4</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>, <span class="hljs-number">1</span>) both slide-from-right;
   }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
}
</code></pre>
<p>In partial, we instruct <strong>htmx</strong> to use transition.</p>
<pre><code class="lang-xml">@model (string Title, string Button)

<span class="hljs-tag">&lt;<span class="hljs-name">h6</span>&gt;</span>@Model.Title<span class="hljs-tag">&lt;/<span class="hljs-name">h6</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">hx-get</span>=<span class="hljs-string">"@Url.Handler("</span><span class="hljs-attr">NewContent</span>")" <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"innerHTML transition:true"</span> <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"closest div"</span>&gt;</span>
    @Model.Button
<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
</code></pre>
<p>This gives us nice animation.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693340068108/71bbf5b0-e514-4bee-bf99-93f956d2802a.gif" alt class="image--center mx-auto" /></p>
<h1 id="heading-6-benefits-of-the-integration">6. Benefits of the Integration</h1>
<p>Simplicity, simplicity, simplicity.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1693302736218/f0fa052e-2ee5-4adc-9b5e-3a40c763f753.gif" alt class="image--center mx-auto" /></p>
<p>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.</p>
<p>Probably, that's why I wouldn't say I like to overcomplicate anything. And like <strong>Razor Pages</strong> was invented to simplify MVC web development, <strong>pico.css</strong> was invented to simplify web design, so as far as I see, <strong>htmx</strong> could be the tool to simplify interactivity.</p>
<p>But these are all personal preferences. Let's evaluate what actual benefits <strong>Razor Pages</strong> together with <strong>htmx</strong> might produce.</p>
<h2 id="heading-61-speed-and-efficiency-faster-page-loads-and-enhanced-user-experience">6.1. Speed and Efficiency: Faster page loads and enhanced user experience.</h2>
<p><strong>htmx</strong> 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.</p>
<p>Due to the lightweight nature of <strong>htmx</strong>, 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.</p>
<h2 id="heading-62-maintainability-separation-of-concerns-with-razors-code-behind-model-and-htmxs-lightweight-nature">6.2. Maintainability: Separation of concerns with Razor's code-behind model and htmx's lightweight nature.</h2>
<p><strong>Razor Pages</strong> 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.</p>
<p>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 <code>cshtml.js</code>, section <code>@Scripts</code> , setup <code>grunt</code> and <code>npm</code> aside dotnet project, but most of the time, js was simply dumped into <code>wwwroot</code> folder.</p>
<p>htmx takes razor page concept of self-contained Page with its associated logic, data model, and view and adds interactivity there but keeps it <strong>clean and maintainable</strong>.</p>
<h2 id="heading-63-simplified-development-workflow">6.3. Simplified Development Workflow</h2>
<p>By merging <strong>Razor Pages</strong> and <strong>htmx</strong>, developers can enjoy a more streamlined development process. The inherent structure of <strong>Razor Pages</strong>, which emphasizes a clear delineation between view and logic, combined with the straightforward integration of <strong>htmx</strong>, reduces the learning curve for new team members.</p>
<p>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.</p>
<h1 id="heading-7-potential-challenges-and-solutions">7. Potential Challenges and Solutions</h1>
<p>While the benefits of this integration are numerous, like all technologies, it comes with its set of challenges.</p>
<h2 id="heading-71-antiforgery-token">7.1. Antiforgery Token</h2>
<p>Running the code below to update data will result in the error: <code>Response Status Error Code 400 from /click-to-edit?handler=Edit</code></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">"@Url.Page("</span><span class="hljs-attr">ClickToEdit</span>", "<span class="hljs-attr">Edit</span>")" 
        <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span> 
        <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span>&gt;</span> 
   <span class="hljs-comment">&lt;!-- form body --&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
</code></pre>
<p>This is because any POST or PUT request to the razor page requires AntiforgeryToken to prevent CSRF attacks.</p>
<blockquote>
<p>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.</p>
<p>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.</p>
<p>The server validates the token with every request; if they don't match, the request is denied, potentially flagging it as a CSRF attempt.</p>
</blockquote>
<p>Antiforgery Token check could be disabled by applying <code>[IgnoreAntiforgeryToken(Order = 1001)]</code> attribute on the page model, but that is a security risk.</p>
<p>You could manually expand forms and add anti-forgery token with a helper method <code>@Html.AntiForgeryToken()</code>. This would provide tokens as a hidden field and include it into form data.</p>
<p>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 <code>hx-headers</code> attribute.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">hx-put</span>=<span class="hljs-string">"@Url.Page("</span><span class="hljs-attr">ClickToEdit</span>", "<span class="hljs-attr">Edit</span>")" 
    <span class="hljs-attr">hx-headers</span>=<span class="hljs-string">'{"RequestVerificationToken": "@Model.AntiforgeryToken"}'</span>
    <span class="hljs-attr">hx-target</span>=<span class="hljs-string">"this"</span> <span class="hljs-attr">hx-swap</span>=<span class="hljs-string">"outerHTML"</span> &gt;</span>
</code></pre>
<p>For forms, it is better to use the helper method. But to add tokens to other HTML elements, such as <code>&lt;button&gt;</code> or <code>&lt;input&gt;</code> I created a helper tag class. It would generate <code>hx-headers</code> tag and set token value when I add an attribute <code>antiforgery="true"</code> to element.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">HtmlTargetElement(<span class="hljs-meta-string">"input"</span>, Attributes = AntiForgeryAttributeName)</span>]
[<span class="hljs-meta">HtmlTargetElement(<span class="hljs-meta-string">"button"</span>, Attributes = AntiForgeryAttributeName)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AntiForgeryHeaderTagHelper</span> : <span class="hljs-title">TagHelper</span>
{
    [<span class="hljs-meta">HtmlAttributeNotBound</span>]
    [<span class="hljs-meta">ViewContext</span>]
    <span class="hljs-keyword">public</span> ViewContext ViewContext { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">HtmlAttributeName(AntiForgeryAttributeName)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> AntiForgery { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> AntiForgeryAttributeName = <span class="hljs-string">"antiforgery"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IAntiforgery _antiforgery;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">AntiForgeryHeaderTagHelper</span>(<span class="hljs-params">IAntiforgery antiforgery</span>)</span>
    {
        _antiforgery = antiforgery;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Process</span>(<span class="hljs-params">TagHelperContext context, TagHelperOutput output</span>)</span>
    {
        <span class="hljs-keyword">if</span> (AntiForgery)
        {
            <span class="hljs-keyword">var</span> token = _antiforgery.GetAndStoreTokens(ViewContext.HttpContext).RequestToken;
            <span class="hljs-keyword">var</span> currentHeaderValue = output.Attributes[<span class="hljs-string">"hx-headers"</span>]?.Value.ToString();
            <span class="hljs-keyword">var</span> newHeaderValue = <span class="hljs-string">$"\"RequestVerificationToken\": \"<span class="hljs-subst">{token}</span>\""</span>;

            <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(currentHeaderValue))
            {
                output.Attributes.SetAttribute(<span class="hljs-string">"hx-headers"</span>, newHeaderValue);
            }
            <span class="hljs-keyword">else</span>
            {
                <span class="hljs-comment">// Append the anti-forgery token to existing hx-headers</span>
                newHeaderValue = <span class="hljs-string">$"<span class="hljs-subst">{currentHeaderValue}</span>, <span class="hljs-subst">{newHeaderValue}</span>"</span>;
                output.Attributes.SetAttribute(<span class="hljs-string">"hx-headers"</span>, newHeaderValue);
            }
        }
    }
}
</code></pre>
<h2 id="heading-72-validation">7.2. Validation</h2>
<p>MVC and DataAttributes provide a lot of helpers to generate validation. Unfortunately, most of it is designed for <code>jquery.validate.js</code> and <code>jquery.validate.unobtrusive.js</code>.</p>
<p>On the other hand, <strong>htmx</strong> is designed to utilize HTML5 validation.</p>
<p>Of course, we could disable client-side validation and write everything by hand, but that sounds annoying.</p>
<pre><code class="lang-csharp">services.AddRazorPages()
    .AddViewOptions(options =&gt;
    {
        options.HtmlHelperOptions.ClientValidationEnabled = <span class="hljs-literal">false</span>;
    });
</code></pre>
<p>At the moment I didn't found a good solution for that. I could see a several options here:</p>
<ul>
<li><p>Link together HTML 5 and MVC-generated validation, by writing custom js code</p>
</li>
<li><p>Link together MVC-generated validation and htmx, but keep using <code>jquery.validate</code></p>
</li>
</ul>
<p>If you know a solution to that problem, feel free to ping me.</p>
<h1 id="heading-8-conclusion">8. Conclusion</h1>
<p>To sum it up, I tried different use cases of <strong>htmx</strong> and dotnet <strong>Razor Pages</strong> during my tests. In this article, I presented the history of <strong>htmx</strong> and <strong>Razor Pages</strong>, and show you how they can be integrated seamlessly.</p>
<p>I was impressed by how a technology from 2009, <strong>partials</strong>, integrates so well with <strong>htmx</strong>. It also fits well with <strong>Razor Pages</strong> and <strong>pico.css</strong>, which I often use for side projects. I only have positive impressions here:</p>
<ul>
<li><p>It works with razor and MVC partials perfectly</p>
</li>
<li><p>It's a good fit with semantic HTML CSS libraries</p>
</li>
<li><p>It's two times smaller than jQuery</p>
</li>
<li><p>It's straightforward to use</p>
</li>
</ul>
<p>Regarding the negative aspects</p>
<ul>
<li><p>There is no IntelliSense support of htmx tags in Visual Studio yet (there is for Visual Studio Code)</p>
</li>
<li><p>Need to figure out validation to reuse all that helpers <strong>Razor Pages</strong> provides</p>
</li>
<li><p>Some kind of <a target="_blank" href="https://htmx.org/essays/template-fragments/">fragments</a> would be nice</p>
</li>
<li><p><strong>htmx</strong> might not be accepted by enterprises where dotnet is most used</p>
</li>
</ul>
<p>That would be all. Once again, the source code of my tests is available on <a target="_blank" href="https://github.com/xakpc/RazorHtmx">github</a>, the demo project <a target="_blank" href="https://razorhtmx.azurewebsites.net/">deployed on Azure</a>, and if you have any questions, feel free to ping <a target="_blank" href="https://x.com/xakpc">me</a> on <s>Twitter</s>, sorry, on 𝕏.</p>
<h1 id="heading-9-further-readingreferences">9. Further Reading/References</h1>
<p>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).</p>
<p>Some useful resources</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/razor-pages/?view=aspnetcore-7.0&amp;tabs=visual-studio">Microsoft Razor Pages documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.learnrazorpages.com/">Learn Razor Pages</a> by Mike Brind</p>
</li>
<li><p><a target="_blank" href="https://htmx.org/docs/">htmx Documentation</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to create Sitemap.xml for ASP.net Core Razor Pages]]></title><description><![CDATA[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 i...]]></description><link>https://xakpc.info/how-to-create-sitemapxml-for-aspnet-core-razor-pages</link><guid isPermaLink="true">https://xakpc.info/how-to-create-sitemapxml-for-aspnet-core-razor-pages</guid><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[razorpages]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 15 Aug 2023 21:18:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1692134099813/7df5bd63-351a-4a29-a18f-ed418803adfe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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.</p>
<blockquote>
<p>The following information is related to Microsoft.AspNetCore.App version 7.0.7.</p>
</blockquote>
<p>In general, creating a <code>sitemap.xml</code> 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.</p>
<h2 id="heading-step-1-understand-the-structure-of-sitemapxml"><strong>Step 1: Understand the Structure of</strong> <code>sitemap.xml</code></h2>
<p>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).</p>
<p>Here is a very basic XML sitemap that includes the location of a single URL:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">urlset</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://www.example.com/foo.html<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2022-06-04<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">urlset</span>&gt;</span>
</code></pre>
<p>There are more parameters defined in the <a target="_blank" href="https://www.sitemaps.org/protocol.html">protocol specification</a>, but Google claims to ignore them, so I think they could be omitted:</p>
<blockquote>
<ul>
<li><p>Google ignores <code>&lt;priority&gt;</code> and <code>&lt;changefreq&gt;</code> values.</p>
</li>
<li><p>Google uses the <code>&lt;lastmod&gt;</code> value if it's consistently and verifiably (for example by comparing to the last modification of the page) accurate.</p>
</li>
</ul>
</blockquote>
<h2 id="heading-step-2-create-a-model-for-the-sitemap"><strong>Step 2: Create a Model for the Sitemap</strong></h2>
<p>First, I created a model for the sitemap node. This model will represent individual URLs, their priority, and other metadata.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapNode</span>
    {
        <span class="hljs-keyword">public</span> SitemapFrequency? Frequency { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> DateTime? LastModified { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span>? Priority { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Url { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> SitemapFrequency
    {
        Never,
        Yearly,
        Monthly,
        Weekly,
        Daily,
        Hourly,
        Always
    }
</code></pre>
<p>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.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">XmlRoot(<span class="hljs-meta-string">"urlset"</span>, Namespace = <span class="hljs-meta-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapUrlSet</span>
{
    [<span class="hljs-meta">XmlElement(<span class="hljs-meta-string">"url"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">List</span>&lt;<span class="hljs-title">SitemapNode</span>&gt; SitemapNodes</span> { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span> List&lt;SitemapNode&gt;();
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapNode</span>
{
    [<span class="hljs-meta">XmlElement(<span class="hljs-meta-string">"loc"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Url { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">XmlElement(<span class="hljs-meta-string">"lastmod"</span>)</span>]
    <span class="hljs-keyword">public</span> DateTime? LastModified { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">XmlElement(<span class="hljs-meta-string">"changefreq"</span>)</span>]
    <span class="hljs-keyword">public</span> SitemapFrequency? Frequency { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">XmlElement(<span class="hljs-meta-string">"priority"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span>? Priority { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> SitemapFrequency
{
    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"never"</span>)</span>]
    Never,

    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"yearly"</span>)</span>]
    Yearly,

    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"monthly"</span>)</span>]
    Monthly,

    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"weekly"</span>)</span>]
    Weekly,

    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"daily"</span>)</span>]
    Daily,

    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"hourly"</span>)</span>]
    Hourly,

    [<span class="hljs-meta">XmlEnum(<span class="hljs-meta-string">"always"</span>)</span>]
    Always
}
</code></pre>
<h2 id="heading-step-3-setting-up-the-method-to-generate-sitemap-nodes"><strong>Step 3: Setting Up the Method to Generate Sitemap Nodes</strong></h2>
<p>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 <code>PageLink</code> or <code>LinkGenerator</code>. I used the last one:</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapModel</span> : <span class="hljs-title">PageModel</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> LinkGenerator _linkGenerator;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">SitemapModel</span>(<span class="hljs-params">LinkGenerator linkGenerator</span>)</span>
        {
            _linkGenerator = linkGenerator;
        }

        <span class="hljs-comment">// ... rest of the code</span>
    }
</code></pre>
<p>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.</p>
<p>Method <code>GetUriByPage</code> from provides an absolute URL based on page name - handy.</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapModel</span> : <span class="hljs-title">PageModel</span>
    {   
        <span class="hljs-comment">// ... rest of the code   </span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> IReadOnlyCollection&lt;SitemapNode&gt; <span class="hljs-title">GetSitemapNodes</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> nodes = <span class="hljs-keyword">new</span> List&lt;SitemapNode&gt;
            {
                <span class="hljs-keyword">new</span>()
                {
                    Url = _linkGenerator.GetUriByPage(HttpContext, <span class="hljs-string">"/Index"</span>),
                    Priority = <span class="hljs-number">1</span>,
                },
                <span class="hljs-keyword">new</span>()
                {
                    Url = _linkGenerator.GetUriByPage(HttpContext, <span class="hljs-string">"/Tools/CreateCode"</span>),
                    Priority = <span class="hljs-number">0.9</span>
                },
                <span class="hljs-keyword">new</span>()
                {
                    Url = _linkGenerator.GetUriByPage(HttpContext, <span class="hljs-string">"/Legal/Privacy"</span>),
                    Priority = <span class="hljs-number">0.6</span>
                },
                <span class="hljs-keyword">new</span>()
                {
                    Url = _linkGenerator.GetUriByPage(HttpContext, <span class="hljs-string">"/Legal/TermsOfService"</span>),
                    Priority = <span class="hljs-number">0.6</span>
                }
            };

            <span class="hljs-keyword">foreach</span>(...)
            {
                <span class="hljs-comment">// fill nodes from blog index</span>
            }

            <span class="hljs-keyword">return</span> nodes;
        }
    }
</code></pre>
<h2 id="heading-step-4-creating-the-sitemap-page"><strong>Step 4: Creating the Sitemap Page</strong></h2>
<p>Then I added a new Razor Page <code>Sitemap.cshtml</code> that will be responsible for generating the <code>sitemap.xml</code>. 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 <code>Sitemap.cshtml</code> as an XML file.</p>
<pre><code class="lang-csharp">@page <span class="hljs-string">"/sitemap.xml"</span>
@model Xakpc.Project.Pages.SitemapModel
@{
    Layout = <span class="hljs-literal">null</span>;
    Response.ContentType = <span class="hljs-string">"text/xml"</span>;
}
&lt;?xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"UTF-8"</span> ?&gt;
@Html.Raw(Model.RawXmlData)
</code></pre>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Note <code>@page “/sitemap.xml”</code> to serve the page as sitemap</div>
</div>

<h2 id="heading-step-5-formatting-the-xml"><strong>Step 5: Formatting the XML</strong></h2>
<p>On the sitemap Razor Page model, I formatted the sitemap nodes into XML format. For that, I manually built XML file with <code>XElements</code></p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapModel</span> : <span class="hljs-title">PageModel</span>
    {   
        <span class="hljs-comment">// ... rest of the code  </span>

        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Serializes to raw XML</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetSitemapDocument</span>(<span class="hljs-params">IEnumerable&lt;SitemapNode&gt; sitemapNodes</span>)</span>
        {
            XNamespace xmlns = <span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>;
            <span class="hljs-keyword">var</span> root = <span class="hljs-keyword">new</span> XElement(xmlns + <span class="hljs-string">"urlset"</span>);

            <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> sitemapNode <span class="hljs-keyword">in</span> sitemapNodes)
            {
                <span class="hljs-keyword">var</span> urlElement = <span class="hljs-keyword">new</span> XElement(
                    xmlns + <span class="hljs-string">"url"</span>,
                    <span class="hljs-keyword">new</span> XElement(xmlns + <span class="hljs-string">"loc"</span>, Uri.EscapeUriString(sitemapNode.Url)),
                    sitemapNode.LastModified == <span class="hljs-literal">null</span> ? <span class="hljs-literal">null</span> : <span class="hljs-keyword">new</span> XElement(
                        xmlns + <span class="hljs-string">"lastmod"</span>,
                        sitemapNode.LastModified.Value.ToLocalTime().ToString(<span class="hljs-string">"yyyy-MM-ddTHH:mm:sszzz"</span>)),
                    sitemapNode.Frequency == <span class="hljs-literal">null</span> ? <span class="hljs-literal">null</span> : <span class="hljs-keyword">new</span> XElement(
                        xmlns + <span class="hljs-string">"changefreq"</span>,
                        sitemapNode.Frequency.Value.ToString().ToLowerInvariant()),
                    sitemapNode.Priority == <span class="hljs-literal">null</span> ? <span class="hljs-literal">null</span> : <span class="hljs-keyword">new</span> XElement(
                        xmlns + <span class="hljs-string">"priority"</span>,
                        sitemapNode.Priority.Value.ToString(<span class="hljs-string">"F1"</span>, CultureInfo.InvariantCulture)));
                root.Add(urlElement);
            }

            <span class="hljs-keyword">var</span> document = <span class="hljs-keyword">new</span> XDocument(root);
            <span class="hljs-keyword">return</span> document.ToString();
        }
    }
</code></pre>
<p>The other option is to use serialization (if you map classes with attributes - I skipped that part)</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GetSitemapDocument</span>(<span class="hljs-params">IEnumerable&lt;SitemapNode&gt; sitemapNodes</span>)</span>
{
    <span class="hljs-keyword">var</span> sitemapUrlSet = <span class="hljs-keyword">new</span> SitemapUrlSet { SitemapNodes = sitemapNodes.ToList() };

    <span class="hljs-keyword">var</span> xmlSerializer = <span class="hljs-keyword">new</span> XmlSerializer(<span class="hljs-keyword">typeof</span>(SitemapUrlSet));

    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> stringWriter = <span class="hljs-keyword">new</span> StringWriterUtf8();
    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> xmlTextWriter = XmlWriter.Create(stringWriter, <span class="hljs-keyword">new</span> XmlWriterSettings { Indent = <span class="hljs-literal">true</span> });

    xmlSerializer.Serialize(xmlTextWriter, sitemapUrlSet);

    <span class="hljs-keyword">return</span> stringWriter.ToString();
}
</code></pre>
<p>And all that is left is to call methods in <code>OnGet</code> method and set bound property</p>
<pre><code class="lang-csharp">    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SitemapModel</span> : <span class="hljs-title">PageModel</span>
    {   
        <span class="hljs-comment">// ... rest of the code  </span>

        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> Gets the raw XML data</span>
        <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
        [<span class="hljs-meta">BindProperty(SupportsGet = true)</span>]
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> RawXmlData { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> nodes = GetSitemapNodes();
            RawXmlData = GetSitemapDocument(nodes);
        }
    }
</code></pre>
<h2 id="heading-step-6-register-the-sitemap-with-search-engines"><strong>Step 6: Register the Sitemap with Search Engines</strong></h2>
<p>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.</p>
<ol>
<li><p><strong>Google</strong>: Use Google Search Console to submit your sitemap.</p>
</li>
<li><p><strong>Bing</strong>: Use Bing Webmaster Tools to submit your sitemap.</p>
</li>
</ol>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Final Sitemap.xml</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">urlset</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://contoso.com/<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>1.0<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://contoso.com/make-code<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.9<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://contoso.com/legal/privacy<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.6<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://contoso.com/legal/termsofservice<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.6<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
    ...
<span class="hljs-tag">&lt;/<span class="hljs-name">urlset</span>&gt;</span>
</code></pre>
<p>As you can see, setting up a <code>sitemap.xml</code> 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.</p>
]]></content:encoded></item><item><title><![CDATA[How to build Telegram Chatbots with Azure Durable Functions]]></title><description><![CDATA[First I wanted to write this article on my own, just to explore Azure Durable Functions and build something that sat on my not-good-not-terrible chatbot ideas list for years. 
And then I found out that #PlanetScaleHackathon launched the same month an...]]></description><link>https://xakpc.info/building-telegram-chatbots-with-azure-durable-functions</link><guid isPermaLink="true">https://xakpc.info/building-telegram-chatbots-with-azure-durable-functions</guid><category><![CDATA[PlanetScale]]></category><category><![CDATA[PlanetScaleHackathon]]></category><category><![CDATA[Azure]]></category><category><![CDATA[.NET]]></category><category><![CDATA[chatbot]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Sun, 31 Jul 2022 17:14:22 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659272842894/q9K6CkvLb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>First I wanted to write this article on my own, just to explore Azure Durable Functions and build something that sat on my <em>not-good-not-terrible</em> chatbot ideas list for years. </p>
<p>And then I found out that <a target="_blank" href="https://townhall.hashnode.com/planetscale-hackathon">#PlanetScaleHackathon</a> launched the same month and I decided to take a part in it. I required a database and PlanetScale looks like a new and interesting solution to play with. </p>
<p><strong>So what kind of chatbot we are building here?</strong> I believe that messenger could be the most convenient way to gather feedback in almost any scenario, but especially in the physical world. But sharing your messenger contacts with anyone will bring spam, trolls, and sometimes unwanted night calls.</p>
<p>Recently Telegram <a target="_blank" href="https://telegram.org/blog/700-million-and-premium/">became</a> one of the top-5 downloaded apps worldwide in 2022 and now has over 700 million monthly active users, so the timing is quite good.</p>
<p>It would be nice to have the ability for your customers, clients, or subscribers to contact you directly in Telegram without telegraphing your personal account to everyone. </p>
<p>In this bot scenario we would actually have two bots:</p>
<ul>
<li><strong>MasterBot</strong> will have master users who receive and answer the messages</li>
<li><strong>ClientBot</strong> will be attached to the master user and receive messages from clients to resend them to the master user.</li>
</ul>
<p>Clients will not know the master user's private account name, and masters could know client users' names if they have a username set in their telegram profile.</p>
<p>WhatsApp does this as part of its separate Business App.
So let's build a similar solution for Telegram.</p>
<h1 id="heading-what-is-a-telegram-chatbot">What is a Telegram Chatbot</h1>
<blockquote>
<p>(Chat)Bots are like small programs that run right inside Telegram. They are made by third-party developers.
Bots are safe to use. They can <strong>see your public name</strong>, <strong>username</strong>, and <strong>profile pictures</strong>, and they <strong>can see messages</strong> you send to them, that's it. They <strong>can't access</strong> your last seen <strong>status</strong> and <strong>don't see your phone number</strong> (unless you decide to give it to them yourself).</p>
</blockquote>
<h1 id="heading-what-is-a-durable-function">What is a Durable Function?</h1>
<p>The <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview?tabs=csharp">official definition</a></p>
<blockquote>
<p>Durable Functions is an extension of Azure Functions that lets you write stateful functions in a serverless compute environment.</p>
</blockquote>
<p>There are two types of durable functions: <em>orchestrator functions</em> and <em>entity functions</em>. </p>
<p>In easy words, the <strong>orchestrator function</strong> is a function that contains <strong>orchestrator</strong>, and it could be <strong>stopped and resumed at any moment</strong>. The main purpose of the orchestrator is to <strong>launch other functions</strong> (they are called activity functions) and the result could be <strong>stored</strong> in local variables, durable entities, and <strong>used in logic</strong> constructions. </p>
<p>This allows the implementation of a lot of cool patterns in a serverless environment, like function chaining for example. 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657318603543/3tpCjyIrY.png" alt="function chaining.png" class="image--center mx-auto" />
You could read more about it in <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-orchestrations?tabs=csharp">docs</a>.</p>
<p><strong>The entity function</strong> is again, a function, that allows saving, reading, and manipulating data in a serverless environment. I think it could be treated as a repository: we have data and a set of operations to manipulate it. The data is stored serialized as JSON in Azure Table Storage.</p>
<p>Entity function provides convenient <strong>class-based syntax</strong> where entities and operations are represented as classes and methods.</p>
<p>Now when we are familiar with tech, let's design ourselves a chatbot.</p>
<h1 id="heading-chatbot-design">Chatbot design</h1>
<p>Chatbots always contain some kind of state machine under the hood. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656974748106/bgppLjjy0.png?height=300" alt="example of state machine" class="image--center mx-auto" /></p>
<p>There is an entire <a target="_blank" href="https://en.wikipedia.org/wiki/Finite-state_machine">theory</a> behind that, but in simple words applied to our case: </p>
<ul>
<li>a <strong>user</strong> always is in some <strong>state</strong> (p0-p5)</li>
<li>any <strong>message</strong> a user sends to a chatbot will move him into another <strong>state</strong> (a,b,...)</li>
<li>each time the state changes, our application performs some <strong>action</strong> and returns a response <strong>message</strong> to be sent back to the user.</li>
</ul>
<p>Each state implies some logic it could do and the next state where it could navigate. To implement it in a scalable (and a bit overengineering) way we would use State <a target="_blank" href="https://refactoring.guru/design-patterns/state">design pattern</a>.</p>
<p><strong>You might ask yourself: do I need that?</strong> Well, the answer is "it depends". If you building a simple chatbot with a couple of commands, or plan to connect your chatbot to a question-answering "AI" or plan to heavily depend on some chatbot framework or <a target="_blank" href="https://www.api.chat/">engine</a> - you probably could make a several <code>ifs</code> and that would be enough. Because you should <a target="_blank" href="https://en.wikipedia.org/wiki/KISS_principle">K.I.S.S</a>. </p>
<h2 id="heading-state-machine-design">State Machine design</h2>
<p>Our state machine will be only for the <strong>MasterBot</strong>. Client bots are extremely simple: they forward messages to the master user, and forward responses back.</p>
<p>For the master bot we would need the next operations:</p>
<ul>
<li><code>/start</code> - this operation will move the master user to a <code>MainState</code></li>
<li><code>/add</code> - attach new client bot</li>
<li><code>/remove</code> - detach client bot</li>
<li><em><code>/subscribe</code> - update to pro-plan (we want to pay for hosting somehow)</em></li>
<li><em><code>/unsubscribe</code> - mandatory</em></li>
<li><code>/qr</code> - generate QR code with client bot invite</li>
<li><code>Reply</code> - not a command, but an actual reply to a client message, it requires a state.</li>
<li><code>Ban</code> - is not a command and does not require a state</li>
</ul>
<p>Not everything here requires a state, a lot of operations have only one step and could be done by going from <code>MainState</code> to <code>MainState</code>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659212039155/J4hsHpheR.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Each arrow would have an associated <code>Activity Function</code> with it, so each time we change the state of the master user we will execute some action, then generate and send a response back.</p>
<p>The state machine will not be doing any hard work by itself. It will validate input, execute steps and return a name of <code>Activity Function</code> to be executed by the orchestrator.</p>
<h2 id="heading-state-machine-implementation">State Machine Implementation</h2>
<p>To implement our state machine in the serverless environment we need three main components:</p>
<ol>
<li>We need a <code>Context</code> -  it defines the interface to clients and maintains a reference to an instance of a State subclass, which represents the current state of the User.</li>
<li>We need our states - all of them will be inherited from the base <code>State</code> abstract class and implement a do-step method.</li>
<li>We need a <em>Durable Entity</em> to store the current user state.</li>
</ol>
<p>But it's not that easy with Durable Entity. As I mentioned earlier it is stored serialized as JSON by the <code>Newtonsoft.JSON</code> library. And because we want to use inheritance and we want the State to have reference to a user Context - it really messes up serialization.</p>
<p>There is a way to set up <code>Newtonsoft.JSON</code> to store type names and deserialize them into concrete classes by applying <code>TypeNameHandling = TypeNameHandling.All</code> setting. For durable functions it's <code>TypeNameHandling.None</code> by default.</p>
<p>To change this setting we need to override <code>IMessageSerializerSettingsFactory</code> in the <code>Startup</code> class like that</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Startup</span> : <span class="hljs-title">FunctionsStartup</span>
{       
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params">IFunctionsHostBuilder builder</span>)</span>
    {
        ...
        builder.Services.AddSingleton&lt;IMessageSerializerSettingsFactory, CustomMessageSerializerSettingsFactory&gt;();
    }

    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CustomMessageSerializerSettingsFactory</span> : <span class="hljs-title">IMessageSerializerSettingsFactory</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> JsonSerializerSettings <span class="hljs-title">CreateJsonSerializerSettings</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> JsonSerializerSettings
            {
                TypeNameHandling = TypeNameHandling.All,
                DateParseHandling = DateParseHandling.None,         
            };
        }
    }
}
</code></pre>
<p>Unfortunately, that didn't work for me for some reason. I decide to go another way and add two extra components</p>
<ol>
<li><code>States</code> enum to store </li>
<li><code>StateFactory</code> to rebuild concrete state class from stored enum value.</li>
</ol>
<h3 id="heading-1-context-class">1. Context class</h3>
<p>First of all, we need a user context. Context holds the current user state, allows perform transition to other states, and has a single method <code>GetAction</code>.</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Context</span>
{
    <span class="hljs-comment">// A reference to the current state of the Context.</span>
    <span class="hljs-keyword">public</span> State State { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Context</span>(<span class="hljs-params">State state</span>)</span>
    {
        TransitionTo(state);
    }

    <span class="hljs-comment">// The Context allows changing the State object at runtime.</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TransitionTo</span>(<span class="hljs-params">State state</span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">$"Context: Transition to <span class="hljs-subst">{state.GetType().Name}</span>."</span>);
        State = state;
        State.SetContext(<span class="hljs-keyword">this</span>);
    }

    <span class="hljs-comment">// The Context delegates part of its behavior to the current State</span>
    <span class="hljs-comment">// object.</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> StateAction <span class="hljs-title">GetAction</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageText</span>)</span> =&gt; State?.GetAction(messageText);
}
</code></pre>
<h3 id="heading-2-base-state">2. Base State</h3>
<p>Base State classes require each inherited class to implement which <code>States</code> it represents. It is required for the factory to reconstruct user context based on Durable Entity which stores user state.</p>
<pre><code class="lang-C#"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> The base State class declares methods that all Concrete State should implement</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">State</span>
{
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> Property should be implemented in concrete state for our factory to construct concrete type of state</span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;remarks&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> It could be Type if we want to use reflection</span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/remarks&gt;</span></span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> States Type { <span class="hljs-keyword">get</span>; }

    <span class="hljs-keyword">protected</span> Context _context;

    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> a backreference to the Context object, associated with the State. </span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> This backreference can be used by States to transition the Context to another State.</span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetContext</span>(<span class="hljs-params">Context context</span>)</span>
    {
        _context = context;
    }

    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> Get Activity Function name</span>
    <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> StateAction <span class="hljs-title">GetAction</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageText</span>)</span>;
}
</code></pre>
<p>We also create a <code>StateAction</code> record as a result of the <code>GetAction</code> method. This record returns to the orchestrator what needs to be done next: either just send the response, or perform an activity, or maybe both.</p>
<pre><code class="lang-C#"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">StateAction</span>(<span class="hljs-params">MasterBotResponse Response = <span class="hljs-keyword">default</span>, <span class="hljs-keyword">string</span> Activity = <span class="hljs-keyword">default</span></span>)</span>;
</code></pre>
<p>I will not show an implementation of all states, just a couple of classes to understand the pattern. </p>
<h4 id="heading-main-state">Main state</h4>
<p>This would be the starting point for every user. Based on the input of this state our chatbot will provide one or another answer. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659264478461/kCydegEPN.png" alt="image.png" class="image--center mx-auto" /></p>
<p>If input a valid command <code>MainState</code> would return either message or activity to launch. </p>
<p>If an input is an unknown command the fallback message is sent back to a user.</p>
<pre><code class="lang-C#"><span class="hljs-keyword">class</span> <span class="hljs-title">MainState</span> : <span class="hljs-title">State</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> States Type =&gt; States.Main;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> StateAction <span class="hljs-title">GetAction</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageText</span>)</span>
    {
        <span class="hljs-keyword">switch</span> (messageText)
        {
            <span class="hljs-keyword">case</span> <span class="hljs-string">"/start"</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Response: <span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">@"Welcome to a Feedbacks Master Bot"</span>), Activity: <span class="hljs-keyword">nameof</span>(MasterBotActivityFunctions.ActivityRegisterMasterUser));
            <span class="hljs-keyword">case</span> <span class="hljs-string">"/add"</span>:
                _context.TransitionTo(<span class="hljs-keyword">new</span> AddState());
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Response: <span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">"Create client-facing chatbot through @BotFather and send me generated UserToken"</span>));
            <span class="hljs-keyword">case</span> <span class="hljs-string">"/remove"</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Activity: <span class="hljs-keyword">nameof</span>(MasterBotActivityFunctions.ActivityDoRemove));
            <span class="hljs-keyword">case</span> <span class="hljs-string">"/qr"</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Activity: <span class="hljs-keyword">nameof</span>(MasterBotActivityFunctions.ActivityCreateQrCode));
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Response: <span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">"Unknown command, press **Menu** button for list of commands"</span>, Markdown: <span class="hljs-literal">true</span>));
    }
</code></pre>
<h4 id="heading-workflows">Workflows</h4>
<p>Based on this design it's very easy to combine several steps (states) into a workflow. Imagine a user who wants to delete an item. The flow looks like that</p>
<ol>
<li>Send <code>/add</code> command</li>
<li>Bot prompt to send bot token</li>
<li>Send token</li>
<li>Validate token, perform the action</li>
<li>Back to the <code>main</code></li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659264616588/R6c5q0snC.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Code of the state might look something like that</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AddState</span> : <span class="hljs-title">State</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> States Type =&gt; States.Add;

    <span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">IsValidToken</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageText</span>)</span>
    {
        <span class="hljs-keyword">return</span> Regex.IsMatch(messageText, <span class="hljs-string">"[0-9]{9}:[a-zA-Z0-9_-]{35}"</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> StateAction <span class="hljs-title">GetAction</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageText</span>)</span>
    {
        <span class="hljs-keyword">if</span> (messageText.Equals(<span class="hljs-string">"/cancel"</span>, StringComparison.OrdinalIgnoreCase))
        {
            _context.TransitionTo(<span class="hljs-keyword">new</span> MainState());
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(<span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">"Cancelled"</span>));
        }

        <span class="hljs-keyword">if</span> (IsValidToken(messageText))
        {
            _context.TransitionTo(<span class="hljs-keyword">new</span> MainState());
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Activity: <span class="hljs-keyword">nameof</span>(MasterBotActivityFunctions.ActivityDoAdd));
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(<span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">"Invalid input, try again or /cancel"</span>));
    }
}
</code></pre>
<p>Users could send the <code>/cancel</code> command to undo the adding.</p>
<h4 id="heading-responding-state">Responding State</h4>
<p>If other states are triggered by a sent message, this state is triggered by replying. When a user responds to any message it will be forced to <code>RespondingState</code>. We also save a <code>MessageReferenceId</code> from our storage of received messages to use later in the activity function.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659264694065/Oc5pOPrbb.png" alt="image.png" class="image--center mx-auto" /></p>
<p>Here is what we should add to <code>MasterBotWebhook</code> to handle replies:</p>
<pre><code class="lang-C#"><span class="hljs-comment">// the message is a reply, so we process it into reply state</span>
<span class="hljs-keyword">if</span> (update.Message.ReplyToMessage != <span class="hljs-literal">null</span>)
{
    <span class="hljs-keyword">var</span> reply = update.Message.ReplyToMessage;
    <span class="hljs-keyword">var</span> messageReference = <span class="hljs-keyword">await</span> _database.GetClientMessageReference(messageId: reply.MessageId);

    <span class="hljs-keyword">if</span> (messageReference == <span class="hljs-literal">null</span>)
    {
        log.LogError(<span class="hljs-string">"Message to answer not found"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> OkResult();
    }

    <span class="hljs-comment">// change user to responding state</span>
    <span class="hljs-keyword">var</span> userEntity = <span class="hljs-keyword">new</span> EntityId(<span class="hljs-keyword">nameof</span>(DurableUserState), update.Message.From.Id.ToString());
    <span class="hljs-keyword">await</span> durableClient.SignalEntityAsync&lt;IDurableUserState&gt;(userEntity, us =&gt; us.SetUserState(States.Responding));

    <span class="hljs-comment">// store original message id                    </span>
    <span class="hljs-keyword">await</span> durableClient.SignalEntityAsync&lt;IDurableUserState&gt;(userEntity, us =&gt; us.SetReplyToMessageId(messageReference.Id));
}
</code></pre>
<p>After that, we start the orchestrator function as usual.</p>
<p>In <code>RespondingState</code> we handle <code>/cancel</code>, we handle sending other command and we launch <code>ActivityResponse</code></p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">RespondingState</span> : <span class="hljs-title">State</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> States Type =&gt; States.Responding;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> StateAction <span class="hljs-title">GetAction</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> messageText</span>)</span>
    {
        <span class="hljs-keyword">if</span> (messageText.Equals(<span class="hljs-string">"/cancel"</span>, StringComparison.OrdinalIgnoreCase))
        {
            _context.TransitionTo(<span class="hljs-keyword">new</span> MainState());
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(<span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">"Cancelled"</span>));
        }          

        <span class="hljs-keyword">var</span> ms = <span class="hljs-keyword">new</span> MainState();
        _context.TransitionTo(ms);

        <span class="hljs-keyword">if</span> (messageText.StartsWith(<span class="hljs-string">"/"</span>))
        {
            <span class="hljs-keyword">return</span> ms.GetAction(messageText);
        }

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> StateAction(Activity: <span class="hljs-keyword">nameof</span>(MasterBotActivityFunctions.ActivityResponse));
    }
}
</code></pre>
<p>The <code>ActivityResponse</code> function </p>
<pre><code class="lang-C#">[<span class="hljs-meta">FunctionName(nameof(ActivityResponse))</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;MasterBotResponse&gt; <span class="hljs-title">ActivityResponse</span>(<span class="hljs-params">[ActivityTrigger] Message message, [DurableClient] IDurableEntityClient client, ILogger log</span>)</span>
{
    <span class="hljs-comment">// update message (icon)</span>
    <span class="hljs-keyword">await</span> _telegramService.SetAnsweredAsync(message); <span class="hljs-comment">// todo not working</span>

    <span class="hljs-comment">// get client chat messageRef and botToken</span>
    <span class="hljs-keyword">var</span> entityId = <span class="hljs-keyword">new</span> EntityId(<span class="hljs-keyword">nameof</span>(DurableUserState), message.From.Id.ToString());
    <span class="hljs-keyword">var</span> userState = <span class="hljs-keyword">await</span> client.ReadEntityStateAsync&lt;DurableUserState&gt;(entityId);
    <span class="hljs-keyword">var</span> messageRef = <span class="hljs-keyword">await</span> _database.GetClientMessageReference(messageReferenceId: userState.EntityState.MessageReferenceId);
    <span class="hljs-keyword">var</span> botToken = <span class="hljs-keyword">await</span> _database.GetClientBotToken(messageRef.ClientBotChatId);

    <span class="hljs-comment">// send responce to a client chat</span>
    <span class="hljs-keyword">await</span> _telegramService.ResponseAsync(<span class="hljs-keyword">new</span> MasterBotResponse(message.Text, messageRef.OriginalFromId), botToken);

    <span class="hljs-comment">// no answer needed</span>
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">default</span>;
}
</code></pre>
<h3 id="heading-3-durable-user-state-implementation">3. Durable User State Implementation</h3>
<p>This is the place where we use the full power of durable function. On the call, our function will execute the <code>GetAction</code> method of the current state and will navigate to the next state by sending a fire-and-forget signal to itself.</p>
<p>A durable user state is defined by Interface <code>IDurableUserState</code>.</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IDurableUserState</span>
{
    <span class="hljs-function">Task&lt;States&gt; <span class="hljs-title">GetUserState</span>(<span class="hljs-params"></span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">SetUserState</span>(<span class="hljs-params">States state</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">SetReplyToMessageId</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> messageReferenceId</span>)</span>;
}
</code></pre>
<p>This allows to access Durable Entity in a safe way:</p>
<pre><code class="lang-C#"><span class="hljs-comment">// get user context (user state) from durable entity</span>
<span class="hljs-keyword">var</span> userState = <span class="hljs-keyword">await</span> userProxy.GetUserState();
...
<span class="hljs-comment">// save new state            </span>
userProxy.SetUserState(userContext.State.Type);
</code></pre>
<p>And this is how it's stored in Azure Storage</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659265193810/5TKkonEmw.png" alt="image.png" class="image--center mx-auto" /></p>
<h3 id="heading-4-states-enum">4. States enum</h3>
<p>Our states enum currently is very small and required only for <code>ContextFactory</code></p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> States
{
    Main,
    Add,
    Responding
}
</code></pre>
<h3 id="heading-5-context-factory">5. Context Factory</h3>
<p>Context Factory is required for restoring stored user State enum value to a State object. It simplifies serialization and deserialization but made code a bit bulkier.</p>
<p>Note that by default we put the user into a <code>Main</code> state.</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ContextFactory</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Context <span class="hljs-title">RestoreContext</span>(<span class="hljs-params">States userState</span>)</span>
    {
        <span class="hljs-keyword">var</span> state = RestoreState(userState) ?? RestoreState(States.Main);
        <span class="hljs-keyword">var</span> context = <span class="hljs-keyword">new</span> Context(state);
        <span class="hljs-keyword">return</span> context;            
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> State <span class="hljs-title">RestoreState</span>(<span class="hljs-params">States state</span>)</span>
    {
        <span class="hljs-keyword">switch</span> (state)
        {
            <span class="hljs-keyword">case</span> States.Main:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> MainState();
            <span class="hljs-keyword">case</span> States.Add:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AddState();
            <span class="hljs-keyword">case</span> States.Responding:
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> RespondingState();
            <span class="hljs-keyword">default</span>:
                <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
        }
    }
}
</code></pre>
<h1 id="heading-functions-design-and-implementation">Functions design and implementation</h1>
<p>Let's design our functions</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659265624274/E8VwKtyxO.png" alt="image.png" class="image--center mx-auto" /></p>
<p>The flow goes like this </p>
<p>1) The <code>HTTP-trigger</code> function accepts the telegram webhook and launches the orchestrator function</p>
<pre><code class="lang-C#">[<span class="hljs-meta">FunctionName(<span class="hljs-meta-string">"FeedbackBotsWeebhook"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">HttpStart</span>(<span class="hljs-params">
    [HttpTrigger(AuthorizationLevel.Anonymous, <span class="hljs-string">"get"</span>, <span class="hljs-string">"post"</span></span>)] HttpRequest req,
    [DurableClient] IDurableOrchestrationClient starter,
    ILogger log)</span>
{
    log.LogInformation(<span class="hljs-string">"C# HTTP trigger function processed a webhook."</span>);

    <span class="hljs-comment">// Function input comes from the request content.</span>
    Message message = <span class="hljs-keyword">await</span> GetMessageFrom(req);

    <span class="hljs-comment">// Start MessageProcessFunctions Orchestration function</span>
    <span class="hljs-keyword">string</span> instanceId = <span class="hljs-keyword">await</span> starter.StartNewAsync(MessageProcessFunctions, message);
    log.LogInformation(<span class="hljs-string">$"Started orchestration with ID = '<span class="hljs-subst">{instanceId}</span>'."</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> OkResult();
}
</code></pre>
<p>2) Orchestration function process incoming message in this way</p>
<ul>
<li>Get the current User Context from the Durable Entity associated with it by <code>ChatId</code></li>
<li>Feed a Message to the current state, get an Action to execute</li>
<li>Execute the Action Function, which will provide a response</li>
<li>Send the response</li>
</ul>
<pre><code class="lang-C#">[<span class="hljs-meta">FunctionName(MessageProcessFunctions)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">RunOrchestrator</span>(<span class="hljs-params">[OrchestrationTrigger] IDurableOrchestrationContext context</span>)</span>
{
    <span class="hljs-keyword">var</span> message = context.GetInput&lt;Message&gt;();

    <span class="hljs-comment">// create unique id of DurableUserState based on user id</span>
    <span class="hljs-keyword">var</span> userEntity = <span class="hljs-keyword">new</span> EntityId(<span class="hljs-keyword">nameof</span>(DurableUserState), message.From.Id.ToString());
    <span class="hljs-keyword">var</span> userProxy = context.CreateEntityProxy&lt;IDurableUserState&gt;(userEntity);

    <span class="hljs-comment">// get user context (user state) from durable entity</span>
    <span class="hljs-keyword">var</span> userState = <span class="hljs-keyword">await</span> userProxy.GetUserState();

    <span class="hljs-comment">// restore context</span>
    <span class="hljs-keyword">var</span> userContext = contextFactory.RestoreContext(userState);

    <span class="hljs-comment">// do FSM step and get action to execute</span>
    <span class="hljs-keyword">var</span> action = userContext.GetAction(message.Text);

    <span class="hljs-comment">// save new state            </span>
    userProxy.SetUserState(userContext.State.Type);

    <span class="hljs-comment">// execute action and get response</span>
    <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> context.CallActivityAsync&lt;MasterBotResponse&gt;(action, message);

    <span class="hljs-comment">// send response</span>
    <span class="hljs-keyword">await</span> context.CallActivityAsync(SendResponse, response);
}
</code></pre>
<p>It's important to understand how the orchestrator will process this code. You can read all technical details <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-orchestrations?tabs=csharp#reliability">here</a>, but in easy words:</p>
<ol>
<li>The function executes until it meets <code>await</code> and then will be stopped, and another function (activity or entity) will be launched. <em>If you have a good understanding of the <code>async-await</code> pattern, this is very similar to it.</em></li>
<li>After the other function is finished the orchestrator wakes up and <strong>re-executes the entire function from the start to rebuild the local state</strong>.</li>
</ol>
<blockquote>
<p>During the replay, if the code tries to call a function (or do any other async work), the Durable Task Framework consults the execution history of the current orchestration. If it finds that the activity function has already executed and yielded a result, <strong>it replays that function's result</strong> and the orchestrator code continues to run.</p>
</blockquote>
<p>With this in mind, here how the function calls look like:</p>
<pre><code>[2022-07-22T00:05:23.459Z] Executing 'FeedbackBotsWeebhook' (Reason='This function was programmatically called via the host APIs.', Id=69435207-90fa-4da5-97b5-d15d2aa2c4fb)
[2022-07-22T00:05:23.472Z] C<span class="hljs-comment"># HTTP trigger function processed a webhook.</span>
[2022-07-22T00:05:23.559Z] Got message: User sent message 53 to chat ... at 22.07.2022 0:05:23. It is a reply to message  and has 1 message entities.
[2022-07-22T00:05:23.682Z] Started orchestration <span class="hljs-keyword">with</span> <span class="hljs-keyword">ID</span> = <span class="hljs-string">'7eb2fffba7a54e979c7cf05666812f28'</span>.
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">23.698</span>Z] Executed <span class="hljs-string">'FeedbackBotsWeebhook'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">69435207</span><span class="hljs-number">-90</span>fa<span class="hljs-number">-4</span>da5<span class="hljs-number">-97</span>b5-d15d2aa2c4fb, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">270</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">23.813</span>Z] Executing <span class="hljs-string">'MessageProcessFunctions'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=f04ab79e<span class="hljs-number">-8</span>cf3<span class="hljs-number">-42</span>cf-a38b<span class="hljs-number">-99</span>ac7ce0b594)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">23.892</span>Z] Executed <span class="hljs-string">'MessageProcessFunctions'</span> (Succeeded, <span class="hljs-keyword">Id</span>=f04ab79e<span class="hljs-number">-8</span>cf3<span class="hljs-number">-42</span>cf-a38b<span class="hljs-number">-99</span>ac7ce0b594, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">92</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">24.000</span>Z] Executing <span class="hljs-string">'DurableUserState'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=<span class="hljs-number">46997e18</span><span class="hljs-number">-95</span>a9<span class="hljs-number">-4523</span>-af0c<span class="hljs-number">-3</span>fc029a17b8c)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">24.045</span>Z] Executed <span class="hljs-string">'DurableUserState'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">46997e18</span><span class="hljs-number">-95</span>a9<span class="hljs-number">-4523</span>-af0c<span class="hljs-number">-3</span>fc029a17b8c, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">45</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">24.089</span>Z] Executing <span class="hljs-string">'MessageProcessFunctions'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=<span class="hljs-number">6308</span>c902<span class="hljs-number">-06</span>eb<span class="hljs-number">-4</span>c25<span class="hljs-number">-953e-2</span>a95690d39a5)
<span class="hljs-keyword">Context</span>: Transition <span class="hljs-keyword">to</span> MainState.
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">28.187</span>Z] Executed <span class="hljs-string">'MessageProcessFunctions'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">6308</span>c902<span class="hljs-number">-06</span>eb<span class="hljs-number">-4</span>c25<span class="hljs-number">-953e-2</span>a95690d39a5, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">4098</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">28.232</span>Z] Executing <span class="hljs-string">'DurableUserState'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=<span class="hljs-number">6</span>d918bbe-f647<span class="hljs-number">-4</span>c6a-bb22-b8c185512922)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">28.241</span>Z] Executed <span class="hljs-string">'DurableUserState'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">6</span>d918bbe-f647<span class="hljs-number">-4</span>c6a-bb22-b8c185512922, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">9</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">28.254</span>Z] Executing <span class="hljs-string">'ActivityWelcome'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=ca71be23<span class="hljs-number">-192</span>c<span class="hljs-number">-4881</span><span class="hljs-number">-8483</span><span class="hljs-number">-84316492425</span>f)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">28.264</span>Z] Executed <span class="hljs-string">'ActivityWelcome'</span> (Succeeded, <span class="hljs-keyword">Id</span>=ca71be23<span class="hljs-number">-192</span>c<span class="hljs-number">-4881</span><span class="hljs-number">-8483</span><span class="hljs-number">-84316492425</span>f, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">14</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">28.313</span>Z] Executing <span class="hljs-string">'MessageProcessFunctions'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=<span class="hljs-number">64</span>f4b18d<span class="hljs-number">-746</span>f<span class="hljs-number">-48</span>ef-a039-e309be39b63e)
<span class="hljs-keyword">Context</span>: Transition <span class="hljs-keyword">to</span> MainState.
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">33.383</span>Z] Executed <span class="hljs-string">'MessageProcessFunctions'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">64</span>f4b18d<span class="hljs-number">-746</span>f<span class="hljs-number">-48</span>ef-a039-e309be39b63e, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">5069</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">33.417</span>Z] Executing <span class="hljs-string">'ActivitySendResponse'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=<span class="hljs-number">3</span>da5cf30-be33<span class="hljs-number">-463</span>f<span class="hljs-number">-804</span>c<span class="hljs-number">-36791</span>a791a1e)
Feedback Bots <span class="hljs-keyword">Master</span> sent message <span class="hljs-number">54</span> <span class="hljs-keyword">to</span> chat ... <span class="hljs-keyword">at</span> <span class="hljs-number">22.07</span><span class="hljs-number">.2022</span> <span class="hljs-number">0</span>:<span class="hljs-number">05</span>:<span class="hljs-number">34.</span> It <span class="hljs-keyword">is</span> a reply <span class="hljs-keyword">to</span> message  <span class="hljs-keyword">and</span> has  message entities.
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">33.795</span>Z] Executed <span class="hljs-string">'ActivitySendResponse'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">3</span>da5cf30-be33<span class="hljs-number">-463</span>f<span class="hljs-number">-804</span>c<span class="hljs-number">-36791</span>a791a1e, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">380</span>ms)
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">33.832</span>Z] Executing <span class="hljs-string">'MessageProcessFunctions'</span> (Reason=<span class="hljs-string">'(null)'</span>, <span class="hljs-keyword">Id</span>=<span class="hljs-number">23177e66</span><span class="hljs-number">-1113</span><span class="hljs-number">-43</span>c7<span class="hljs-number">-9</span>a56<span class="hljs-number">-8</span>a8f3391083c)
<span class="hljs-keyword">Context</span>: Transition <span class="hljs-keyword">to</span> MainState.
[<span class="hljs-number">2022</span><span class="hljs-number">-07</span><span class="hljs-number">-22</span>T00:<span class="hljs-number">05</span>:<span class="hljs-number">33.845</span>Z] Executed <span class="hljs-string">'MessageProcessFunctions'</span> (Succeeded, <span class="hljs-keyword">Id</span>=<span class="hljs-number">23177e66</span><span class="hljs-number">-1113</span><span class="hljs-number">-43</span>c7<span class="hljs-number">-9</span>a56<span class="hljs-number">-8</span>a8f3391083c, <span class="hljs-keyword">Duration</span>=<span class="hljs-number">13</span>ms)
</code></pre><p>Note that message from restoring context <code>Context: Transition to MainState.</code> was repeated 3 times here.  That's because <code>MessageProcessFunctions</code> was relaunched 3 times. This is why the orchestrator function code must be <strong>deterministic</strong> - always returns the same value given the same input, no matter when or how often it's called.</p>
<h2 id="heading-activity-functions">Activity Functions</h2>
<p>For simplicity, I combined all state's activity functions under a single class, except <code>ActivitySendResponse</code>. Activity functions are a place where all hard work is done. They are designed to consume Telegram messages, process them in some way, and return responses.</p>
<p>For example <code>DoAdd</code> function read client input, extracts token from it, and attach a new chatbot.</p>
<pre><code class="lang-C#">[<span class="hljs-meta">FunctionName(nameof(ActivityDoAdd))</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;MasterBotResponse&gt; <span class="hljs-title">ActivityDoAdd</span>(<span class="hljs-params">[ActivityTrigger] Message message, ILogger log</span>)</span>
{
    log.LogInformation(<span class="hljs-string">$"Adding client bot for <span class="hljs-subst">{message.From.Id}</span>"</span>);
    <span class="hljs-keyword">var</span> botToken = Regex.Match(message.Text, <span class="hljs-string">"[0-9]+:[a-zA-Z0-9_-]{35}"</span>).Value;
    <span class="hljs-keyword">var</span> bot = <span class="hljs-keyword">await</span> _telegramService.GetBot(botToken);

    <span class="hljs-keyword">await</span> _database.AddUserTokenAsync(message.From.Id, botToken, bot.Id, bot.Username);
    <span class="hljs-keyword">await</span> _telegramService.Setup(message.From.Id, botToken);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">$"Chatbot <span class="hljs-subst">{bot.Username}</span> attached as a client bot"</span>);
}
</code></pre>
<p>And <code>DoRemove</code> do the opposite</p>
<pre><code class="lang-C#">[<span class="hljs-meta">FunctionName(nameof(ActivityDoRemove))</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;MasterBotResponse&gt; <span class="hljs-title">ActivityDoRemove</span>(<span class="hljs-params">[ActivityTrigger] Message message, ILogger log</span>)</span>
{
    log.LogInformation(<span class="hljs-string">$"Removing client bot for <span class="hljs-subst">{message.From.Id}</span>"</span>);

    <span class="hljs-keyword">var</span> botToken = <span class="hljs-keyword">await</span> _database.GetMasterBotToken(message.From.Id);
    <span class="hljs-keyword">var</span> bot = <span class="hljs-keyword">await</span> _telegramService.GetBot(botToken);
    <span class="hljs-keyword">await</span> _telegramService.UnSetup(botToken);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> MasterBotResponse(<span class="hljs-string">$"Chatbot <span class="hljs-subst">{bot.Username}</span> detached as client bot"</span>);
}
</code></pre>
<p>I like how every activity function becomes an independent testable piece of functionality. Separation of concerns at its best.</p>
<h1 id="heading-storage">Storage</h1>
<p>Because I love to try new things I decided to try <a target="_blank" href="https://planetscale.com/">PlanetScale</a> as a database for the project.</p>
<p>First of all, I love when a database is compatible with something common used. It's frustrating to have a custom API for each DB out there. But this usually comes with some trade-off, fortunately, non of the existing limitations of PlanetScale will prevent us from using it in the project.</p>
<p>We will need a database in several places</p>
<ul>
<li>To store user profile</li>
<li>To store user-attached bots</li>
<li>To store attached bots message references (we will not store messages themselves)</li>
</ul>
<p>For simplicity all database operations will be provided by a single service:</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IDatabase</span>
{
    <span class="hljs-function">Task&lt;MasterBotUser&gt; <span class="hljs-title">NewMasterUser</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> fromId, <span class="hljs-keyword">string</span> userName</span>)</span>;
    <span class="hljs-function">Task <span class="hljs-title">AddUserTokenAsync</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> fromId, <span class="hljs-keyword">string</span> botToken, <span class="hljs-keyword">long</span> chatId, <span class="hljs-keyword">string</span> botName</span>)</span>;

    <span class="hljs-function">Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">GetClientBotToken</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> clientBotChatId</span>)</span>;
    <span class="hljs-function">Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">GetClientBotName</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> fromId</span>)</span>;
    <span class="hljs-function">Task&lt;<span class="hljs-keyword">long</span>&gt; <span class="hljs-title">GetMasterBotChatId</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> clientToken</span>)</span>;        
    <span class="hljs-function">Task&lt;<span class="hljs-keyword">long</span>&gt; <span class="hljs-title">GetMasterBotFromId</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> clientToken</span>)</span>;
    <span class="hljs-function">Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">GetMasterBotToken</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> fromId</span>)</span>;

    <span class="hljs-function">Task&lt;<span class="hljs-keyword">long</span>&gt; <span class="hljs-title">SaveClientMessageReference</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> clientToken, <span class="hljs-keyword">long</span> clientId, <span class="hljs-keyword">int</span> messageId, <span class="hljs-keyword">int</span> resendMessagId</span>)</span>;
    <span class="hljs-function">Task&lt;ClientBotsMessageReference&gt; <span class="hljs-title">GetClientMessageReference</span>(<span class="hljs-params"><span class="hljs-keyword">long</span>? messageReferenceId = <span class="hljs-keyword">default</span>, <span class="hljs-keyword">long</span>? messageId = <span class="hljs-keyword">default</span></span>)</span>;
}
</code></pre>
<p>Maybe I overdid it with the atomization of getting a master bot user, it's a tiny anyway.</p>
<h2 id="heading-database-structure">Database Structure</h2>
<p>Right now our database schema contains only three tables </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659273749435/dbO5WfiYd.png" alt="image.png" class="image--center mx-auto" /></p>
<p>The banned user's table does not exist yet but will be added in the future.</p>
<h3 id="heading-to-store-user-profile">To store user profile</h3>
<p>This table will store the user profile, its limits and link paddle account for handling user subscription.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> MasterBotUsers (
  FromId <span class="hljs-built_in">BIGINT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
  Username <span class="hljs-built_in">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  IsPro <span class="hljs-built_in">TINYINT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>,
);
</code></pre>
<h3 id="heading-to-store-user-attached-bots">To store user-attached bots</h3>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> ClientBots (
  ChatId <span class="hljs-built_in">BIGINT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
  <span class="hljs-keyword">Name</span> <span class="hljs-built_in">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  Token <span class="hljs-built_in">varchar</span>(<span class="hljs-number">255</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,  
  MasterFromId <span class="hljs-built_in">BIGINT</span>,
  <span class="hljs-keyword">KEY</span> master_from_id_idx (MasterFromId)
);
</code></pre>
<h3 id="heading-to-store-attached-bots-message-references">To store attached bots message references</h3>
<p>Here we store the original message id and original token to be able to send answers, the id of resending the message to handle replies, and <code>IsAnswered</code> flag to use in the future for statistics.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> ClientBotsMessageReference (
  <span class="hljs-keyword">Id</span> <span class="hljs-built_in">BIGINT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> AUTO_INCREMENT PRIMARY <span class="hljs-keyword">KEY</span>,

  OriginalMessageId <span class="hljs-built_in">INT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  OriginalFromId <span class="hljs-built_in">BIGINT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
  ResendMessageId <span class="hljs-built_in">INT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,

  IsAnswered <span class="hljs-built_in">TINYINT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> <span class="hljs-keyword">default</span> <span class="hljs-number">0</span>,
  ClientBotChatId <span class="hljs-built_in">BIGINT</span>,
  <span class="hljs-keyword">KEY</span> client_chat_id_idx (ClientBotChatId)
);
</code></pre>
<h2 id="heading-database-queries">Database queries</h2>
<p>First I think about using EF here because EF Code First is very good. But in fact, most of the data manipulation is quite simple here, so I used Dapper for my SQL mapping and that's it.</p>
<p>I will not show you all queries, you could check them in github if you wish, only couple to understand the pattern.</p>
<p>Here we have an upsert operation with <code>INSERT IGNORE</code> for adding a new master user.</p>
<pre><code class="lang-C#"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;MasterBotUser&gt; <span class="hljs-title">NewMasterUser</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> fromId, <span class="hljs-keyword">string</span> userName</span>)</span>
{
    <span class="hljs-keyword">var</span> masterBot = <span class="hljs-keyword">new</span> MasterBotUser
    {
        FromId = fromId,
        Username = userName,
    };

    <span class="hljs-keyword">string</span> sqlQuery = <span class="hljs-string">"INSERT IGNORE INTO MasterBotUsers (FromId, Username) VALUES(@FromId, @Username)"</span>;
    <span class="hljs-keyword">await</span> _connection.ExecuteAsync(sqlQuery, masterBot);

    <span class="hljs-keyword">return</span> masterBot;
}
</code></pre>
<p>Here we store information about attached client chatbot</p>
<pre><code class="lang-C#"><span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">AddUserTokenAsync</span>(<span class="hljs-params"><span class="hljs-keyword">long</span> fromId, <span class="hljs-keyword">string</span> botToken, <span class="hljs-keyword">long</span> chatId, <span class="hljs-keyword">string</span> botName</span>)</span>
{
    <span class="hljs-keyword">var</span> clientBot = <span class="hljs-keyword">new</span> ClientBot
    {
        ChatId = chatId,
        Token = botToken,
        MasterFromId = fromId,
        Name = botName
    };

    <span class="hljs-keyword">string</span> sqlQuery = <span class="hljs-string">"INSERT IGNORE INTO ClientBots (ChatId, Name, Token, MasterFromId) VALUES(@ChatId, @Name, @Token, @MasterFromId)"</span>;
    <span class="hljs-keyword">return</span> _connection.ExecuteAsync(sqlQuery, clientBot);
}
</code></pre>
<h2 id="heading-getting-messagereferencesid">Getting MessageReferencesId</h2>
<p>Usually in MySQL to get an auto-generated primary key value after <code>INSERT</code> we could use
<code>select LAST_INSERT_ID();</code></p>
<p>But instead of results, Dapper returns exception <code>The given key '0' was not present in the dictionary</code> when trying to execute the select. I didn't figure out why this happens and wonder if it could be a limitation on the PlanetSide part. </p>
<p>Official Vitess project board <a target="_blank" href="https://github.com/vitessio/vitess/projects/4?card_filter_query=last_insert_id">mentions</a> that <code>LAST_INSERT_ID</code> with attributes is not supported yet, but nothing about an attributeless.</p>
<p>So I was forced to manually get the latest inserted value in <code>SaveClientMessageReference</code>:</p>
<pre><code class="lang-C#"><span class="hljs-keyword">var</span> messageRef = <span class="hljs-keyword">new</span> ClientBotsMessageReference
{
    OriginalMessageId = messageId,
    OriginalFromId = clientId,
    ClientBotChatId = <span class="hljs-keyword">await</span> GetMasterBotChatId(clientToken),

    IsAnswered = <span class="hljs-literal">false</span>,
    ResendMessageId = resendMessagId,
};

<span class="hljs-comment">// insert new client message ref</span>
<span class="hljs-keyword">string</span> sqlQuery = <span class="hljs-string">@"INSERT INTO ClientBotsMessageReference (OriginalMessageId, OriginalFromId, ClientBotChatId, IsAnswered, ResendMessageId) 
    VALUES(@OriginalMessageId, @OriginalFromId, @ClientBotChatId, @IsAnswered, @ResendMessageId);"</span>;
<span class="hljs-keyword">await</span> _connection.ExecuteAsync(sqlQuery, messageRef);

<span class="hljs-comment">// get latest ID        </span>
<span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> _connection.ExecuteScalarAsync&lt;<span class="hljs-keyword">long</span>&gt;(<span class="hljs-string">"SELECT Id FROM ClientBotsMessageReference WHERE "</span> +
    <span class="hljs-string">"OriginalMessageId = @OriginalMessageId AND OriginalFromId = @OriginalFromId AND ResendMessageId = @ResendMessageId"</span>,
    <span class="hljs-keyword">new</span> { messageRef.OriginalMessageId, messageRef.OriginalFromId, messageRef.ResendMessageId });

<span class="hljs-keyword">return</span> result;
</code></pre>
<h1 id="heading-deploying">Deploying</h1>
<p>Deploying and setup of Master Chatbot might be a bit complicated at the moment. Maybe in the future, I would be able to add an ARM Template and make a one-click setup. But for now, to deploy you should do the next:</p>
<p><strong>Deploy</strong></p>
<ol>
<li>Deploy Feedback bots to Azure, open Azure Function App configuration</li>
<li>Create Database tables from <code>Database.sql</code> in PlanetScale and publish it to production</li>
</ol>
<p><strong>Setup Azure Function App</strong></p>
<ol>
<li>Set <code>Uri</code> to a FQDN of your Azure Function App (<strong>without https://</strong>)</li>
<li>Set <code>ConnectionString:Default</code> to a connection string of your MySQL-compatible database</li>
<li>Create a Host key for your functions in <code>App Keys</code> blade of your Azure Function App</li>
<li>Set <code>ClientId</code> to a created key - it will be used to auth webhooks</li>
</ol>
<p><strong>Setup Master Bot</strong></p>
<ol>
<li>Create a Master Bot using <a target="_blank" href="https://t.me/BotFather">@BotFather</a></li>
<li>Set <code>MasterToken</code> setting to the chatbot token obtained from BotFather</li>
<li>Execute <code>api/setup</code> function with the admin key - this will setup the webhook for your master bot</li>
<li>Send <code>/start</code> to master bot, check logs of <code>MasterBotWebhook</code> for message like <code>Got message: User sent message 123 to chat **123456789** at 30.07.2022 22:23:14.</code></li>
<li>Set <code>MasterChatId</code> to your chat number id</li>
</ol>
<p><strong>Setup Client Bots</strong>
Now your Master Bot is operational and you are ready to add client bots</p>
<ol>
<li>Create a Client Bot using <a target="_blank" href="https://t.me/BotFather">@BotFather</a></li>
<li>Send <code>/add</code> command to Master Bot</li>
<li>Send the client bot's token to a master bot, the confirmation message should return</li>
</ol>
<p>Setup is done, now any message to the Client Bot will be forwarded to a Master Bot. 
Reply to the Message in Master Bot to send back an answer.</p>
<h1 id="heading-conclusion-and-saas-version">Conclusion and SaaS version</h1>
<p>Now you have a fully functional chatbot based on Azure Durable Functions. The source code is <a target="_blank" href="https://github.com/xakpc/FeedbackBots">available on GitHub</a> with an AGPL license.</p>
<p>Feel free to deploy and use it for yourself.</p>
<p>If you don't want to bother with deployment and just want to set up your feedback chatbot I also built a SaaS version of FeedbackBots available <a target="_blank" href="https://feedbackbots.com">here</a>. It's exactly the same, only multi-tenant with a subscription.</p>
<h1 id="heading-what-is-the-future-of-the-project">What is the future of the project?</h1>
<p>I like how it's looking so far. Azure grant 1 million function executions per month for free, Azure Storage costs a penny, and PlanetScale has a free plan, so I will not spend anything until (with strong if) the product gets some traction.</p>
<p>I'm not fully happy with that though</p>
<ul>
<li>My payment provider Paddle temporarily suspended my account yesterday - <strong>beautiful timing</strong>, until I provide them with some legal documents. So right now subscription does not work.</li>
<li>I need to implement the <em>Block</em> feature, it requires a new database table and another action function.</li>
<li>Add support of different types of answers: image, videos, locations (maybe even pay link?)</li>
<li>I don't really like the current texts on the landing page and in a chatbot. I need to invest in some better wording.</li>
<li>Maybe ProductHunt? Nah, probably it's not good enough.</li>
</ul>
<p>Anyway, thanks for your attention, and if you have any questions feel free to contact me on the bot <a target="_blank" href="https://t.me/FeedbackBotsSupportBot">@FeedbackBotsSupportBot</a> or on <a target="_blank" href="https://twitter.com/xakpc">Twitter</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659201311438/y6MUPLeY_.png?height=200" alt="qr.png" class="image--center mx-auto" /></p>
<p><strong>Note for Hackathon Judges</strong></p>
<p>The easiest way to test a FeedbackBots solution is </p>
<ul>
<li>use <a target="_blank" href="https://t.me/FeedbackBotsMasterBot">@FeedbackBotsMasterBot</a></li>
<li>create your own client bot with <a target="_blank" href="https://t.me/BotFather">@BotFather</a></li>
<li>attach your client bot to MasterBot with <code>/add</code> command</li>
<li>send some messages to the client bot and answer them in the master bot</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Let's build a simple countdown timer with dotnet Blazor]]></title><description><![CDATA[Last weekend I was free and bored, so I decided to refresh my Blazor skills and build something small and funny. I decided to make and made a simple stupid game "find dissimilar emoji". 

Part of the game was a countdown on top, which is a separate B...]]></description><link>https://xakpc.info/lets-build-a-simple-countdown-timer-with-dotnet-blazor</link><guid isPermaLink="true">https://xakpc.info/lets-build-a-simple-countdown-timer-with-dotnet-blazor</guid><category><![CDATA[dotnet]]></category><category><![CDATA[dotnetcore]]></category><category><![CDATA[Blazor ]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Sun, 05 Jun 2022 18:31:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1654453423451/WfioVwh3i.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last weekend I was free and bored, so I decided to refresh my Blazor skills and build something small and funny. I decided to make and made a simple stupid game "find dissimilar emoji". </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654446455822/epoKFnFb6.png" alt="overall-screenshot.png" class="image--center mx-auto" /></p>
<p>Part of the game was a countdown on top, which is a separate Blazor component I made. </p>
<p>In this article, I will demonstrate to you how to create an easy Blazor countdown component. A full code of component <a target="_blank" href="https://gist.github.com/xakpc/a5621a3147a6b8fc48e66b1611538973">available here</a>.</p>
<h1 id="heading-intro">Intro</h1>
<p>A component is a <strong>self-contained portion of the user interface</strong> (UI) with processing logic to enable dynamic behavior.</p>
<p>Let's deep dive into this component to understand how it works and how you could make the same countdown for your Blazor app.</p>
<p>The countdown component consists of several parts:</p>
<ul>
<li>HTML Markup</li>
<li>C# code</li>
<li>CSS styles</li>
</ul>
<p>Which could be isolated into separate files</p>
<ul>
<li><code>Countdown.razor</code></li>
<li><code>Countdown.razor.cs</code></li>
<li><code>Countdown.razor.css</code></li>
</ul>
<p>Or it could be combined into a single <code>Countdown.razor</code> file for simplicity. But for me, isolation is a better practice, especially if I plan to expand the component further.</p>
<p>When we isolate code into separate files with proper naming Visual Studio will combine them under one nifty tree</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1654451834187/aFfIPWxuH.png" alt="tree.png" class="image--center mx-auto" /></p>
<h3 id="heading-html-markup">HTML Markup</h3>
<p>For markup, we have a <code>div</code> container and a <code>h1</code> item inside of it to make our countdown big enough. We will make our container <code>flex</code> with CSS later to easily center our timer.</p>
<p><code>Time</code> will be our property to hold the string value of the timer.</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span> = <span class="hljs-string">"clock"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>@Time<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<h3 id="heading-c-code">C# code</h3>
<p>First, we need to declare the timer, field to store seconds left, and <code>Time</code> property for the UI part. For the timer itself dotnet provide us with several options, we will use <code>System.Timers.Timer</code> as the simplest one.</p>
<pre><code class="lang-C#">    <span class="hljs-keyword">private</span> System.Timers.Timer _timer = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _secondsToRun = <span class="hljs-number">0</span>;

    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">string</span> Time { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-string">"00:00"</span>;
</code></pre>
<p>To properly use it we need a <code>Start</code> and <code>Stop</code> methods to launch the timer and some kind of callback to handle time out. Note that we should manually call here <code>StateHasChanged</code> to notify Blazor that the component needs to redraw. This is because we call <code>Start</code> programmatically by code.</p>
<pre><code class="lang-C#">    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> EventCallback TimerOut { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Start</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> secondsToRun</span>)</span>
    {
        _secondsToRun = secondsToRun;

        <span class="hljs-keyword">if</span> (_secondsToRun &gt; <span class="hljs-number">0</span>)
        {
            Time = TimeSpan.FromSeconds(_secondsToRun).ToString(<span class="hljs-string">@"mm\:ss"</span>);
            StateHasChanged();
            _timer.Start();
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Stop</span>(<span class="hljs-params"></span>)</span>
    {
        _timer.Stop();
    }
</code></pre>
<p>The <code>TimerOut</code> event has an additional attribute - <code>Parameter</code> to make it accessible from outside of the component.</p>
<p>We need to set up the timer, we could make it in the <code>OnInitialized</code> event override</p>
<pre><code class="lang-C#">    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnInitialized</span>(<span class="hljs-params"></span>)</span>
    {
        _timer = <span class="hljs-keyword">new</span> System.Timers.Timer(<span class="hljs-number">1000</span>);
        _timer.Elapsed += OnTimedEvent;
        _timer.AutoReset = <span class="hljs-literal">true</span>;
    }
</code></pre>
<p>This timer will exist in the scope of a single user (because it's owned by a component, which is owned by a page, which exists in the scope of a single-user connection). It will tick every second, firing <code>OnTimedEvent</code> until stopped.</p>
<p>The event should decrease the number of seconds left, update the <code>Time</code> value and check if the timer is out to stop it and fire <code>TimerOut</code> event.</p>
<pre><code class="lang-C#">    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnTimedEvent</span>(<span class="hljs-params"><span class="hljs-keyword">object</span>? sender, ElapsedEventArgs e</span>)</span>
    {
        _secondsToRun--;

        <span class="hljs-keyword">await</span> InvokeAsync(() =&gt;
        {
            Time = TimeSpan.FromSeconds(_secondsToRun).ToString(<span class="hljs-string">@"mm\:ss"</span>);
            StateHasChanged();
        });

        <span class="hljs-keyword">if</span> (_secondsToRun &lt;= <span class="hljs-number">0</span>)
        {
            _timer.Stop();
            <span class="hljs-keyword">await</span> TimerOut.InvokeAsync();
        }
    }
</code></pre>
<p>Because the callback is invoked outside of Blazor's synchronization context, the component must wrap the logic of <code>OnTimedEvent</code> in <code>ComponentBase.InvokeAsync</code> to move it onto the renderer's (UI) synchronization context. <code>StateHasChanged</code> can only be called from the renderer's synchronization context and throws an exception otherwise. We do not care in what context the timer stops, so we could not wrap it.</p>
<p>And finally <code>System.Timers.Timer</code> need to be properly disposed with the component. For that, we implement <code>IDisposable</code> interface in our class.</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Timer</span> : <span class="hljs-title">ComponentBase</span>, <span class="hljs-title">IDisposable</span>
{
    ...

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Dispose</span>(<span class="hljs-params"></span>)</span>
    {
        _timer.Dispose();
    }
}
</code></pre>
<h3 id="heading-css-styles">CSS styles</h3>
<p>For style, we go for a simple flexbox with centered content. No additional styles are required right now. <code>Timer.razor.css</code> file will be bundled into <code>{ASSEMBLY NAME}.styles.css</code> file and <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/blazor/components/css-isolation?view=aspnetcore-6.0">included</a> in your application. </p>
<pre><code class="lang-CSS"><span class="hljs-selector-class">.clock</span> {
    <span class="hljs-attribute">color</span>: <span class="hljs-built_in">var</span>(--color-tone-<span class="hljs-number">1</span>);
    <span class="hljs-attribute">font-family</span>: sans-serif;
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">justify-content</span>: center;
    <span class="hljs-attribute">align-items</span>: center;
    <span class="hljs-attribute">flex-grow</span>: <span class="hljs-number">1</span>;
    <span class="hljs-attribute">overflow</span>: hidden;
}
</code></pre>
<h2 id="heading-usage">Usage</h2>
<p>To use the component we simply add it to our page</p>
<pre><code class="lang-HTML"><span class="hljs-tag">&lt;<span class="hljs-name">Countdown</span> @<span class="hljs-attr">ref</span>=<span class="hljs-string">"timer"</span> <span class="hljs-attr">TimerOut</span>=<span class="hljs-string">"TimerOutCallback"</span>/&gt;</span>
</code></pre>
<p>With <code>@ref</code> attribute we obtain a reference to an HTML element, which we could use to call our <code>Start</code> and <code>Stop</code> methods. For example, we could start the timer after the page is rendered and display a message after it is timed out.</p>
<pre><code class="lang-C#">    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnAfterRender</span>(<span class="hljs-params"><span class="hljs-keyword">bool</span> firstRender</span>)</span>
    {
        <span class="hljs-keyword">if</span> (firstRender)
        {
            timer.Start(<span class="hljs-number">10</span>);
        }
    }

    <span class="hljs-keyword">protected</span> Shared.Countdown timer;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TimerOutCallback</span>(<span class="hljs-params"></span>)</span>
    {
        ...
    }
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>That concludes our countdown component. There is not much left to be done to improve codebase of it. </p>
<p>The visuals of the countdown might be improved but here is a catch: Blazor is not very suitable to work with CSS animation as JS does. </p>
<p>It is definitely possible but requires either a lot of "hacks" or JS functions to call from Blazor. Maybe one day I will deep dive into it.</p>
]]></content:encoded></item><item><title><![CDATA[Why C# Might be the Best as a First Programming Language]]></title><description><![CDATA[Roaming the internet I see a lot of attention to why you should start to learn JavaScript, or why you should learn Python as your first programming language. And I have a feeling that there lack of attention for C# out there. 
But I'm ready to challe...]]></description><link>https://xakpc.info/why-csharp-might-be-the-best-as-a-first-programming-language</link><guid isPermaLink="true">https://xakpc.info/why-csharp-might-be-the-best-as-a-first-programming-language</guid><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Thu, 23 Dec 2021 19:47:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1640287798195/SbxXqsgGE.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Roaming the internet I see a lot of attention to why you should start to learn JavaScript, or why you should learn Python as your first programming language. And I have a feeling that there lack of attention for C# out there. </p>
<p>But I'm ready to challenge you and prove that C# might be <strong>the best</strong> first language and you should consider starting your developer path from it.</p>
<p>I worked with it for more than 10 years, and tried almost anything: Desktop, IoT, Mobile, Web, Cloud - and everywhere C# and .NET perfectly fitted (except maybe early days of Xamarin TBH), and here are my thoughts about it.</p>
<blockquote>
<p>C# is a programming language, .NET is a software framework built upon it. For simplicity when I say C# I assume it with combination with .NET and vice versa.</p>
</blockquote>
<h1 id="heading-1-net-allows-you-to-build-anything-everywhere">1. .NET allows you to build anything everywhere.</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1634578047861/mB_ZOyYxN.png" alt="image.png" /></p>
<p>With a single language and a single framework you could build any kind of application: <em>Desktop</em> and <em>Mobile</em>, you can create <em>games</em>, and explore <em>AI</em>. And .NET is <strong>cross-platform</strong>: you could build applications for Windows, Linux, Android, iOS, and even for IoT devices. That's a lot when you are not sure where you want to move next. </p>
<p>Imagine that you could adopt a single language, try every direction, and improve your skills in the process. You still would need to study direction specifics though.</p>
<p>After that, you could improve in the direction you love most or you could even begin to work with languages and tools more specific for selected fields if you wish. For example, for mobile dev, I have seen people move from Xamarin to native development on iOS or Android.</p>
<h1 id="heading-2-c-is-the-fifth-popular-language">2. C# is the fifth popular language.</h1>
<p>According to the <a target="_blank" href="https://www.tiobe.com/tiobe-index/">TIOBE Index</a> (index of the popularity of programming languages based on search engine results), C# holds a solid 5th place as the most popular language. During the last 20 years, it has always been in the top 10.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640190562011/KHfrZZkVM.png" alt="image.png" /></p>
<p>And even more - due to fast growth in popularity this year C# might get the "TIOBE index programming language of the year award" for its highest increase in ratings in 2021.</p>
<h1 id="heading-3-cnet-has-one-of-the-best-learning-materials">3. C#/.NET has one of the best learning materials.</h1>
<p>Microsoft and .NET Community provided tons of materials in any form: articles, videos, courses, tutorials, and books to give you the ability to become a successful .NET developer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640284223393/f14iBTrgW.png" alt="image.png" /></p>
<p>You could check all available materials on <a target="_blank" href="https://dotnet.microsoft.com/en-us/learn/csharp">Microsoft Learning Portal</a>  </p>
<p>The community provides amazing all-included roadmaps to master the skills and knowledge you need to achieve success, like this ASP.NET Core roadmap for a backend developer. You could easily google roadmaps for other directions.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/MoienTajik/AspNetCore-Developer-Roadmap">https://github.com/MoienTajik/AspNetCore-Developer-Roadmap</a></div>
<p>And Microsoft itself trying to do different fun activities to engage more developers.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1640283892157/6AsFh5lT6.jpeg" alt="f999ee4f454fdd528f513044a50a9fdc0e4a4994.jpg" />
<em>For example, this NFT badge is awarded to ones who proved their knowledge of building modern web apps and services with .NET 6 and Azure in the .NET Conf 2021 Cloud Skills Challenge</em></p>
<p>If you looking for where to start I recommend you to check these places</p>
<ul>
<li><a target="_blank" href="https://www.youtube.com/playlist?list=PLdo4fOcmZ0oVxKLQCHpiUWun7vlJJvUiN">C# 101 Course</a> by Microsoft with Scott Hanselman and Kendra Havens</li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=gfkTfcpWqAY">Video tutorials</a> by Mosh Hamedani</li>
</ul>
<h1 id="heading-4-net-6-simplified-a-quick-start-to-the-ground">4. .NET 6 simplified a quick-start to the ground.</h1>
<p>The latest version of .NET took a huge step to simplify the getting started experience. The <strong>Minimal API</strong> feature gave us a way to create HTTP APIs with minimal dependencies, in a single file, with clean code.</p>
<p>You need exactly 4 lines of code that can be created by a single command <code>dotnet new web -o MinimalApiSample</code> to start your web app with .NET 6.</p>
<pre><code><span class="hljs-keyword">var</span> builder <span class="hljs-operator">=</span> WebApplication.CreateBuilder(args);
<span class="hljs-keyword">var</span> app <span class="hljs-operator">=</span> builder.Build();

app.MapGet(<span class="hljs-string">"/"</span>, () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> <span class="hljs-string">"Hello World!"</span>);

app.Run();
</code></pre><p>If you wish to try it for yourself check the <a target="_blank" href="https://docs.microsoft.com/en-us/aspnet/core/tutorials/min-web-api?view=aspnetcore-6.0&amp;tabs=visual-studio">tutorial</a> from Microsoft.</p>
<h1 id="heading-summary">Summary.</h1>
<p>In conclusion, when choosing a first language you should ask yourself two questions:</p>
<ol>
<li>Is it a good programming language to learn if you want to get a job?</li>
<li>Is it a good language to start learning to program?</li>
</ol>
<p>For C# my answer is <strong>definitely yes</strong>. There is high demand for C#/.NET developers, it's a C-like language with a strong object-oriented approach, and the learning curve becomes more and more acceptable for each version.</p>
<p>But, as you probably already figured out, I am a fanboi 😅, so try it and decide it for yourself. Good luck in your learnings, and if you decided to join: welcome to <strong>#dotnet</strong></p>
]]></content:encoded></item><item><title><![CDATA[Monitoring Azure API Management metrics in a chatbot with Azure Functions]]></title><description><![CDATA[I use Azure API Management (AAM thereof) to provide my bot-builder APIs to chatbot developers. One of the important parts of it is to monitor usage, DAU (daily active users), most used endpoints, errors, and other metrics.
To do so AAM provides an An...]]></description><link>https://xakpc.info/monitoring-azure-api-management-metrics-in-a-chatbot-with-azure-functions</link><guid isPermaLink="true">https://xakpc.info/monitoring-azure-api-management-metrics-in-a-chatbot-with-azure-functions</guid><category><![CDATA[Azure]]></category><category><![CDATA[chatbot]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Mon, 22 Mar 2021 15:30:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1616424720381/BVd3jU9bq.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I use Azure API Management (AAM thereof) to provide my bot-builder APIs to chatbot developers. One of the important parts of it is to monitor usage, DAU (daily active users), most used endpoints, errors, and other metrics.</p>
<p>To do so AAM provides an Analytics tab where all information aggregated. The problem here that this is usage analytics, not product analytics, and it's also not very convenient to use. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616403527451/zjFepbDf_.png" alt="AAM Analytics Chart" /></p>
<p>To solve this you could use the power of Application Insights to build a custom dashboard, or, as I prefer, built a simple reporting into a chatbot.</p>
<p>In this article, I describe my experience of how you could use Azure Functions to build product analytics and setup daily reports into a chatbot with it. We would connect and use Azure SDK with MSAL, HTTP-triggered and Timer-based functions, and API.chat proactive API.</p>
<h2 id="functions">Functions</h2>
<p>You would need two Azure Functions with one ProductAnalytics service class. Service will be gathering and calculating analytics through Azure SDK and functions will provide data to the chatbot on request and on daily basis.</p>
<h3 id="service-productanalytics">Service ProductAnalytics</h3>
<p>To better understand AAM users you should monitor at least the next metrics</p>
<ul>
<li>DAU - daily active users, how many users use your API yesterday</li>
<li>MAU - monthly active users, same but for month</li>
<li>Failed Request - how many requests failed, good to find some peaks.</li>
</ul>
<p>You would most likely need a separate set of metrics for each AAM product you have.</p>
<p>That's how AnalyticsResult class might look</p>
<pre><code class="lang-C#"><span class="hljs-keyword">class</span> <span class="hljs-title">AnalyticsResult</span>
 {
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Dau { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Mau { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
     <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> FailedRequests { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
 }
</code></pre>
<h4 id="credentials">Credentials</h4>
<p>Because you would use Azure SDK, first you should get credentials to access your AAM. To do so you should register a new application on the <code>App registrations</code> tab of your <code>Azure Active Directory</code> blade</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616406432991/cgCAbtr3_.png" alt="p1.png" /></p>
<p>Register it with the <code>Single tenant</code> account type and leave <code>Redirect Uri</code> blank - you don't need it.</p>
<p>Store Client ID, Tenant ID and create new Client secret on <code>Certificates &amp; secrets</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616406848118/umgnb4Zl1.png" alt="p2.png" /></p>
<h4 id="auth">Auth</h4>
<p>To authenticate the client you could use several ways. The latest, and probably the most valid, way is to use <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-overview">MSAL</a>.</p>
<blockquote>
<p>Starting June 30th, 2020 we will no longer add any new features to Azure Active Directory Authentication Library (ADAL) and Azure AD Graph. We will continue to provide technical support and security updates but we will no longer provide feature updates. Applications will need to be upgraded to Microsoft Authentication Library (MSAL) and Microsoft Graph.</p>
</blockquote>
<p>To use it, first, you need to add an Identity package</p>
<pre><code><span class="hljs-selector-tag">Install-Package</span> <span class="hljs-selector-tag">Microsoft</span><span class="hljs-selector-class">.Identity</span><span class="hljs-selector-class">.Client</span> <span class="hljs-selector-tag">-Version</span> 4<span class="hljs-selector-class">.28</span><span class="hljs-selector-class">.0</span>
</code></pre><p>And then you should implement a token provider for AAM to use in the Azure SDK client later, like this</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApiManagementTokenProvider</span> : <span class="hljs-title">ITokenProvider</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> ScopeApiManagement = <span class="hljs-string">"https://management.azure.com/.default"</span>;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">string</span> _tenantId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">string</span> _clientId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> <span class="hljs-keyword">string</span> _clientSecret;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ApiManagementTokenProvider</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Bad example here, plz don't store secrets in code</span>
        _tenantId = <span class="hljs-string">"..."</span>;
        _clientId = <span class="hljs-string">"..."</span>;
        _clientSecret = <span class="hljs-string">"..."</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;AuthenticationHeaderValue&gt; <span class="hljs-title">GetAuthenticationHeaderAsync</span>(<span class="hljs-params">CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-comment">// For app only authentication, we need the specific tenant id in the authority url</span>
        <span class="hljs-keyword">var</span> tenantSpecificUrl = <span class="hljs-string">$"https://login.microsoftonline.com/<span class="hljs-subst">{_tenantId}</span>/"</span>;

        <span class="hljs-comment">// Create a confidential client to authorize the app with the AAD app</span>
        IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
                                                                        .Create(_clientId)
                                                                        .WithClientSecret(_clientSecret)
                                                                        .WithAuthority(tenantSpecificUrl)
                                                                        .Build();
        <span class="hljs-comment">// Make a client call if Access token is not available in cache</span>
        <span class="hljs-keyword">var</span> authenticationResult = <span class="hljs-keyword">await</span> clientApp
            .AcquireTokenForClient(<span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt; { ScopeApiManagement })
            .ExecuteAsync();

        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AuthenticationHeaderValue(<span class="hljs-string">"Bearer"</span>, authenticationResult.AccessToken);
    }
}
</code></pre><h4 id="client">Client</h4>
<p>To get Analytics from AAM with Azure SDK first you need to add relevant NuGet to your project</p>
<pre><code><span class="hljs-selector-tag">Install-Package</span> <span class="hljs-selector-tag">Microsoft</span><span class="hljs-selector-class">.Azure</span><span class="hljs-selector-class">.Management</span><span class="hljs-selector-class">.ApiManagement</span> <span class="hljs-selector-tag">-Version</span> 6<span class="hljs-selector-class">.0</span><span class="hljs-selector-class">.0-preview</span>
</code></pre><p>To get the analytics you need to use the <a target="_blank" href="https://docs.microsoft.com/en-us/rest/api/apimanagement/2021-01-01-preview/reports">Reports endpoint</a>.</p>
<p>These endpoints get data filtered by OData, so you would need to build several queries.</p>
<p>To get MAU and DAU you should use <code>ListByUsers</code>. Note what filter operation does it support - it's quite limited, to be honest.</p>
<p>Check this for example. Here is 100% valid code for creating ODataFilter, even better - it's built with LINQ and <code>SetFilter</code> method</p>
<pre><code>var <span class="hljs-keyword">filter</span> = <span class="hljs-built_in">new</span> ODataQuery&lt;ReportRecordContract&gt;()
{
    OrderBy = "callCountTotal desc"
};
<span class="hljs-keyword">filter</span>.SetFilter(i =&gt; i.ProductId == productId &amp;&amp; i.Timestamp &gt;= dateOnly &amp;&amp; i.Timestamp &lt;= dateEnd);
</code></pre><p>Here is a resulting filter string <code>productId eq 'unlimited' and timestamp ge '2021-03-20T21:00:00Z' and timestamp le '2021-03-21T21:00:00Z'</code></p>
<p>But this filter string is not valid for Azure SDK! The error is <code>Invalid filter clause specified: '...'.</code></p>
<p>It needs to be modified or rewritten like so to work: <code>(timestamp ge 2021-03-21T21:00:00.000Z and  timestamp le 2021-03-22T11:29:59.999Z) and (productId eq 'unlimited')</code>. What the difference you ask? AAM OData doesn't like single quotes around dates. </p>
<p><em>Annoying thing</em>: AAM OData require datetime in exact <code>2021-03-22T11:29:59.999Z</code> format, which is neither standard <code>UniversalSortableDateTimePattern</code> or <code>SortableDateTimePattern</code> formats of <code>DateTime.ToString()</code>.</p>
<p><em>Another annoying thing</em>: <code>ODataQuery</code> does not allow to set <code>$select</code> parameter.
Anyway,</p>
<p>DAU</p>
<pre><code><span class="hljs-keyword">var</span> dateStart = date.Date;
<span class="hljs-keyword">var</span> dateEnd = date.Date.AddDays(<span class="hljs-number">1</span>);
<span class="hljs-keyword">var</span> filter = <span class="hljs-keyword">new</span> ODataQuery&lt;ReportRecordContract&gt;(<span class="hljs-string">$"productId eq '<span class="hljs-subst">{productId}</span>' and timestamp ge <span class="hljs-subst">{dateStart:yyyy<span class="hljs-string">'-'</span>MM<span class="hljs-string">'-'</span>dd<span class="hljs-string">'T'</span>HH<span class="hljs-string">':'</span>mm<span class="hljs-string">':'</span>ss<span class="hljs-string">'Z'</span>}</span> and timestamp le <span class="hljs-subst">{dateEnd:yyyy<span class="hljs-string">'-'</span>MM<span class="hljs-string">'-'</span>dd<span class="hljs-string">'T'</span>HH<span class="hljs-string">':'</span>mm<span class="hljs-string">':'</span>ss<span class="hljs-string">'Z'</span>}</span>"</span>)
{
    OrderBy = <span class="hljs-string">"callCountTotal desc"</span>
};
</code></pre><p>MAU</p>
<pre><code><span class="hljs-keyword">var</span> dateStart = <span class="hljs-keyword">new</span> DateTimeOffset(date.Year,date.Month,<span class="hljs-number">1</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,<span class="hljs-number">0</span>,date.Offset);
<span class="hljs-keyword">var</span> dateEnd = date.Date.AddDays(<span class="hljs-number">1</span>);
<span class="hljs-keyword">var</span> filter = <span class="hljs-keyword">new</span> ODataQuery&lt;ReportRecordContract&gt;(<span class="hljs-string">$"productId eq '<span class="hljs-subst">{productId}</span>' and timestamp ge <span class="hljs-subst">{dateStart:yyyy<span class="hljs-string">'-'</span>MM<span class="hljs-string">'-'</span>dd<span class="hljs-string">'T'</span>HH<span class="hljs-string">':'</span>mm<span class="hljs-string">':'</span>ss<span class="hljs-string">'Z'</span>}</span> and timestamp le <span class="hljs-subst">{dateEnd:yyyy<span class="hljs-string">'-'</span>MM<span class="hljs-string">'-'</span>dd<span class="hljs-string">'T'</span>HH<span class="hljs-string">':'</span>mm<span class="hljs-string">':'</span>ss<span class="hljs-string">'Z'</span>}</span>"</span>)
{
    OrderBy = <span class="hljs-string">"callCountTotal desc"</span>
};
</code></pre><p><code>ListByUser</code> call returns paginated data wrapped into the <code>IPage</code> interface. Page 'size' be changed by setting <code>$skip</code> and <code>$top</code> parameters. And I am not sure about the default max page size. That forces us to read page by page until we get a user with zero callCountTotal - because we need only active users. Full method</p>
<pre><code><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task&lt;List&lt;ReportRecordContract&gt;&gt; GetReports(DateTimeOffset dateStart, DateTimeOffset dateEnd, <span class="hljs-keyword">string</span> productId)
{
    <span class="hljs-keyword">var</span> apiManagement = <span class="hljs-keyword">new</span> ApiManagementClient(<span class="hljs-keyword">new</span> TokenCredentials(_tokenProvider))
    {
        SubscriptionId = _subscriptionId
    };
    <span class="hljs-keyword">var</span> filter = <span class="hljs-keyword">new</span> ODataQuery&lt;ReportRecordContract&gt;(<span class="hljs-string">$"productId eq '<span class="hljs-subst">{productId}</span>' and timestamp ge <span class="hljs-subst">{dateStart:yyyy<span class="hljs-string">'-'</span>MM<span class="hljs-string">'-'</span>dd<span class="hljs-string">'T'</span>HH<span class="hljs-string">':'</span>mm<span class="hljs-string">':'</span>ss<span class="hljs-string">'Z'</span>}</span> and timestamp le <span class="hljs-subst">{dateEnd:yyyy<span class="hljs-string">'-'</span>MM<span class="hljs-string">'-'</span>dd<span class="hljs-string">'T'</span>HH<span class="hljs-string">':'</span>mm<span class="hljs-string">':'</span>ss<span class="hljs-string">'Z'</span>}</span>"</span>)
    {
        OrderBy = <span class="hljs-string">"callCountTotal desc"</span>,
    };

    <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">new</span> List&lt;ReportRecordContract&gt;();
    <span class="hljs-keyword">bool</span> hasMore = <span class="hljs-literal">true</span>;

    <span class="hljs-keyword">while</span> (hasMore)
    {
        <span class="hljs-keyword">var</span> page = <span class="hljs-keyword">await</span> apiManagement.Reports.ListByUserAsync(filter, _resourceGroupName, _serviceName);
        result.AddRange(page);

        hasMore = page.Any() &amp;&amp; page.Last().CallCountTotal &gt; <span class="hljs-number">0</span>;
    }
    <span class="hljs-keyword">return</span> result.ToList();
};
</code></pre><p>After you got our data you could calculate metrics.</p>
<h3 id="actual-functions">Actual Functions</h3>
<p>First of all, to make it easier you could override <code>ToString</code> of the <code>AnalyticsResult</code> class</p>
<pre><code>        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">string</span> <span class="hljs-title">ToString</span>(<span class="hljs-params"></span>)</span>
        {
            <span class="hljs-keyword">var</span> sb = <span class="hljs-keyword">new</span> StringBuilder();

            sb.AppendLine(<span class="hljs-string">$"_DAU_: <span class="hljs-subst">{Dau}</span>"</span>);
            sb.AppendLine(<span class="hljs-string">$"_MAU_: <span class="hljs-subst">{Mau}</span>"</span>);
            sb.AppendLine(<span class="hljs-string">$"_Failed_: <span class="hljs-subst">{FailedRequests}</span>"</span>);

            <span class="hljs-keyword">return</span> sb.ToString();
        }
</code></pre><h4 id="request">Request</h4>
<p>Let's start with a request. This function will receive a GET request and return a bot-compatible response. It will support <code>today</code>(default), <code>yesterday</code>, and date input which would be provided by the chatbot.</p>
<pre><code>[<span class="hljs-meta">FunctionName(<span class="hljs-meta-string">"GetDailyReports"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">Run</span>(<span class="hljs-params">
            [HttpTrigger(AuthorizationLevel.Anonymous, <span class="hljs-string">"get"</span>, Route = <span class="hljs-string">"reports/daily"</span></span>)] HttpRequest req,
            ILogger log)</span>
{
    log.LogInformation(<span class="hljs-string">"C# HTTP trigger function processed a request."</span>);

    DateTimeOffset date = DateTimeOffset.UtcNow.Date;
    <span class="hljs-keyword">string</span> day = req.Query[<span class="hljs-string">"day"</span>];

    <span class="hljs-keyword">if</span> (!<span class="hljs-keyword">string</span>.IsNullOrEmpty(day) &amp;&amp; !day.Equals(<span class="hljs-string">"today"</span>, StringComparison.InvariantCultureIgnoreCase))
    {
        <span class="hljs-keyword">if</span> (day.Equals(<span class="hljs-string">"yesterday"</span>, StringComparison.InvariantCultureIgnoreCase))
            date = DateTimeOffset.UtcNow.AddDays(<span class="hljs-number">-1</span>).Date;
        <span class="hljs-keyword">else</span>
            DateTimeOffset.TryParse(day, <span class="hljs-keyword">out</span> date);
    }

    <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">new</span> StringBuilder();

    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Personal Plan*"</span>, <span class="hljs-string">"personal"</span>);
    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Team Plan*"</span>, <span class="hljs-string">"team"</span>);
    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Business Plan*"</span>, <span class="hljs-string">"business"</span>);
    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Ultimate Plan*"</span>, <span class="hljs-string">"ultimate"</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> OkObjectResult(<span class="hljs-keyword">new</span> { Messages = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt;() { result.ToString() } });

    <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">AppendPlan</span>(<span class="hljs-params">DateTimeOffset date, StringBuilder result, <span class="hljs-keyword">string</span> title, <span class="hljs-keyword">string</span> plan</span>)</span>
    {
        <span class="hljs-keyword">var</span> service = <span class="hljs-keyword">new</span> Services.AnalyticsService();
        result.AppendLine(title);
        <span class="hljs-keyword">var</span> personalReport = <span class="hljs-keyword">await</span> service.GetDailyAnalytics(date, plan);
        result.AppendLine(personalReport.ToString());
        result.AppendLine();
    }
}
</code></pre><p>Here you should first check the <code>date</code> parameter, then form an answer and send it to a user in a single message. You could split it into several messages, but IMO it would be a bit spammy.</p>
<h4 id="daily">Daily</h4>
<p>The daily function is basically the same, but instead of responding to the chatbot, it sends a proactive message into the bot.</p>
<pre><code>[<span class="hljs-meta">FunctionName(<span class="hljs-meta-string">"SendDailyReport"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Run</span>(<span class="hljs-params">[TimerTrigger(<span class="hljs-string">"0 0 13 * * ?"</span></span>)] TimerInfo myTimer, ILogger log)</span>
{
    log.LogInformation(<span class="hljs-string">$"C# Timer trigger function executed at: <span class="hljs-subst">{DateTime.Now}</span>"</span>);

    <span class="hljs-keyword">var</span> date = DateTimeOffset.UtcNow.Date;
    <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">new</span> StringBuilder(<span class="hljs-string">$"Report for *<span class="hljs-subst">{date:d}</span>*."</span>);

    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Personal Plan*"</span>, <span class="hljs-string">"personal"</span>);
    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Team Plan*"</span>, <span class="hljs-string">"team"</span>);
    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Business Plan*"</span>, <span class="hljs-string">"business"</span>);
    <span class="hljs-keyword">await</span> AppendPlan(date, result, <span class="hljs-string">"*Unlimited Plan*"</span>, <span class="hljs-string">"unlimited"</span>);

    <span class="hljs-comment">// send push to a chatbot</span>
    <span class="hljs-keyword">await</span> SendToChatbot(result.ToString());
}
</code></pre><p>For <code>SendToChatbot</code> you would need to obtain <code>Subscription-Key</code>, set <code>botName</code> and <code>channel</code> from the API.chat developer portal. The easiest way to do that is to navigate to <a target="_blank" href="https://developer.api.chat/api-details#api=proactive-api&amp;operation=Proactive_Post">Proactive API page</a>, fill all data under your account, and copy generated C# code.</p>
<p>To obtain your chat id send a special <code>/chatid</code> command to the bot.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SendToChatbot</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message</span>)</span>
{
    <span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> HttpClient();
    client.DefaultRequestHeaders.Add(<span class="hljs-string">"Cache-Control"</span>, <span class="hljs-string">"no-cache"</span>);
    client.DefaultRequestHeaders.Add(<span class="hljs-string">"Ocp-Apim-Subscription-Key"</span>, <span class="hljs-string">"{your-api-key}"</span>);

    <span class="hljs-keyword">var</span> uri = <span class="hljs-string">"https://bot.api.chat/proactive/{your-bot}/{your-channel}/{your-chat-id}"</span>;

    <span class="hljs-keyword">using</span> <span class="hljs-keyword">var</span> content = <span class="hljs-keyword">new</span> StringContent(JsonConvert.SerializeObject(<span class="hljs-keyword">new</span> { Messages = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt;() { message } }), Encoding.UTF8, <span class="hljs-string">"application/json"</span>);
    <span class="hljs-keyword">var</span> response = <span class="hljs-keyword">await</span> client.PostAsync(uri, content);
}
</code></pre><h2 id="chatbot">Chatbot</h2>
<p>For the chatbot, you could use my <a target="_blank" href="https://api.chat">API.chat</a> chatbot platform. The platform allows building a chatbot based on an XML-based scenario with the ability to perform third-party HTTP requests. Here is the simple scenario for the chatbot.</p>
<pre><code class="lang-XML"><span class="hljs-tag">&lt;<span class="hljs-name">bot</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Start"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"/report"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Report"</span> <span class="hljs-attr">buttons</span>=<span class="hljs-string">"Today,Yesterday"</span>&gt;</span>Select or enter the day<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">buttons</span>=<span class="hljs-string">"Report"</span>&gt;</span>echo: {msg}<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Report"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">morphology</span>=<span class="hljs-string">"msg"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"http://contoso.azurewebsites.net/api/reports/daily?day={msg}"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">bot</span>&gt;</span>
</code></pre>
<p>User stays in <code>Start</code> state, send /report command and either press buttons or send a date. That will call the Azure function, provide answers and return the user into the <code>Start</code> state. Actually on any message in the Report state user will trigger the action. You could prevent it by expanding the scenario with regex validation of the user input.</p>
<pre><code class="lang-XML"><span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Report"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"today"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"http://contoso.azurewebsites.net/api/reports/daily?day"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"yesterday"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"http://contoso.azurewebsites.net/api/reports/daily?day=yesterday"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"@(0?[1-9]|[12][0-9]|3[01])[\/\-](0?[1-9]|1[012])[\/\-]\d{4}"</span> <span class="hljs-attr">morphology</span>=<span class="hljs-string">"msg"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"http://contoso.azurewebsites.net/api/reports/daily?day={msg}"</span>/&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">morphology</span>=<span class="hljs-string">"msg"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Report"</span>&gt;</span>Invalid date format.<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
</code></pre>
<p>All that is left is to <a target="_blank" href="https://www.api.chat/blog/create-telegram-chatbot-in-api-chat">connect</a> API.chat chatbot to a telegram, and test it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616426765401/4CmPA2zRb.png" alt="telegram demo" /></p>
<h2 id="future-usage">Future usage</h2>
<p>From this point, the chatbot might be expanded even more. More complex analytics reports, more data, generated charts images - anything that you might need to monitor your Azure API Management usage.</p>
<p>One of the obvious improvements - rewrite GetDailyReports as an asynchronous HTTP Request, because building the report might take a lot of time if you have many users.</p>
]]></content:encoded></item><item><title><![CDATA[Connecting Azure API Management, Azure AD B2C, and Paddle with .net 5 Razor App, Part 2]]></title><description><![CDATA[This is part 2 of the two-parts article on how I wrapped my pre-existing internal chatbot builder API with Azure API Management and monetize it with Paddle.
Check Part 1 here 

Introduction
Understand how to provide and monetize our API 
Delegate Sig...]]></description><link>https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-2</link><guid isPermaLink="true">https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-2</guid><category><![CDATA[Azure]]></category><category><![CDATA[C#]]></category><category><![CDATA[Web API]]></category><category><![CDATA[ASP.NET]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 02 Mar 2021 22:09:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613575165937/CYu67LvNs.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is part 2 of the two-parts article on how I wrapped my pre-existing internal chatbot builder API with Azure API Management and monetize it with Paddle.</p>
<p>Check Part 1 <a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1-ckkn68uhg00v81qs13yf15565">here</a> </p>
<ol>
<li><a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1#introduction">Introduction</a></li>
<li><a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1#1-introduction-to-aam-and-how-to-monetize-apis-with-it">Understand how to provide and monetize our API</a> </li>
<li><a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1#2-delegate-sign-in-and-sign-up-to-azure-ad-b2c">Delegate SignIn/SignUp and write a code to process and redirect AAM sign-in to AAD B2C</a></li>
<li><a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1#3-use-azure-sdk-to-create-and-authorize-users">Use Azure SDK REST API to create and authorize users</a></li>
<li><a class="post-section-overview" href="#delegate-subscription-management-and-write-your-own-custom-subscription-process-with-paddle">Delegate subscription management and write your own custom subscription process with Paddle.</a></li>
<li><a class="post-section-overview" href="#use-azure-sdk-rest-api-to-manage-subscriptions">Use Azure SDK REST API to manage subscriptions</a></li>
<li><a target="_blank" href="delegate-all-other-operations-like-changepassword-changeprofile-closeaccount-signout-renew">Delegate all other operations like <em>ChangePassword, ChangeProfile,         CloseAccount, SignOut, Renew</em></a></li>
<li>Stick with as little code as possible</li>
<li>Open source <a target="_blank" href="https://github.com/API-chat/ApiChat.Web">solution app</a> to save you some time</li>
</ol>
<p>Before diving into part 2, I would like to expand Part 1 a little with recent findings.</p>
<h2 id="azure-api-management-delegation-verification-formulas">Azure API Management Delegation verification formulas</h2>
<p>When you delegate operations it's recommended to verify that the request is coming from Azure API Management. To do so you need to calculate the HMAC-SHA512 hash. The problem here that <a target="_blank" href="https://docs.microsoft.com/en-US/azure/api-management/api-management-howto-setup-delegation">documentation</a> provides hash formulas only for two operations, while there are at least four different formulas needed.</p>
<p>Azure API Management delegate the next operations: </p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> Operations
{
    SignUp,
    SignIn,
    ChangePassword,
    ChangeProfile,
    CloseAccount,
    SignOut,
    Subscribe,
    Unsubscribe,
    Renew
}
</code></pre>
<p>For validation purposes, these operations can be split into four blocks with corresponding formulas</p>
<p><strong>SignIn, SignUp</strong></p>
<blockquote>
<p>HMAC(salt + '\n' + returnUrl)</p>
</blockquote>
<p><strong>ChangePassword, ChangeProfile, SignOut, CloseAccount</strong></p>
<blockquote>
<p>HMAC(salt + '\n' + userId)</p>
</blockquote>
<p><strong>Subscribe</strong></p>
<blockquote>
<p>HMAC(salt + '\n' + productId + '\n' + userId)</p>
</blockquote>
<p>Renew, Unsubscribe</p>
<blockquote>
<p>HMAC(salt + '\n' + subscriptionId)</p>
</blockquote>
<h2 id="github-user-api">GitHub user API</h2>
<p>AAD B2C GitHub auth does not give any information about a user (which is odd, probably due to missing claims or bindings). Luckily after auth, we get a token we can use to request user data from GitHub user API. The minimal required information for new AAM users is email, name, and surname. </p>
<p>First, we need to ensure that our sign-up flow returns the Identity provider token
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614705139635/ZxccIDvYD.png" alt="image.png" /></p>
<p>To get emails we will perform GET request on the <code>emails</code> endpoint</p>
<pre><code><span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> GitHubApiForEmail = <span class="hljs-string">"https://api.github.com/user/emails"</span>;

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">FindGitHubEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> accessToken, <span class="hljs-keyword">string</span> email</span>)</span>
{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(accessToken))
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentNullException(<span class="hljs-keyword">nameof</span>(accessToken));

    <span class="hljs-keyword">var</span> client = <span class="hljs-keyword">new</span> RestClient(GitHubApiForEmail);
    <span class="hljs-keyword">var</span> request = <span class="hljs-keyword">new</span> RestRequest(Method.GET);
    request.AddHeader(<span class="hljs-string">"Authorization"</span>, <span class="hljs-string">$"Basic <span class="hljs-subst">{Base64Encode(<span class="hljs-string">"xakpc:"</span> + accessToken)}</span>"</span>);
    <span class="hljs-keyword">var</span> gitResult = <span class="hljs-keyword">await</span> client.ExecuteAsync(request);

    <span class="hljs-keyword">if</span> (gitResult.IsSuccessful)
    {
        <span class="hljs-keyword">var</span> emails = JsonConvert.DeserializeObject&lt;List&lt;EmailGitHub&gt;&gt;(gitResult.Content);
        email = emails.First(o =&gt; o.primary == <span class="hljs-literal">true</span>).email;
    }

    <span class="hljs-keyword">return</span> email;
}
</code></pre><p><code>GitHubApiForEmail</code> will return all user email, only one of them will be marked as primary - we would use it.</p>
<p>To get token from claims</p>
<pre><code><span class="hljs-selector-tag">httpContext</span><span class="hljs-selector-class">.User</span><span class="hljs-selector-class">.FindFirst</span>(<span class="hljs-selector-tag">IdpAccessToken</span>)?<span class="hljs-selector-class">.Value</span>
</code></pre><h2 id="filling-user-profile-with-additional-data">Filling user profile with additional data</h2>
<p>I found out that by default Azure AD B2C fills nothing in the extended user profile. At least we need an email. If a user created a new account email is stored in the <code>User Principal Name</code>. But if GitHub is used for registration - it does not store at all. That limits my possibilities to reach users if needed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614705621565/2hbfeWsVR.png" alt="image.png" /></p>
<p>To fix it I need to use Microsoft.Graph API to manually fill parameters after a user is created.</p>
<p>That's how you can do it</p>
<h3 id="setup-secret-key-for-app-in-azure-ad-b2c-tenant-and-give-it-readall-rights">Setup secret key for App in Azure AD B2C Tenant and give it ReadAll rights</h3>
<p>We already have an application we use for AAD B2C auth flows - we only need to create a secret key and give proper rights to it.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613574299294/5FM-76-IM.png" alt="image.png" /></p>
<p>So we add new client secret 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613574400265/axky-YBNE.png" alt="image.png" /></p>
<p>And read/write access to Azure Graph. Don't forget to press <em>Grant admin consent</em>, because we use here  <a target="_blank" href="https://docs.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#ClientCredentialsProvider">Client Credentials Provider</a> for backend-only authentication.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613574467531/CjH-F_yS3.png" alt="image.png" /></p>
<h3 id="add-nuget-packages">Add NuGet Packages</h3>
<p>To work with Graph we need to install two packages</p>
<ol>
<li>Microsoft.Graph</li>
<li>Microsoft.Graph.Auth - this one is available only in preview, so check <em>Include prerelease</em> or use <code>Install-Package Microsoft.Graph.Auth -PreRelease</code></li>
</ol>
<h3 id="create-client-and-update-user-after-aam">Create client and update user after AAM</h3>
<p>Rest you can easily peek from the <a target="_blank" href="https://docs.microsoft.com/en-us/graph/api/user-update?view=graph-rest-1.0&amp;tabs=http">docs</a></p>
<h4 id="build-auth-provider">Build auth provider</h4>
<pre><code>_confidentialClientApplication = ConfidentialClientApplicationBuilder
                .<span class="hljs-keyword">Create</span>(<span class="hljs-keyword">configuration</span>["AzureAdB2C:ClientId"])
                .WithTenantId(<span class="hljs-keyword">configuration</span>["AzureAdB2C:TenantId"])
                .WithClientSecret(<span class="hljs-keyword">configuration</span>["AzureAdB2C:ClientSecret"])
                .Build();
ClientCredentialProvider authProvider = <span class="hljs-built_in">new</span> ClientCredentialProvider(_confidentialClientApplication);
</code></pre><h4 id="create-a-user-to-patch">Create a user to patch</h4>
<pre><code>var <span class="hljs-keyword">user</span> = <span class="hljs-built_in">new</span> <span class="hljs-keyword">User</span>
{
    Mail = parameters.Email,                           
};
</code></pre><h4 id="do-update">Do update</h4>
<pre><code><span class="hljs-selector-tag">await</span> <span class="hljs-selector-tag">graphClient</span><span class="hljs-selector-class">.Users</span><span class="hljs-selector-attr">[userId]</span>
                    <span class="hljs-selector-class">.Request</span>()
                    <span class="hljs-selector-class">.UpdateAsync</span>(<span class="hljs-selector-tag">user</span>);
</code></pre><p>Now we have all the required information about users, let's move to subscriptions.</p>
<h1 id="delegate-subscription-management-and-write-your-own-custom-subscription-process-with-paddle">Delegate subscription management and write your own custom subscription process with Paddle.</h1>
<p> <a target="_blank" href="https://docs.microsoft.com/en-Us/azure/api-management/api-management-howto-setup-delegation#-delegating-product-subscription">Subscription delegation</a>  starts with the same delegation endpoint where you can perform a redirect based on the operation.</p>
<blockquote>
<p>http://www.yourwebsite.com/apimdelegation?operation={operation}&amp;productId={product to subscribe to}&amp;userId={user making request}&amp;salt={string}&amp;sig={string}</p>
</blockquote>
<p>Based on the type of operation we could prepare a redirect link and navigate the user to the subscription management page. </p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> operation = Enum.Parse&lt;Operations&gt;(Request.Query[<span class="hljs-string">"operation"</span>].FirstOrDefault());
    <span class="hljs-keyword">var</span> returnUrl = Request.Query[<span class="hljs-string">"returnUrl"</span>].FirstOrDefault();

    <span class="hljs-keyword">if</span> (!ValidateHmac(operation, returnUrl))
    {
        <span class="hljs-keyword">return</span> Unauthorized();
    }

    <span class="hljs-keyword">switch</span> (operation)
    {
        ...
        <span class="hljs-keyword">case</span> Operations.Subscribe:
        <span class="hljs-keyword">case</span> Operations.Unsubscribe:
            <span class="hljs-keyword">var</span> urlPaddle = <span class="hljs-keyword">new</span> UriBuilder(<span class="hljs-string">"https"</span>, host)
            {
                Path = <span class="hljs-string">"PaddlePay"</span>,
                Query = Request.QueryString.Value
            };
            <span class="hljs-keyword">return</span> Redirect(urlPaddle.Uri.ToString());
        <span class="hljs-keyword">default</span>:
            <span class="hljs-keyword">break</span>;
    }
}
</code></pre><p>PaddlePay page is the only page of the project that actually contains the frontend part. This is done because I use <a target="_blank" href="https://developer.paddle.com/guides/how-tos/checkout/paddle-checkout">Paddle Overlay Checkout</a>.
But the frontend is extremely simple</p>
<pre><code><span class="hljs-meta">@page</span>
<span class="hljs-meta">@model</span> ApiChat.Web.Auth.Pages.PaddlePayModel
@{
    Layout = <span class="hljs-string">"_LayoutBack"</span>;
}

&lt;div <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">checkout</span>-<span class="hljs-title">container</span>"&gt;&lt;<span class="hljs-type">/div</span>&gt;</span>

@{
}
</code></pre><h2 id="subscribe">Subscribe</h2>
<p>On subscribe, AAM provides you with a product name, which should be matched to the Paddle Subscription Plan.</p>
<p>AAM Products
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614711130108/IaQBwh8sg.png" alt="image.png" /></p>
<p>Paddle Subscription Plans
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614711823292/b2hAsb1wA.png" alt="image.png" /></p>
<p>We should manually match Paddle Id with product name, for example through the dictionary</p>
<pre><code><span class="hljs-keyword">if</span> (operation == Operations.Subscribe)
{
    UserId = HttpContext.User.FindFirst(SignInDelegationModel.NameIdentifierSchemas).Value;
    <span class="hljs-keyword">if</span> (string.IsNullOrWhiteSpace(UserId)) <span class="hljs-keyword">return</span> Unauthorized();

    Product = Request.Query[<span class="hljs-string">"productId"</span>].FirstOrDefault();
    <span class="hljs-keyword">if</span> (string.IsNullOrWhiteSpace(Product)) <span class="hljs-keyword">return</span> BadRequest(Product);
    ProductId = Products[Product];
    <span class="hljs-keyword">if</span> (ProductId == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> BadRequest(Product);
    ...
</code></pre><p>Then we need to extract user email and optionally user Country to make the Paddle experience a bit smoother</p>
<pre><code>    ...
    <span class="hljs-keyword">var</span> user = <span class="hljs-keyword">await</span> _apiManagementService.GetUser(UserId);
    Email = user.Email;

    <span class="hljs-keyword">var</span> country = HttpContext.User.FindFirst(<span class="hljs-string">"country"</span>)?.Value;
    <span class="hljs-keyword">if</span> (country != <span class="hljs-literal">null</span>)
    {
        <span class="hljs-keyword">var</span> regions = CultureInfo.GetCultures(CultureTypes.SpecificCultures).Select(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> <span class="hljs-keyword">new</span> RegionInfo(x.LCID));
        Region = regions.FirstOrDefault(<span class="hljs-function"><span class="hljs-params">region</span> =&gt;</span> region.EnglishName.Contains(country))?.TwoLetterISORegionName;
    }
}
</code></pre><p>All this information will be passed to the JavaScript function <code>OpenPaddle</code>. You can check more about parameters <a target="_blank" href="https://developer.paddle.com/reference/paddle-js/parameters">here</a></p>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">OpenPaddle</span>(<span class="hljs-params">ProductId, UserId, ProductName, SuccessUrl, Email, Region</span>) </span>{
        Paddle.Checkout.open({
            <span class="hljs-attr">product</span>: ProductId,
            <span class="hljs-attr">email</span>: Email,
            <span class="hljs-attr">country</span>: Region,
            <span class="hljs-attr">passthrough</span>: UserId + <span class="hljs-string">':'</span> + ProductName,
            <span class="hljs-attr">allowQuantity</span>: <span class="hljs-literal">false</span>,
            <span class="hljs-attr">disableLogout</span>: <span class="hljs-literal">true</span>,
            <span class="hljs-attr">success</span>: SuccessUrl,
            <span class="hljs-attr">closeCallback</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">data</span>) </span>{
                <span class="hljs-built_in">console</span>.log(data.eventData);
                history.back();
            },
        });
    }
</code></pre>
<p>And to setup</p>
<pre><code>@<span class="hljs-symbol">:SetupPaddle</span>(@Model.VendorPaddle);
@<span class="hljs-symbol">:OpenPaddle</span>(@Model.ProductId, <span class="hljs-string">'@Model.UserId'</span>, <span class="hljs-string">'@Model.Product'</span>, <span class="hljs-string">'@Model.ProfileUrl'</span>, <span class="hljs-string">'@Model.Email'</span>, <span class="hljs-string">'@Model.Region'</span>);
</code></pre><p>Full PaddlePay page <a target="_blank" href="https://github.com/API-chat/ApiChat.Web/blob/main/ApiChat.Web.Auth/Pages/PaddlePay.cshtml">source code</a>.</p>
<h3 id="handle-subscription-success">Handle Subscription Success</h3>
<p>To handle subscription successes you need to add a webhook - that would be the APIController POST method. This method will handle paddle answers and will use already familiar to you  <a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1#32-azure-resource-manager-arm-usage">ApiManagementClient </a> to create or update subscription.</p>
<pre><code>[<span class="hljs-meta">HttpPost</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">Post</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> request = <span class="hljs-keyword">new</span> PaddleWebhookRequest(Request.Form);

    <span class="hljs-keyword">var</span> productId = request.Passthrough.Split(<span class="hljs-string">':'</span>).Last();
    <span class="hljs-keyword">var</span> userId = request.Passthrough.Split(<span class="hljs-string">':'</span>).First();

    <span class="hljs-comment">// subscription processing moved to background worker for quick reply</span>
    _backgroundQueue.Enqueue(<span class="hljs-keyword">async</span> ct =&gt;
    {
    Trace.TraceInformation(<span class="hljs-string">$"Subscription alert: <span class="hljs-subst">{request.AlertId}</span>|<span class="hljs-subst">{request.AlertName}</span>|<span class="hljs-subst">{request.Status}</span>; User:<span class="hljs-subst">{request.UserId}</span>; Sub:<span class="hljs-subst">{request.SubscriptionId}</span>; Plan:<span class="hljs-subst">{request.SubscriptionPlanId}</span>"</span>);
    <span class="hljs-keyword">switch</span> (request.AlertName)
    {
        <span class="hljs-keyword">case</span> <span class="hljs-string">"subscription_created"</span>:
            <span class="hljs-keyword">if</span> (request.Status == <span class="hljs-string">"active"</span>)
            {
                <span class="hljs-keyword">await</span> AddOrUpdateProductSubscribtion(productId, userId, request.SubscriptionId, SubscriptionState.Active);
            }
            <span class="hljs-keyword">break</span>;
     ...
      }
   }
}
</code></pre><h2 id="unsubscribe">Unsubscribe</h2>
<p>Unsubscribe works similarly, but all that you have here is a <code>subscriptionId</code>. When you created a subscription earlier, you used the subscriptionId that Paddle provided. And to cancel it we need a cancel URL. </p>
<p>Usually, it comes back with a subscription webhook and should be stored, but as another option, you can request it separately for every user from Paddle API.</p>
<pre><code><span class="hljs-keyword">if</span> (operation == Operations.Unsubscribe)
{
    <span class="hljs-keyword">var</span> subscriptionId = Request.Query[<span class="hljs-string">"subscriptionId"</span>].FirstOrDefault();

    <span class="hljs-keyword">if</span> (string.IsNullOrWhiteSpace(subscriptionId)) <span class="hljs-keyword">return</span> BadRequest(subscriptionId);
    <span class="hljs-keyword">var</span> user = await _paddleService.GetSubscriptionUsers(subscriptionId);

    <span class="hljs-keyword">if</span> (user == <span class="hljs-literal">null</span>) <span class="hljs-keyword">return</span> BadRequest(subscriptionId);

    CancelUrl = WebUtility.UrlDecode(user.cancel_url);
}
</code></pre><h1 id="use-azure-sdk-rest-api-to-manage-subscriptions">Use Azure SDK REST API to manage subscriptions</h1>
<p>Here I will only show you the required methods to manage subscriptions, it's all standard.</p>
<p>To Subscribe you need to create and set Subscription as Active</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">SubscribeAsync</span><span class="hljs-params">(<span class="hljs-built_in">string</span> productId, <span class="hljs-built_in">string</span> userId, <span class="hljs-built_in">string</span> subscriptionId)</span>
</span>{
    <span class="hljs-keyword">return</span> AddOrUpdateProductSubscribtion(productId, userId, subscriptionId, SubscriptionState.Active);
}
</code></pre><p>To Cancel you need to set the Subscription state as Cancelled.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">UnsubscribeAsync</span><span class="hljs-params">(<span class="hljs-built_in">string</span> productId, <span class="hljs-built_in">string</span> userId, <span class="hljs-built_in">string</span> subscriptionId)</span>
</span>{
    <span class="hljs-keyword">return</span> AddOrUpdateProductSubscribtion(productId, userId, subscriptionId, SubscriptionState.Cancelled);
}
</code></pre><p>And finally, we do an Upsert operation</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SubscribtionCreateOrUpdateAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> sid, SubscriptionCreateParameters parameters</span>)</span>
        {
            <span class="hljs-keyword">try</span>
            {
                <span class="hljs-keyword">var</span> apiManagement = <span class="hljs-keyword">new</span> ApiManagementClient(<span class="hljs-keyword">new</span> TokenCredentials(_tokenProvider))
                {
                    SubscriptionId = _subscriptionId
                };

                <span class="hljs-keyword">await</span> apiManagement.Subscription.CreateOrUpdateAsync(_resourceGroupName, _serviceName, sid, parameters, <span class="hljs-literal">true</span>);
            }
            <span class="hljs-keyword">catch</span> (Exception ex)
            {
                Trace.TraceInformation(ex.ToString());
                <span class="hljs-keyword">throw</span>;
            }
        }
</code></pre><h1 id="delegate-all-other-operations-like-changepassword-changeprofile-closeaccount-signout-renew">Delegate all other operations like <em>ChangePassword, ChangeProfile,         CloseAccount, SignOut, Renew</em></h1>
<p>Most of these operations will be handled by Azure AD B2C. CloseAccount and Renew are not handled by me for now, but I will probably add them in the future.</p>
<p>All these operations can be handled by proper redirection by our auth web app.</p>
<pre><code><span class="hljs-keyword">switch</span> (operation)
{
    <span class="hljs-keyword">case</span> Operations.ChangePassword:
        <span class="hljs-keyword">return</span> Redirect($<span class="hljs-string">"/MicrosoftIdentity/Account/ResetPassword"</span>);
    <span class="hljs-keyword">case</span> Operations.ChangeProfile:
        <span class="hljs-keyword">return</span> Redirect($<span class="hljs-string">"/MicrosoftIdentity/Account/EditProfile"</span>);
    <span class="hljs-keyword">case</span> Operations.CloseAccount:
        <span class="hljs-keyword">break</span>;
    <span class="hljs-keyword">case</span> Operations.SignOut:
        <span class="hljs-keyword">return</span> Redirect($<span class="hljs-string">"/MicrosoftIdentity/Account/SignOut"</span>);
}
</code></pre><p>The only thing I want to add here is a SignOut. By default, if you use MicrosoftIdentity NuGet SignOut will redirect you to the <code>/SignedOut</code> page. </p>
<p>You don't need it, because you already have a default signed-out page of the AAM: developer portal. </p>
<p>To override this redirect you should replace <code>SignedOut.cshtml</code>, you can do it by adding a file with the same name into the same directory, like this
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1614719692386/f6m7n7N_K.png" alt="image.png" />
It will produce a warning message </p>
<pre><code><span class="hljs-attribute">Warning</span>    CS<span class="hljs-number">0436</span>    The type 'Areas_MicrosoftIdentity_Pages_Account_SignedOut' in 'C:\Users\...\ApiChat.Web.Auth\obj\Debug\net<span class="hljs-number">5</span>.<span class="hljs-number">0</span>\Razor\Areas\MicrosoftIdentity\Pages\Account\SignedOut.cshtml.g.cs' conflicts with the imported type 'Areas_MicrosoftIdentity_Pages_Account_SignedOut' in 'Microsoft.Identity.Web.UI.Views, Version=<span class="hljs-number">1.6.0.0</span>, Culture=neutral, PublicKeyToken=<span class="hljs-number">0</span>a<span class="hljs-number">613</span>f<span class="hljs-number">4</span>dd<span class="hljs-number">989</span>e<span class="hljs-number">8</span>ae'. 
<span class="hljs-attribute">Using</span> the type defined in 'C:\Users\...\ApiChat.Web.Auth\obj\Debug\net<span class="hljs-number">5</span>.<span class="hljs-number">0</span>\Razor\Areas\MicrosoftIdentity\Pages\Account\SignedOut.cshtml.g.cs'.    
<span class="hljs-attribute">ApiChat</span>.Web.Auth    C:\Users\...\ApiChat.Web.Auth\obj\Debug\net<span class="hljs-number">5</span>.<span class="hljs-number">0</span>\Razor\Areas\MicrosoftIdentity\Pages\Account\SignedOut.cshtml.g.cs
</code></pre><p>But on the run when SignOut, it will be redirected to your page which should contain a redirect to the AAM developer portal</p>
<pre><code><span class="hljs-keyword">public</span> IActionResult OnGet()
{
    <span class="hljs-keyword">return</span> Redirect(AamHome.ToString());
}
</code></pre><h1 id="conclusion">Conclusion</h1>
<p>It took me and my team almost 2 months to learn everything and create a fully operational API with Azure B2C AD, API Management, GitHub, and Paddle for <a target="_blank" href="https://api.chat">API.chat</a>. </p>
<p>Hope it saves you some time.</p>
]]></content:encoded></item><item><title><![CDATA[Write and Read Google Spreadsheet from Telegram bot with Google Cloud Functions]]></title><description><![CDATA[Recently I faced a request

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 the...]]></description><link>https://xakpc.info/write-and-read-google-spreadsheet-from-telegram-bot-with-google-cloud-functions</link><guid isPermaLink="true">https://xakpc.info/write-and-read-google-spreadsheet-from-telegram-bot-with-google-cloud-functions</guid><category><![CDATA[chatbot]]></category><category><![CDATA[google cloud]]></category><category><![CDATA[google sheets]]></category><category><![CDATA[serverless]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Thu, 18 Feb 2021 17:50:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613670455547/FEONztOOP.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Recently I faced a request</p>
<blockquote>
<p>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?</p>
</blockquote>
<p>The answer is yes, and here is how to do that with Google Cloud Functions and API.chat chatbot platform in <strong>under 100 lines of code</strong>.</p>
<h2 id="setup-google-functions">Setup Google Functions</h2>
<p>First, you need to set up cloud functions and give them access to our document. </p>
<p>Overall you will need two functions: <code>gsheet-get</code> to provide answers and <code>gsheet-add</code> to register users. Let's create them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613663747484/Fas9Hx7_q.png" alt="image.png" /></p>
<p>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 <a target="_blank" href="https://github.com/googleapis/google-api-nodejs-client#service-account-credentials">server to server, app-level authentication</a>  later.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613663799653/PyiIOpK2x.png" alt="image.png" /></p>
<p>But you do need to give access rights to our document to this account.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613662238923/jaVl7Falj.png" alt="image.png" /></p>
<p>The document itself is quite simple 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613663879198/fqWuUfk7H.png" alt="image.png" /></p>
<h2 id="function-google-sheet-add">Function <code>google-sheet-add</code></h2>
<p>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 <code>.net core 3.1</code>, but I found out that node.js gives more compact code, so let's stick to it, and sorry for <em>dotnetish</em> code-style</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> { google } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'googleapis'</span>);

<span class="hljs-built_in">exports</span>.main = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">let</span> auth = <span class="hljs-keyword">await</span> google.auth.getClient({ <span class="hljs-attr">scopes</span>: [<span class="hljs-string">'https://www.googleapis.com/auth/spreadsheets'</span>] });  
  <span class="hljs-keyword">let</span> sheets = google.sheets({ <span class="hljs-attr">version</span>: <span class="hljs-string">'v4'</span>, auth });
  <span class="hljs-keyword">try</span>
  {
    <span class="hljs-keyword">await</span> sheets.spreadsheets.values.append({
      <span class="hljs-attr">spreadsheetId</span>: <span class="hljs-string">"INSERT-SHEET-ID-HERE"</span>,
      <span class="hljs-attr">range</span>: <span class="hljs-string">"Days!A:C"</span>,
      <span class="hljs-attr">valueInputOption</span>: <span class="hljs-string">"RAW"</span>,
      <span class="hljs-attr">insertDataOption</span>: <span class="hljs-string">"INSERT_ROWS"</span>,
      <span class="hljs-attr">resource</span>: {
        <span class="hljs-attr">values</span>: [
          [req.query.channel, req.query.id, req.query.name]
        ],
      },
      <span class="hljs-attr">auth</span>: auth
    });    
    res.status(<span class="hljs-number">200</span>).send();
  }
  <span class="hljs-keyword">catch</span> (err)
  {
    res.status(<span class="hljs-number">500</span>).send({ err })
  }
};
</code></pre>
<p>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.</p>
<p>Don't forget to add <code>googleapis</code> dependency into <code>package.json</code></p>
<pre><code>{
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"googleapis"</span>: <span class="hljs-string">"^42"</span>
  }
}
</code></pre><p>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.</p>
<p>That will give you all the required information about the user and you could start calculating his day-offs.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613665878737/i_tzh-dEv.png" alt="image.png" /></p>
<h2 id="function-google-sheet-get">Function <code>google-sheet-get</code></h2>
<p>The second function is for getting data from Google Spreadsheet. </p>
<pre><code><span class="hljs-keyword">const</span> { google } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'googleapis'</span>);

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

    <span class="hljs-keyword">const</span> rows = sheet.data.values;
    <span class="hljs-keyword">if</span> (rows.length) {
      <span class="hljs-keyword">let</span> finded = rows.find(<span class="hljs-function"><span class="hljs-params">el</span> =&gt;</span> el[<span class="hljs-number">1</span>] === chatId)      
      <span class="hljs-keyword">if</span> (finded === <span class="hljs-literal">undefined</span>) {     
        res.status(<span class="hljs-number">200</span>).send({ <span class="hljs-attr">CorrelationId</span>: req.query.request, <span class="hljs-attr">Messages</span>: [ <span class="hljs-string">"Information not availible"</span> ] });
      } <span class="hljs-keyword">else</span> {      
        res.status(<span class="hljs-number">200</span>).send({ <span class="hljs-attr">CorrelationId</span>: req.query.request, <span class="hljs-attr">Messages</span>: [ <span class="hljs-string">`You have <span class="hljs-subst">${finded[<span class="hljs-number">3</span>]}</span> days off`</span> ] });
      }
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'No data found.'</span>);
      res.status(<span class="hljs-number">200</span>).send({ <span class="hljs-attr">CorrelationId</span>: req.query.request, <span class="hljs-attr">Messages</span>: [ <span class="hljs-string">"No data found"</span> ] });
    }
    res.status(<span class="hljs-number">200</span>).send();
  }
  <span class="hljs-keyword">catch</span>  (err)
  {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'The API returned an error: '</span> + err)
    res.status(<span class="hljs-number">500</span>).send({ err })
  }
};
</code></pre><p>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</p>
<pre><code>{
  <span class="hljs-attr">"Messages"</span>: [
    <span class="hljs-string">"You have 7 days off"</span>
  ]
}
</code></pre><h2 id="chatbot-scenario">Chatbot Scenario</h2>
<p>Now when you have all endpoints in place and working you can create an actual chatbot. I will use my very own <a target="_blank" href="https://api.chat">chatbot builder API.chat</a>. Scenario might be very simple, like that </p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">bot</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">states</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Start"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"register"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registration"</span>&gt;</span>Please enter your name<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">pending_keyboard</span>=<span class="hljs-string">"Register Me"</span>&gt;</span>Hello. Please Register first<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>      
    <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Registration"</span>&gt;</span>      
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registered"</span> <span class="hljs-attr">morphology</span>=<span class="hljs-string">"msg"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://us-central1-REDACTED.cloudfunctions.net/gsheet-add?name={msg}"</span> <span class="hljs-attr">no_stop</span>=<span class="hljs-string">"true"</span>&gt;</span>Welcome!<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Registered"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"days"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registered"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://us-central1-REDACTED.net/gsheet-get"</span> <span class="hljs-attr">pending_keyboard</span>=<span class="hljs-string">"Days Off"</span>/&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registered"</span> <span class="hljs-attr">pending_keyboard</span>=<span class="hljs-string">"Days Off"</span>&gt;</span>Press the button to get your Days off<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">states</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">bot</span>&gt;</span>
</code></pre><p>What we do here is register the user, asking his or her name, and pass it into <code>gsheet-add</code> action in query. After registration user stays in the <code>Registered</code> state forever with the ability to request their days-off through <code>gsheet-get</code> action at any time.</p>
<p>All that's left is to PUT this scenario to the chatbot scenario endpoint and our bot is ready to deliver.</p>
<pre><code>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 '<span class="hljs-tag">&lt;<span class="hljs-name">bot</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">states</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Start"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"register"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registration"</span>&gt;</span>Please enter your name<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Start"</span> <span class="hljs-attr">pending_keyboard</span>=<span class="hljs-string">"Register Me"</span>&gt;</span>Hello. Please Register first<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>      
    <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Registration"</span>&gt;</span>      
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registered"</span> <span class="hljs-attr">morphology</span>=<span class="hljs-string">"msg"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://us-central1-REDACTED.cloudfunctions.net/gsheet-add?name={msg}"</span> <span class="hljs-attr">no_stop</span>=<span class="hljs-string">"true"</span>&gt;</span>Welcome!<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">state</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Registered"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"days"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registered"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://us-central1-REDACTED.cloudfunctions.net/gsheet-get"</span> <span class="hljs-attr">pending_keyboard</span>=<span class="hljs-string">"Days Off"</span>/&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">transition</span> <span class="hljs-attr">input</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">next</span>=<span class="hljs-string">"Registered"</span> <span class="hljs-attr">pending_keyboard</span>=<span class="hljs-string">"Days Off"</span>&gt;</span>Press the button to get your Days off<span class="hljs-tag">&lt;/<span class="hljs-name">transition</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">state</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">states</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">bot</span>&gt;</span>'
</code></pre><p>Let's count code lines</p>
<ul>
<li><code>google-sheet-add</code>: 25</li>
<li><code>google-sheet-get</code>: 30</li>
<li><code>scenario</code>: 15</li>
<li><strong>overall</strong>: 70 lines of code</li>
</ul>
<p>Not bad</p>
]]></content:encoded></item><item><title><![CDATA[Connecting Azure API Management, Azure AD B2C, and Paddle with .net 5 Razor App, Part 1]]></title><description><![CDATA[If you ever wish to wrap your API into a usable SaaS product with subscriptions, payments, plans, limitations, using no-code (or minimal code) feel free to check how I did this for my own product.
Full disclosure
I am a founder of API.chat and this a...]]></description><link>https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1</link><guid isPermaLink="true">https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-1</guid><category><![CDATA[Azure]]></category><category><![CDATA[APIs]]></category><category><![CDATA[ASP.NET]]></category><category><![CDATA[REST API]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Mon, 01 Feb 2021 22:55:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1612220647264/E2Qa2LeFB.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you ever wish to wrap your API into a usable SaaS product with subscriptions, payments, plans, limitations, using no-code (or minimal code) feel free to check how I did this for my own product.</p>
<h3 id="full-disclosure">Full disclosure</h3>
<p>I am a founder of <a target="_blank" href="https://api.chat">API.chat</a> and this article is based on my and my team's experience of connecting Azure API Management, Azure AD B2C, and Paddle for the needs of my product. I hope it will be useful for you if you ever want to monetize your APIs with Azure API Management and Paddle.</p>
<h3 id="please-note">Please, note</h3>
<ul>
<li>This article will not cover the setup of Azure API Management <em>(thereof AAM)</em>, you can read more about it <a target="_blank" href="https://docs.microsoft.com/en-us/azure/api-management/get-started-create-service-instance">here</a> </li>
<li>This article will not cover the setup of Azure AD B2C <em>(thereof  AAD B2C)</em>, you can read more about it <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant">here</a> </li>
<li>This article will not cover the setup of the Paddle</li>
</ul>
<p>Ok, now when all this out the way, let's begin.</p>
<h1 id="introduction">Introduction</h1>
<p>In 2015 Telegram introduced chatbots and I created the first version of XML-based Telegram chatbot builder. The idea was to give developers an easy way to build and host chatbots with XML-like language. 
Using our builder we built what I presume was <strong>the first in the world</strong> chatbot for selling apparel for Shopify (we called it "Siri for Shop" then) - which bring us victory on HackJunction hackathon in Helsinki, Finland in 2015. In the end, chatbots didn't become the future of e-commerce but rather took some small part in that market. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612193497765/L4-BzQust.png" alt="Photo from 2018, HackJunction become much bigger since 2014" /></p>
<p>Anyway, soon after that, we moved to B2E and for the past five years used and improved our platform by building chatbots for HR and internal communications (you can call it "Siri for Employees").</p>
<p>But the idea of providing the easy way never left me, and a couple of months ago I decided to open a platform to every developer who wants it (if any ;)). But our builder consists of 6 microservices, some queues, SQL, and no-SQL databases combined with minimal UI - purely for in-house usage. To implement the idea I would need to build an entire client-oriented web app by myself with subscription management, payments, tokens, plans, etc. And that's for a tool that might be not used by anyone except my team.</p>
<p>Luckily today no-code has become a mighty thing, and there are new types of no-code tools: API Management. AWS has one, Azure has one, and there are some independent solutions as well. I, as a proficient Azure user, decided to use whatever Microsoft provides.</p>
<p><strong> <a target="_blank" href="https://azure.microsoft.com/en-us/services/api-management/">Azure API Management</a> </strong></p>
<blockquote>
<p>Hybrid, a multi-cloud management platform for APIs across all environments
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610746960416/vPNPK8-43.png" alt="image.png" /></p>
</blockquote>
<p>It is a set of tools that claim that you can easily wrap your API, build a developers portal, subscription management, and much more. And that's kinda true.</p>
<p>The problem, like in many no-code tools, comes when you need to move from standard patterns to something not-supported by the platform - like in the case of AAM - payments and GitHub auth. Let's explore how we solved it and hopefully, you can as well.</p>
<h1 id="0-the-plan">0. The Plan</h1>
<p>The plan was quite simple:</p>
<ol>
<li><a class="post-section-overview" href="#1-introduction-to-aam-and-how-to-monetize-apis-with-it">Understand how to provide and monetize our API</a> </li>
<li><a class="post-section-overview" href="#2-delegate-sign-in-and-sign-up-to-azure-ad-b2c">Delegate SignIn/SignUp and write a code to process and redirect AAM sign-in to AAD B2C</a> (<a target="_blank" href="https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-setup-delegation"><em>docs</em></a>)</li>
<li><a class="post-section-overview" href="#3-use-azure-sdk-to-create-and-authorize-users">Use Azure SDK REST API to create and authorize users</a></li>
<li>Delegate subscription management and write your own custom subscription process with Paddle.</li>
<li>Use Azure SDK REST API to manage subscriptions</li>
<li>Delegate all other operations like <em>ChangePassword, ChangeProfile,         CloseAccount, SignOut,  Unsubscribe, Renew</em></li>
<li>Stick with as little code as possible</li>
<li>Open source <a target="_blank" href="https://github.com/API-chat/ApiChat.Web">solution app</a> to save you some time</li>
</ol>
<h1 id="1-introduction-to-aam-and-how-to-monetize-apis-with-it">1. Introduction to AAM and how to monetize APIs with it.</h1>
<p>To provide our APIs I decided to wrap it to an extra-layer that normalizes it, hides internal services, and presents a clean, properly versioned, and easy-to-use API from a single point. AAM provides us API gateway to do so.</p>
<p>It also must have convenient documentation, subscription management, reports, and the ability to test endpoint directly from the browser. AAM provides us with a developer portal filled with these features.</p>
<p>To monetize our APIs we stuck to <strong>Subscription Billing</strong>, where we charge customers a flat monthly fee to access APIs, according to the selected Plan. Plan (or Product in terms of AAM) will provide the limitations: a certain number of API calls per month, maximum users, a certain APIs access, and so on. </p>
<p>Most of these limitations can be defined in so-called <a target="_blank" href="https://docs.microsoft.com/en-us/azure/api-management/api-management-policies">Policies</a> - a powerful feature of the API Management platform. Not every limitation can be defined through policy though: for example, we were forced to add max users limit manually.</p>
<p>Users can subscribe to a Plan/Product and get access to APIs on the Product terms. By default AAM allows users to request a subscription and allows the administrator (you) to accept or decline it. And you can create a Plan/Product that can be accepted by default, like for free or trial. To be honest, that's more than enough to start - just get subscription requests, bill users manually, and approve the access.</p>
<p>If you want more self-service here, like us, you need to delegate subscription management to third-party service, and that's where the hard part begins. You need a preferably out-a-box solution to process payments, manage recurrent payments and subscriptions. Stripe is a nice option, or Paddle if the first one is not supported in your area.</p>
<p>To make it all click together you need to match your Subscriptions in payment providers to your Products in AAM. Next, you will need to set up delegation redirects and callbacks to process subscribe, unsubscribe, renew. And that can't be done without setting up sign-in and sign-up delegation first.</p>
<p>The overall solution might look like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610747367857/VNl0ClySc.png" alt="Solution.png" /></p>
<p>You can read a bit more about it <a target="_blank" href="https://azure.microsoft.com/en-us/blog/how-to-monetize-apis-with-azure-api-management/">here</a> </p>
<h1 id="2-delegate-sign-in-and-sign-up-to-azure-ad-b2c">2. Delegate sign-in and sign-up to Azure AD B2C.</h1>
<p>To allow developers to sign-in and sign-up through GitHub you need to delegate these processes from AAM to a third-party web-app, your web-app. To save a lot of time we used AAD B2C for user management in the web-app. We could use AAD B2C directly in AAM, but to delegate subscriptions management, we need to delegate sign-in anyway.</p>
<p>As an in-between service, I chose a simple .net razor app. In fact, .net  <a target="_blank" href="https://github.com/AzureAD/microsoft-identity-web/wiki#getting-started-with-microsoft-identity-web">provides</a> ready-to-use template of a web-app with built-in AAD B2C auth. </p>
<h2 id="21-create-in-between-service-from-template">2.1 Create in-between service from template</h2>
<p>To create a web-app from the template you should use the <code>dotnet new</code> command</p>
<p>First, install templates from <a target="_blank" href="https://www.nuget.org/packages/Microsoft.Identity.Web.ProjectTemplates/">NuGet</a> </p>
<pre><code><span class="hljs-selector-tag">dotnet</span> <span class="hljs-selector-tag">new</span> <span class="hljs-selector-tag">--install</span> <span class="hljs-selector-tag">Microsoft</span><span class="hljs-selector-class">.Identity</span><span class="hljs-selector-class">.Web</span><span class="hljs-selector-class">.ProjectTemplates</span><span class="hljs-selector-pseudo">::1.5.1</span>
</code></pre><p>Next, create a new app from the template </p>
<pre><code>dotnet <span class="hljs-built_in">new</span> AuthApp <span class="hljs-comment">--auth IndividualB2C</span>
</code></pre><p>Don't forget to fill in AAD B2C settings in <code>appsettings.json</code>. I assume that you already created AAD B2C - there are  <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-tenant">good manuals</a> on that</p>
<pre><code>  <span class="hljs-string">"AzureAdB2C"</span>: {
    <span class="hljs-string">"Instance"</span>: <span class="hljs-string">"https://contoso.b2clogin.com/"</span>,
    <span class="hljs-string">"ClientId"</span>: <span class="hljs-string">"11111111-1111-1111-1111-111111111111"</span>,
    <span class="hljs-string">"CallbackPath"</span>: <span class="hljs-string">"/signin-oidc"</span>,
    <span class="hljs-string">"Domain"</span>: <span class="hljs-string">"contoso.onmicrosoft.com"</span>,
    <span class="hljs-string">"SignUpSignInPolicyId"</span>: <span class="hljs-string">"B2C_1_Default"</span>,
    <span class="hljs-string">"ResetPasswordPolicyId"</span>: <span class="hljs-string">"B2C_1_Password_Reset"</span>,
    <span class="hljs-string">"EditProfilePolicyId"</span>: <span class="hljs-string">"B2C_1_Profile_Edit"</span>
  }
</code></pre><p>That will give you a fully-function AAD B2C app with sign-in, sign-out, profile, and password reset features. Don't mind the design or content of the app - it will not be shown in 80% of cases. I think it might be even possible to reduce this app to a couple of controllers without any visuals whats so ever.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612197552908/UQ0ouPo75.png" alt="items" /></p>
<p>To ensure the app works properly with AAM you need to do an additional setup of AAD B2C and its user-flows</p>
<ul>
<li>In your <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-applications?tabs=app-reg-ga">registered App</a> page, on the Authentication tab, you should add the URL of your localhost test server. You can add it as Web or SPA - both will work fine (at least for me). You can add a production URL as well if it's ready.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612198192162/mIoIpUk1c.png" alt="image.png" /></li>
</ul>
<p>Test URL can be seen in in <code>Properties/Debug/Web Service Settings</code> of your project
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612198260682/XD19O4lTL.png" alt="image.png" /></p>
<ul>
<li>Ensure that you <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/identity-provider-github?WT.mc_id=Portal-Microsoft_AAD_B2CAdmin&amp;pivots=b2c-user-flow">add GitHub</a> identity provider.</li>
<li>Now it's a good time to setup Company Branding as well</li>
<li>You should  <a target="_blank" href="https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-create-user-flows">create user-flows</a> at this point already, but for AAM we need setup it a bit more.</li>
<li>Your sign-in user-flow should have GitHub as an identity provider
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612199139753/LvRvkJITF.png" alt="image.png" /></li>
<li>AAM <strong>requires</strong> Name and Surname not to be null, and because you add GitHub as identity provider - you need to set relevant claims as well.
These are user attributes that are required for AAM for sign-up (Country is not required, you can omit it)
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612198978311/YEJ_w1wPT.png" alt="image.png" />
And these are claims that I return on sign-up and profile edit user-flows (Country is optional)
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612199386537/dtA9lj6Lw.png" alt="image.png" /></li>
</ul>
<h3 id="hint">Hint</h3>
<p>What I like to do with user-flows is to create two of them for sign-in/sign-up: for prod and for debug, and disable email validation for debugging. It saves a lot of time in testing. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612200047557/IKm68118Y.png" alt="image.png" /></p>
<p>To do so you need to create a new user-flow, get into <em>Page Layouts</em> of <em>Local Sign-Up</em> and disable email validation (set it to No)
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612200119171/bv-KRswqE.png" alt="image.png" />
Note that this setting will reset every time you save this user-flow for some reason, so you need to disable it on every change.</p>
<p>You can sign in, sign up and do all other user-related stuff.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612197680338/do-ghxVQv.png" alt="page" /></p>
<h2 id="22-add-delegation-handler">2.2 Add delegation handler</h2>
<p>There are several ways to add a callback handler for delegation. The easiest way IMO is to add the Razor page with the backend and handle everything in the <code>OnGet()</code> method.</p>
<p>Let's add a new Razor Page <code>ApimDelegation.cshtml</code>. This page will come with the model <code>ApimDelegation.cs.cshtml</code> and it will be your primary entry point for all delegated calls. You should not care about page content itself, only for the <code>OnGet</code> method. </p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
{
...
}
</code></pre><p>For now, you want to handle only sign-in/sign-up operations. AAD B2C built in a way that you can't navigate directly to the sign-up page (or at least I didn't found the way), so both these operations should be handled together.
First, let's introduce operations enum</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">enum</span> <span class="hljs-title">Operations</span>
</span>{
    <span class="hljs-type">SignIn</span>,
    <span class="hljs-type">SignOut</span>,
    ...
}
</code></pre><p>All required information will come in GET request from AAM</p>
<pre><code><span class="hljs-attribute">http</span>:<span class="hljs-comment">//www.yourwebsite.com/apimdelegation?operation=SignIn&amp;returnUrl={URL of source page}&amp;salt={string}&amp;sig={string}</span>
</code></pre><p>which is very easy to process</p>
<pre><code><span class="hljs-keyword">var</span> operation = Enum.Parse&lt;Operations&gt;(Request.Query[<span class="hljs-string">"operation"</span>].FirstOrDefault());
<span class="hljs-keyword">var</span> returnUrl = Request.Query[<span class="hljs-string">"returnUrl"</span>].FirstOrDefault();

<span class="hljs-keyword">if</span> (!_validationService.TryValidation(Request, returnUrl))
{
    <span class="hljs-keyword">return</span> Unauthorized();
}
</code></pre><p><code>validationService.TryValidation</code>- For security reasons you are advised to verify that the request is coming from Azure API Management. <code>sig</code> and <code>salt</code> parameters are used to calculate and verify the hash. If the hash does not match, we break the execution.</p>
<p>If validation OK you can go to sign-in/sign-up processing:</p>
<pre><code><span class="hljs-keyword">switch</span> (operation)
{
    <span class="hljs-keyword">case</span> Operations.SignUp:
    <span class="hljs-keyword">case</span> Operations.SignIn:
    <span class="hljs-keyword">var</span> parameters = HttpUtility.ParseQueryString(<span class="hljs-keyword">string</span>.<span class="hljs-keyword">Empty</span>);
    parameters[SignInDelegationModel.RequestQueryRedirectUrl] = returnUrl;

    <span class="hljs-keyword">var</span> urlBuider = <span class="hljs-keyword">new</span> UriBuilder(<span class="hljs-string">"https"</span>, Request.Host.Host, (<span class="hljs-keyword">int</span>)Request.Host.Port)
    {
        Path = <span class="hljs-string">"SignInDelegation"</span>,
        Query = parameters.ToString()
    };
    <span class="hljs-keyword">var</span> returnUrlAfterSignIn = urlBuider.Uri.ToString();
    <span class="hljs-keyword">return</span> Redirect($<span class="hljs-string">"/MicrosoftIdentity/Account/Challenge?redirectUri={returnUrlAfterSignIn}"</span>);
}
</code></pre><p>There is a built-in <em>SingIn </em>method coming with the template, but it's hardcoded to redirect to <code>~/</code>. Instead of the built-in method, you should use the <code>MicrosoftIdentity/Account/Challenge</code> method where you could provide a custom redirect endpoint to the Razor page for sign-in processing.</p>
<p>Now, you need to create a separate endpoint for handling successful sign-in/sign-up. The easiest way is the same - create a new Razor page <code>SignInDelegation.cshtml</code> and use <code>OnGet</code> of its code-behind.</p>
<pre><code class="lang-C#"> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
 {
     <span class="hljs-keyword">var</span> returnUrl = Request.Query[RequestQueryRedirectUrl].FirstOrDefault();
     <span class="hljs-keyword">var</span> user = HttpContext.User;

    <span class="hljs-keyword">if</span> (!user.Identity.IsAuthenticated)
    {
        <span class="hljs-keyword">return</span> Unauthorized();
    }
}
</code></pre>
<p>Here you get your User and all his claims. Now you can create and authorize User in AAM.</p>
<p>At this point, you can enable delegation in AAM settings and set it up to the newly created endpoint - after that when you press sing-in in your developers portal you would be redirected to web-app, then to AAD B2C, then back to web-app, which would redirect you back to AAM.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612214479043/MMiNqMQ0g.png" alt="image.png" /></p>
<h1 id="3-use-azure-sdk-to-create-and-authorize-users">3 Use Azure SDK to create and authorize users</h1>
<p>After you get authenticated User you can create it in AAM and authorize it. There are two options for how you can create a user: Management API available through AAM settings and Azure Resource Manager (ARM) available through Azure SDK
You should never use Management API. In fact, there should not be any Management API - because it does not even have documentation available anymore anywhere.
You can make fun of my frustration about that fact in this <a target="_blank" href="https://twitter.com/xakpc/status/1351180519757778951">Twitter thread</a>.</p>
<h2 id="31-azure-resource-manager-arm-setup">3.1 Azure Resource Manager (ARM) setup</h2>
<p>ARM is used in the web-app through <strong>Azure SDK</strong> package <code>Microsoft.Azure.Management.ApiManagement</code>. It allows you to perform any operation that can be performed through portal UI and sometimes even more. 
There are separate packages for each service, but we interested only in AAM, and for now - <a target="_blank" href="https://docs.microsoft.com/en-us/rest/api/apimanagement/2019-12-01/user">only users</a> </p>
<p>To use ARM REST API first you need to create an Application and service principal in Azure Tenant and provide access to AAM through this application. To do so you should navigate to Azure Active Directory blade of your tenant where AAM is located, then to App Registration and create new Registration.</p>
<p>You should give it a name and select <code>Accounts in this organizational directory only (ChatFirst only - Single-tenant)</code> - all other stuff can be left by default.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1612217760085/rl-GHHgWw.png" alt="image.png" />
It's all in details explained in the <a target="_blank" href="https://docs.microsoft.com/en-US/azure/active-directory/develop/app-objects-and-service-principals">documentation</a>.</p>
<p>That will give you an application on behalf of which you will be executing REST requests to Azure SDK API.</p>
<p>Next, you need to create a Client Secret on Certificates &amp; secrets
 to use for subscribing REST requests.</p>
<p>Overall you need the next settings</p>
<pre><code>  <span class="hljs-string">"AzureSdkClient"</span>: {
    <span class="hljs-string">"ClientSecret"</span>: <span class="hljs-string">"your-app-secret"</span>,
    <span class="hljs-string">"ClientId"</span>: <span class="hljs-string">"your-app-client-id-from-overview"</span>,
    <span class="hljs-string">"TenantId"</span>: <span class="hljs-string">"your-app-tenant-id-from-overview"</span>
  }
</code></pre><p>And finally, you need to <a target="_blank" href="https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal">give access</a> to AAM for the created service principal (Assign Role). That will ensure that the application has access only to AAM and nothing else.</p>
<h2 id="32-azure-resource-manager-arm-usage">3.2 Azure Resource Manager (ARM) usage</h2>
<p>Performing any request to Azure SDK API always requires two steps:</p>
<ol>
<li>You need to acquire a token with your application credentials</li>
<li>You need to perform REST request subscribed by the token</li>
</ol>
<p>There are so many ways to get this token it's almost exhausting. Official manuals provide <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/azure/authentication?view=azure-dotnet">this way</a>. We didn't use it.</p>
<p>Instead, we go with a much simpler, but the not-recommended way by using <code>ClientSecretCredential</code> from <code>Azure.Identity</code>.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;AccessToken&gt; <span class="hljs-title">GetAccessTokenAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> credential = <span class="hljs-keyword">new</span> ClientSecretCredential(_tenantId, _clientId, _clientSecret);
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> credential.GetTokenAsync(<span class="hljs-keyword">new</span> TokenRequestContext(<span class="hljs-keyword">new</span>[] { ScopeApiManagement }));
}
</code></pre><p>Apparently, this should not be used, so we switch to the more valid method - custom ITokenProvider implementation</p>
<pre><code class="lang-C#"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApiManagementTokenProvider</span> : <span class="hljs-title">ITokenProvider</span>
    {
        ...
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ApiManagementTokenProvider</span>(<span class="hljs-params">IConfiguration config</span>)</span>
        {
            _config = config;
        }
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;AuthenticationHeaderValue&gt; <span class="hljs-title">GetAuthenticationHeaderAsync</span>(<span class="hljs-params">CancellationToken cancellationToken</span>)</span>
        {
            <span class="hljs-comment">// For app only authentication, we need the specific tenant id in the authority url</span>
            <span class="hljs-keyword">var</span> tenantSpecificUrl = <span class="hljs-string">$"https://login.microsoftonline.com/<span class="hljs-subst">{_tenantId}</span>/"</span>;
            <span class="hljs-comment">// Create a confidential client to authorize the app with the AAD app</span>
            IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
                                                                            .Create(_clientId)
                                                                            .WithClientSecret(_clientSecret)
                                                                            .WithAuthority(tenantSpecificUrl)
                                                                            .Build();
            <span class="hljs-comment">// Make a client call if Access token is not available in cache</span>
            <span class="hljs-keyword">var</span> authenticationResult = <span class="hljs-keyword">await</span> clientApp
                .AcquireTokenForClient(<span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt; { ScopeApiManagement })
                .ExecuteAsync();
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> AuthenticationHeaderValue(<span class="hljs-string">"Bearer"</span>, authenticationResult.AccessToken);
        }
    }
</code></pre>
<p>Fill free to select whatever suits you best. There is probably an even more valid way, so feel free to add it in the comments.</p>
<p>Anyway, the process of using Azure SDK REST API is pretty straightforward. <code>TokenCredentials</code> is created with <code>token</code> or <code>tokenProvider</code>, depending on what method you use.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">UserCreateOrUpdateAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> token, <span class="hljs-keyword">string</span> userId, UserCreateParameters parameters</span>)</span>
{
    <span class="hljs-keyword">try</span>
    {
        <span class="hljs-keyword">var</span> apiManagement = <span class="hljs-keyword">new</span> ApiManagementClient(<span class="hljs-keyword">new</span> TokenCredentials(_tokenProvider))
        {
            SubscriptionId = _subscriptionId
        };
        <span class="hljs-keyword">await</span> apiManagement.User.CreateOrUpdateAsync(_resourceGroupName, _serviceName, userId, parameters);
    }
    <span class="hljs-keyword">catch</span> (Exception ex)
    {
          Trace.TraceInformation(ex.ToString());
          <span class="hljs-keyword">throw</span>;
    }
}
</code></pre><p>To execute the request you would need <code>SubscriptionId</code>, <code>ResourceGroupName</code>, and <code>ServiceName</code> - this information available in the Overview tab of your AAM in the Azure portal.</p>
<p>UserCreateParameters populated from user claims in <code>SignInDelegation.OnGet</code></p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
{
   ...
    <span class="hljs-keyword">var</span> token = <span class="hljs-keyword">await</span> _clientCredentialService.GetAccessTokenAsync();

    <span class="hljs-keyword">var</span> userId = HttpContext.User.FindFirst(NameIdentifierSchemas).Value;
    <span class="hljs-keyword">var</span> isNew = HttpContext.User.FindFirst(NewUser)?.Value;

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">bool</span>.TryParse(isNew, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> result))
    {
        <span class="hljs-keyword">if</span> (result)
        {
            <span class="hljs-keyword">var</span> email = HttpContext.User.FindFirst(EMailAddress)?.Value;
            <span class="hljs-keyword">var</span> firstName = HttpContext.User.FindFirst(GivenNameSchemas)?.Value ?? <span class="hljs-string">"-"</span>;
            <span class="hljs-keyword">var</span> lastName = HttpContext.User.FindFirst(SurnameSchemas)?.Value ?? <span class="hljs-string">"-"</span>;
            <span class="hljs-comment">// Create corresponding account in API Management</span>
            <span class="hljs-keyword">await</span> _apiManagementService.UserCreateOrUpdateAsync(token.Token, userId, <span class="hljs-keyword">new</span> UserCreateParameters(email, firstName, lastName,
                confirmation: <span class="hljs-string">"signup"</span>)); <span class="hljs-comment">// we do not need invites - so let's skip invite email</span>
        }
    }
}
</code></pre><p><code>isNew</code> claim is used to determine the need for a new user.
<code>EmailAddress</code>, <code>GivenNameSchemas</code>, <code>SurnameSchemas</code> might be null in claims and AAD B2C, but should not be in AAM. For better UX FirstName and Surname of the user is optional on sign-up, so you should put placeholders if they missing. Users can always add them later In profile. Note, that the code example here is a bit obsolete - I use a "not proper" method of getting tokens.</p>
<h2 id="32-authorizing-user-into-aam">3.2 Authorizing user into AAM</h2>
<p>Authorizing into AAM works exactly <a target="_blank" href="https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-setup-delegation">how docs specified</a> </p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">OnGet</span>(<span class="hljs-params"></span>)</span>
{
   ...
    <span class="hljs-keyword">var</span> tokenResult = <span class="hljs-keyword">await</span> _apiManagementService.GetSharedAccessTokenAsync(token.Token, userId, <span class="hljs-keyword">new</span> UserTokenParameters() { Expiry = DateTime.UtcNow.AddHours(<span class="hljs-number">3</span>), KeyType = KeyType.Primary });

    <span class="hljs-keyword">var</span> parameters = HttpUtility.ParseQueryString(<span class="hljs-keyword">string</span>.Empty);
    parameters[<span class="hljs-string">"token"</span>] = tokenResult.Value;
    parameters[<span class="hljs-string">"returnUrl"</span>] = returnUrl;
    <span class="hljs-keyword">var</span> urlBuider = <span class="hljs-keyword">new</span> UriBuilder
    {
        Host = _ssoUrl,
        Scheme = <span class="hljs-string">"https"</span>,
        Path = <span class="hljs-string">"signin-sso"</span>,
        Query = parameters.ToString()
    };

    <span class="hljs-keyword">return</span> Redirect(urlBuider.Uri.ToString());
}
</code></pre><ol>
<li>You request Shared Access Token for User for AAM through Azure SDK REST API</li>
<li>You append a returnUrl parameter to the SSO URL you have received from the API call above</li>
<li>You redirect to AAM signin-sso endpoint.</li>
</ol>
<p>You can check the entire method <a target="_blank" href="https://github.com/API-chat/ApiChat.Web/blob/4b88321d00116259d74de2ca55c624dc36ebeb43/ApiChat.Web.Auth/Pages/SignInDelegation.cshtml.cs#L34">here</a></p>
<h2 id="33-github-as-an-identity-provider-in-aad-b2c">3.3 GitHub as an identity provider in AAD B2C</h2>
<p>While adding GitHub I found out that after singing in through GitHub there are no email or user-name in claims. It looks like AAD B2C does not add the required scope or bindings into the request - which is quite sad, because we need this information to create an AAM user. </p>
<p>You might solve this by adding GitHub as an OpenID provider but GitHub does not implement OpenID Connect to spec: it does not support <code>well-known/openid-connect-discovery</code> and you have no option to manually set endpoints in AAD B2C.</p>
<p>So you should use Github user API to fetch all required data, that's how we do it:</p>
<ol>
<li>Set <code>Display Name</code> and <code>Identity Provider Access Token</code> in Application Claims of your User Flow</li>
<li>On GitHub auth you will get <code>name</code> aka <code>username</code> and <code>idp_access_token</code> aka <code>token</code></li>
<li>That allows us to call GitHub user API <code>curl -u username:token https://api.github.com/user</code></li>
<li>By default user API returns a public user profile, which might not have a set email
<code>curl -u username:token https://api.github.com/user/emails</code> will return all user associated emails</li>
<li>You will need the primary one<pre><code>{
 <span class="hljs-attr">"email"</span>: <span class="hljs-string">"***@gmail.com"</span>,
 <span class="hljs-attr">"primary"</span>: <span class="hljs-literal">true</span>,
 <span class="hljs-attr">"verified"</span>: <span class="hljs-literal">true</span>,
 <span class="hljs-attr">"visibility"</span>: <span class="hljs-string">"public"</span>
}
</code></pre></li>
</ol>
<p>Part two  <a target="_blank" href="https://xakpc.info/connecting-azure-api-management-azure-ad-b2c-and-paddle-with-net-5-razor-app-part-2">here</a> </p>
]]></content:encoded></item><item><title><![CDATA[Bootstrap+Blazor essentials]]></title><description><![CDATA[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...]]></description><link>https://xakpc.info/bootstrapblazor-essentials</link><guid isPermaLink="true">https://xakpc.info/bootstrapblazor-essentials</guid><category><![CDATA[Blazor ]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[dotnet]]></category><dc:creator><![CDATA[Pavel Osadchuk]]></dc:creator><pubDate>Tue, 19 Jan 2021 09:33:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1611049161350/ryYTR7Dhh.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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.</p>
<p>In this post, I want to give you some advice on how and which Bootstrap components you can wrap into <code>.razor</code> components to make your code simpler, easier to maintain, and expandable.</p>
<p><a target="_blank" href="https://github.com/xakpc/Xakpc.BlazorBits.Bootstrap">📥 Sources to this article </a> </p>
<p>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.</p>
<p>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;</p>
<p>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.</p>
<ul>
<li>Button</li>
<li>Button Group</li>
<li>Card</li>
<li>Progress</li>
<li>Spinner</li>
<li>Toast</li>
<li>Modals</li>
</ul>
<p><strong>Next time</strong> - Form and Form Components in Blazor with MVVM</p>
<h1 id="button">Button</h1>
<p>The most basic component is a button. Because I use <a target="_blank" href="https://dev.to/xakpc/using-blazor-consider-mvvm-59dm">MVVM</a> my button is based on the <code>ICommand</code> interface</p>
<pre><code>@using System.Windows.Input

&lt;button type=<span class="hljs-string">"button"</span> <span class="hljs-keyword">class</span>=<span class="hljs-string">"@($"</span>btn {Class}<span class="hljs-string">")"</span> @onclick=<span class="hljs-string">"Callback"</span> @attributes=<span class="hljs-string">"InputAttributes"</span>&gt;@ChildContent&lt;/button&gt;

@code {
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> ICommand Command { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> RenderFragment ChildContent { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Class { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-string">"btn-primary"</span>;

    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">object</span> Parameter { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Parameter(CaptureUnmatchedValues = true)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Dictionary</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">object</span>&gt; InputAttributes</span> { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">object</span>&gt;();

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Callback</span>(<span class="hljs-params"></span>)</span>
    {
        Command?.Execute(Parameter);
    }
}
</code></pre><p>When you add action to button's ICommand <code>AddCommand = new Command&lt;int&gt;(DoAdd);</code> take notice of the action</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">DoAdd</span><span class="hljs-params">(<span class="hljs-keyword">int</span> p)</span>
</span>{
    Value += p;
    StateHasChanged(); <span class="hljs-comment">// &lt;--- not gonna work without it</span>
}
</code></pre><p>I <a target="_blank" href="https://twitter.com/xakpc/status/1314235866605379585">found out</a> that <code>onaction</code> 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 <code>StateHasChanged</code> call here because the View should be subscribed to <code>INotifyPropertyChanged</code> anyway.</p>
<h1 id="button-group">Button Group</h1>
<p>Button group wraps several command buttons in a single neat component. I usually use it for data table editing
<img src="https://dev-to-uploads.s3.amazonaws.com/i/g8swcuac9pdm2xt54bup.PNG" alt="Alt Text" /></p>
<pre><code>@using System.Windows.Input

&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn-group"</span> role=<span class="hljs-string">"group"</span> aria-label=<span class="hljs-string">"Basic example"</span>&gt;
    &lt;CommandButton <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn-sm btn-primary"</span> Command=<span class="hljs-string">"EditCommand"</span> Parameter=<span class="hljs-string">"Parameter"</span>&gt;Edit&lt;/CommandButton&gt;
    &lt;CommandButton <span class="hljs-keyword">class</span>=<span class="hljs-string">"btn-sm btn-danger"</span> Command=<span class="hljs-string">"DeleteCommand"</span> Parameter=<span class="hljs-string">"Parameter"</span>&gt;Delete&lt;/CommandButton&gt;
&lt;/div&gt;

@code {
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> ICommand EditCommand { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> ICommand DeleteCommand { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">object</span> Parameter { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre><p>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's way of building tables.
You could pass commands and current items 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.</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">tbody</span>&gt;</span>
    @foreach (var item in Items)
    {
        <span class="hljs-tag">&lt;<span class="hljs-name">tr</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span>@item<span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">td</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">RowButtonGroup</span> 
                    <span class="hljs-attr">EditCommand</span>=<span class="hljs-string">"EditItemCommand"</span> 
                    <span class="hljs-attr">DeleteCommand</span>=<span class="hljs-string">"DeleteItemCommand"</span> 
                    <span class="hljs-attr">Parameter</span>=<span class="hljs-string">"item"</span>/&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">td</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">tr</span>&gt;</span>
    }
<span class="hljs-tag">&lt;/<span class="hljs-name">tbody</span>&gt;</span>
</code></pre><h1 id="card">Card</h1>
<p>For me, the card is the primary component of any web app. This component wrap most of the basic HTML card into several optional <code>RenderFragments</code>. You can expand it further to add an optional <code>card-image</code> or <code>list-group</code> block.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="093c85494c623954107ea7cfc8b88dd6"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/xakpc/093c85494c623954107ea7cfc8b88dd6" class="embed-card">https://gist.github.com/xakpc/093c85494c623954107ea7cfc8b88dd6</a></div><p>I noticed that some folks missing the fact that you <strong>can use several RenderFragment</strong> inside your component. Well, you can and it allows you to use your components like that</p>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">Card</span> <span class="hljs-attr">Title</span>=<span class="hljs-string">"Special title treatment"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-text"</span>&gt;</span>With supporting text below as a natural lead-in to additional content.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn-primary"</span>&gt;</span>Go somewhere<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Footer</span>&gt;</span>
        2 days ago
    <span class="hljs-tag">&lt;/<span class="hljs-name">Footer</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">Card</span>&gt;</span>
</code></pre><p>Please note </p>
<pre><code>[Parameter(CaptureUnmatchedValues = <span class="hljs-keyword">true</span>)]
<span class="hljs-built_in">public</span> <span class="hljs-keyword">Dictionary</span>&lt;string, <span class="hljs-keyword">object</span>&gt; InputAttributes { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } 
    = <span class="hljs-built_in">new</span> <span class="hljs-keyword">Dictionary</span>&lt;string, <span class="hljs-keyword">object</span>&gt;();
</code></pre><p>This parameter is used on the card to catch-all unmatched attributes that you want to assign to it <code>&lt;div class="card @Class" @attributes="InputAttributes"&gt;</code>. 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.</p>
<h1 id="progress-bar">Progress Bar</h1>
<p>Small component but I want to show it here as an example of the power of Blazor components and proper MVVM usage.</p>
<pre><code>&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"progress @Class"</span> style=<span class="hljs-string">"@(Height == null ? "</span><span class="hljs-string">" : $"</span>height: {Height}px<span class="hljs-string">")"</span>&gt;
  &lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"progress-bar"</span> role=<span class="hljs-string">"progressbar"</span> style=<span class="hljs-string">"@($"</span>width: {Progress}%<span class="hljs-string">")"</span> aria-valuenow=<span class="hljs-string">"@Progress"</span> aria-valuemin=<span class="hljs-string">"0"</span> aria-valuemax=<span class="hljs-string">"100"</span>&gt;@ChildContent&lt;/div&gt;
&lt;/div&gt;

@code {
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Class {<span class="hljs-keyword">get</span>;<span class="hljs-keyword">set</span>;}
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Progress {<span class="hljs-keyword">get</span>;<span class="hljs-keyword">set</span>;}
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span>? Height {<span class="hljs-keyword">get</span>;<span class="hljs-keyword">set</span>;}
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> RenderFragment ChildContent { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    [<span class="hljs-meta">Parameter(CaptureUnmatchedValues = true)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Dictionary</span>&lt;<span class="hljs-title">string</span>, <span class="hljs-title">object</span>&gt; InputAttributes</span> { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">object</span>&gt;();
}
</code></pre><h2 id="progress-bar-with-mvvm-example">Progress Bar with MVVM Example</h2>
<p>Let's dig into the example of usage of ProgressBar with backing it ViewModel.</p>
<h3 id="view">View</h3>
<pre><code>&lt;h2 <span class="hljs-class"><span class="hljs-keyword">class</span></span>=<span class="hljs-string">"mt-3"</span>&gt;Interactive (<span class="hljs-keyword">with</span> VM)&lt;/h2&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ProgressBar</span> <span class="hljs-attr">Progress</span>=<span class="hljs-string">"@DataContext.Progress"</span> <span class="hljs-attr">Class</span>=<span class="hljs-string">"mt-2 mb-2"</span>&gt;</span>@((DataContext.Progress/100f).ToString("P0"))<span class="hljs-tag">&lt;/<span class="hljs-name">ProgressBar</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">CommandButton</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn-primary"</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"@DataContext.Add"</span>&gt;</span>+<span class="hljs-tag">&lt;/<span class="hljs-name">CommandButton</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">CommandButton</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn-secondary"</span> <span class="hljs-attr">Command</span>=<span class="hljs-string">"@DataContext.Subtract"</span>&gt;</span>-<span class="hljs-tag">&lt;/<span class="hljs-name">CommandButton</span>&gt;</span></span>
</code></pre><p>As you can see View is quite simple: we have progress which is transformed like that <code>@((DataContext.Progress/100f).ToString("P0"))</code> for display only and couple buttons with commands <code>Add</code> and <code>Subtract</code>. 
These actions are calculated in ViewModel. To ensure that ViewModel created, initialized, and linked I use <a target="_blank" href="https://gist.github.com/xakpc/6d15b7b10ac9d01fcf9dbe30e89fcde8">ContextComponentBase</a> as a base class for all my pages.</p>
<p>Page code-behind looks like this</p>
<pre><code>    <span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Progresses</span>
    {
        [<span class="hljs-meta">Inject</span>]
        <span class="hljs-keyword">protected</span> <span class="hljs-keyword">new</span> ProgressViewModel DataContext
        {
            <span class="hljs-keyword">get</span> =&gt; <span class="hljs-keyword">base</span>.DataContext <span class="hljs-keyword">as</span> ProgressViewModel;
            <span class="hljs-keyword">set</span> =&gt; <span class="hljs-keyword">base</span>.DataContext = <span class="hljs-keyword">value</span>;
        }
    }
</code></pre><p>Ensure that the <code>.razor</code> page itself is inherited from the proper class</p>
<pre><code><span class="hljs-keyword">@page</span> <span class="hljs-string">"/progress"</span>
<span class="hljs-variable">@inherits</span> Xakpc.BlazorBits.Bootstrap.ViewModels.ContextComponentBase;
</code></pre><p>That's all we need for View, let's switch to ViewModel!</p>
<h3 id="viewmodel">ViewModel</h3>
<p>First, check out the previous code snippet. Our ViewModel is Injected into our View - so let's not forget to add it in <code>Startup.cs</code></p>
<pre><code>services.AddScoped<span class="hljs-tag">&lt;<span class="hljs-name">ProgressViewModel</span>&gt;</span>();
</code></pre><p>I recommend read several times about <a target="_blank" href="https://docs.microsoft.com/en-Us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetime/">Scopes</a> in Blazor. Once I lost a day trying to fix a problem caused by misunderstanding Blazor DI Scopes.
Finally a ViewModel itself</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProgressViewModel</span> : <span class="hljs-title">ViewModelBase</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> _progress;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ProgressViewModel</span>(<span class="hljs-params"></span>)</span>
    {
        Add = <span class="hljs-keyword">new</span> Command&lt;<span class="hljs-keyword">int</span>&gt;(i =&gt; Progress += <span class="hljs-number">1</span>, i =&gt; Progress &lt; <span class="hljs-number">100</span>);
        Subtract = <span class="hljs-keyword">new</span> Command&lt;<span class="hljs-keyword">int</span>&gt;(i =&gt; Progress -= <span class="hljs-number">1</span>, i =&gt; Progress &gt; <span class="hljs-number">0</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Progress
    {
        <span class="hljs-keyword">get</span> =&gt; _progress;
        <span class="hljs-keyword">set</span> { _progress = <span class="hljs-keyword">value</span>; OnPropertyChanged(<span class="hljs-keyword">nameof</span>(Progress)); }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> Task <span class="hljs-title">InitializeAsync</span>(<span class="hljs-params"></span>)</span>
    {
        Progress = <span class="hljs-number">22</span>;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">base</span>.InitializeAsync();
    }

    <span class="hljs-keyword">public</span> ICommand Add { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> ICommand Subtract { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre><p>I will not go deep into VM, only point to main things</p>
<ul>
<li><code>InitializeAsync</code> called once per scope on first page-load - that means once per connection for Blazor Server because we added it with <code>AddScope</code>;</li>
<li>Commands set <code>Progress</code> which fire <code>OnPropertyChanged</code> which fire <code>StateHasChanged</code> which fire page redraw;</li>
<li>Commands have <code>CanExecute</code> func to ensure that Progress in range 0-100;</li>
<li>CanExecute also used to disable buttons when needed;</li>
</ul>
<h1 id="spinner">Spinner</h1>
<p>Spinner is another simple example of how easy to wrap into a Razor component.
<img src="https://dev-to-uploads.s3.amazonaws.com/i/suoy0ho1b7yuz6vsc5ut.PNG" alt="Alt Text" /></p>
<pre><code>&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"spinner-border @Class"</span> role=<span class="hljs-string">"status"</span>&gt;
    &lt;span <span class="hljs-keyword">class</span>=<span class="hljs-string">"sr-only"</span>&gt;@Text&lt;/span&gt;
&lt;/div&gt;

@code {
    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Class { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Parameter</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Text { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-string">"Loading..."</span>;
}
</code></pre><h1 id="toasts">Toasts</h1>
<p>For toasts, I usually don't use a Bootstrap component but a nice Blazor port of very popular toastr.js </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/sotsera/sotsera.blazor.toaster">https://github.com/sotsera/sotsera.blazor.toaster</a></div>
<p>I should probably try Bootrstap toasts someday.</p>
<h1 id="modal">Modal</h1>
<p>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 <code>Blazored.Modal</code> - an amazing native component for Modals. You should check it out.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/Blazored/Modal">https://github.com/Blazored/Modal</a></div>
<p>And that's all for now. I am not very good at writing such long articles, so will be appreciated for any feedback. </p>
]]></content:encoded></item></channel></rss>