<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[CodeWithHans]]></title><description><![CDATA[CodeWithHans]]></description><link>https://codewithhans.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 08:38:37 GMT</lastBuildDate><atom:link href="https://codewithhans.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Part 1 - Design: Automate Azure Policy Remediation directly in your repository with Event Grid System Topics]]></title><description><![CDATA[Please note: This post is part of a two-part series. This article focuses on the design and architecture of the solution, while the next part will be a deep dive into building the implementation.

Around seven years ago, when I started working as a d...]]></description><link>https://codewithhans.com/part-1-design-automate-azure-policy-remediation-directly-in-your-repository-with-event-grid-system-topics</link><guid isPermaLink="true">https://codewithhans.com/part-1-design-automate-azure-policy-remediation-directly-in-your-repository-with-event-grid-system-topics</guid><category><![CDATA[Azure]]></category><category><![CDATA[Azure Policy]]></category><category><![CDATA[automation]]></category><category><![CDATA[.NET]]></category><category><![CDATA[Cloud Computing]]></category><dc:creator><![CDATA[Hans Muns]]></dc:creator><pubDate>Tue, 27 Jan 2026 18:06:19 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>Please note: This post is part of a two-part series. This article focuses on the design and architecture of the solution, while the next part will be a deep dive into building the implementation.</strong></p>
</blockquote>
<p>Around seven years ago, when I started working as a developer, my mentality was purely: <strong>"</strong>Ship fast, break fast.<strong>"</strong></p>
<p>While I still partially agree with that sentiment, my perspective shifted significantly after I switched to consultancy around three years into my career. Consultancy landed me a project at a large corporate client where, for the first time, I encountered a separate Compliance Department.</p>
<p>I was initially fascinated by the granular detail of their work. However, that fascination quickly turned into frustration. Every week, I would hit the same deployment wall at least two or three times:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769526906111/b9f1f377-e505-443f-a6a3-2d23c60c4493.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-the-problem">The problem</h1>
<p>The compliance department enforced strict policies. While valid and necessary for security and governance, they became a bottleneck. My workflow for resolving these errors was repetitive and tedious:</p>
<ol>
<li><p>Deploy resources.</p>
</li>
<li><p>Fail due to a policy error.</p>
</li>
<li><p>Investigate why the policy declined the changes.</p>
</li>
<li><p>Attempt a fix and redeploy.</p>
</li>
<li><p>Repeat 4-5 times until successful.</p>
</li>
</ol>
<p>Because this process was so cumbersome, I had a realization: <em>I’m a developer. I can automate this.</em></p>
<h1 id="heading-design">Design</h1>
<h2 id="heading-current-situation">Current situation</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769530908952/fecea3eb-33ab-48aa-ad70-8728e192d600.png" alt class="image--center mx-auto" /></p>
<p>Before automating the solution, I needed to map out the manual process I was trying to replace. It looked straightforward but required manual intervention at every step. I needed a tool that could notify me instantly when a deployment failed, so I wouldn't have to manually monitor deployments or dig through logs.</p>
<p>That is where I discovered Azure Event Grid System Topics.</p>
<h2 id="heading-what-are-event-grid-system-topics">What are Event Grid System Topics?</h2>
<p>To summarize, Azure Event Grid is a publish-subscribe service for message distribution. It allows services to send events (publish) and receive events (subscribe).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769527500220/1a7ea702-8fb7-48e4-a265-f272067d030a.png" alt class="image--center mx-auto" /></p>
<p><em>Source:</em> <a target="_blank" href="https://learn.microsoft.com/en-us/azure/event-grid/overview"><em>Microsoft Learn</em></a></p>
<p>These events are sent through a Topic, essentially a queue where events wait to be consumed. A Subscriber (the event handler) listens to a topic via a Subscription. You can add filters to these subscriptions so you only receive the events you actually care about.</p>
<p>A topic is called a System Topic when Azure Services themselves (like your Azure Subscription or Resource Group) are the ones publishing the events.(see <a target="_blank" href="https://learn.microsoft.com/en-us/azure/event-grid/system-topics">https://learn.microsoft.com/en-us/azure/event-grid/system-topics</a> for a full list).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769528008512/21edab24-5c69-41c7-80fd-0c1279562c16.gif" alt class="image--center mx-auto" /></p>
<h2 id="heading-automation-design">Automation design</h2>
<p>Now that we understand the tools, let’s look at the automation architecture.</p>
<p>We can create a System Topic at the Subscription leve<strong>l</strong>. This topic exposes several events, including <code>Microsoft.Resources.ResourceWriteFailure</code>. This event is published exactly when a resource deployment fails, this is what happens when a policy denies my changes.</p>
<p>When we look at the raw event payload, it looks something like this (Note: The structure below is a <code>ResourceWriteSuccess</code> example, but the schema adheres to the <a target="_blank" href="https://cloudevents.io/">CloudEvents spec</a> and is identical for failures):</p>
<pre><code class="lang-json">[
    {
        <span class="hljs-attr">"subject"</span>: <span class="hljs-string">"/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}"</span>,
        <span class="hljs-attr">"topic"</span>: <span class="hljs-string">"/subscriptions/{subscription-id}"</span>,
        <span class="hljs-attr">"type"</span>: <span class="hljs-string">"Microsoft.Resources.ResourceWriteSuccess"</span>,
        <span class="hljs-attr">"time"</span>: <span class="hljs-string">"2018-07-19T18:38:04.6117357Z"</span>,
        <span class="hljs-attr">"id"</span>: <span class="hljs-string">"4db48cba-50a2-455a-93b4-de41a3b5b7f6"</span>,
        <span class="hljs-attr">"data"</span>: {
            <span class="hljs-attr">"authorization"</span>: {
                <span class="hljs-attr">"scope"</span>: <span class="hljs-string">"/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}"</span>,
                <span class="hljs-attr">"action"</span>: <span class="hljs-string">"Microsoft.Storage/storageAccounts/write"</span>,
                <span class="hljs-attr">"evidence"</span>: {
                    <span class="hljs-attr">"role"</span>: <span class="hljs-string">"Subscription Admin"</span>
                }
            },
            <span class="hljs-attr">"claims"</span>: {},
            <span class="hljs-attr">"correlationId"</span>: <span class="hljs-string">"{ID}"</span>,
            <span class="hljs-attr">"resourceProvider"</span>: <span class="hljs-string">"Microsoft.Storage"</span>,
            <span class="hljs-attr">"resourceUri"</span>: <span class="hljs-string">"/subscriptions/{subscription-id}/resourcegroups/{resource-group}/providers/Microsoft.Storage/storageAccounts/{storage-name}"</span>,
            <span class="hljs-attr">"operationName"</span>: <span class="hljs-string">"Microsoft.Storage/storageAccounts/write"</span>,
            <span class="hljs-attr">"status"</span>: <span class="hljs-string">"Succeeded"</span>,
            <span class="hljs-attr">"subscriptionId"</span>: <span class="hljs-string">"{subscription-id}"</span>,
            <span class="hljs-attr">"tenantId"</span>: <span class="hljs-string">"{tenant-id}"</span>
        },
        <span class="hljs-attr">"specversion"</span>: <span class="hljs-string">"1.0"</span>
    }
]
</code></pre>
<p>This JSON gives us the event type, the data (what we were trying to do), and, most importantly, a correlationId.</p>
<h2 id="heading-correlation-ids">Correlation IDs</h2>
<p>There is a catch, the Event Grid event tells us that a failure happened, but it doesn't strictly tell us why ( which specific policy was violated).</p>
<p>Luckily, Azure logs everything. If we look at the Activity Logs of the subscription, we can see the full policy violation details. The magic key here is the CorrelationId. The ID in the Event Grid message matches the ID in the Activity Log.</p>
<h3 id="heading-the-solution-workflow">The solution workflow</h3>
<p>By combining these elements, we can build a fully automated loop:</p>
<ol>
<li><p>Event Grid detects a <code>ResourceWriteFailure</code>.</p>
</li>
<li><p>An Azure Function receives the event and uses the <code>CorrelationId</code> to query the Azure Activity Log.</p>
</li>
<li><p>The Function extracts the <code>PolicyDefinitionId</code> and <code>AssignmentId</code> from the log, and fetches the relevant policy definitions and assignments in Azure.</p>
</li>
<li><p>The function fetches all infrastructure as code files from the GitHub repository.</p>
</li>
<li><p>All this data (Policy, Error, IaC code) is sent to Azure AI Foundry. The AI analyzes the conflict and generates a GitHub issue and assigns it to GitHub Copilot.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1769528783654/77bdb044-9003-47ab-8641-d2c6c59d5207.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-whats-next">Whats next?</h1>
<p>We now have a solid architectural blueprint. We know how to capture the error signal via Event Grid, how to bridge that signal to the detailed Activity Logs using the Correlation ID, and how we intend to use AI to generate the fix.</p>
<p>However, a design is only as good as its execution.</p>
<p>In Part 2 of this series, we will stop planning and start coding. I will walk you through the actual implementation, including:</p>
<ul>
<li><p>Setting up the Event Grid System Topic in Azure.</p>
</li>
<li><p>Writing the Azure Function.</p>
</li>
<li><p>Integrating Azure AI Foundry to automatically generate a detailed GitHub issue for our policy violations and assign them to Github Copilot.</p>
</li>
</ul>
<p>Stay tuned for the code deep dive!</p>
]]></content:encoded></item><item><title><![CDATA[Three ways to handle third-party components when writing bUnit tests]]></title><description><![CDATA[Writing trustworthy and reliable tests for your Blazor components can be hard. Especially if you’re heavy relaying on third-party components. In this short blog post I want to provide three ways to handle third-party components when writing bUnit tes...]]></description><link>https://codewithhans.com/three-ways-to-handle-third-party-components-when-writing-bunit-tests</link><guid isPermaLink="true">https://codewithhans.com/three-ways-to-handle-third-party-components-when-writing-bunit-tests</guid><category><![CDATA[Blazor]]></category><category><![CDATA[.NET]]></category><category><![CDATA[unit testing]]></category><category><![CDATA[C#]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Hans Muns]]></dc:creator><pubDate>Fri, 05 Sep 2025 12:46:56 GMT</pubDate><content:encoded><![CDATA[<p>Writing trustworthy and reliable tests for your Blazor components can be hard. Especially if you’re heavy relaying on third-party components. In this short blog post I want to provide three ways to handle third-party components when writing bUnit tests. I assume you already know bUnit. If not, please check out their docs at <a target="_blank" href="https://bunit.dev/">https://bunit.dev/</a>.</p>
<p>For all demo code in this project, I’m using <a target="_blank" href="https://xunit.net">xUnit</a> as testing framework and <a target="_blank" href="https://mudblazor.com/">MudBlazor</a> as example third-party component library. Though, every option is also viable with other libraries. Lets dive straight into the first option!</p>
<h1 id="heading-option-1-loading-the-third-party-components">Option 1 - Loading the third-party components</h1>
<p>Within our bUnit tests, we can actually load in our third-party components as we normally would within our Blazor application.</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CheckoutCartTests</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// We need to add these so we can use MudBlazor components correctly</span>
    Services.AddMudServices();
    JSInterop.Mode = JSRuntimeMode.Loose;
}
</code></pre>
<p>As you can see, we add the default MudBlazor services and we then set the mode of the JSInterop to loose. The JSInterop is provided by bUnit and makes it possible to mock responses from JS. This is necessary because bUnit does not run any JS. By default the mode is set to <code>Strict</code> which will throw an exception if any JS is invoked that is not mocked. By setting it to <code>Loose</code> we tell it to just ignore any calls to the JS and return the default value if a JS method is called.</p>
<p>This option will work, though it might have some serious downsides. To illustrate I’ve provided a short list below.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>✅ Upsides</td><td>❌ Downsides</td></tr>
</thead>
<tbody>
<tr>
<td>Easy and fast to setup</td><td>Can make tests brittle because they can become depended on the third-party library if not careful</td></tr>
<tr>
<td>Requires minimal mocking</td><td>Won’t really work if the third-party components are heavily depended on JS. We should then mock every method call, though this makes the tests brittle</td></tr>
<tr>
<td>Most production-like test</td><td>Makes it hard to really unit test because we’re loading more than just the unit</td></tr>
</tbody>
</table>
</div><p>Personally I’m not a big fan of this option because of the downsides. Though, I get it when people use it. I would recommend using this option only if the other options are to hard to implement our if you want a production-like test. Though, if you want a production-like test, I would recommend something like PlayWright for end-to-end tests.</p>
<h1 id="heading-option-2-mocking-the-third-party-components">Option 2 - Mocking the third-party components</h1>
<p>Instead of fully loading in all the third-party components, bUnit offers a great way to mock these components by using the build in <code>ComponentFactories</code>.The <code>ComponentFactories</code> contains a method called <code>AddStub</code> which accepts different parameters. We can for example stub or what I like to call mock all components from the MudBlazor namespace like this:</p>
<pre><code class="lang-csharp">ComponentFactories.AddStub(type =&gt; type.Namespace?.StartsWith(<span class="hljs-string">"MudBlazor"</span>) == <span class="hljs-literal">true</span>);
</code></pre>
<p>When you add this line, all components within the MudBlazor namespace will be stubbed. Per default this will mean it will just render nothing. It will only create empty components of the type <code>Stub&lt;componentType&gt;</code>.</p>
<p>This is great because now our test is not reliant on the third-party components, though it also has a issue. If we use the line above, no third-party components will be rendered including their child content. Personally I use MudBlazor a lot, which offers things like <code>MudGrid</code> and <code>MudContainer</code>. These components help me build a layout fast but their child content often is my own code which I would like to test. So to actually do this, we can add specific stubs for each component we like to stub different.</p>
<pre><code class="lang-csharp">ComponentFactories.AddStub(type =&gt; type.Namespace?.StartsWith(<span class="hljs-string">"MudBlazor"</span>) == <span class="hljs-literal">true</span>);

<span class="hljs-comment">// Stub MudBlazor components but preserve their child content</span>
ComponentFactories.AddStub&lt;MudContainer&gt;(parameters =&gt; 
    @&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"mud-container-stub"</span>&gt;@parameters.Get(x =&gt; x.ChildContent)&lt;/div&gt;);

ComponentFactories.AddStub&lt;MudGrid&gt;(parameters =&gt; 
    @&lt;div <span class="hljs-keyword">class</span>=<span class="hljs-string">"mud-grid-stub"</span>&gt;@parameters.Get(x =&gt; x.ChildContent)&lt;/div&gt;);
</code></pre>
<p>The code above will actually fix our issue because this will stub all MudBlazor components, but for the <code>MudContainer</code> and the <code>MudGrid</code> it will actually load in a <code>div</code> element with it’s child content. This works because the <code>ComponentsFactories</code> works on a last-added order, meaning the latest added stub is checked first.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>✅ Upsides</td><td>❌ Downsides</td></tr>
</thead>
<tbody>
<tr>
<td>Resilient tests because their is no dependency on the third-party library</td><td>Setup can be quite cumbersome if a lot of third-party components need specific stubbing</td></tr>
<tr>
<td>Gives fine-grained control on how third-party components are rendered</td><td>Can make the component behave very different than production because it doesn’t load the third-party components the same way</td></tr>
<tr>
<td>Great for specific testing of your own components logic</td></tr>
</tbody>
</table>
</div><h1 id="heading-option-3-shallow-rendering">Option 3 - Shallow rendering</h1>
<p>The final option Shallow rendering is actually quite similar to option 2. Though the big difference is we invert our if statement like this:</p>
<pre><code class="lang-csharp">ComponentFactories.AddStub(type =&gt; type != <span class="hljs-keyword">typeof</span>(ComponentTypeWeWantToTest));
</code></pre>
<p>What this code does, it actually stubs everything that is not the same type of the component you’re trying to test. This will result nothing being rendered except the component itself. This is a great way if you just want to test some logic of the component itself and do not care about any child components.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>✅ Upsides</td><td>❌ Downsides</td></tr>
</thead>
<tbody>
<tr>
<td>Resilient tests because their is no dependency on other components</td><td>Can give issues if your component is heavily reliant on child components</td></tr>
<tr>
<td>Easy and fast to setup</td><td>Can make the component behave very different than production because it doesn’t load any other components</td></tr>
<tr>
<td>Great for specific testing of your own components logic</td></tr>
</tbody>
</table>
</div><h1 id="heading-conclusion">Conclusion</h1>
<p>To summarize, there are three main ways I personally like to test my Blazor components. When writing unit tests for them, I highly recommend option 3 if possible and if there are a lot of dependencies to child components I would recommend option 2. Option 1 is my personal least favorite because of the dependency that is created within your test, which can make the test very brittle.</p>
<p>In this post we quickly went over three ways to handle third party components. Make sure to check out the official documentation at <a target="_blank" href="https://bunit.dev/">https://bunit.dev/</a> for a more detailed overview of the features bUnit and the <code>ComponentFactories</code> have to offer.</p>
<p>If you got any cool additions or questions, feel free to reach out. I would love to hear how you all test your Blazor applications.</p>
<p>For now, happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Learn to Build a Chat Application Using Microsoft Orleans]]></title><description><![CDATA[In my previous post I’ve written about the basics of Microsoft Orleans. In this post, I’d like to build a chat application together using this amazing technology. Within this post, you’ll learn to build Grains, a silo, and a client that uses the Micr...]]></description><link>https://codewithhans.com/learn-to-build-a-chat-application-using-microsoft-orleans</link><guid isPermaLink="true">https://codewithhans.com/learn-to-build-a-chat-application-using-microsoft-orleans</guid><category><![CDATA[.NET]]></category><category><![CDATA[Microsoft Orleans]]></category><category><![CDATA[software development]]></category><category><![CDATA[Developer Tools]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Hans Muns]]></dc:creator><pubDate>Fri, 04 Oct 2024 10:38:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728038226938/9012f13f-f9fe-4cb6-90c7-1e6cadbe3adf.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my <a target="_blank" href="https://codewithhans.com/learn-the-basics-of-microsoft-orleans">previous post</a> I’ve written about the basics of Microsoft Orleans. In this post, I’d like to build a chat application together using this amazing technology. Within this post, you’ll learn to build Grains, a silo, and a client that uses the Microsoft Orleans Cluster. All the code in this post is available on <a target="_blank" href="https://github.com/hansmuns/MicrosoftOrleansChatExample">GitHub</a>. So without any delay, let’s get started!</p>
<h1 id="heading-overview">Overview</h1>
<p>Before we dive into the coding part, its important to define what we are exactly gonna build.</p>
<p>Within this post we’ll build a silo project that contains all the Grains and acts as a back-end service. We’ll also build a Blazor Client that acts as the front-end application to provide a GUI. This client will connect to the Microsoft Orleans Cluster to interact with the Grains. If we visualize this, it would look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728024970331/c2dbb0b0-898d-4f72-a957-b4a39436221b.png" alt class="image--center mx-auto" /></p>
<p>Within this application we define the following requirements we’ll implement:</p>
<ul>
<li><p>Users should have a random username</p>
</li>
<li><p>Users should be able to create or join chatrooms</p>
</li>
<li><p>The system should remember which rooms a user joined so he can rejoin later</p>
</li>
<li><p>Within a chatroom, users must be able to send message to each other. These message must be persisted.</p>
</li>
</ul>
<p>To get started, we’ll create a new solution called <code>MicrosoftOrleansBasicExample</code> but feel free to choose a other name as you desire. Within this solution we’ll add five different projects as listed below. Please note, this example uses .NET 8.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Project name</strong></td><td><strong>Type</strong></td><td><strong>Goal</strong></td></tr>
</thead>
<tbody>
<tr>
<td>MicrosoftOrleansBasicExample.Client</td><td>Blazor Web App (With Interactive Render Mode Server)</td><td>This is the client application that will contain the GUI and interacts with the Grains in the Silo</td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Common</td><td>Class Library</td><td>Will contain classes shared between Client and Silo</td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Grains</td><td>Class Library</td><td>Will contain the Grain implementations</td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Grains.Interfaces</td><td>Class Library</td><td>Will contain the Interfaces of the Grains</td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Silo</td><td>Console Application</td><td>Will be the host in which the Grains will live. This is the back-end application</td></tr>
</tbody>
</table>
</div><h1 id="heading-the-silo">The silo</h1>
<p>We’ll start off with the Silo. This is the back-end application in which the Grains will live. To get started, we first install multiple NuGet packages in various projects as listed below.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Project</td><td>Nuget Package(s)</td></tr>
</thead>
<tbody>
<tr>
<td>MicrosoftOrleansBasicExample.Silo</td><td><code>Microsoft.Orleans.Server</code> <code>Microsoft.Orleans.Persistence.AzureStorage</code> <code>Microsoft.Orleans.Clustering.AzureStorage</code></td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Grains.Interfaces</td><td><code>Microsoft.Orleans.Sdk</code></td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Grains</td><td><code>Microsoft.Orleans.Sdk</code> <code>Microsoft.Orleans.Persistence.AzureStorage</code></td></tr>
<tr>
<td>MicrosoftOrleansBasicExample.Common</td><td><code>Microsoft.Orleans.Sdk</code> <code>Microsoft.Orleans.Core.Abstractions</code></td></tr>
</tbody>
</table>
</div><p>These packages are needed for Microsoft Orleans to function properly. The <code>AzureStorage</code> packages are optional and only needed if you wish to host it using Azure Storage. This will be covered later in the post. Please not that you might need a other Persistence package if you don’t want the <code>AzureStorage</code>.</p>
<h2 id="heading-clustering">Clustering</h2>
<p>To properly run the Silo, we need to configure the way it uses clustering. To do this we add the following code within the program.cs of the Silo project:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Silo</span>
{
    <span class="hljs-keyword">internal</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
        {
            IHostBuilder builder = Host.CreateDefaultBuilder(args)
                .UseOrleans((context, silo) =&gt;
                {
                    <span class="hljs-keyword">if</span> (context.HostingEnvironment.IsDevelopment())
                    {
                        silo.UseLocalhostClustering()
                            .AddMemoryGrainStorage(<span class="hljs-string">"azure"</span>);
                    }
                })
                .UseConsoleLifetime();

            <span class="hljs-keyword">using</span> IHost host = builder.Build();
            <span class="hljs-keyword">await</span> host.RunAsync();
        }
    }
}
</code></pre>
<p>This code configures Microsoft Orleans to use localhost clustering when running as development (Yes, it’s that simple). Please note the <code>ASPNETCORE_ENVIRONMENT</code> environment variable should have the value <code>Development</code> locally. This is normally handled by the <code>launchSettings.json</code>. Yes, this check is obsolete right now but will make sense later in this post.</p>
<p>You now should be able to successfully run the Silo project. When you launch you should see a output like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728031085262/49ff04e9-ceb1-42bf-8797-c7c62cc24574.png" alt class="image--center mx-auto" /></p>
<p>Now the silo is running, we must create the two Grains containing our logic. Before we dive in these, make sure the Silo project references the <code>MicrosoftOrleansBasicExample.Grains.Interfaces</code> and <code>MicrosoftOrleansBasicExample.Grains</code> projects. This is needed so Orleans can detect the Grains.</p>
<h2 id="heading-usergrain">UserGrain</h2>
<p>The first Grain we’ll focus on is the <code>UserGrain</code>. This Grain will resemble a user interacting with our application. To get started we create a new interface called <code>IUser</code> in the <code>MicrosoftOrleansBasicExample.Grains.Interfaces</code> project. This interface will inherit <code>IGrainWithGuidKey</code> which is a marker interface provided by Microsoft Orleans to tell a Grain is using a GUID as key. Within this interface we’ll define multiple methods to support all requirements as stated in the beginning of this post. The interface should look like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains.Interfaces</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IUser</span> : <span class="hljs-title">IGrainWithGuidKey</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> ValueTask <span class="hljs-title">SetUsernameAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username</span>)</span>;
        <span class="hljs-function"><span class="hljs-keyword">public</span> ValueTask <span class="hljs-title">JoinChatroomAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> chatroom</span>)</span>;
        <span class="hljs-keyword">public</span> ValueTask&lt;<span class="hljs-keyword">string</span>?&gt; GetUsernameAsync();
        <span class="hljs-keyword">public</span> ValueTask&lt;List&lt;<span class="hljs-keyword">string</span>&gt;&gt; GetChatroomsAsync();
    }
}
</code></pre>
<p>Now we have the interface, we’ll define the model to preserve the users state. This state is used to remember the username and the chatrooms he joined. To do this, we’ll add a new folder called <code>States</code> within the <code>MicrosoftOrleansBasicExample.Grains</code> project. Within this folder, we add a class called <code>UserState</code> which looks like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains.States</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserState</span>
    {
        <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? Username { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

        <span class="hljs-keyword">public</span> List&lt;<span class="hljs-keyword">string</span>&gt; Chatrooms { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span>();
    }
}
</code></pre>
<p>Now that the interface and state class are defined, we can now build the Grain itself. Within the <code>MicrosoftOrleansBasicExample.Grains</code> project we add a class called <code>UserGrain</code>. This class should inherit from the <code>Grain</code> class. This is a class provided by Microsoft Orleans. Furthermore the <code>UserGrain</code> must implement the <code>IUser</code> interface we created earlier. To also preserve state, we add the following code add the beginning of the class:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IPersistentState&lt;UserState&gt; user;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UserGrain</span>(<span class="hljs-params">[PersistentState(stateName: <span class="hljs-string">"User"</span>, storageName: <span class="hljs-string">"azure"</span></span>)] IPersistentState&lt;UserState&gt; user)</span>
{
    <span class="hljs-keyword">this</span>.user = user;
}
</code></pre>
<p>This makes sure the Microsoft Orleans framework knows we use the State and automatically fetches and saves it for us. This also makes it possible for use to do things like <code>user.WriteStateAsync();</code> and <code>user.State.Username = username;</code> within the Grain to safe/modify its state. To make sure the state is persisted when a grain is deactivated, we add the following code within the Grain:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnDeactivateAsync</span>(<span class="hljs-params">DeactivationReason reason, CancellationToken cancellationToken</span>)</span>
{
    <span class="hljs-comment">// store the state</span>
    <span class="hljs-keyword">await</span> user.WriteStateAsync();
}
</code></pre>
<p>As you can see in the code snippet provided earlier, we use a storage with the name <code>azure</code> which we defined in the program.cs of the Silo project with the following code:</p>
<pre><code class="lang-csharp">silo.UseLocalhostClustering()
    .AddMemoryGrainStorage(<span class="hljs-string">"azure"</span>);
</code></pre>
<p>This code will add a in memory storage with the name <code>azure</code>.</p>
<p>When you followed the steps above and implemented the <code>IUser</code> interface, the Grain should look something like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Grains.Interfaces;
<span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Grains.States;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UserGrain</span> : <span class="hljs-title">Grain</span>, <span class="hljs-title">IUser</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IPersistentState&lt;UserState&gt; user;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UserGrain</span>(<span class="hljs-params">[PersistentState(stateName: <span class="hljs-string">"User"</span>, storageName: <span class="hljs-string">"azure"</span></span>)] IPersistentState&lt;UserState&gt; user)</span>
        {
            <span class="hljs-keyword">this</span>.user = user;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> ValueTask <span class="hljs-title">JoinChatroomAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> chatroom</span>)</span>
        {
            chatroom = chatroom.ToLower();
            <span class="hljs-keyword">if</span> (!user.State.Chatrooms.Contains(chatroom))
            {
                user.State.Chatrooms.Add(chatroom);
            }

            <span class="hljs-keyword">return</span> ValueTask.CompletedTask;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> ValueTask <span class="hljs-title">SetUsernameAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username</span>)</span>
        {
            <span class="hljs-keyword">this</span>.user.State.Username = username;
            <span class="hljs-keyword">return</span> ValueTask.CompletedTask;
        }

        <span class="hljs-keyword">public</span> ValueTask&lt;<span class="hljs-keyword">string</span>?&gt; GetUsernameAsync()
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ValueTask&lt;<span class="hljs-keyword">string</span>?&gt;(user.State.Username);
        }

        <span class="hljs-keyword">public</span> ValueTask&lt;List&lt;<span class="hljs-keyword">string</span>&gt;&gt; GetChatroomsAsync()
        {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ValueTask&lt;List&lt;<span class="hljs-keyword">string</span>&gt;&gt;(user.State.Chatrooms);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnDeactivateAsync</span>(<span class="hljs-params">DeactivationReason reason, CancellationToken cancellationToken</span>)</span>
        {
            <span class="hljs-comment">// store the state</span>
            <span class="hljs-keyword">await</span> user.WriteStateAsync();
        }
    }
}
</code></pre>
<h2 id="heading-chatroomgrain">ChatroomGrain</h2>
<p>The second and last Grain we will need to implement is the ChatroomGrain. To do this we start with creating the interface <code>IChatroom</code> in the <code>MicrosoftOrleansBasicExample.Grains.Interfaces</code> project. Since every chatroom will have a unique name, we’ll inherit the <code>IGrainWithStringKey</code> interface to let Microsoft Orleans know the Grain is identifiable with a string.</p>
<p>We then define the methods so someone can join, leave, send a message and get the message history. This would look something like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Common;
<span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Grains.Interfaces.Observers;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains.Interfaces</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IChatroom</span> : <span class="hljs-title">IGrainWithStringKey</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> ValueTask <span class="hljs-title">JoinAsync</span>(<span class="hljs-params">IReceiveMessage observer, <span class="hljs-keyword">string</span> username</span>)</span>;
        <span class="hljs-function"><span class="hljs-keyword">public</span> ValueTask <span class="hljs-title">LeaveAsync</span>(<span class="hljs-params">IReceiveMessage observer, <span class="hljs-keyword">string</span> username</span>)</span>;
        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">PublishAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message, <span class="hljs-keyword">string</span> username</span>)</span>;
        <span class="hljs-keyword">public</span> ValueTask&lt;List&lt;ChatMessage&gt;&gt; GetMessageHistory();
    }
}
</code></pre>
<p>As you can see we use a interface called <code>IReceiveMessage</code>. This is a interface we have to create. This interface is used to make sure all clients (receivers of the message) implement the needed interface to receive the message. Within the <code>MicrosoftOrleansBasicExample.Grains.Interfaces</code> project we’ll add an folder called <code>Observers</code>. Within this folder create a interface like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Common;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains.Interfaces.Observers</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IReceiveMessage</span> : <span class="hljs-title">IGrainObserver</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">ReceiveMessage</span>(<span class="hljs-params">ChatMessage message</span>)</span>;
    }
}
</code></pre>
<p>This interface implements the <code>IGrainObserver</code> which is a interface Microsoft Orleans uses to implement observers. More information about observers within Microsoft Orleans can be found here: <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/grains/observers">https://learn.microsoft.com/en-us/dotnet/orleans/grains/observers</a>.</p>
<p>Besides the <code>IReceiveMessage</code> there is also a <code>ChatMessage</code> object which we have to implement. This object will resemble the chat messages send and received by the clients. To implement this, we add a record called <code>ChatMessage</code> within the <code>MicrosoftOrleansBasicExample.Common</code> project. This record has to store the message itself, the username of the user who send it and the DateTimeOffset it was send. By default objects can’t be used in communication between clients and Grains. To support this, we’ll add an attribute called <code>GenerateSerializer</code> to the record. This tells Microsoft Orleans to automatically create a serializer for the record on build. When implementing we should have something like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Common</span>
{
    [<span class="hljs-meta">GenerateSerializer</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">ChatMessage</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> Message, <span class="hljs-keyword">string</span> Username, DateTimeOffset DateTimeSend</span>)</span>;
}
</code></pre>
<p>Now the ChatMessage is done, we’ll create the state class for the Grain. Within the <code>MicrosoftOrleansBasicExample.Grains</code> project add a new class called <code>ChatroomState</code> in the <code>States</code> folder. Within this state we store a list of ChatMessages. This should look like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Common;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains.States</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ChatroomState</span>
    {
        <span class="hljs-keyword">public</span> List&lt;ChatMessage&gt; ChatMessages { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span>();
    }
}
</code></pre>
<p>Next we create the class <code>ChatroomGrain</code> in the <code>MicrosoftOrleansBasicExample.Grains</code> project. This Grain has to inherit the <code>Grain</code> class of Microsoft Orleans and implement our interface <code>IChatroom</code>. If you are building this project with me, I’ll like to challenge you to implement this Grain by yourself. All information that you need is already provided when we created the UserGrain. For the <code>PublishAsync</code> method you might need the information provided on <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/grains/observers">https://learn.microsoft.com/en-us/dotnet/orleans/grains/observers</a>.</p>
<p>When you are done, you can check if your code looks something like below.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Microsoft.Extensions.Logging;
<span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Common;
<span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Grains.Interfaces;
<span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Grains.Interfaces.Observers;
<span class="hljs-keyword">using</span> MicrosoftOrleansBasicExample.Grains.States;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MicrosoftOrleansBasicExample.Grains</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ChatroomGrain</span> : <span class="hljs-title">Grain</span>, <span class="hljs-title">IChatroom</span>
    {
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> List&lt;IReceiveMessage&gt; observers = <span class="hljs-keyword">new</span>();

        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IPersistentState&lt;ChatroomState&gt; chatroom;
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ILogger logger;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ChatroomGrain</span>(<span class="hljs-params">ILogger&lt;ChatroomGrain&gt; logger,
                             [PersistentState(stateName: <span class="hljs-string">"Chatroom"</span>, storageName: <span class="hljs-string">"azure"</span></span>)] IPersistentState&lt;ChatroomState&gt; chatroom)</span>
        {
            <span class="hljs-keyword">this</span>.logger = logger;
            <span class="hljs-keyword">this</span>.chatroom = chatroom;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> ValueTask <span class="hljs-title">JoinAsync</span>(<span class="hljs-params">IReceiveMessage observer, <span class="hljs-keyword">string</span> username</span>)</span>
        {
            logger.LogInformation(<span class="hljs-string">$"<span class="hljs-subst">{username}</span> joined the chatroom '<span class="hljs-subst">{<span class="hljs-keyword">this</span>.GetPrimaryKeyString()}</span>'"</span>);

            observers.Add(observer);

            <span class="hljs-keyword">await</span> PublishAsync(<span class="hljs-string">$"User <span class="hljs-subst">{username}</span> joined the chat :D"</span>, <span class="hljs-string">"System"</span>);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> ValueTask <span class="hljs-title">LeaveAsync</span>(<span class="hljs-params">IReceiveMessage observer, <span class="hljs-keyword">string</span> username</span>)</span>
        {
            logger.LogInformation(<span class="hljs-string">$"<span class="hljs-subst">{username}</span> left the chatroom '<span class="hljs-subst">{<span class="hljs-keyword">this</span>.GetPrimaryKeyString()}</span>'"</span>);
            observers.Remove(observer);

            <span class="hljs-keyword">await</span> PublishAsync(<span class="hljs-string">$"User <span class="hljs-subst">{username}</span> left the chat &gt;:("</span>, <span class="hljs-string">"System"</span>);
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">PublishAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> message, <span class="hljs-keyword">string</span> username</span>)</span>
        {
            <span class="hljs-keyword">var</span> chatMessage = <span class="hljs-keyword">new</span> ChatMessage(message, username, DateTimeOffset.UtcNow);
            logger.LogInformation(<span class="hljs-string">$"<span class="hljs-subst">{username}</span> send '<span class="hljs-subst">{chatMessage.Message}</span>' at <span class="hljs-subst">{chatMessage.DateTimeSend}</span> in '<span class="hljs-subst">{<span class="hljs-keyword">this</span>.GetPrimaryKeyString()}</span>'"</span>);

            chatroom.State.ChatMessages.Add(chatMessage);
            <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> observer <span class="hljs-keyword">in</span> observers)
            {
                observer.ReceiveMessage(chatMessage);
            }

            <span class="hljs-keyword">return</span> Task.CompletedTask;
        }

        <span class="hljs-keyword">public</span> ValueTask&lt;List&lt;ChatMessage&gt;&gt; GetMessageHistory()
        {
            <span class="hljs-keyword">return</span> ValueTask.FromResult(chatroom.State.ChatMessages.OrderBy(x =&gt; x.DateTimeSend).ToList());
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnDeactivateAsync</span>(<span class="hljs-params">DeactivationReason reason, CancellationToken cancellationToken</span>)</span>
        {
            <span class="hljs-comment">// Store the state</span>
            <span class="hljs-keyword">await</span> chatroom.WriteStateAsync();
        }
    }
}
</code></pre>
<p>The most important part is we have a private list of observers. These are used to add the clients and send them the messages when there is a new message. We don’t want this in the state since clients might not be there when the Grain instantiate later.</p>
<h2 id="heading-adding-azure-hosting-support">Adding Azure hosting support</h2>
<p>Currently we only are able to run this locally using a in memory storage and localhost clustering. But what if we wanna host this on something like Azure? This is easily implemented. We already installed all the needed NuGet packages earlier in this post. All we have to do is tweak the program.cs so that we use different clustering and storage when not running as development. In this example we implement clustering and storage with Azure Table Storage. For more information see: <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence/azure-storage">https://learn.microsoft.com/en-us/dotnet/orleans/grains/grain-persistence/azure-storage</a>.</p>
<p>Lets modify the <code>program.cs</code> within the <code>MicrosoftOrleansBasicExample.Silo</code> by changing the main method like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">static</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
{
    IHostBuilder builder = Host.CreateDefaultBuilder(args)
        .UseOrleans((context, silo) =&gt;
        {
            <span class="hljs-keyword">if</span> (context.HostingEnvironment.IsDevelopment())
            {
                silo.UseLocalhostClustering()
                    .AddMemoryGrainStorage(<span class="hljs-string">"azure"</span>);
            }
            <span class="hljs-keyword">else</span>
            {
                silo.Configure&lt;ClusterOptions&gt;(options =&gt;
                {
                    options.ClusterId = <span class="hljs-string">"default"</span>;
                    options.ServiceId = <span class="hljs-string">"defaultService"</span>;
                })
                .UseAzureStorageClustering(options =&gt;
                {
                    options.TableServiceClient = <span class="hljs-keyword">new</span>(context.Configuration.GetConnectionString(<span class="hljs-string">"ORLEANS_AZURE_STORAGE_CONNECTION_STRING"</span>));
                    options.TableName = <span class="hljs-string">$"defaultCluster"</span>;
                })
                .AddAzureTableGrainStorage(<span class="hljs-string">"azure"</span>, options =&gt; {
                    options.TableServiceClient = <span class="hljs-keyword">new</span>(context.Configuration.GetConnectionString(<span class="hljs-string">"ORLEANS_AZURE_STORAGE_CONNECTION_STRING"</span>));
                    options.TableName = <span class="hljs-string">$"defaultPersistence"</span>;
                });

                <span class="hljs-comment">//The application is hosted in an Azure Web App</span>
                <span class="hljs-comment">//This Web App has some pre defined environment variables, these are used to get the correct IP and ports</span>
                <span class="hljs-keyword">var</span> endpointAddress = IPAddress.Parse(context.Configuration[<span class="hljs-string">"WEBSITE_PRIVATE_IP"</span>]!);
                <span class="hljs-keyword">var</span> strPorts = context.Configuration[<span class="hljs-string">"WEBSITE_PRIVATE_PORTS"</span>]!.Split(<span class="hljs-string">','</span>);
                <span class="hljs-keyword">if</span> (strPorts.Length &lt; <span class="hljs-number">2</span>)
                {
                    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Insufficient private ports configured."</span>);
                }
                <span class="hljs-keyword">var</span> (siloPort, gatewayPort) = (<span class="hljs-keyword">int</span>.Parse(strPorts[<span class="hljs-number">0</span>]), <span class="hljs-keyword">int</span>.Parse(strPorts[<span class="hljs-number">1</span>]));

                silo.ConfigureEndpoints(endpointAddress, siloPort, gatewayPort);
            }
        })
        .UseConsoleLifetime();

    <span class="hljs-keyword">using</span> IHost host = builder.Build();
    <span class="hljs-keyword">await</span> host.RunAsync();
}
</code></pre>
<p>Please note this code also provides configuration for hosting it on an Azure App Service. See the official documentation for more information about hosting it on a Azure App service: <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/deployment/deploy-to-azure-app-service">https://learn.microsoft.com/en-us/dotnet/orleans/deployment/deploy-to-azure-app-service</a></p>
<h1 id="heading-the-client">The client</h1>
<p>Now that the silo is finished, we’ll start implementing the client application. To get started we’ll need to install the following NuGet packes in the <code>MicrosoftOrleansBasicExample.Client</code> project:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>NuGet package</td><td>Purpose</td></tr>
</thead>
<tbody>
<tr>
<td><code>Blazored.LocalStorage</code></td><td>Package so we can store some data locally on the client.</td></tr>
<tr>
<td><code>Microsoft.Orleans.Client</code></td><td>Used to communicate with the Microsoft Orleans Cluster</td></tr>
<tr>
<td><code>Microsoft.Orleans.Clustering.AzureStorage</code></td><td>Same package as the silo. Needed to connect with the Cluster if hosted in Azure Tables.</td></tr>
<tr>
<td><code>MudBlazor</code></td><td>Opensource front-end library so we can easily build good looking applications.</td></tr>
</tbody>
</table>
</div><p>When all packages are installed, we start with correctly configuring <code>MudBlazor</code>. To do this follow the steps described at: <a target="_blank" href="https://www.mudblazor.com/getting-started/installation#manual-install-add-imports">https://www.mudblazor.com/getting-started/installation#manual-install-add-imports</a> .</p>
<p>When MudBlazor is correctly installed, we’ll clean up the client project so that only the <code>Home.razor</code> and <code>Error.razor</code> pages are left. We can also remove the <code>NavMenu.razor</code> file and replace the content of MainLayout.razor with the following code:</p>
<pre><code class="lang-csharp">@inherits LayoutComponentBase

@* Required *@
&lt;MudThemeProvider /&gt;
&lt;MudPopoverProvider /&gt;

@* Needed <span class="hljs-keyword">for</span> dialogs *@
&lt;MudDialogProvider /&gt;

@* Needed <span class="hljs-keyword">for</span> snackbars *@
&lt;MudSnackbarProvider /&gt;

&lt;MudLayout&gt;
    &lt;MudAppBar Elevation=<span class="hljs-string">"1"</span>&gt;
        &lt;MudText Typo=<span class="hljs-string">"Typo.h5"</span> Class=<span class="hljs-string">"ml-3"</span>&gt;Simple Chat Example&lt;/MudText&gt;
        &lt;MudSpacer /&gt;
        &lt;MudIconButton Icon=<span class="hljs-string">"@Icons.Material.Filled.MoreVert"</span> Color=<span class="hljs-string">"Color.Inherit"</span> Edge=<span class="hljs-string">"Edge.End"</span> /&gt;
    &lt;/MudAppBar&gt;
    &lt;MudMainContent Class=<span class="hljs-string">"px-6"</span>&gt;
        @Body
    &lt;/MudMainContent&gt;
&lt;/MudLayout&gt;
</code></pre>
<p>We wont remove the <code>MainLayout.razor.css</code> file, though you could still remove it if desired.</p>
<p>Now that the files are cleaned up, we’ll start configuring the <code>Program.cs</code>. Within the Main method before the <code>var app = builder.Build();</code> we’ll add the following code to configure the connection to Microsoft Orleans:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Add Orleans client.</span>
builder.UseOrleansClient((client) =&gt;
{
    <span class="hljs-keyword">if</span> (builder.Environment.IsDevelopment())
    {
        client.UseLocalhostClustering();
    }
    <span class="hljs-keyword">else</span>
    {
        client.Configure&lt;ClusterOptions&gt;(options =&gt;
        {
            options.ClusterId = <span class="hljs-string">"default"</span>;
            options.ServiceId = <span class="hljs-string">"defaultService"</span>;
        })
        .UseAzureStorageClustering(options =&gt;
        {
            options.TableServiceClient = <span class="hljs-keyword">new</span>(builder.Configuration.GetConnectionString(<span class="hljs-string">"ORLEANS_AZURE_STORAGE_CONNECTION_STRING"</span>));
            options.TableName = <span class="hljs-string">$"defaultCluster"</span>;
        });
    }
});
</code></pre>
<p>As you may noticed, the code is very similar to the configuration of the Silo. We should make sure the Client is 100% connecting to the same Cluster as the Silo uses.</p>
<p>To also make sure we can use the localstorage NuGet package we installed, add the following code right before the <code>var app =</code> <a target="_blank" href="http://builder.Build"><code>builder.Build</code></a><code>();</code> :</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Add Localstorage</span>
builder.Services.AddBlazoredLocalStorage();
</code></pre>
<p>We should now be able to successfully run the client. If not, make sure all steps are followed correctly. The page should look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728032796631/beb21f57-fd06-47e4-97cd-0084d21664a4.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-home-page">Home page</h2>
<p>The first page we’ll implement is the Home page. This is the first page a user lands on. On this page we need to make sure a user gets a username, can join or create a room and can see a list of rooms he already joined.</p>
<p>To do this, we’ll start off by editing the <code>Home.razor</code> file. We’ll use MudBlazor components for the visuals. Please see the <a target="_blank" href="https://www.mudblazor.com/docs/overview">MudBlazor documentation</a> if you want to learn more about those. On this page we start of by showing the user his username (we’ll add logic to set his username later). Under his username, we’ll show a input field with a button to start the chat. Below this, we’ll show a list of all chatrooms the user already joined. Within the <code>Home.razor</code> file this should look something like this:</p>
<pre><code class="lang-csharp">@page <span class="hljs-string">"/"</span>
@rendermode InteractiveServer

&lt;PageTitle&gt;Chat selection&lt;/PageTitle&gt;

&lt;MudText Typo=<span class="hljs-string">"Typo.caption"</span>&gt;Username: @username&lt;/MudText&gt;
&lt;MudStack&gt;
    &lt;MudStack Row&gt;
        &lt;MudTextField TextUpdateSuppression=<span class="hljs-string">"false"</span> @bind-Value=<span class="hljs-string">"chatroomName"</span> OnKeyUp=<span class="hljs-string">"KeyUpCreateChatAsync"</span> Label=<span class="hljs-string">"Chatroom"</span> /&gt;
        &lt;MudButton OnClick=<span class="hljs-string">"CreateChatAsync"</span>&gt;Start Chat&lt;/MudButton&gt;
    &lt;/MudStack&gt;

    @foreach (<span class="hljs-keyword">var</span> room <span class="hljs-keyword">in</span> chatrooms)
    {
        &lt;MudButton Variant=<span class="hljs-string">"Variant.Filled"</span> OnClick=<span class="hljs-string">"() =&gt; JoinChatAsync(room)"</span>&gt;@room&lt;/MudButton&gt;
    }
&lt;/MudStack&gt;
</code></pre>
<p>Note, you’ll see some errors because we use values that are not defined yet, well do this next.</p>
<p>Now that the interface is set, lets add some logic to it. To do this, we start with creating a <code>Home.razor.cs</code> file within the same folder. This will nest the cs file below the razor file. Make sure the class within the file is marked <code>public partial</code> so its linked to the razor file.</p>
<p>Within this file, start injecting various services so we can use them later</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required IClusterClient clusterClient { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required ILocalStorageService localStorage { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required NavigationManager navigationManager { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
</code></pre>
<p>We’ll also define some private field so we have some temporary state within the page:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> List&lt;<span class="hljs-keyword">string</span>&gt; chatrooms = <span class="hljs-keyword">new</span>();
<span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> Random random = <span class="hljs-keyword">new</span>();
<span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> username = <span class="hljs-keyword">string</span>.Empty;
<span class="hljs-keyword">private</span> Guid userGuid = Guid.Empty;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> chatroomName = <span class="hljs-keyword">string</span>.Empty;
</code></pre>
<p>Within the Silo we created a UserGrain. This Grain has a GUID key and resembles a user in our application. To get/set his data, we first need to make sure we have a GUID. To do this, we’ll create the following method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SetCorrectUserGuidAsync</span>(<span class="hljs-params"></span>)</span>
{
    userGuid = <span class="hljs-keyword">await</span> localStorage.GetItemAsync&lt;Guid&gt;(<span class="hljs-string">"userGuid"</span>);
    <span class="hljs-keyword">if</span> (userGuid == Guid.Empty)
    {
        userGuid = Guid.NewGuid();
        <span class="hljs-keyword">await</span> localStorage.SetItemAsync(<span class="hljs-string">"userGuid"</span>, userGuid);
    }
}
</code></pre>
<p>The above code checks if there is already a GUID set on the localstorage. If not, it creates it and stores is. It also writes it to the private field so we can use it somewhere else later.</p>
<p>Now that we have a GUID, we can implement the logic to get or set the users username. To do this we’ll add two methods. One method to generate a random username, the other to get or set the username on the corresponding UserGrain. We do this with the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SetCorrectUsernameAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-comment">// Ensure the user exists with the username</span>
    <span class="hljs-keyword">var</span> user = clusterClient.GetGrain&lt;IUser&gt;(userGuid);
    <span class="hljs-keyword">var</span> currentUsername = <span class="hljs-keyword">await</span> user.GetUsernameAsync();
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(currentUsername))
    {
        username = GenerateRandomUsername();
        <span class="hljs-keyword">await</span> user.SetUsernameAsync(username);
    }
    <span class="hljs-keyword">else</span>
    {
        username = currentUsername;
    }
}
<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> <span class="hljs-title">GenerateRandomUsername</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">string</span>[] adjectives = { <span class="hljs-string">"Quick"</span>, <span class="hljs-string">"Lazy"</span>, <span class="hljs-string">"Happy"</span>, <span class="hljs-string">"Sad"</span>, <span class="hljs-string">"Brave"</span>, <span class="hljs-string">"Clever"</span>, <span class="hljs-string">"Bold"</span>, <span class="hljs-string">"Shy"</span>, <span class="hljs-string">"Calm"</span>, <span class="hljs-string">"Eager"</span> };
    <span class="hljs-keyword">string</span>[] nouns = { <span class="hljs-string">"Fox"</span>, <span class="hljs-string">"Dog"</span>, <span class="hljs-string">"Cat"</span>, <span class="hljs-string">"Mouse"</span>, <span class="hljs-string">"Bear"</span>, <span class="hljs-string">"Lion"</span>, <span class="hljs-string">"Tiger"</span>, <span class="hljs-string">"Wolf"</span>, <span class="hljs-string">"Eagle"</span>, <span class="hljs-string">"Shark"</span> };
    <span class="hljs-keyword">return</span> <span class="hljs-string">$"<span class="hljs-subst">{adjectives[random.Next(adjectives.Length)]}</span><span class="hljs-subst">{nouns[random.Next(nouns.Length)]}</span><span class="hljs-subst">{random.Next(<span class="hljs-number">1000</span>, <span class="hljs-number">9999</span>)}</span>"</span>;
}
</code></pre>
<p>As you can see, we connect to the cluster using the <code>clusterClient</code>. We try to get the UserGrain by the userGuid. We then try to get the username. If it does exist, we store it in a private field. If it does not, we’ll generate a random one and write it to the Grain and private field. We now have successfully created the first call to a Grain from the client!</p>
<p>So now we have correctly implemented the logic for the username, we should add the logic to get the chatrooms the user already joined. This is easily implemented by the following code:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">LoadChatroomsAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> user = clusterClient.GetGrain&lt;IUser&gt;(userGuid);
    chatrooms = <span class="hljs-keyword">await</span> user.GetChatroomsAsync();
}
</code></pre>
<p>To make sure all the code is executed when opening the page, we’ll override the <code>OnAfterRenderAsync</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnAfterRenderAsync</span>(<span class="hljs-params"><span class="hljs-keyword">bool</span> firstRender</span>)</span>
{
    <span class="hljs-keyword">if</span> (!firstRender)
    {
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">await</span> SetCorrectUserGuidAsync();
    <span class="hljs-keyword">await</span> SetCorrectUsernameAsync();
    <span class="hljs-keyword">await</span> LoadChatroomsAsync();

    StateHasChanged();
}
</code></pre>
<p>Next we still need to logic to join or create a chatroom. The code for this is very similar to each other. I’ll like to challenge you to implement it yourself before continuing this post with the provided code. Make sure add the end of the join/create methods you navigate to a page <code>/chats/[chatroomname]</code>. Tip: We already implemented logic to join a chatroom on the UserGrain.</p>
<p>If you figured it out, or just want to continue, the code should look something like this:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">JoinChatAsync</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> chatroom</span>)</span>
{
    chatroom = chatroom.ToLower();

    <span class="hljs-keyword">var</span> userGrain = clusterClient.GetGrain&lt;IUser&gt;(userGuid);
    <span class="hljs-keyword">await</span> userGrain.JoinChatroomAsync(chatroom);
    navigationManager.NavigateTo(<span class="hljs-string">$"chats/<span class="hljs-subst">{chatroom}</span>"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">KeyUpCreateChatAsync</span>(<span class="hljs-params">KeyboardEventArgs args</span>)</span>
{
    <span class="hljs-keyword">if</span> (args.Code == <span class="hljs-string">"Enter"</span> || args.Code == <span class="hljs-string">"NumpadEnter"</span>)
    {
        <span class="hljs-keyword">await</span> CreateChatAsync();
    }
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">CreateChatAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(chatroomName))
    {
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">var</span> userGrain = clusterClient.GetGrain&lt;IUser&gt;(userGuid);
    <span class="hljs-keyword">await</span> userGrain.JoinChatroomAsync(chatroomName.ToLower());
    navigationManager.NavigateTo(<span class="hljs-string">$"chats/<span class="hljs-subst">{chatroomName.ToLower()}</span>"</span>);
}
</code></pre>
<p>This code should finish the Home page. You now should be able to run the application, get a username and create chatrooms. The page should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728033160254/33b17f10-304e-4d72-9f01-790d370aeb06.png" alt class="image--center mx-auto" /></p>
<p>Next up is actually adding the chatroom page.</p>
<h2 id="heading-chatroom-page">Chatroom page</h2>
<p>To get started, add a <code>Chat.razor</code> file in the same folder as the <code>Home.razor</code> file lives. Also add a <code>Chat.razor.cs</code> file the same way as we did for the <code>Home.razor</code> file.</p>
<p>Just as the home page, we start off by creating the graphical interface. Edit the <code>Chat.razor</code> like below:</p>
<pre><code class="lang-csharp">@page <span class="hljs-string">"/chats/{Chatroom}"</span>
@rendermode InteractiveServer

&lt;PageTitle&gt;@Chatroom&lt;/PageTitle&gt;

&lt;MudText Typo=<span class="hljs-string">"Typo.caption"</span>&gt;Username: @username&lt;/MudText&gt;
&lt;MudStack Row&gt;
    &lt;MudIconButton Icon=<span class="hljs-string">"@Icons.Material.Filled.ArrowBack"</span> OnClick=<span class="hljs-string">"() =&gt; NavigationManager.NavigateTo(string.Empty)"</span> Size=<span class="hljs-string">"Size.Large"</span> /&gt;
    &lt;MudText Typo=<span class="hljs-string">"Typo.h3"</span>&gt;@Chatroom&lt;/MudText&gt;
&lt;/MudStack&gt;
&lt;MudStack&gt;
    &lt;MudStack id=<span class="hljs-string">"chat"</span> Style=<span class="hljs-string">"height:70vh; overflow-x:scroll; display:flex; flex-direction: column-reverse;"</span>&gt;
        @foreach (<span class="hljs-keyword">var</span> message <span class="hljs-keyword">in</span> messages)
        {
            &lt;MudCard Outlined Elevation=<span class="hljs-string">"3"</span> Class=<span class="hljs-string">"pa-3"</span>&gt;
                &lt;MudStack AlignItems=<span class="hljs-string">"message.Username == this.username ? AlignItems.End : AlignItems.Start"</span>&gt;
                    &lt;MudText Typo=<span class="hljs-string">"Typo.subtitle2"</span>&gt;@message.Username&lt;/MudText&gt;
                    &lt;MudText Typo=<span class="hljs-string">"Typo.body1"</span>&gt;@message.Message&lt;/MudText&gt;
                    &lt;MudText Typo=<span class="hljs-string">"Typo.caption"</span>&gt;@message.DateTimeSend.ToLocalTime()&lt;/MudText&gt;
                &lt;/MudStack&gt;
            &lt;/MudCard&gt;
        }
    &lt;/MudStack&gt;
    &lt;MudStack Row&gt;
        &lt;MudTextField TextUpdateSuppression=<span class="hljs-string">"false"</span> @bind-Value=<span class="hljs-string">"draftMessage"</span> OnKeyUp=<span class="hljs-string">"KeyUpSendMessageAsync"</span> Label=<span class="hljs-string">"Message"</span> /&gt;
        &lt;MudButton OnClick=<span class="hljs-string">"SendMessageAsync"</span>&gt;Send&lt;/MudButton&gt;
    &lt;/MudStack&gt;
&lt;/MudStack&gt;

&lt;script&gt;
    <span class="hljs-function">function <span class="hljs-title">updateScroll</span>(<span class="hljs-params"></span>)</span> {
        <span class="hljs-keyword">var</span> element = document.getElementById(<span class="hljs-string">"chat"</span>);
        element.scrollTop = element.scrollHeight;
    }
&lt;/script&gt;
</code></pre>
<p>The above code makes sure we show the users username, a scrollable view with all messages and a input field with a button to send messages. We also added a javascript method we’ll invoke from the <code>.cs</code> file so make sure the view is scrolled to the bottom when a new message is received.</p>
<p>Just as the <code>Home.razor.cs</code> file, we have to make the <code>Chat.razor.cs</code> class <code>public partial</code>. As you might remember, we created a interface called <code>IReceiveMessage</code> when we were building the Silo. The chat class must implement this interface to receive messages. It also has to implement the <code>IAsyncDisposable</code> method so we can unsubscribe from the chat when the page closes.</p>
<p>Within the <code>Chat.razor.cs</code> file we inject various services:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required IClusterClient ClusterClient { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required ILocalStorageService LocalStorage { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required IJSRuntime Js { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

[<span class="hljs-meta">Inject</span>]
<span class="hljs-keyword">public</span> required NavigationManager NavigationManager { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
</code></pre>
<p>We also have to add the Chatroom parameter we get from the url:</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">Parameter</span>]
<span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Chatroom { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
</code></pre>
<p>Besides these injections and parameter, we also add multiple private fields:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> username = <span class="hljs-keyword">string</span>.Empty;
<span class="hljs-keyword">private</span> List&lt;ChatMessage&gt; messages = <span class="hljs-keyword">new</span>();
<span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> draftMessage = <span class="hljs-keyword">string</span>.Empty;

<span class="hljs-keyword">private</span> IReceiveMessage? chatroomClientReference;
</code></pre>
<p>To show the users username, we start by getting the username again. If we can not find it, we want to return to the homepage. To implement this, we’ll add the following method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SetUsernameAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> userGuid = <span class="hljs-keyword">await</span> LocalStorage.GetItemAsync&lt;Guid&gt;(<span class="hljs-string">"userGuid"</span>);
    <span class="hljs-keyword">if</span> (userGuid == Guid.Empty)
    {
        <span class="hljs-comment">// Return to home</span>
        NavigationManager.NavigateTo(<span class="hljs-keyword">string</span>.Empty);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">var</span> user = ClusterClient.GetGrain&lt;IUser&gt;(userGuid);
    username = <span class="hljs-keyword">await</span> user.GetUsernameAsync() ?? <span class="hljs-keyword">string</span>.Empty;
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(username))
    {
        NavigationManager.NavigateTo(<span class="hljs-keyword">string</span>.Empty);
    }
}
</code></pre>
<p>Besides getting the username, we also have to subscribe to the chatroom to start receiving messages. To do this we implement this method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SubscribeToChatroomAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> chatroomGrain = ClusterClient.GetGrain&lt;IChatroom&gt;(Chatroom.ToLower());

    messages = <span class="hljs-keyword">await</span> chatroomGrain.GetMessageHistory();

    chatroomClientReference = ClusterClient.CreateObjectReference&lt;IReceiveMessage&gt;(<span class="hljs-keyword">this</span>);
    <span class="hljs-keyword">await</span> chatroomGrain.JoinAsync(chatroomClientReference, username);
}
</code></pre>
<p>Within this method we get the corresponding ChatroomGrain by the name of the Chatroom. We then call the <code>GetMessageHistory()</code> method to get all messages already send in the chat. To subscribe to the chat, we have to create ObjectReference for the ClusterClient by adding this line <code>chatroomClientReference = ClusterClient.CreateObjectReference&lt;IReceiveMessage&gt;(this);</code>. We then call our own <code>JoinAsync</code> method on the Grain to subscribe.</p>
<p>Both this method and the <code>SetUsernameAsync</code> must be executed when the page is loaded. To this we add the following override:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnAfterRenderAsync</span>(<span class="hljs-params"><span class="hljs-keyword">bool</span> firstRender</span>)</span>
{
    <span class="hljs-keyword">if</span> (!firstRender)
    {
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// We have to do this in the OnAfterRenderAsync method since we run serverside</span>
    <span class="hljs-comment">// and cant't use the JSRuntime and localStorage in the OnInitializedAsync method</span>
    <span class="hljs-keyword">await</span> SetUsernameAsync();
    <span class="hljs-keyword">await</span> SubscribeToChatroomAsync();
}
</code></pre>
<p>So now we are subscribed to a chatroom when we open the page, though we still do nothing when we receive a message. To show the new message, implement the <code>ReceiveMessage(ChatMessage message)</code> method from the <code>IReceiveMessage</code> like this:</p>
<pre><code class="lang-csharp"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> The receive message method that will be called by the chatroom grain</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> This method must be void since Microsoft Orleans cant't handle async observers</span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span>
<span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;param name="message"&gt;</span>The new message<span class="hljs-doctag">&lt;/param&gt;</span></span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ReceiveMessage</span>(<span class="hljs-params">ChatMessage message</span>)</span>
{
    messages.Add(message);
    InvokeAsync(StateHasChanged).Wait();
    Js.InvokeVoidAsync(identifier: <span class="hljs-string">"updateScroll"</span>, args: <span class="hljs-literal">null</span>).AsTask().Wait();
}
</code></pre>
<p>In this method we simply add the message to our list, notify Blazor that the state has changed and invoke javascript to scroll the messages to the bottom.</p>
<p>Now if a user decides to leave the page, we’ll start getting exceptions because the ChatroomGrain still tries to call it when a new message is received. To make sure we unsubscribe, we implement the <code>DisposeAsync</code> method:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> ValueTask <span class="hljs-title">DisposeAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">if</span> (chatroomClientReference <span class="hljs-keyword">is</span> <span class="hljs-literal">null</span>)
    {
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// If the chat gets disposed, it means the page is closed</span>
    <span class="hljs-comment">// Leave the chat</span>
    <span class="hljs-keyword">var</span> chatroomGrain = ClusterClient.GetGrain&lt;IChatroom&gt;(Chatroom);
    <span class="hljs-keyword">await</span> chatroomGrain.LeaveAsync(chatroomClientReference, username);
}
</code></pre>
<p>The lastly, we have to make it possible to send messages. To this we create the following two methods:</p>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">KeyUpSendMessageAsync</span>(<span class="hljs-params">KeyboardEventArgs args</span>)</span>
{
    <span class="hljs-keyword">if</span> (args.Code == <span class="hljs-string">"Enter"</span> || args.Code == <span class="hljs-string">"NumpadEnter"</span>)
    {
        <span class="hljs-keyword">await</span> SendMessageAsync();
    }
}

<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">SendMessageAsync</span>(<span class="hljs-params"></span>)</span>
{
    <span class="hljs-keyword">var</span> chatroomGrain = ClusterClient.GetGrain&lt;IChatroom&gt;(Chatroom);

    <span class="hljs-keyword">await</span> chatroomGrain.PublishAsync(draftMessage, username);

    <span class="hljs-comment">// Reset the message</span>
    draftMessage = <span class="hljs-keyword">string</span>.Empty;
    <span class="hljs-keyword">await</span> InvokeAsync(StateHasChanged);
}
</code></pre>
<p>Just as the other methods, this method gets the ChatroomGrain and simply calls a method to invoke the desired action.</p>
<p>And that’s all! You should now be able to run the client and silo (make sure you run them both) and start chatting! The result should look something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728029766011/47794c4e-aaf7-4cb4-afc5-613a7fcb8250.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-wrapping-up">Wrapping up</h1>
<p>So here we are, we’ve successfully built a chat application using Microsoft Orleans. In this post, we created a Silo that uses localhost or Azure Tables clustering, and a Blazor Web App client that connects to the Silo. We also implemented real-time communication between these applications using Microsoft Orleans Observers. Please note that all code in this post is for demo purposes. Some code (especially in the client) could definitely be more robust and/or optimized and should not directly be used in a production environment.</p>
<p>I hope you enjoyed building this chat app as much as I did. If you have any suggestions, feedback, or questions, feel free to reach out to me! All the code in this post is also on GitHub: <a target="_blank" href="https://github.com/hansmuns/MicrosoftOrleansChatExample">https://github.com/hansmuns/MicrosoftOrleansChatExample</a></p>
<p>That’s all for now, enjoy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Learn the Basics of Microsoft Orleans]]></title><description><![CDATA[Building scalable and maintainable solutions can be challenging, especially when dealing with distributed systems. While microservices are a common approach, they aren't the only option for achieving scalability. In this post, I’ll introduce you to M...]]></description><link>https://codewithhans.com/learn-the-basics-of-microsoft-orleans</link><guid isPermaLink="true">https://codewithhans.com/learn-the-basics-of-microsoft-orleans</guid><category><![CDATA[.NET]]></category><category><![CDATA[Microsoft Orleans]]></category><category><![CDATA[software development]]></category><category><![CDATA[software architecture]]></category><category><![CDATA[C#]]></category><dc:creator><![CDATA[Hans Muns]]></dc:creator><pubDate>Thu, 26 Sep 2024 14:57:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727420499065/65af529d-aa59-43b9-9b3e-cf33ef3f4748.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Building scalable and maintainable solutions can be challenging, especially when dealing with distributed systems. While microservices are a common approach, they aren't the only option for achieving scalability. In this post, I’ll introduce you to Microsoft Orleans, a powerful framework designed to simplify the process of building scalable, distributed applications. Whether you’re running on a single server or scaling across thousands of instances, Orleans offers a flexible and developer-friendly solution that might be exactly what your .NET application needs.</p>
<h3 id="heading-overview-of-microsoft-orleans">Overview of Microsoft Orleans</h3>
<p>For those who have never heard about Microsoft Orleans, Microsoft Orleans is a cross-platform framework for building scalable, distributed apps that can easily scale to thousands of distributed instances but also function perfectly on just a single instance.</p>
<p>This framework is especially built with .NET developers in mind. It extends familiar concepts that .NET developers know (and hopefully love) from a single server environment to a multi-server environment. This all while also simplifying the complexities that come with a multi-server environment.</p>
<h2 id="heading-core-concepts-of-microsoft-orleans">Core Concepts of Microsoft Orleans</h2>
<p>To understand how this framework works, it's important to know it's based on the actor model. The actor model is a programming model where each actor is a small, concurrent, immutable object that encapsulates its own behavior and state. These actors can only communicate with each other through asynchronous messages.</p>
<h3 id="heading-virtual-actors">Virtual Actors</h3>
<p>Microsoft Orleans invented the idea of <em>virtual actors.</em> A virtual actor is an abstraction that provides a straightforward approach to building distributed applications. Within Microsoft Orleans, these are represented by Grains.</p>
<p>Microsoft also wrote about Virtual actors which you can read <a target="_blank" href="https://www.microsoft.com/en-us/research/project/orleans-virtual-actors/">here</a> if you want to learn more.</p>
<h3 id="heading-grains">Grains</h3>
<p>So grains are the centerpiece of the Orleans framework. Grains represent the (virtual) actors within the framework. Grains define the state, behavior and identity of an entity. An example could be an invoice, or a customer. Grains are identified with an user-defined key. This means that a grain can be accessed by other grains and clients by the key. To better show it, here is an visual representation of what a grain is:</p>
<p><img src="https://learn.microsoft.com/en-us/dotnet/orleans/media/grain-formulation.svg" alt="A grain is composed of a stable identity, behavior, and state." /></p>
<p><em>Source:</em> <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/overview"><em>Microsoft Learn</em></a></p>
<p>To further understand grains, let’s implement one within .NET. To do this, we simply create a normal class that inherits the grain base class like this:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System.Threading.Tasks;
<span class="hljs-keyword">using</span> Orleans;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">MyOrleansApp.Grains</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IMyGrain</span> : <span class="hljs-title">IGrainWithIntegerKey</span>
    {
        <span class="hljs-function">Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">SayHello</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> greeting</span>)</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MyGrain</span> : <span class="hljs-title">Grain</span>, <span class="hljs-title">IMyGrain</span>
    {
        <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">SayHello</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> greeting</span>)</span>
        {
            <span class="hljs-keyword">return</span> Task.FromResult(<span class="hljs-string">$"Hello, <span class="hljs-subst">{greeting}</span>!"</span>);
        }
    }
}
</code></pre>
<p>In our example, we created two things, an interface and a class. The interface is needed since the Orleans framework forces us to use abstractions when talking to the grain. This is good since it helps us separating implementations and making the code easier to test. On this interface we implemented the <code>IGrainWithIntegerKey</code><em>.</em> This implementation tells Orleans our grain is identifiable with an integer. Not only does Orleans support integers as keys, it supports the following types of keys:</p>
<ul>
<li><p>IGrainWithGuidKey</p>
</li>
<li><p>IGrainWithIntegerKey</p>
</li>
<li><p>IGrainWithStringKey</p>
</li>
<li><p>IGrainWithGuidCompoundKey</p>
</li>
<li><p>IGrainWithIntegerCompoundKey</p>
</li>
</ul>
<p>The state of grains can be volatile and/or persistent. To accommodate and enable automatic scalability and failure recovery, a grain state is kept in memory while the grain is active. This also gives us lower latency and less load on the data stores.</p>
<p>The state of a grain can reside in four different phases:</p>
<ol>
<li><p>Persisted</p>
</li>
<li><p>Activating</p>
</li>
<li><p>Active in memory</p>
</li>
<li><p>Deactivating</p>
</li>
</ol>
<p>To understand, how this works, Microsoft made a visual representation:</p>
<p><img src="https://learn.microsoft.com/en-us/dotnet/orleans/media/grain-lifecycle.svg" alt="The managed lifecycle of an Orleans grain." /></p>
<p><em>Source:</em> <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/overview"><em>Microsoft Learn</em></a></p>
<p>Instantiation of grains is automatically performed on demand by the Orleans runtime. This happens when a client calls for the specific Grain. Grains that aren't used for a while are automatically removed from memory to free up resources. This is possible because of the stable identity a grain has, which enables developers to call the grain without knowing if it's already in memory or not. The Orleans runtime handles the responsibility of activating and deactivating, as well as placing and locating grains as needed.</p>
<h2 id="heading-silo">Silo</h2>
<p>When we are talking about Grains, these are separate entities. To actually scale and manage them, Microsoft Orleans put them in something called a 'Silo'. A Silo can be seen as an instance of the application. When a client makes a call for a specific Grain, the Orleans Runtime checks if the Grain already exists in a Silo and if not, it activates the Grain in a Silo. There are different ways we can configure the logic that is used to assign a Grain to a Silo, though these won't be covered in this post.</p>
<h2 id="heading-cluster">Cluster</h2>
<p>So to manage all the Grains active in one or more Silos we need something to coordinate the work between the Silos. This is where the cluster comes in play. A cluster can be seen as a Group of one or more Silos. The cluster will coordinate all work between the silo. This enables us to use Grains as if we are using a single process. This also means we do not have to worry about knowing in which Silo a Grain is active, the Cluster manages it for us. To visualize the relationship between Clusters, Silos and grains we can use the following diagram:</p>
<p><img src="https://learn.microsoft.com/en-us/dotnet/orleans/media/cluster-silo-grain-relationship.svg" alt="A cluster has one or more silos, and a silo has one or more grains." /></p>
<p><em>Source:</em> <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/orleans/overview"><em>Microsoft Learn</em></a></p>
<h2 id="heading-usecases-for-microsoft-orleans">Usecases for Microsoft Orleans</h2>
<p>So when should we use this framework? If you know your .NET applications will need scaling, Microsoft Orleans can help a lot in achieving this goal. Orleans does this by making smart choices, like using a familiar OOP paradigm. Each entity is a Grain, which is single-threaded (so no concurrency issues) and the system easily scales to multiple instances (silos) without needing to manage networking and much more.</p>
<p>These features make Microsoft Orleans ideal for various use cases like IoT software, banking apps, and chat apps. However, Microsoft Orleans is not suitable for scenarios where memory needs to be shared between entities or where scaling is not required, due to its added complexity.</p>
<h2 id="heading-wrapping-up">Wrapping up</h2>
<p>In this blog, we covered the absolute basics of Microsoft Orleans, though it offers many additional features such as:</p>
<ul>
<li><p>Easy persistence support through storage providers</p>
</li>
<li><p>Grain placement (the logic that decides in which silo a grain should be instantiated)</p>
</li>
<li><p>Timers and reminders (durable and non-durable scheduling mechanisms for grains)</p>
</li>
<li><p>Stateless workers</p>
</li>
<li><p>ACID transactions</p>
</li>
<li><p>Grain versioning</p>
</li>
<li><p>Journaled grains (enabling event sourcing)</p>
</li>
<li><p>Observers</p>
</li>
<li><p>Streams</p>
</li>
</ul>
<p>As I explore the framework further, I'll write more about these features. For now, I'm working on a step-by-step guide to build a small Microsoft Orleans application.</p>
<p>EDIT: I’ve just published the guide <a target="_blank" href="https://codewithhans.com/learn-to-build-a-chat-application-using-microsoft-orleans">here</a>.</p>
<p>I hope you learned something new in this post, and if you have any questions, feel free to reach out to me.</p>
<p>For now, enjoy coding! :D</p>
]]></content:encoded></item></channel></rss>