<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ruxo Zheng</title>
    <description>The latest articles on DEV Community by Ruxo Zheng (@ruxozheng).</description>
    <link>https://dev.to/ruxozheng</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2348945%2F0d52223d-86e0-45e5-a778-83273a3844f0.jpg</url>
      <title>DEV Community: Ruxo Zheng</title>
      <link>https://dev.to/ruxozheng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ruxozheng"/>
    <language>en</language>
    <item>
      <title>My journey to Go</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Sat, 25 Oct 2025 00:39:44 +0000</pubDate>
      <link>https://dev.to/ruxozheng/my-journey-to-go-2ji2</link>
      <guid>https://dev.to/ruxozheng/my-journey-to-go-2ji2</guid>
      <description>&lt;p&gt;I'm now starting to look at the Go language seriously, whereas before I just thought it was an "Indy" language. In the past month, I've had the chance to work with a team that uses Go, which led me to read up on it. I stumbled upon one of its core ideas: Go doesn't have "async" programming (it has green threads—logical threads that handle concurrency via software through context switching). But Go is actually an asynchronous language by default! (It's comparable to every single function in Go using async/await.) The interesting part is that Go makes all the code look synchronous (but it handles the async parts where needed automatically). Personally, I really like this code style, but people who haven't read the specs might misunderstand and think Go is a single-threaded application, much like JS or Python.&lt;/p&gt;

&lt;p&gt;If anyone has tried Haskell's IO monad, you'll find it's the same concept: you wrap a function (whether sync or async) with an IO type, and it ultimately makes the code look synchronous even though it can be both sync and async. In the past, I tried to apply this concept to several languages (C#, F#, TypeScript) but gave up because it involved a lot of boilerplate, and the resulting syntax was very "alien" (for anyone who wants to try the IO monad with JS/TS, check out &lt;a href="https://effect.website/" rel="noopener noreferrer"&gt;https://effect.website/&lt;/a&gt;). So I never used it seriously. But Go embeds this idea through Go routines, whether by coincidence or not.&lt;/p&gt;




&lt;p&gt;Another thing that made me uninterested in Go before: over ten years ago, I read about the language's origin, wondering why it was designed to be so "indy." I remember one interview quote from a designer: "Go is a language designed for junior developers to help them catch up with a codebase quickly." This idea led Go to cut out many abstract concepts (even generic types!). I had tried Go 2-3 times before but got annoyed because the lack of abstract ideas, especially generics, resulted in a lot of code duplication.&lt;/p&gt;

&lt;p&gt;But recently, I had the chance to manage a team of Chula graduates (honors, new grads). The team uses C++, but they were using it at a very basic level, and some didn't understand core concepts like RAII. (Oh, and this made me go back and read the C++11 specs and discover that C++ has had the 'move' concept for over a decade! At first, I thought Rust introduced this idea.)&lt;/p&gt;

&lt;p&gt;So I set up a 3-hour course to teach them about C++ memory pitfalls and RAII and found that... 3 hours was not nearly enough. And honestly, to use this stuff correctly, it takes months of practice and understanding, especially since C++ has so many nuances if you want to write truly idiomatic code.&lt;/p&gt;

&lt;p&gt;It was only this past month that I finally had an "aha" moment about the Go designer's words. Go wants devs to get up to speed quickly while still remaining low-level enough to write system programming—even though it has mostly become an application language now.&lt;/p&gt;

</description>
      <category>go</category>
    </item>
    <item>
      <title>Use Your Machine Name (Not Just localhost) with HTTPS on ASP.NET Core — and Make Node.js Trust It</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Fri, 19 Sep 2025 23:13:43 +0000</pubDate>
      <link>https://dev.to/ruxozheng/use-your-machine-name-not-just-localhost-with-https-on-aspnet-core-and-make-nodejs-trust-it-2fpe</link>
      <guid>https://dev.to/ruxozheng/use-your-machine-name-not-just-localhost-with-https-on-aspnet-core-and-make-nodejs-trust-it-2fpe</guid>
      <description>&lt;p&gt;When you run &lt;code&gt;dotnet dev-certs https --trust&lt;/code&gt;, .NET creates and trusts a &lt;strong&gt;localhost-only&lt;/strong&gt; developer certificate. That’s great for &lt;code&gt;https://localhost:1234&lt;/code&gt;, but it &lt;strong&gt;won’t&lt;/strong&gt; cover &lt;code&gt;https://my-machine:1234&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you want to reach your service by &lt;strong&gt;machine name&lt;/strong&gt; (e.g., &lt;code&gt;https://my-machine:7164&lt;/code&gt;) you need to create your own self-signed certificate that lists all the names you’ll use (machine name, &lt;code&gt;localhost&lt;/code&gt;, and optionally &lt;code&gt;127.0.0.1&lt;/code&gt;) and then configure Kestrel to use it. You’ll also need to trust that certificate for browsers — and tell Node.js how to trust it.&lt;/p&gt;

&lt;p&gt;Below are clean, copy-pasteable steps for Windows/PowerShell.&lt;/p&gt;




&lt;h2&gt;
  
  
  1) Create a self-signed certificate (.pfx) for your machine name
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Names you want this cert to cover (add/remove as needed)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$dns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="s2"&gt;"my-machine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Create a leaf certificate in CurrentUser\My&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-SelfSignedCertificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DnsName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-CertStoreLocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\CurrentUser\My&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Export a PFX with a password (change YOUR-PASSWORD and FILENAME)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$pwd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-SecureString&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR-PASSWORD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AsPlainText&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Export-PfxCertificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Cert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\FILENAME.pfx"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pwd&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Tip: Keep &lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;127.0.0.1&lt;/code&gt; in the SAN list so existing tooling continues to work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  2) (Optional) Replace the default &lt;code&gt;dotnet dev-certs&lt;/code&gt; certificate
&lt;/h2&gt;

&lt;p&gt;If you want your new cert to become the default dev cert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev-certs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--clean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\FILENAME.pfx"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;YOUR-PASSWORD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--trust&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3) Or: Point Kestrel to your PFX in &lt;code&gt;appsettings.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If you don’t want to replace the global dev cert, configure your app explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Kestrel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Certificates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:/Users/USER/FILENAME.pfx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"YOUR-PASSWORD"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4) Trust the certificate (for browsers)
&lt;/h2&gt;

&lt;p&gt;Export a &lt;code&gt;.cer&lt;/code&gt; (public cert) from the cert you created, then import it into &lt;strong&gt;Trusted Root Certification Authorities&lt;/strong&gt; for &lt;strong&gt;Current User&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$cer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\FILENAME.cer"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Export the public part of the certificate to .cer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Export-Certificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Cert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cer&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Import into CurrentUser\Root (Trusted Root)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Import-Certificate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-CertStoreLocation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cert:\CurrentUser\Root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Recommendation: &lt;strong&gt;Do not&lt;/strong&gt; generate directly into the Trust store. Create the cert in &lt;code&gt;CurrentUser\My&lt;/code&gt;, then export/import. Your actual private key lives with the leaf cert; keeping the flow explicit reduces mistakes.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  5) Make it work with Node.js
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Node.js 22.x
&lt;/h3&gt;

&lt;p&gt;Node 22 does &lt;strong&gt;not&lt;/strong&gt; use the Windows trust store by default and also doesn’t accept &lt;code&gt;.cer&lt;/code&gt; via &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt;. Convert the &lt;code&gt;.cer&lt;/code&gt; to &lt;code&gt;.pem&lt;/code&gt; and set &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Convert CER -&amp;gt; PEM (Base64)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;certutil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-encode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\USER\FILENAME.cer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\Users\USER\FILENAME.pem&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# For the current PowerShell session:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;NODE_EXTRA_CA_CERTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Users\USER\FILENAME.pem"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Or persist for new shells:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# setx NODE_EXTRA_CA_CERTS "C:\Users\USER\FILENAME.pem"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;fetch("https://my-machine:7164/...")&lt;/code&gt; should stop complaining about a self-signed certificate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js 23.8+
&lt;/h3&gt;

&lt;p&gt;Newer Node supports using the OS trust store directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--use-system-ca&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;your-script.js&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6) Deleting the certificate later
&lt;/h2&gt;

&lt;p&gt;If you still have the &lt;code&gt;$cert&lt;/code&gt; object from creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$thumb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$cert&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Thumbprint&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Remove from CurrentUser\My and delete the private key as well&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Remove-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cert:\CurrentUser\My\&lt;/span&gt;&lt;span class="nv"&gt;$thumb&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DeleteKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you no longer have &lt;code&gt;$cert&lt;/code&gt;, find it by subject/issuer in &lt;code&gt;Cert:\CurrentUser\My&lt;/code&gt; and remove by thumbprint.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name mismatch&lt;/strong&gt;: The URL’s host must be listed in the certificate’s &lt;strong&gt;DNS names&lt;/strong&gt; (SAN). If you access &lt;code&gt;https://my-machine:7164&lt;/code&gt;, &lt;code&gt;my-machine&lt;/code&gt; must be in the cert.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node still failing&lt;/strong&gt;: On Node 22, ensure &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt; points to a &lt;strong&gt;PEM&lt;/strong&gt; file, not &lt;code&gt;.cer&lt;/code&gt;. Restart the shell or set it in the same session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser trust&lt;/strong&gt;: Make sure you imported the &lt;strong&gt;public&lt;/strong&gt; cert into &lt;strong&gt;Current User → Trusted Root Certification Authorities&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paths&lt;/strong&gt;: Use absolute Windows paths for Kestrel’s &lt;code&gt;"Path"&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Generate a self-signed PFX with SANs for &lt;code&gt;my-machine&lt;/code&gt;, &lt;code&gt;localhost&lt;/code&gt;, and &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Either import it as the global dev cert &lt;strong&gt;or&lt;/strong&gt; reference it in Kestrel.&lt;/li&gt;
&lt;li&gt;Export the &lt;code&gt;.cer&lt;/code&gt; and import it into &lt;strong&gt;Trusted Root&lt;/strong&gt; so browsers trust it.&lt;/li&gt;
&lt;li&gt;For Node: use &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt; with a &lt;code&gt;.pem&lt;/code&gt; (Node 22) or &lt;code&gt;--use-system-ca&lt;/code&gt; (Node 23.8+).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This setup lets you develop and test HTTPS endpoints using your &lt;strong&gt;machine name&lt;/strong&gt;, avoiding the limitations of &lt;code&gt;dotnet dev-certs&lt;/code&gt;’ default &lt;code&gt;localhost&lt;/code&gt; scope.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>node</category>
      <category>browser</category>
    </item>
    <item>
      <title>Host React with Blazor Server</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Sun, 26 Jan 2025 02:43:27 +0000</pubDate>
      <link>https://dev.to/ruxozheng/host-react-with-blazor-server-6ff</link>
      <guid>https://dev.to/ruxozheng/host-react-with-blazor-server-6ff</guid>
      <description>&lt;h1&gt;
  
  
  Host React with Blazor Server
&lt;/h1&gt;

&lt;p&gt;This article explores my experiment of hosting React within a Blazor Server project. Blazor offers a hosting model called Hosted Blazor, which combines Blazor Server and Blazor WebAssembly (WASM) within a single solution. This allows the application to render HTML with either server-side or client-side generation.&lt;/p&gt;

&lt;p&gt;However, React excels due to its robust ecosystem in virtually every aspect, particularly its wide array of UI library choices. This potential advantage motivated this experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Here are the essentials you'll need to follow along:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt; + &lt;strong&gt;pnpm&lt;/strong&gt; (or npm or similar). This guide uses &lt;code&gt;pnpm&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vite&lt;/strong&gt; for React setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;.NET Core&lt;/strong&gt; (.NET 7 or later -- that supports Blazor project)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Install pnpm
&lt;/h3&gt;

&lt;p&gt;Refer to the official installation instructions here: &lt;a href="https://pnpm.io/installation" rel="noopener noreferrer"&gt;pnpm Installation Guide&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can also install Node.js using the &lt;code&gt;pnpm&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--global&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lts&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup React Side
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up the React application without .NET interoperability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a directory to host a solution:&lt;/p&gt;

&lt;p&gt;For example, create the directory called &lt;code&gt;HostedReact&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In that directory, create a solution and a new Blazor Server app:&lt;/p&gt;

&lt;p&gt;We'll have a solution directory to host two projects: one is a Blazor server project, another is a React project.&lt;/p&gt;

&lt;p&gt;Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sln&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;gitignore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;blazor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-o&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/Sample&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;dotnet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sln&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;/src/Sample&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a React app using Vite in the &lt;code&gt;src&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vite&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Your-New-Project-Name&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install node type definitions for better type safety:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;types/node&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;code&gt;nodePolyfilles&lt;/code&gt; for handling environment variabls with Vite:&lt;/p&gt;

&lt;p&gt;Vite uses environment variables such as &lt;code&gt;process&lt;/code&gt; for resolving paths, and these tools are essential for compatibility:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;node-stdlib-browser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-D&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;vite-plugin-node-stdlib-browser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a module entry point for the React project to be consumed by the Blazor:&lt;/p&gt;

&lt;p&gt;Create a new file &lt;code&gt;entrypoint.jsx&lt;/code&gt; in the React &lt;code&gt;src&lt;/code&gt; directory.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./App.jsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Modify &lt;code&gt;main.jsx&lt;/code&gt; to reuse this entry point function.&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./entrypoint.jsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure Vite for library generation (instead of a static web app):&lt;/p&gt;

&lt;p&gt;By default, Vite generates a static web app that bundles and &lt;code&gt;index.html&lt;/code&gt; and necessary JavaScript files. However, to integrate with the Blazor Server project, we need it to output a JavaScript module. Update your Vite configuration (&lt;code&gt;vite.config.js&lt;/code&gt; or &lt;code&gt;vite.config.ts&lt;/code&gt;) as follow:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;react&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@vitejs/plugin-react-swc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// @ts-expect-error Test&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;nodePolyfills&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-plugin-node-stdlib-browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;// https://vite.dev/config/&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/react/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;lib&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;src/entrypoint.jsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ReactApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ReactApp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;formats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;es&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;outDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../Sample/wwwroot/react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;emptyOutDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;react&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;nodePolyfills&lt;/span&gt;&lt;span class="p"&gt;()],&lt;/span&gt;
&lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;outDir&lt;/code&gt; field specifies the location to generate files, aligning with Blazor's static &lt;code&gt;wwwroot&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;base&lt;/code&gt; key is for Vite's dev server and does not include the &lt;code&gt;wwwroot&lt;/code&gt; part.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I recommend using React JavaScript (instead of TypeScript) to avoid additional overhead when calling .NET methods from JS. With JavaScript, you won't need to declare explicit types during interop. However, this is optional and depends on your preference.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build the React project:&lt;/p&gt;

&lt;p&gt;Run the following command to generate necessary files under the &lt;code&gt;wwwroot/react&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;The output will include &lt;code&gt;ReactApp.css&lt;/code&gt; and &lt;code&gt;ReactApp.js&lt;/code&gt;. Ensure that you add &lt;code&gt;/src/Sample/wwwroot/react&lt;/code&gt; to your project's &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Setting Up the Blazor Side
&lt;/h2&gt;

&lt;p&gt;Now that you've configured the React project, it's time to integrate it with Blazor so it can host the React app. This involves making use of the &lt;code&gt;Initialize&lt;/code&gt; function declared earlier in the React &lt;code&gt;entrypoint.jsx&lt;/code&gt; file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Simplify the Blazor layout:&lt;/p&gt;

&lt;p&gt;Since the React app comes with its own style (css), we may want to get rid of Blazor's style related stuffs. You may even create a layout compoonent specifically for React rendering purpose.&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;MainLayout.razor&lt;/code&gt; to an empty template:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;@inherits LayoutComponentBase

@Body
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;For &lt;code&gt;App.razor&lt;/code&gt;, keep it minimal:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"utf-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;base&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"@Assets["&lt;/span&gt;&lt;span class="na"&gt;Sample.styles.css&lt;/span&gt;&lt;span class="err"&gt;"]"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ImportMap&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"icon"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"image/png"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"favicon.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;HeadOutlet&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;Routes&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"_framework/blazor.web.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a JS interop class for React integration:&lt;/p&gt;

&lt;p&gt;Blazor uses JavaScript Interop to invoke JavaScript functions and receive data back from JavaScript side. To render the React components, we'll use JavaScript Interop to call the &lt;code&gt;Initialize&lt;/code&gt; function you defined in your &lt;code&gt;entrypoint.jsx&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;ReactJsInterop.cs&lt;/code&gt; file in the project root (or another convenient namespace). Add the following code:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.AspNetCore.Components&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.JSInterop&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;HostedReact&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ReactJsInterop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IJSRuntime&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Lazy&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IJSObjectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;importModule&lt;/span&gt;
        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IJSObjectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"import"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./react/ReactApp.js"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;AsTask&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ElementReference&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Initialize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;el&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;params&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?[]?&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;importModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;params&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?[]?&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;importModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;importModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsValueCreated&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;import&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This class dynamically imports the React app’s JavaScript module (&lt;code&gt;ReactApp.js&lt;/code&gt;) at runtime and uses the &lt;code&gt;Initialize&lt;/code&gt; function to render React components in a DOM element.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ElementReference&lt;/code&gt; parameter is used to pass the DOM element where React will inject the UI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, add the &lt;code&gt;ReactJsInterop&lt;/code&gt; service to your dependency injection (DI) container. Update the &lt;code&gt;Program.cs&lt;/code&gt; file to include the registration:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddScoped&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ReactJsInterop&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Render the React App with Interop:&lt;/p&gt;

&lt;p&gt;Use the &lt;code&gt;ReactJsInterop&lt;/code&gt; class in a Razor page to host the React components. Let's modify &lt;code&gt;Home.razor&lt;/code&gt; as an example:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;

&lt;span class="n"&gt;@rendermode&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InteractiveServerRenderMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prerender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;ReactJsInterop&lt;/span&gt; &lt;span class="n"&gt;ReactJs&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"./react/ReactApp.css"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;@ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"div"&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;ElementReference&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAfterRenderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ReactJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add the reference to the generated React stylesheet &lt;code&gt;ReactApp.css&lt;/code&gt; so that React styles are applied in Blazor.&lt;/li&gt;
&lt;li&gt;Use the &lt;code&gt;@ref&lt;/code&gt; directive to link a Blazor-controlled &lt;code&gt;div&lt;/code&gt; element (with &lt;code&gt;id="root"&lt;/code&gt;) for React to render its components.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run and Verify&lt;/p&gt;

&lt;p&gt;Start the Blazor app using the &lt;code&gt;dotnet run&lt;/code&gt; command, and navigate to the configured route (e.g. &lt;code&gt;/&lt;/code&gt;) in your browser. You should see the React app rendered withint the Blazor app's DOM.&lt;/p&gt;

&lt;p&gt;If any changes are made to the React app, you must rebuild it using:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pnpm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This regenerates the &lt;code&gt;ReactApp.js&lt;/code&gt; and &lt;code&gt;ReactApp.css&lt;/code&gt; files inside the Blazor &lt;code&gt;wwwroot/react&lt;/code&gt; directory.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Add two-way interoperability
&lt;/h2&gt;

&lt;p&gt;So far, we've configured React to render inside a Blazor component using the &lt;code&gt;Initialize&lt;/code&gt; function. While this setup enables on-way communication -- from .NET (Blazor) to React -- we can extend it to allow React to call back into .NET, enabling full two-way interaction. This is achieved using Blazor's JavaScript Interop (&lt;code&gt;[JSInvokable]&lt;/code&gt;) alongside enhancements to the React entry point.&lt;/p&gt;

&lt;p&gt;This interoperability can be helpful for scenarios like updating React UI components based on data fetched from a Blazor service or triggering Blazor logic from user actions in the React app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Expose Methods in Blazor for React to Call&lt;/p&gt;

&lt;p&gt;You'll need to define methods in Blazor that React can invoke using the .NET-to-JS interop mechanism. Let's update the &lt;code&gt;Home.razor&lt;/code&gt; file to include a &lt;code&gt;[JSInvokable]&lt;/code&gt; method that React can call.&lt;/p&gt;

&lt;p&gt;Update &lt;code&gt;Home.razor&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;

&lt;span class="n"&gt;@rendermode&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InteractiveServerRenderMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prerender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;ReactJsInterop&lt;/span&gt; &lt;span class="n"&gt;ReactJs&lt;/span&gt;
&lt;span class="n"&gt;@implements&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"./react/ReactApp.css"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt; &lt;span class="n"&gt;@ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"div"&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;...&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="n"&gt;ElementReference&lt;/span&gt; &lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;DotNetObjectReference&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DotNetObjectReference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAfterRenderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ReactJs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;div&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JSInvokable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;GetSecretNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;me&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;&lt;code&gt;GetSecretNumber&lt;/code&gt; is the method that will be called from the React App. Notice that we need to mark JS callable methods with &lt;code&gt;JSInvokableAttribute&lt;/code&gt;. A JS method can be either sync or async.&lt;/p&gt;

&lt;p&gt;Also notice that &lt;code&gt;IDisposable&lt;/code&gt; implementation is needed to release resources allocated by &lt;code&gt;DotNetObjectReference&lt;/code&gt;, to avoid the memory leakage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Update the React Initialize Function:&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;entrypoint.jsx&lt;/code&gt; file, extend the &lt;code&gt;Initialize&lt;/code&gt; function to accept the additional &lt;code&gt;.NET&lt;/code&gt; object reference. Use this to call the .NET method from React.&lt;/p&gt;

&lt;p&gt;Update the &lt;code&gt;entrypoint.jsx&lt;/code&gt; file like so:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-dom/client&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./App.jsx&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dotnet&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dotnet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeMethodAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GetSecretNumber&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="na"&gt;n&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;And that's it. Now we have two-way communication between JS and .NET.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;This experiment was initiated to take advantage of React's superior ecosystem and developer experience, particularly its Hot Module Replacement (HMR) feature, which allows faster UI development. While Blazor does have HMR, changes to .NET code or Razor files often require a rebuild, making development less efficient in comparison.&lt;/p&gt;

&lt;p&gt;However, this hybrid approach has its trade-offs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While React speeds up UI development with better HMR and its rich ecosystem, it introduces an additional API layer between .NET and JavaScript, adding complexity and potential overhead compared to pure Blazor applications.&lt;/li&gt;
&lt;li&gt;The additional &lt;code&gt;pnpm run build&lt;/code&gt; step for React takes 3–4 seconds to complete, which is roughly comparable to Blazor’s HMR compilation time, minimizing the perceived benefits in simple use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That said, this setup shows its value in projects where React's strengths in UI development already align with an existing API-driven architecture, such as those using HTTP or gRPC for communication. In such cases, the React-Blazor integration can provide a powerful and cohesive solution by leveraging React’s flexibility while still benefiting from Blazor's robust backend/server-side capabilities.&lt;/p&gt;

&lt;p&gt;In summary, while this hybrid approach might not be a one-size-fits-all solution, it can be a compelling choice for applications that require both React's dynamic UI ecosystem and seamless integration with Blazor's full-stack .NET environment.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>blazor</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Pre-render issue in Blazor server interaction</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Fri, 20 Dec 2024 11:17:16 +0000</pubDate>
      <link>https://dev.to/ruxozheng/pre-render-issue-in-blazor-server-interaction-3n50</link>
      <guid>https://dev.to/ruxozheng/pre-render-issue-in-blazor-server-interaction-3n50</guid>
      <description>&lt;p&gt;I have been using Reactive UI with Blazor Server for years, and I’ve often noticed double data loadings in applications I’ve developed. This issue can significantly impact application performance by unnecessarily increasing network usage, slowing down page loads, and creating a poor user experience. I fixed this by turning off pre-rendering, but I’ve never fully understood why it happened.&lt;/p&gt;

&lt;p&gt;Recently, I experimented with &lt;code&gt;PersistentComponentState&lt;/code&gt;, hoping to transfer state between the pre-rendering phase and the final rendering phase. My goal was to resolve the double loading issue while still benefiting from pre-rendering. However, I discovered that pre-rendering—even in .NET 9 (SDK 9.0.101)—behaves inconsistently. There also seems to be an unnecessary “page-loading” phase that wastes CPU and memory resources without achieving anything meaningful. I reported this issue on the .NET GitHub repository: &lt;a href="https://github.com/dotnet/aspnetcore/issues/59569" rel="noopener noreferrer"&gt;Issue #59569&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To better understand the problem, let’s review how Blazor pre-rendering works in .NET 9.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-render Effects in Blazor Server Interaction
&lt;/h2&gt;

&lt;p&gt;Pre-rendering is enabled by default for Blazor Server. Whenever a user navigates to a page marked with &lt;code&gt;@rendermode InteractiveServer&lt;/code&gt;, Blazor renders the page twice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static Rendering&lt;/strong&gt;: The first render is for static content, which benefits search engine crawling and robots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interactive Rendering&lt;/strong&gt;: The second render initializes the in-memory state of the Blazor circuit.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During static rendering, Blazor triggers lifecycle methods such as &lt;code&gt;OnInitialized&lt;/code&gt;, &lt;code&gt;OnParametersSet&lt;/code&gt;, and &lt;code&gt;SetParametersAsync&lt;/code&gt;, but not &lt;code&gt;OnAfterRender&lt;/code&gt;. In the interactive phase, all lifecycle methods are triggered. Developers typically load data in &lt;code&gt;OnInitialized&lt;/code&gt;, which leads to the double loading issue. To mitigate this, Microsoft introduced &lt;code&gt;PersistentComponentState&lt;/code&gt; to share state between these phases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Static Rendering
&lt;/h2&gt;

&lt;p&gt;Static rendering behaves like an MVC framework where it has access to &lt;code&gt;HttpContext&lt;/code&gt; as a &lt;code&gt;CascadingParameter&lt;/code&gt;. It is stateless, meaning Blazor creates temporary states, including DI contexts, for static rendering and discards them afterward. &lt;code&gt;PersistentComponentState&lt;/code&gt; is the only mechanism to carry serializable state into the interactive phase.&lt;/p&gt;

&lt;p&gt;Static rendering occurs not only on the page’s initial load but also during navigation via &lt;code&gt;NavigationManager&lt;/code&gt; (e.g., clicking a &lt;code&gt;&amp;lt;NavLink&amp;gt;&lt;/code&gt;), which might be understandable when occasional data updates on the screen are necessary. However, &lt;code&gt;PersistentComponentState&lt;/code&gt; is only effective during the initial load. Static rendering during navigation doesn’t utilize &lt;code&gt;PersistentComponentState&lt;/code&gt;, leading to the creation and discarding of states without updating the screen. This behavior appears more like a defect than an intentional design choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Code
&lt;/h2&gt;

&lt;p&gt;Here’s an example demonstrating the behavior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;@page&lt;/span&gt; &lt;span class="s"&gt;"/"&lt;/span&gt;
&lt;span class="n"&gt;@rendermode&lt;/span&gt; &lt;span class="n"&gt;InteractiveServer&lt;/span&gt;

&lt;span class="n"&gt;@implements&lt;/span&gt; &lt;span class="n"&gt;IDisposable&lt;/span&gt;
&lt;span class="n"&gt;@inject&lt;/span&gt; &lt;span class="n"&gt;PersistentComponentState&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PageTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;PageTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;world&lt;/span&gt;&lt;span class="p"&gt;!&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;@myCount&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="n"&gt;Welcome&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;@code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PersistingComponentStateSubscription&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CascadingParameter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;HttpContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;myCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Interlocked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Increment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnInitialized&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterOnPersisting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Persist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryTakeFromJson&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;myCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Created with persisted=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;existed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, c=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, service=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHashCode&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s"&gt;, and context=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Persist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PersistAsJson&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnAfterRender&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;myCount&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. OnAfterRender &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;firstRender&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ps&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;Dispose&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This modified example from the Blazor skeleton project assigns a counting number whenever &lt;code&gt;OnInitialized&lt;/code&gt; is called and prints out state details. Below is the output when loading the page, navigating away, and returning to the page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Created with persisted=False, c=0, service=16484805, and context=Microsoft.AspNetCore.Http.DefaultHttpContext
2. Created with persisted=True, c=1, service=23091838, and context=
2. OnAfterRender True
3. Created with persisted=False, c=0, service=23749772, and context=Microsoft.AspNetCore.Http.DefaultHttpContext
4. Created with persisted=False, c=0, service=23091838, and context=
4. OnAfterRender True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Explanation of the Output
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Step 1&lt;/strong&gt;: The first static render during the initial load. &lt;code&gt;HttpContext&lt;/code&gt; is accessible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2&lt;/strong&gt;: The interactive render after static rendering. &lt;code&gt;PersistentComponentState&lt;/code&gt; retrieves the persisted state. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3&lt;/strong&gt;: Static rendering occurs during navigation, creating and discarding states without utilizing them. This takes place after navigating to another page and returning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4&lt;/strong&gt;: The interactive render after navigating back, but &lt;code&gt;PersistentComponentState&lt;/code&gt; is no longer effective.&lt;/p&gt;

&lt;p&gt;Another unexpected behavior occurs when navigating to the current page, such as &lt;code&gt;&amp;lt;NavLink&amp;gt;&lt;/code&gt; with &lt;code&gt;href="/"&lt;/code&gt;. In this case, only the static rendering takes place, but the screen remains unchanged, leading to a confusing and seemingly redundant action.&lt;/p&gt;

&lt;p&gt;To clarify, observe the service numbers in the output panel. This number is a unique hash that functions like an object ID. Steps 1 and 3 have entirely different numbers, while steps 2 and 4 share the same number. This happens because the &lt;code&gt;PersistentComponentState&lt;/code&gt; service is registered with a "scoped" lifetime in the DI context, meaning the scoped states in the interactive session are shared. The service is created when the Blazor circuit is initialized and is disposed of when the circuit ends. Since steps 2 and 4 are in interactive mode, they use the same service instance, resulting in the same number. However, steps 1 and 3 are static renders, and their instances are temporary, preventing them from sharing states.&lt;/p&gt;

&lt;p&gt;It would be acceptable if PersistentComponentState worked consistently in step 4. However, even with that functionality, the peculiar behavior of pre-rendering when navigating to the current page remains problematic, particularly for applications that experience heavy usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Two Cents
&lt;/h2&gt;

&lt;p&gt;The current pre-rendering in interactive server mode is not yet suitable for production due to its tendency to perform unintentional and wasteful loads. To make it production-ready, improvements are necessary, such as consistent handling of &lt;code&gt;PersistentComponentState&lt;/code&gt; during navigation and eliminating redundant static rendering phases when revisiting the same page. Until these issues are resolved, it’s advisable to disable pre-rendering using &lt;code&gt;@rendermode @(new InteractiveServerRenderMode(prerender: false))&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>blazor</category>
    </item>
    <item>
      <title>Dev Drive's shortcut guide</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Wed, 04 Dec 2024 06:00:00 +0000</pubDate>
      <link>https://dev.to/ruxozheng/dev-drives-shortcut-guide-3c7a</link>
      <guid>https://dev.to/ruxozheng/dev-drives-shortcut-guide-3c7a</guid>
      <description>&lt;p&gt;Dev Drive is a new type of storage volume introduced in Windows 11. It works with the &lt;a href="https://learn.microsoft.com/en-us/windows-server/storage/refs/refs-overview" rel="noopener noreferrer"&gt;Resilient File System (ReFS)&lt;/a&gt;. One interesting feature I came across is that when copying a file, both the original and the copy might share the same disk space (though I haven't confirmed this yet). I'll look into it more in the future.&lt;/p&gt;

&lt;p&gt;Anyway, here's a quick guide on working with Dev Drive, summarized from the &lt;a href="https://learn.microsoft.com/en-us/windows/dev-drive/" rel="noopener noreferrer"&gt;Microsoft Document&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a New Dev Drive
&lt;/h2&gt;

&lt;p&gt;A Dev Drive is essentially a partition inside a Virtual Hard Disk (&lt;code&gt;.vhd&lt;/code&gt;). Creating a Dev Drive involves creating a virtual disk, formatting it with ReFS, and then mounting it to a logical drive.&lt;/p&gt;

&lt;p&gt;To create a Dev Drive, navigate to &lt;u&gt;System &amp;gt; Storage &amp;gt; Advanced Storage Settings &amp;gt; Disks &amp;amp; volumes&lt;/u&gt;. Select &lt;u&gt;Create Dev Drive&lt;/u&gt;.&lt;/p&gt;

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

&lt;p&gt;If you want to disable antivirus for the Dev Drive, you can use the following command line with administrative privileges:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;fsutil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;devdrv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/disallowAv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resizing a Dev Drive
&lt;/h2&gt;

&lt;p&gt;The Microsoft document provides a way to resize a Dev Drive. However, if you're like me, you might run into a problem where the resize utility doesn't detect your Dev Drive and instead shows irrelevant volumes. There's a PowerShell command called &lt;code&gt;Resize-VHD&lt;/code&gt;, but it requires installing Hyper-V on your machine.&lt;/p&gt;

&lt;p&gt;A better solution is to use &lt;code&gt;diskpart&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Take the VHD of the Dev Drive offline in the &lt;u&gt;Disk &amp;amp; volumes&lt;/u&gt; setting.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jehyeiei4xpo7pxoftj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0jehyeiei4xpo7pxoftj.png" alt="Detach button" width="764" height="716"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open a terminal and run &lt;code&gt;diskpart&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the following commands:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select vdisk file="&amp;lt;Your VDH Path&amp;gt;"
attach vdisk
extend size=&amp;lt;size in MB&amp;gt;
detach vdisk
exit
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The good news is that this method does not require Hyper-V.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move a VHD across machines
&lt;/h2&gt;

&lt;p&gt;The Microsoft documentation includes a recommendation:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When you create a Virtual Hard Disk (VHD) hosted on a fixed disk (HDD or SSD), it is not recommended to copy the VHD, move it to a different machine, and then continue using it as a Dev Drive.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No specific reason is provided, but from my research I believe it might relate to the security settings on the volume. When a VHD is moved to a new machine, it loses its security descriptor, and you need to "trust" the Dev Drive again. The procedure is detailed in &lt;a href="https://learn.microsoft.com/en-us/windows/dev-drive/#what-is-a-trusted-dev-drive" rel="noopener noreferrer"&gt;the same document&lt;/a&gt;, but essentially it's just a command line (with administrator access):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;fsutil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;devdrv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;trust&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;drive-letter&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>microsoft</category>
      <category>windows</category>
    </item>
    <item>
      <title>การใช้งาน Polyglot notebook กับ Python</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Tue, 03 Dec 2024 05:09:21 +0000</pubDate>
      <link>https://dev.to/ruxozheng/kaaraichngaan-polyglot-notebook-kab-python-1iel</link>
      <guid>https://dev.to/ruxozheng/kaaraichngaan-polyglot-notebook-kab-python-1iel</guid>
      <description>&lt;p&gt;โพสนี้รวบรวมเทคนิคที่เกี่ยวข้องกับการใช้ Polyglot notebook เพื่อ run Python interpreter (Polyglot ณ เวลาที่เขียน คือ version &lt;code&gt;1.0.5568010&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Polyglot เป็น plugin อันนึงของ Visual Studio code ที่ run .NET Interactive (ซึ่งคือ Jupyter version .NET นั่นเอง)&lt;/p&gt;

&lt;h2&gt;
  
  
  การ enable Python ใน Polyglot
&lt;/h2&gt;

&lt;p&gt;Polyglot มีคู่มือในการใช้ Python อยู่ที่ &lt;a href="https://github.com/dotnet/interactive/blob/HEAD/docs/jupyter-in-polyglot-notebooks.md" rel="noopener noreferrer"&gt;Manual&lt;/a&gt; ซึ่งมีอยู่ 2 วิธี คือ 1) ใช้ Python ตรงๆ กับ 2) ใช้ผ่าน Anaconda&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;u&gt;อะไรคือ Anaconda?&lt;/u&gt;&lt;br&gt;
Anaconda เป็น toolset สำหรับ data scientists ใช้ Python ในการทำ data processing โดย Adaconda จะมี python repositories ของตัวเอง ที่รับประกันเรื่องปัญหาความเข้ากัน (compatibility issues) ของ Package ไว้ และมันง่ายต่อการติดตั้ง และใช้งาน (โดยเฉพาะ Windows ซึ่งถ้าเป็น Python module บางตัว จะต้องติดตั้ง C++/Rust compilers ไว้เวลาจะใช้งาน แต่ถ้าผ่าน Anaconda เค้าจะมี package พร้อมใช้ให้เลย แม้จะเป็น Windows ก็ตาม!)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  การใช้งานกับ Python ตรงๆ
&lt;/h3&gt;

&lt;p&gt;การใช้งานแบบนี้มี trick เล็กน้อย ถ้าคุณอยากใช้ virtual environment แต่ถ้าคุณ install Python package เป็นแบบ global หมด ก็สามารถใช้งานได้เลย ผ่าน "magic command" ของ Polyglot&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!connect jupyter --kernel-name pythonkernel --kernel-spec python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ให้ copy magic command ลงไปใน Code cell (จะภาษาอะไรก็ได้) แล้วก็กด run ถ้าทุกอย่างโอเค ก็จะขึ้นข้อความประมาณนี้&lt;/p&gt;

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

&lt;p&gt;Note: &lt;code&gt;pythonkernel&lt;/code&gt; เป็นชื่อของ Kernel ที่จะขึ้นมาเลือกเวลาเลือก ภาษาใน Polyglot ดังรูป&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flny11rpyjj7egj3eum5c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flny11rpyjj7egj3eum5c.png" alt="ตัวอย่าง Kernel list" width="560" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ตัวอย่างการทำงาน&lt;/p&gt;

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

&lt;p&gt;แต่ถ้าคุณอยากจะใช้ virtual environment จะมีข้อจำกัดอยู่ นั่นคือคุณจะต้องสร้าง virtual environment ใน directory ของ Notebook นั้น (ก็ด้วยคำสั่งมาตรฐาน Python &lt;code&gt;py -m venv .venv&lt;/code&gt;) ส่วน magic command ยังคงไว้เหมือนเดิม เพียงแต่ Polyglot จะใช้ Python จาก environment เรา พร้อม package ต่างๆที่ installed ไว้&lt;/p&gt;

&lt;p&gt;หลังจาก Preview หวังว่าจะมี option refer virtual environment ที่ตำแหน่งอื่นได้ 🤞&lt;/p&gt;

&lt;h3&gt;
  
  
  การใช้งานกับ Anaconda
&lt;/h3&gt;

&lt;p&gt;ข้อดีของการใช้กับ Anaconda คือ เราสามารถสร้าง virtual environment ไว้ที่อื่นได้ และโดยเฉพาะอย่างยิ่งถ้าคุณอยากจะเล่น data science ของ Python อยู่แล้ว เช่น &lt;code&gt;NumPy&lt;/code&gt; package&lt;/p&gt;

&lt;p&gt;ปกติเมื่อลง Anaconda เสร็จ มันจะมี default environment ที่ชื่อ &lt;code&gt;base&lt;/code&gt; มาให้อยู่แล้ว ซึ่งเราสามารถใช้ magic command ดังต่อไปนี้ได้เลย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!connect jupyter --kernel-name pythonkernel --conda-env base --kernel-spec python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;สังเกตว่าจะมี parameter &lt;code&gt;-conda-env base&lt;/code&gt; เพิ่มขึ้นมา คำว่า &lt;code&gt;base&lt;/code&gt; คือ base environment ของ Anaconda&lt;/p&gt;

&lt;p&gt;แต่ในทางปฏิบัติ เราไม่ควรใช้ base ของ Anaconda โดยเฉพาะถ้าเราอยากจะเพิ่มเติม/ลด package ซึ่งมันอาจจะกระทบ package dependencies และทำให้ tools หลายๆตัวของ Anaconda run ไม่ได้ก็ได้&lt;/p&gt;

&lt;p&gt;เราควรสร้าง Anaconda environment ขึ้นมาใหม่ และวิธีง่ายที่สุดคือใช้ Anaconda Navigator&lt;/p&gt;

&lt;h3&gt;
  
  
  สร้าง environment ใหม่บน Anaconda
&lt;/h3&gt;

&lt;p&gt;Anaconda Navigator เป็น notebook management และ environment management ในตัว&lt;/p&gt;

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

&lt;p&gt;วิธีสร้างก็แสนง่าย กดปุ่ม Create ตั้งชื่อ environment และเลือก Python interpreter ซึ่งแนะนำว่าใช้ตัวที่ recommend โดย Anaconda เพราะจะมีปัญหาเรื่อง compatibility น้อยสุด&lt;/p&gt;

&lt;p&gt;เมื่อกดปุ่มสร้างแล้ว รอซักครู่ ก็จะปรากฎ environment ใหม่ของเราขึ้นมาอยู่ใน list&lt;/p&gt;

&lt;p&gt;แต่เราจะต้อง install package ชื่อ &lt;code&gt;ipykernel&lt;/code&gt; เพิ่มเติมด้วย ซึ่งสามารถทำได้เลยจาก Navigator window เช่นกัน โดย search คำว่า &lt;code&gt;ipykernel&lt;/code&gt; ใน panel ด้านขวาของ environment เรา (สำคัญ ต้องเลือก dropdown เป็น Not installed หรือ All) check ที่ package และกดปุ่ม Apply ด้านล่าง) package นี้จำเป็นสำหรับ Polyglot plugin &lt;/p&gt;

&lt;p&gt;ในตัวอย่าง มีสร้างไว้อันนึงชื่อ &lt;code&gt;ai&lt;/code&gt; เวลาจะใช้ใน Polyglot ก็เปลี่ยน &lt;code&gt;--conda-env&lt;/code&gt; ให้เป็นชื่อ environment เรา&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!connect jupyter --kernel-name pythonkernel --conda-env ai --kernel-spec python3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Missing package!? ใน Anaconda
&lt;/h3&gt;

&lt;p&gt;อาจจะมีบางกรณีที่ Python package ของเราไม่อยู่ใน default list ของ Anaconda ซึ่งเราสามารถ manual install ได้ จาก 2 แหล่งคือ 1) Conda forge และ 2) PyPI&lt;/p&gt;

&lt;p&gt;แนะนำว่าให้หา Python package จาก Conda forge ก่อน ผ่าน &lt;a href="https://anaconda.org/conda-forge" rel="noopener noreferrer"&gt;https://anaconda.org/conda-forge&lt;/a&gt; package หลายอย่างไม่ได้อยู่ใน default list แต่หาได้ที่นี่เช่น &lt;code&gt;langchain-community&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;จะเห็นว่ามันบอกคำสั่งสำหรับ install ใน Anaconda ให้ด้วย&lt;/p&gt;

&lt;p&gt;วิธีการ install package ลงใน environment เรา ก็ต้องเปิด terminal ขึ้นมาก่อน ง่ายสุดก็ใช้ Navigator &lt;/p&gt;

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

&lt;p&gt;แล้วสามารถพิมพ์คำสั่งเพื่อ install package ได้เลย&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;conda &lt;span class="nb"&gt;install &lt;/span&gt;conda-forge::langchain-community
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  แต่ถ้าใน Conda forge ก็ไม่มี?
&lt;/h3&gt;

&lt;p&gt;ก็ต้องกลับมาใช้ &lt;code&gt;pip install&lt;/code&gt; ซึ่งแนะนำให้เปิด terminal ผ่าน Navigator เหมือนกัน ข้อควรระวังคืออาจจะมี compatible issue ระหว่าง conda packages กับ PyPI package แต่เท่าที่ลองมา so far ยัง so good 😇&lt;/p&gt;

</description>
      <category>python</category>
      <category>vscode</category>
      <category>dotnet</category>
      <category>windows</category>
    </item>
    <item>
      <title>สำรวจงาน DigiTech Nov 2024</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Thu, 21 Nov 2024 10:49:19 +0000</pubDate>
      <link>https://dev.to/ruxozheng/samrwcchngaan-digitech-nov-2024-584</link>
      <guid>https://dev.to/ruxozheng/samrwcchngaan-digitech-nov-2024-584</guid>
      <description>&lt;p&gt;ไปงาน DigiTech 2024 ที่เมืองทองมา เดินสำรวจดูว่า trend เป็นอย่างไร&lt;/p&gt;

&lt;p&gt;อย่างแรก ก่อนเข้างานก็คิดไว้ว่า อยากจะมีเครื่องมืออะไรซักอย่างสำหรับนับว่า industry ในงานมีอะไร หรือประเภท/ปัญหาของแต่ละเจ้าที่มาตั้ง booth เป็นเรื่องเกี่ยวกับอะไรบ้าง เลยก็คิดในใจว่า มันน่าจะมีเครื่องมือสำหรับสร้างตัวนับแบบง่ายๆนะ และ search ใน Perplexity ก็มีจริงๆ! 😅&lt;/p&gt;

&lt;p&gt;Tally และ keepthescore เป็นตัวอย่างของเวปที่ทำหน้าที่แค่สร้าง counter แบบง่ายๆ (limit counter 8 ตัว อยากได้ unlimit ต้องจ่ายตังค์ 😮)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvqym9em5vdylw9p4vby.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvqym9em5vdylw9p4vby.jpg" alt="ตัวอย่างตัวเลขที่ได้จากการนับ" width="800" height="1865"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ในแง่ของการจัดงาน งานเหมือนทำมาเพื่อ B2B หา business partner กัน แต่เท่าที่เดินดู เห็นกลุ่มใหญ่เป็นนักศึกษาเสียมากกว่า ดันมาจัดวันธรรมดาซะด้วย &lt;/p&gt;

&lt;p&gt;เราเดินดูงานแล้วใช้ counter นับลักษณะของกลุ่ม industry หลักๆได้ "ประมาณ" ตามตาราง (อย่างน้อยประมาณนี้ เดินไป ดูไป คุยไป)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Industry&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HR&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No code&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;iOT&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E-Commerce&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Training&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPA&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image recog&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;แสดงเป็นกราฟสัดส่วนดังนี้&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Observation
&lt;/h2&gt;

&lt;p&gt;เท่าที่ดู Low code platform มาจาก SG และ JP หมด, หนึ่งในนั้นคือ TOPPAN (จาก SG) ซึ่งพึ่ง launch lowcode platform ปีที่แล้ว และ&lt;/p&gt;

&lt;p&gt;Note 2: CRM + AI มีให้เห็น 2-3 platforms&lt;/p&gt;

&lt;h2&gt;
  
  
  ผู้เล่นใหม่
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://toppanecquaria.com/wp-content/uploads/2024/09/Company-Introduction-and-Capability.pdf" rel="noopener noreferrer"&gt;TOPPAN Ecquaria -- KAIZEN&lt;/a&gt; - Low code targets government sector&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.enablesurvey.com/home" rel="noopener noreferrer"&gt;EnableSurvey&lt;/a&gt; ของไทยเอง ทำมาแข่ง SurveyMonkey พึ่ง launch ปีที่แล้ว ราคา individual ถูกกว่า แต่ถ้าแบบ team ก็พอๆกัน พึ่งสังเกตว่าราคา individual แพงกว่าแบบ team ... ทำไมนะ? -_-a&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.sms-kub.com" rel="noopener noreferrer"&gt;SMSKUB&lt;/a&gt; พึ่งจดทะเบียนเมื่อ 2 ปีก่อน&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://web.khunai.com/" rel="noopener noreferrer"&gt;KhunAI&lt;/a&gt; AI aggregator + pre-prompt platform -- สำหรับให้คนทั่วไปใช้ AI ง่ายขึ้น พึ่งเปิดตัวกันยา ปีนี้&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FunCrowd -- CRM platform launched 3 ปีก่อน&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  ผู้เล่นเก่า
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.desknets.my/appsuite" rel="noopener noreferrer"&gt;desknet's NEO -- AppSuite&lt;/a&gt; app no code ตั้งแต่ปี 2013 -- ทำ subscription model ที่ ฿280 ต่อ user ต่อเดือน (น่าจะ cloud based)

&lt;ul&gt;
&lt;li&gt;มี ChatLuck อีก น่าจะเป็นคู่แข่ง Slack, MS Team&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

</description>
      <category>industry</category>
    </item>
    <item>
      <title>[F#] Bring Chrome Window to front in Selenium (Windows)</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Mon, 18 Nov 2024 03:45:47 +0000</pubDate>
      <link>https://dev.to/ruxozheng/f-bring-chrome-window-to-front-in-selenium-windows-2il7</link>
      <guid>https://dev.to/ruxozheng/f-bring-chrome-window-to-front-in-selenium-windows-2il7</guid>
      <description>&lt;p&gt;(Note: This article uses &lt;strong&gt;.NET&lt;/strong&gt; Web Driver and .NET Interoperability to work with the Win32 API.)&lt;/p&gt;

&lt;p&gt;In this article, I'll show you how to bring a Chrome browser window to the front, ensuring it becomes the active, focused window during Selenium automation. Usually, Selenium is used to automate web tasks in the background, allowing you to continue working on other tasks. However, some websites 😉 get suspicious if they detect actions happening while the browser is inactive—after all, how could there be interactions if the browser isn't even in focus?&lt;/p&gt;

&lt;p&gt;To help address this, let's take a closer look at how browser focus works. In JavaScript, the &lt;code&gt;onfocus&lt;/code&gt; and &lt;code&gt;onblur&lt;/code&gt; events allow websites to determine if the browser is the active window. The &lt;code&gt;onfocus&lt;/code&gt; event triggers when the window gains input focus (meaning it can receive keyboard input and is the active window on the screen), while the &lt;code&gt;onblur&lt;/code&gt; event triggers when the browser loses focus. These simple events can give websites clues about whether interactions seem automated.&lt;/p&gt;

&lt;p&gt;Many websites also use more sophisticated approaches to detect human-like interaction patterns. Therefore, if you're trying to convincingly simulate human behavior, controlling the browser window—bringing it to the front or switching between windows—can be essential. This is where the Win32 API comes in handy.&lt;/p&gt;

&lt;h5&gt;
  
  
  Little Note about F#
&lt;/h5&gt;

&lt;p&gt;This article uses F#. Here is a little explanation of its syntax:In C# if you have a method/function named &lt;code&gt;f&lt;/code&gt; which takes one argument (supposed it's &lt;code&gt;x&lt;/code&gt;), you can call it with &lt;code&gt;var y = f(x)&lt;/code&gt;. But F# version, the possibilities are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// normal call&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;    &lt;span class="c1"&gt;// parentheses are optional&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c1"&gt;// forward pipe call&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Bringing Chrome to the Front with Selenium
&lt;/h1&gt;

&lt;p&gt;On Windows, you can control any application window using the Win32 API, but you need a "window handle" to do so. A window handle (often abbreviated as HWND) is a token provided by the Windows OS that uniquely identifies a window in the system's GUI environment.&lt;/p&gt;

&lt;p&gt;When using Selenium, starting &lt;code&gt;ChromeDriver&lt;/code&gt; automatically opens the browser, but it doesn't directly provide the window handle. Instead, we can get to it indirectly via the &lt;code&gt;ChromeDriver&lt;/code&gt; process ID, which is accessible through &lt;code&gt;ChromeDriverService&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Obtain &lt;code&gt;ChromeDriver&lt;/code&gt; Process ID
&lt;/h2&gt;

&lt;p&gt;When we create the &lt;code&gt;ChromeDriver&lt;/code&gt; instance, we can access its process ID through the given &lt;code&gt;ChromeDriverService&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromeOptions&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromeDriverService&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChromeDriver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"Process ID = {service.ProcessId}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Find the Browser's Process ID
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ChromeDriver&lt;/code&gt; executable (&lt;code&gt;chromedriver.exe&lt;/code&gt;) is responsible for launching the Chrome browser instance, but we need the browser's process ID, not the driver's. To find it, we need to iterate through all running Chrome processes on the machine and identify the one spawned by our driver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Diagnostics&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getBrowserId&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver_process_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;chrome_processes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GetProcessesByName&lt;/span&gt; &lt;span class="s2"&gt;"chrome"&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chrome_processes&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;getParentId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                          &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="nc"&gt;Id&lt;/span&gt;
                                          &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toArray&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// must be one Chrome window&lt;/span&gt;
        &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="n"&gt;chrome_processes&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;_.&lt;/span&gt;&lt;span class="nc"&gt;Dispose&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.1: Get the Parent Process ID
&lt;/h3&gt;

&lt;p&gt;Getting the parent process ID from a given process ID is a bit tricky — it requires querying detailed process information using the &lt;code&gt;NtQueryInformationProcess&lt;/code&gt; function. Essentially, we open the process with the known ID, call this function, and extract the required information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;InteropServices&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ProcessAccessFlags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;QueryInformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x400&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Struct&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;StructLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;LayoutKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Sequential&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;ProcessBasicInformation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Reserved1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;
    &lt;span class="nc"&gt;PebBaseAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;
    &lt;span class="nc"&gt;Reserved2_0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;
    &lt;span class="nc"&gt;Reserved2_1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;
    &lt;span class="nc"&gt;UniqueProcessId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;
    &lt;span class="nc"&gt;InheritedFromUniqueProcessId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;member&lt;/span&gt; &lt;span class="nc"&gt;Default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Reserved1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;PebBaseAddress&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Reserved2_0&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Reserved2_1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;UniqueProcessId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;InheritedFromUniqueProcessId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"kernel32.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OpenProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ProcessAccessFlags&lt;/span&gt; &lt;span class="n"&gt;dwDesiredAccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;bInheritHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;dwProcessId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"kernel32.dll"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SetLastError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;CloseHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;hObject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ntdll.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;NtQueryInformationProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="nc"&gt;ProcessHandle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;ProcessInformationClass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ProcessBasicInformation&lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nc"&gt;ProcessInformation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nc"&gt;ProcessInformationLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nc"&gt;ReturnLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;getParentId&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;ProcessAccessFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;QueryInformation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="n"&gt;failwith&lt;/span&gt; &lt;span class="s2"&gt;"OpenProcess failed"&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;pbi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ProcessBasicInformation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Default&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;returnLength&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SizeOf&lt;/span&gt; &lt;span class="n"&gt;pbi&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NtQueryInformationProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;returnLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;failwithf&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"NtQueryInformationProcess failed with status %d{status}"&lt;/span&gt;
        &lt;span class="n"&gt;pbi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;InheritedFromUniqueProcessId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ToInt32&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="nc"&gt;CloseHandle&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Locate the Window Handle
&lt;/h2&gt;

&lt;p&gt;Once we have the process ID for the browser, the next step is to enumerate the window handles owned by this process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EnumWindowsProc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;delegate&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user32.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;EnumWindows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EnumWindowsProc&lt;/span&gt; &lt;span class="n"&gt;lpEnumFunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;lParam&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user32.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;GetWindowThreadProcessId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;lpdwProcessId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user32.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;IsWindowVisible&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;findBrowserHandle&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;browser_pid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;enumWindowsProc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(_:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mutable&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="nc"&gt;GetWindowThreadProcessId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ignore&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser_pid&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;hWnd&lt;/span&gt; &lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
        &lt;span class="bp"&gt;true&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;EnumWindowsProc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enumWindowsProc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;|&lt;/span&gt; &lt;span class="nc"&gt;EnumWindows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;failwith&lt;/span&gt; &lt;span class="s2"&gt;"EnumWindows failed"&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="nc"&gt;IsWindowVisible&lt;/span&gt;
           &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Seq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exactlyOne&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since there could be multiple windows, we specifically look for the visible one—this is the Chrome window that we want to interact with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Step: Bring Chrome to the Front!
&lt;/h2&gt;

&lt;p&gt;Now that we have the correct window handle, we can bring it to the front by using the &lt;code&gt;SetForegroundWindow&lt;/code&gt; API. This call will make Chrome the active window, ready for any user input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"kernel32.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="n"&gt;uint&lt;/span&gt; &lt;span class="nc"&gt;GetLastError&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt;

&lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;DllImport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"user32.dll"&lt;/span&gt;&lt;span class="o"&gt;)&amp;gt;]&lt;/span&gt;
&lt;span class="k"&gt;extern&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;SetForegroundWindow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;IntPtr&lt;/span&gt; &lt;span class="n"&gt;hWnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;NoError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;[&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;]&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;AccessDeniedError&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;setForegroundWindow&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;IntPtr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hwnd&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;SetForegroundWindow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nc"&gt;GetLastError&lt;/span&gt;&lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;AccessDeniedError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"Cannot set foreground, process doesn't own foreground privilege"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;NoError&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// window is already on top... I think...&lt;/span&gt;
        &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;failwithf&lt;/span&gt; &lt;span class="o"&gt;$&lt;/span&gt;&lt;span class="s2"&gt;"""SetForegroundWindow failed (0x%X{code})"""&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="bp"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat here is that in order to successfully set the foreground window, the process making the request (your Selenium script) must already be the foreground process. In practical terms, this means your Selenium script needs to be the active application before it can bring the browser to the front. To make this strategy work smoothly, ensure your script maintains foreground status whenever necessary.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;In this tutorial, we used a combination of Win32 API calls to obtain and manipulate the window handle of a Chrome browser, allowing us to bring it to the front during Selenium automation. While this may seem like a lot of effort for something seemingly simple, it's often necessary if you need to convincingly simulate human interactions. Ideally, Selenium would offer more direct support for this, but until then, this approach ensures your automation scripts remain effective and human-like.&lt;/p&gt;

</description>
      <category>selenium</category>
      <category>dotnet</category>
      <category>windows</category>
      <category>win32</category>
    </item>
    <item>
      <title>Blazor's Authentication</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Thu, 07 Nov 2024 03:48:52 +0000</pubDate>
      <link>https://dev.to/ruxozheng/blazors-authorization-13hf</link>
      <guid>https://dev.to/ruxozheng/blazors-authorization-13hf</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer&lt;/em&gt;: based on my observation, not readings.&lt;/p&gt;

&lt;p&gt;Recently, I had to implement Firebase authentication for a Blazor WASM project. To my surprise, I discovered there's no ready-made authentication library for Firebase on .NET. While I found the Firebase Admin library, it wasn't quite what I needed for client-side authentication. So, I ended up building my own solution for .NET.&lt;/p&gt;

&lt;p&gt;Fortunately, I didn't have to start from scratch. Firebase provides a JavaScript SDK for authentication, which is quite straightforward to use and integrates well with Blazor WASM.&lt;/p&gt;

&lt;h1&gt;
  
  
  Blazor Authentication
&lt;/h1&gt;

&lt;p&gt;Blazor handles authorization through a class derived from &lt;code&gt;AuthenticationStateProvider&lt;/code&gt;. This class requires you to override a key method: &lt;code&gt;GetAuthenticationStateAsync&lt;/code&gt;. You can find more details in the &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.authorization.authenticationstateprovider.getauthenticationstateasync" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GetAuthenticationStateAsync&lt;/code&gt; method is responsible for returning an instance of &lt;code&gt;AuthenticationState&lt;/code&gt;, which encapsulates the current user as a &lt;code&gt;ClaimsPrincipal&lt;/code&gt;. The &lt;code&gt;ClaimsPrincipal&lt;/code&gt; contains a &lt;code&gt;ClaimsIdentity&lt;/code&gt;, which the framework uses to determine whether the user is authenticated by checking the &lt;code&gt;IsAuthenticated&lt;/code&gt; property.&lt;/p&gt;

&lt;p&gt;By overriding &lt;code&gt;GetAuthenticationStateAsync&lt;/code&gt; and returning the appropriate user state, you can manage the application's authentication flow as well as user permissions. Essentially, this means you have full control over how authentication is implemented and when users are considered authenticated or not.&lt;/p&gt;

&lt;h1&gt;
  
  
  Firebase Authentication Overview
&lt;/h1&gt;

&lt;p&gt;Firebase offers comprehensive documentation for their authentication libraries, which can be found here (&lt;a href="https://firebase.google.com/docs/auth/web/start" rel="noopener noreferrer"&gt;JavaScript SDK&lt;/a&gt;). Since I was working with Blazor WASM, it made sense to leverage browser modules. Luckily, Firebase also supports this approach, as mentioned in a note on this &lt;a href="https://firebase.google.com/docs/web/setup#add-sdk-and-initialize" rel="noopener noreferrer"&gt;page&lt;/a&gt;.&lt;/p&gt;

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

&lt;h1&gt;
  
  
  Blazor integration
&lt;/h1&gt;

&lt;p&gt;The idea is to use the JS SDK to provide the Firebase authentication, and once we get the authenticated JWT, just hands over to .NET Cookie authentication for storing as .NET cookie, or manually managing the token yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  JS interop script
&lt;/h2&gt;

&lt;p&gt;First, we need a Javascript-side interop code to call Firebase authentication. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initializeApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.gstatic.com/firebasejs/10.14.1/firebase-app.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getAuth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GoogleAuthProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signInWithPopup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.gstatic.com/firebasejs/10.14.1/firebase-auth.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initializeApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleAuthProvider&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signInWithPopup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GoogleAuthProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;credentialFromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessToken&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encoded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeMethodAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AfterSignIn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// you may use local storage or session storage, etc.&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`after-signin=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;encoded&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;; path=/; Secure; SameSite=Strict`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invokeMethodAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AfterSignIn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;permission-needed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firebase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signin&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to use the SDK, you need Firebase configuration, which is passed to the function via &lt;code&gt;config&lt;/code&gt; variable. You can find the configuration in Firebase's console, in the application section under your project's settings.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;output&lt;/code&gt; variable encapsulates .NET interop class with a method name &lt;code&gt;AfterSignIn&lt;/code&gt;. It is an integration for the script and .NET Blazor code. My &lt;code&gt;AfterSignIn&lt;/code&gt; method looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;JSInvokable&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AfterSignIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SignInInfo&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// basically you may want to cache the result of login&lt;/span&gt;
        &lt;span class="n"&gt;afterSignInInfo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AfterSignInInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// return serialized sign-in data as string for storing in the cookie&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Serialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the code to invoke the &lt;code&gt;signin&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;Login&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;jsRef&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DotNetObjectReference&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;fbConfig&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FirebaseConfig&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt; &lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"FirebaseConfig"&lt;/span&gt;&lt;span class="p"&gt;]!)!;&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeVoidAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"window.firebase.signin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jsRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fbConfig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;afterSignInInfo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;IsSuccess&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;returnUrl&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$"?ReturnUrl=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HttpUtility&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UrlEncode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;returnUrl&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;navManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NavigateTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"/login/success&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  That's it
&lt;/h1&gt;

&lt;p&gt;The JS code should be generalized enough to be used with any kind of Blazor project (Blazor Server, Blazor Hosted WASM, or just Blazor WASM).&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>dotnetcore</category>
      <category>blazor</category>
      <category>firebase</category>
    </item>
    <item>
      <title>Casual Bayesian Network</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Thu, 07 Nov 2024 01:50:22 +0000</pubDate>
      <link>https://dev.to/ruxozheng/casual-bayesian-network-i76</link>
      <guid>https://dev.to/ruxozheng/casual-bayesian-network-i76</guid>
      <description>&lt;p&gt;A causal Bayesian network (CBN) is a diagram that shows cause and effect to make it easier to understand Bayesian reasoning... The key is being able to identify what is the cause and what is the effect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blue&lt;/strong&gt; represents forward problems (deduction)&lt;br&gt;
&lt;strong&gt;Red&lt;/strong&gt; represents inverse problems (induction)&lt;/p&gt;

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

</description>
      <category>bayesian</category>
      <category>probability</category>
    </item>
    <item>
      <title>p-value</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Wed, 06 Nov 2024 09:42:31 +0000</pubDate>
      <link>https://dev.to/ruxozheng/p-value-2eo1</link>
      <guid>https://dev.to/ruxozheng/p-value-2eo1</guid>
      <description>&lt;p&gt;Just recently understood p-value... I’ve seen many papers mention this value, but I didn’t really know what it meant.&lt;/p&gt;

&lt;p&gt;The p-value is the probability of getting the observed experimental results assuming that our hypothesis is not true or has no effect.&lt;/p&gt;

&lt;p&gt;For example, if we say that watering plant X every morning makes it grow 20% faster compared to watering it at other times of the day:&lt;/p&gt;

&lt;p&gt;If the p-value is high, it means there's a high chance that plant X could grow 20% without us watering it in the morning (suggesting that our hypothesis might not be true).&lt;/p&gt;

&lt;p&gt;For instance, if the p-value is 0.1 or 10%, it means there’s a 10% chance that the plant will grow 20% without being watered in the morning.&lt;/p&gt;

&lt;p&gt;On Brilliant.org, it explains that the issue with p-values is that they represent confidence in a single experiment only. In practice, a good experiment should be independently replicated and consistently yield significant results. (In Bayesian terms, this means that confidence in the experimental results multiplies with each successful replication.)&lt;/p&gt;

&lt;p&gt;The bottom line is... trusting a single paper isn’t very reliable.&lt;/p&gt;

</description>
      <category>bayesian</category>
      <category>probability</category>
    </item>
    <item>
      <title>Apply Bayes กับงาน 😆</title>
      <dc:creator>Ruxo Zheng</dc:creator>
      <pubDate>Tue, 05 Nov 2024 07:35:34 +0000</pubDate>
      <link>https://dev.to/ruxozheng/apply-bayes-kabngaan-3390</link>
      <guid>https://dev.to/ruxozheng/apply-bayes-kabngaan-3390</guid>
      <description>&lt;p&gt;สมมติถ้าเรากำลังหางาน แต่เราพยายามหลีกเลี่ยงบริษัทที่ management team มี lead ควบหลาย role (เรียกว่า duck leader -- Ld 😆 -- ละกัน d = duck🦆) โดยเราเชื่อว่า ในบริษัทที่มี lead แบบนี้ จะไม่น่ามีความสุขในการทำงาน 😏&lt;/p&gt;

&lt;p&gt;บังเอิญว่าเราได้งานบริษัท A.. โอกาสที่บริษัท A จะเป็นที่ทำงานที่มีความสุขเทียบกับโอกาสที่บริษัท A เป็นที่ทำงานที่ไม่มีความสุข... โดยที่เราไม่รู้อะไรเลย เขียนเป็น:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P(H):P(¬H)=1:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;โดย &lt;code&gt;P(H)&lt;/code&gt; เป็นความน่าจะเป็นที่บริษัท A เป็นที่ทำงานที่มีความสุข&lt;br&gt;
และ &lt;code&gt;P(¬H)&lt;/code&gt; เป็นความน่าจะเป็นที่บริษัท A ไม่น่าจะมีความสุข&lt;br&gt;
มันเป็น 1:1 เพราะเราไม่รู้อะไรเลย ดังนั้นโอกาสที่จะมี/หรือไม่มีความสุขเท่ากับ 50% นั่นคืออัตราส่วน 1:1&lt;br&gt;
บังเอิญตอนสัมภาษณ์เราพบว่าบริษัท A มี duck leader และเริ่มสงสัยว่าเราจะมีความสุขในการทำงานที่บริษัท A มั้ย..❓ ด้วย evidence ใหม่... อัตราส่วนของที่ทำงานที่มีความสุขของเรากลายเป็น&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P(H)∙P(Ld│H):P(¬H)∙P(Ld│¬H)
หรือ P(Ld│H):P(Ld│¬H) เพราะ P(H):P(¬H)=1:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ถามว่า ถ้าในบริษัทที่ทำงานไม่สุขทั้งหมดในโลก(จักรวาล ฯลฯ) มี duck leader หรือเปล่า? จริงๆอาจจะมีหรือไม่มีก็ได้  เพราะอาจจะไม่สุขจากสาเหตุอื่นก็ได้ เราคิดว่ามันอาจจะเป็น 50% (subjective evidence) ดังนั้นเราให้&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P(Ld│¬H)=½
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ดังนั้นอัตราส่วนจึงเป็น &lt;code&gt;P(Ld│H):½&lt;/code&gt;&lt;br&gt;
นั่นคือ บริษัทจะเป็นที่ที่มีความสุขหรือเปล่า ขึ้นอยู่กับ &lt;code&gt;P(Ld|H)&lt;/code&gt; หรือโอกาสที่มี duck leader อยู่ในบริษัทที่มีความสุขเท่าไหร่&lt;/p&gt;

&lt;p&gt;ถ้าโดยเฉลี่ยเราคิดว่า มันจะมีหรือไม่มีก็ได้ โอกาสก็จะเป็น 50% หรือ ½ ซึ่งจะทำให้อัตราส่วนกลับมาเป็น ½:½ หรือ 1:1 เท่าเดิม แปลว่า duck leader ไม่มีผลต่อความสุข&lt;/p&gt;

&lt;p&gt;แต่ถ้าเรา "เชื่อ" (subjective evidence) ว่า บริษัทที่มีความสุขไม่น่าจะมี duck leader อยู่ได้ เช่น assume 5 บริษัทที่สุข อาจจะมีแค่ ที่เดียวที่มี duck leader (เดาเอา) &lt;br&gt;
อัตราส่วนของ H กลายเป็น ⅕:½ หรือ 2:5 นั่นคือ ถ้ามีบริษัททั่วไป 7 แห่งแล้ว 2 ใน 7 (~29%) จะเป็นที่มีความสุข และ 5 ใน 7 (~71%) เป็นที่ไม่มีความสุข&lt;/p&gt;

&lt;p&gt;แต่ถ้า somehow มีผลสำรวจทางสถิติ (objective evidence) บอกว่าบริษัทที่มีความสุข มี duck leader อยู่ 80% ก็จะกลายเป็น ⅘:½ หรือ 8:5 แปลว่าโอกาสที่จะเป็นที่ทำงานมีสุขเท่ากับ 8/13 (61.5%) และไม่มีสุข 5/13 (38.5%)&lt;/p&gt;

&lt;p&gt;อยากทำงานกับบริษัท A มั้ย เลือกเอา 😁&lt;/p&gt;

</description>
      <category>bayesian</category>
      <category>probability</category>
    </item>
  </channel>
</rss>
