{"data":{"post":{"fields":{"slug":"/net-in-my-browser/","date":"2018-10-25"},"html":"<h1>Introduction</h1>\n<p>.NET ecosystem has seen some rapid changes in the recent times. With advent of <a href=\"https://dot.net/core\">.NET Core</a>, the official cross-platform implementation of the runtime (and SDK) Linux and Mac users have gained a way to write and run .NET code in the comfort of their favourite OSes, all supported and ratified by Microsoft. The new and shiny tooling as well as ASP.NET Core’s impressive performance (in top 10 of TechEmpower’s <a href=\"https://www.techempower.com/benchmarks/#section=data-r16&#x26;hw=ph&#x26;test=fortune\">Fortunes benchmark</a>) have overshadowed .NET Core’s older x-plat sibling, the Mono runtime. But it is one of Mono’s targets that interests us today - namely WebAssembly. Ladies and gentlemen, allow me to introduce…</p>\n<h1>Blazor</h1>\n<p><strong>An experimental .NET web framework using C#/Razor and HTML that runs in the browser via WebAssembly</strong></p>\n<p>…but not exclusively, more on that later.</p>\n<h2>Getting started</h2>\n<p>Disclaimer: this article assumes at least some familiarity with .NET, Razor, C# and web technologies.</p>\n<p>Another disclaimer: as stated above, Blazor is an experimental piece of technology and as such everything about it can change, so the contents of this article might not reflect the current state 1:1.</p>\n<p>You will need <a href=\"https://dot.net/core\">.NET Core SDK</a> v2.1.402 or above.</p>\n<p>And then, depending on your editor of choice grab\n<a href=\"https://go.microsoft.com/fwlink/?linkid=870389\">Blazor Languge Services Extension</a> for Visual Studio 2017 v15.8 or above. Note: you will need <em>ASP.NET and web development</em> workload installed.</p>\n<p>Alternatively, for VS Code users out there there’s <a href=\"https://marketplace.visualstudio.com/items?itemName=austincummings.razor-plus\">Razor+</a> extension. To create projects using the CLI, run the following command:</p>\n<p><code class=\"language-text\">dotnet new -i Microsoft.AspNetCore.Blazor.Templates</code></p>\n<p>Admittedly, I recommend going the Visual Studio way because the tooling is objectively better and regularly updated.</p>\n<h2>Creating a new project</h2>\n<p>For Visual Studio users out there, simply create a new ASP.NET Core project and select one of three Blazor options presented in the dialog. And what options they are!</p>\n<ul>\n<li>Blazor (standalone) - for all intents and purposes a static website, uses Mono on WASM to execute your code. The CLI command to create a project is <code class=\"language-text\">dotnet new blazor</code>. Since no server code is required whatsoever, you can host it like you would any static website, including Github/Gitlab pages!</li>\n<li>Blazor (ASP.NET Core hosted) - pretty similar to the standalone version but with an ASP.NET Core backend. CLI commmand: <code class=\"language-text\">dotnet new blazorhosted</code>.</li>\n<li>Blazor (server-side in ASP.NET Core) - all code is run by the server with DOM updates transmitted using SignalR connection. Since this is pure ASP.NET Core, you will enjoy full debugger support and, if all goes well, an official release with .NET Core 3 under a name <em>Razor Components</em>. CLI command: <code class=\"language-text\">dotnet new blazorserverside</code></li>\n</ul>\n<p>I mention full debugger support for server-side Blazor - that’s because for the other two options the debugging experience is currently extremely limited and can be done exclusively using Google Chrome. See <a href=\"https://blazor.net/docs/debugging.html\">the docs</a> for details.</p>\n<h2>First run</h2>\n<p>Let’s create a new <em>standalone</em> project. Once that’s done, you can launch it by either using <code class=\"language-text\">ctrl + F5</code> in Visual Studio or <code class=\"language-text\">dotnet run</code> CLI command. Either way, you will know all went well if the following appears in your browser:</p>\n<p><img src=\"/img/blazor_1.png\" alt=\"Your first Blazor app\"></p>\n<p>Should the app fail to run, it is worth checking if there are conflicting .NET Core runtime versions. Uninstalling the old ones should help.</p>\n<p>As of now, there’s no live reload support. If you’re using Visual Studio, then app should rebuild automagically when you refresh the page and changes are detected. For CLI users, you will need to add</p>\n<p><code class=\"language-text\">&lt;Watch Include=&quot;**\\*.cshtml&quot; /&gt;</code></p>\n<p>under <code class=\"language-text\">&lt;ItemGroup&gt;</code> tag in your project’s .csproj file and run the app using <code class=\"language-text\">dotnet watch run</code> command.</p>\n<h2>Components</h2>\n<h3>Pages</h3>\n<p>In Blazor, everything is a component, be it a layout, a page or, well, a component. Let’s inspect <em>Counter.cshtml</em> sitting in <em>Pages</em> folder. The code should look similar to this:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">@page &quot;/counter&quot;\n\n&lt;h1&gt;Counter&lt;/h1&gt;\n\n&lt;p&gt;Current count: @currentCount&lt;/p&gt;\n\n&lt;button class=&quot;btn btn-primary&quot; onclick=&quot;@IncrementCount&quot;&gt;Click me&lt;/button&gt;\n\n@functions {\n    int currentCount = 0;\n\n    void IncrementCount()\n    {\n        currentCount++;\n    }\n}</code></pre></div>\n<p>The code should be familiar to anyone who had worked with Razor before which should not be a surprise, given that <code class=\"language-text\">Blazor = browser + Razor</code>. Click the button and you’ll see the counter increment just as you would expect, given the C# code above. Yes, C#!</p>\n<p>Let’s deconstruct the code piece by piece.</p>\n<p><code class=\"language-text\">@page &quot;/counter&quot;</code> directive tells Blazor that this component is a page and can be routed to.</p>\n<p><code class=\"language-text\">&lt;h1&gt;Counter&lt;/h1&gt;</code> is good ol’ plain HTML, no surprises here, unless you’re easily startled by headers or XML tags in general.</p>\n<p><code class=\"language-text\">&lt;p&gt;Current count: @currentCount&lt;/p&gt;</code> is where the interesting stuff begins. The <code class=\"language-text\">@currentCount</code> is a reference to the field defined in the <code class=\"language-text\">@functions</code> block below and will cause the current value in the variable to be rendered there.</p>\n<p><code class=\"language-text\">&lt;button class=&quot;btn btn-primary&quot; onclick=&quot;@IncrementCount&quot;&gt;Click me&lt;/button&gt;</code> is where we see the onclick event handler which is also a reference to the <code class=\"language-text\">IncrementCount()</code> method below.</p>\n<p>Finally, there’s the <code class=\"language-text\">@functions</code> block where we define our variables, parameters, functions, etc.</p>\n<h3>Routing</h3>\n<p>One page can have multiple routes assigned to it and routes can have parameters. Let’s modify the code to allow a route which would set an initial value to the counter:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">@page &quot;/counter&quot;\n@page &quot;/counter/{CurrentCount:int}&quot;\n\n&lt;h1&gt;Counter&lt;/h1&gt;\n\n&lt;p&gt;Current count: @CurrentCount&lt;/p&gt;\n\n&lt;button class=&quot;btn btn-primary&quot; onclick=&quot;@IncrementCount&quot;&gt;Click me&lt;/button&gt;\n\n@functions {\n    [Parameter] int CurrentCount { get; set; } = 0;\n\n    void IncrementCount()\n    {\n        CurrentCount++;\n    }\n}</code></pre></div>\n<p>We’ve added an additional route with parameter constrained to <code class=\"language-text\">int</code> type, changed <code class=\"language-text\">currentCount</code> field to a property (properly PascalCased) and decorated it with <code class=\"language-text\">[Parameter]</code> attribute which allows us to navigate to <code class=\"language-text\">http://localhost:5000/counter/3</code> (adjust the port if needed but 5000 is the default for <code class=\"language-text\">dotnet run</code>) and we should see that the counter is initialized with 3. Neat!</p>\n<h3>Separate markup and code</h3>\n<p>At this point you might be thinking “But do I <em>have to</em> have everything in one file?” and short answer to that is <code class=\"language-text\">no</code>. The long answer is that we can make use of <code class=\"language-text\">@inherits</code> directive and extract the logic to a separate class which our component would inhertit from. The “code behind” class must inherit <code class=\"language-text\">BlazorComponent</code> for that to work:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">abstract class CounterComponent : BlazorComponent\n{\n    [Parameter] protected int CurrentCount { get; set; } = 0;\n\n    protected void IncrementCount()\n    {\n        CurrentCount++;\n    }\n}</code></pre></div>\n<p>together with</p>\n<div class=\"gatsby-highlight\" data-language=\"html\"><pre class=\"language-html\"><code class=\"language-html\">@inherits CounterComponent\n@page \"/counter\"\n@page \"/counter/{CurrentCount:int}\"\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>h1</span><span class=\"token punctuation\">></span></span>Counter<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>h1</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>p</span><span class=\"token punctuation\">></span></span>Current count: @CurrentCount<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>p</span><span class=\"token punctuation\">></span></span>\n\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>button</span> <span class=\"token attr-name\">class</span><span class=\"token attr-value\"><span class=\"token punctuation\">=</span><span class=\"token punctuation\">\"</span>btn btn-primary<span class=\"token punctuation\">\"</span></span> <span class=\"token attr-name\">onclick</span><span class=\"token attr-value\"><span class=\"token punctuation\">=</span><span class=\"token punctuation\">\"</span>@IncrementCount<span class=\"token punctuation\">\"</span></span><span class=\"token punctuation\">></span></span>Click me<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>button</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>will result in the same thing but split in twain.</p>\n<h3>Layouts</h3>\n<p>As most frameworks do, Blazor allows components to use layouts to reduce code repetition. A brief look at <em>MainLayout.cshtml</em> file in <em>Shared</em> folder will reveal that to create a layout we need to:</p>\n<ul>\n<li>Inherit from <code class=\"language-text\">BlazorLayoutComponent</code>.</li>\n<li>Render the body using <code class=\"language-text\">@Body</code> somewhere in the layout’s markup.</li>\n</ul>\n<p>In <em>Pages</em> folder there’s a file called _<em>ViewImports.cshtml</em> which is a reserved name meaning something along the lines of “add the content of this file to all page components that are siblings of this file or in child folders”. Inside, there’s one line <code class=\"language-text\">@layout MainLayout</code> that controls which component is to be used as the layout. Each page can override this by using the same syntax or refuse the layout at all by using <code class=\"language-text\">@layout null</code>.</p>\n<h3>Regular components</h3>\n<p>Obviously, not every component needs to be a routable page, but a reusable piece instead. The way we define those is almost identical to pages sans the <code class=\"language-text\">@page</code> directive. Even though the cool factor of this framework is already pretty high, let’s have it go through the roof by adding a new file <em>CoolCounter.cshtml</em> in <em>Shared</em> folder. Once we have it, let’s write some code:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">&lt;div class=&quot;alert alert-primary&quot;&gt;\n    @ChildContent\n    Curent count: @Count\n    @ChildContent\n&lt;/div&gt;\n\n@functions {\n    [Parameter] int Count { get; set; }\n    [Parameter] RenderFragment ChildContent { get; set; }\n}</code></pre></div>\n<p>In <em>Counter.cshtml</em> let’s change</p>\n<p><code class=\"language-text\">&lt;p&gt;Current count: @CurrentCount&lt;/p&gt;</code></p>\n<p>to</p>\n<p><code class=\"language-text\">&lt;CoolCounter Count=&quot;CurrentCount&quot;&gt;😎😎&lt;/CoolCounter&gt;</code></p>\n<p>After reloading the counter page you should see something like this:</p>\n<p><img src=\"/img/blazor_2.png\" alt=\"A cooler counter\"></p>\n<p>Let’s dissect the code. Using components is as simple as writing a tag with its name, <code class=\"language-text\">CoolComponent</code> being the case here. The <code class=\"language-text\">ChildContent</code> property is a reserved name for, as the name suggests, child content that you can put in the component. In the example above our child content are two smiling faces with sunglasses emojis rendered twice with <code class=\"language-text\">@ChildContent</code> to ensure maximum coolness.</p>\n<p>A familiar <code class=\"language-text\">[Parameter]</code> attribute has made a reappearance but means something different for a non-page component. We can use it to pass parameters in the markup, <code class=\"language-text\">Count=&quot;CurrentCount&quot;</code> being the syntax in the tag here. What other things can be passed as parameters? Sky is the limit. Say that we want to add some extensibility to the <code class=\"language-text\">CoolComponent</code> and have it somehow react to click event. Easy peasy, just add the following line in the <code class=\"language-text\">@functions</code> block:</p>\n<p><code class=\"language-text\">[Parameter] Action Click { get; set; }</code></p>\n<p>and also add the <code class=\"language-text\">onclick</code> handler to the div element:</p>\n<p><code class=\"language-text\">&lt;div class=&quot;alert alert-primary&quot; onclick=&quot;@(() =&gt; Click?.Invoke())&quot;&gt;</code></p>\n<p>Finally, we need to pass some action as a parameter where we invoke our component:</p>\n<p><code class=\"language-text\">&lt;CoolCounter Count=&quot;CurrentCount&quot; Click=&quot;() =&gt; CurrentCount = 0&quot;&gt;😎😎&lt;/CoolCounter&gt;</code></p>\n<p>This handler could, of course, be extracted to a separate method but inline lambda expressions are acceptable as well. So let’s reload the counter page, increment it a bit, click the component and… Nothing. That’s because the action wasn’t directly called in a DOM event handler. We need to give the framework a small nudge. Let’s change our component invocation a bit:</p>\n<p><code class=\"language-text\">&lt;CoolCounter Count=&quot;CurrentCount&quot; Click=&quot;() =&gt; { CurrentCount = 0; StateHasChanged(); }&quot;&gt;😎😎&lt;/CoolCounter&gt;</code></p>\n<p>By calling the <code class=\"language-text\">StateHasChanged()</code> method we inform Blazor that something has happened outside the area it keeps under watch and that the document tree needs to be updated. The example also shows the point at which we could start seriously considering dropping the lambda in favour of a separate method.</p>\n<h3>Templated components</h3>\n<p>Components are reusable by themselves but there is a way to make them even more universal and that is to have them accept templates. Let’s have a look at <em>FetchData.cshtml</em> in <em>Pages</em> folder. It’s a page component demoing the use of HttpClient provided by DI and <code class=\"language-text\">@inject</code> directive and displaying the weather data fetched with it. The table where the data is shown is perfectly fine but we can make it reusable by extracting it to a separate component. Let’s do just that - create a new <em>TemplatedTable.cshtml</em> file in <em>Shared</em> folder and add the following code inside:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">@typeparam T\n\n&lt;table class=&quot;table&quot;&gt;\n    &lt;thead&gt;\n        &lt;tr&gt;@Header&lt;/tr&gt;\n    &lt;/thead&gt;\n    &lt;tbody&gt;\n        @foreach (var item in Items)\n        {\n            &lt;tr&gt;@Row(item)&lt;/tr&gt;\n        }\n    &lt;/tbody&gt;\n&lt;/table&gt;\n\n@functions {\n    [Parameter] RenderFragment Header { get; set; }\n    [Parameter] RenderFragment&lt;T&gt; Row { get; set; }\n    [Parameter] IEnumerable&lt;T&gt; Items { get; set; }\n}</code></pre></div>\n<p>The <code class=\"language-text\">@typeparam</code> directive makes the component a generic class and allows it to accept any type of item which is vital for list-type scenarios. Unfortunately, there’s no way to put type constraints on the generics as of now.</p>\n<p>With that ready, we can throw out most of the table code from <em>FetchData.cshtml</em> and replace it with the following:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">&lt;TemplatedTable Items=&quot;forecasts&quot;&gt;\n    &lt;Header&gt;\n        &lt;th&gt;Date&lt;/th&gt;\n        &lt;th&gt;Temp. (C)&lt;/th&gt;\n        &lt;th&gt;Temp. (F)&lt;/th&gt;\n        &lt;th&gt;Summary&lt;/th&gt;\n    &lt;/Header&gt;\n    &lt;Row Context=&quot;forecast&quot;&gt;\n        &lt;td&gt;@forecast.Date.ToShortDateString()&lt;/td&gt;\n        &lt;td&gt;@forecast.TemperatureC&lt;/td&gt;\n        &lt;td&gt;@forecast.TemperatureF&lt;/td&gt;\n        &lt;td&gt;@forecast.Summary&lt;/td&gt;\n    &lt;/Row&gt;\n&lt;/TemplatedTable&gt;</code></pre></div>\n<p>After reloading the fetch data page, we will see exactly the same thing but what’s important is that we did it the templated way.</p>\n<h2>Final excercise</h2>\n<p>Nearing the end, let’s add an entirely new page and have it display an old-fashioned clock with hands. So, in the <em>Pages</em> folder let’s add a new <em>Clock.cshtml</em> file with the following content:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">@page &quot;/clock&quot;\n@using System.Threading\n\n&lt;h1&gt;Clock&lt;/h1&gt;\n&lt;div class=&quot;clock&quot;&gt;\n    &lt;div style=&quot;@GetHandStyle(d =&gt; d.Second, 6)&quot; class=&quot;second hand&quot;&gt;&lt;/div&gt;\n    &lt;div style=&quot;@GetHandStyle(d =&gt; d.Minute + d.Second / 60.0, 6)&quot; class=&quot;minute hand&quot;&gt;&lt;/div&gt;\n    &lt;div style=&quot;@GetHandStyle(d =&gt; d.Hour % 12 + d.Minute / 60.0 + d.Second / 3600.0, 30)&quot; class=&quot;hour hand&quot;&gt;&lt;/div&gt;\n&lt;/div&gt;\n&lt;div&gt;@currentTime.ToString(&quot;s&quot;)&lt;/div&gt;\n\n@functions {\n    DateTime currentTime = DateTime.Now;\n    Timer clockTimer = null;\n\n    protected override void OnInit()\n    {\n        clockTimer = new Timer(_ =&gt;{\n            currentTime = DateTime.Now;\n            StateHasChanged();\n        }, null, 0, 1000);\n    }\n\n    string GetHandStyle(Func&lt;DateTime, double&gt; unitSelector, int multiplier) =&gt;\n        $&quot;transform: rotateZ({unitSelector(currentTime) * multiplier + 180}deg);&quot;;\n}</code></pre></div>\n<p>We will also need to add some CSS to <em>site.css</em> file sitting in <em>wwwroot/css</em> folder:</p>\n<div class=\"gatsby-highlight\" data-language=\"css\"><pre class=\"language-css\"><code class=\"language-css\"><span class=\"token selector\">.clock</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">height</span><span class=\"token punctuation\">:</span> 300px<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">width</span><span class=\"token punctuation\">:</span> 300px<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">border-radius</span><span class=\"token punctuation\">:</span> 50%<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">border</span><span class=\"token punctuation\">:</span> 2px solid black<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">position</span><span class=\"token punctuation\">:</span> relative<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token selector\">.hand</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">width</span><span class=\"token punctuation\">:</span> 2px<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">position</span><span class=\"token punctuation\">:</span> absolute<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">left</span><span class=\"token punctuation\">:</span> 50%<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">top</span><span class=\"token punctuation\">:</span> 50%<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">transform-origin</span><span class=\"token punctuation\">:</span> 0 0<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token selector\">.hour</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">height</span><span class=\"token punctuation\">:</span> 75px<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">background-color</span><span class=\"token punctuation\">:</span> black<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token selector\">.minute</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">height</span><span class=\"token punctuation\">:</span> 100px<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">background-color</span><span class=\"token punctuation\">:</span> gray<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token selector\">.second</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token property\">height</span><span class=\"token punctuation\">:</span> 125px<span class=\"token punctuation\">;</span>\n    <span class=\"token property\">background-color</span><span class=\"token punctuation\">:</span> red<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>But given that we cannot have users need to guess the URL of every page in our app, let’s also expand the navigation in <em>NavMenu.cshtml</em> in <em>Shared</em> folder with a new item:</p>\n<div class=\"gatsby-highlight\" data-language=\"cs\"><pre class=\"language-cs\"><code class=\"language-cs\">&lt;li class=&quot;nav-item px-3&quot;&gt;\n    &lt;NavLink class=&quot;nav-link&quot; href=&quot;clock&quot;&gt;\n        &lt;span class=&quot;oi oi-list-rich&quot; aria-hidden=&quot;true&quot;&gt;&lt;/span&gt; Clock\n    &lt;/NavLink&gt;\n&lt;/li&gt;</code></pre></div>\n<p><code class=\"language-text\">NavLink</code> is Blazor’s built-in component which renders as an anchor element but adds <code class=\"language-text\">.active</code> class when you navigate to a page in the <code class=\"language-text\">href</code> parameter.</p>\n<p>Ok, but what has just happened? Well, we’ve built a half-decent clock using Blazor and a smidge of CSS. By using <code class=\"language-text\">void OnInit()</code> override, we could hook into the component’s lifecycle and initialize the timer that is set to tick every second and update the <code class=\"language-text\">currentTime</code> field. Because it’s not happening as an effect of some DOM event, we need to notify of changes using <code class=\"language-text\">StateHasChanged()</code> method. But wait, the time’s wrong. At least if you are not living in the UTC timezone area, that is. That’s another limitation of the experimental state of Blazor - the current locale are not being pulled from the browser, so the framework doesn’t know what timezone the user is in. This is to be amended in some future release.</p>\n<h2>Wrap up</h2>\n<p>I hope that you’ve found this article informative and maybe, just maybe, you will give this amazing piece of tech a go. For any further reading, I recommend visiting the <a href=\"https://blazor.net\">Blazor website</a> and their <a href=\"https://github.com/aspnet/blazor\">GitHub repo</a>, the best source of Blazor info.</p>\n<p>That’s it! Thanks for reading and happy hacking.</p>","frontmatter":{"title":".NET in MY browser?","subTitle":"Blazor - a WASM powered front-end framework","postAuthor":"Krzysztof Miczkowski","cover":"/img/dotnet.png"}},"tags":{"group":[{"name":"$OSTYPE","totalCount":1,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}}]},{"name":".env","totalCount":1,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}}]},{"name":".nvrmc","totalCount":1,"edges":[{"node":{"fields":{"slug":"/travis-problematic-python-and-node/"}}}]},{"name":"AUTO_INCREMENT","totalCount":1,"edges":[{"node":{"fields":{"slug":"/is-mysql-autoincrement-really-monotonic/"}}}]},{"name":"Angular","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"Command","totalCount":1,"edges":[{"node":{"fields":{"slug":"/complex-command-handler-in-javascript/"}}}]},{"name":"DQL","totalCount":2,"edges":[{"node":{"fields":{"slug":"/doctrine-new-dql-operator-and-objects/"}}},{"node":{"fields":{"slug":"/mysql-decimal-format/"}}}]},{"name":"Data Transfer Object","totalCount":1,"edges":[{"node":{"fields":{"slug":"/doctrine-new-dql-operator-and-objects/"}}}]},{"name":"Docker","totalCount":3,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}},{"node":{"fields":{"slug":"/quick-import-of-mysql-database-dump/"}}},{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"Docker Compose","totalCount":3,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}},{"node":{"fields":{"slug":"/quick-import-of-mysql-database-dump/"}}},{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"Doctrine","totalCount":2,"edges":[{"node":{"fields":{"slug":"/doctrine-new-dql-operator-and-objects/"}}},{"node":{"fields":{"slug":"/mysql-decimal-format/"}}}]},{"name":"Doctrine NEW","totalCount":1,"edges":[{"node":{"fields":{"slug":"/doctrine-new-dql-operator-and-objects/"}}}]},{"name":"ES7","totalCount":1,"edges":[{"node":{"fields":{"slug":"/async-await-with-express/"}}}]},{"name":"Elastica","totalCount":1,"edges":[{"node":{"fields":{"slug":"/elasticsearch-script-unknown-field-source-parser-not-found/"}}}]},{"name":"Elasticsearch","totalCount":1,"edges":[{"node":{"fields":{"slug":"/elasticsearch-script-unknown-field-source-parser-not-found/"}}}]},{"name":"Elasticsearch script","totalCount":1,"edges":[{"node":{"fields":{"slug":"/elasticsearch-script-unknown-field-source-parser-not-found/"}}}]},{"name":"Express","totalCount":1,"edges":[{"node":{"fields":{"slug":"/async-await-with-express/"}}}]},{"name":"Git","totalCount":2,"edges":[{"node":{"fields":{"slug":"/git-not-detecting-renames-quick-workarounds/"}}},{"node":{"fields":{"slug":"/git-reverting-branch-removal/"}}}]},{"name":"Git renamed","totalCount":1,"edges":[{"node":{"fields":{"slug":"/git-not-detecting-renames-quick-workarounds/"}}}]},{"name":"HTTP","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-does-slack-communicate-with-spotify/"}}}]},{"name":"JavaScript","totalCount":3,"edges":[{"node":{"fields":{"slug":"/async-await-with-express/"}}},{"node":{"fields":{"slug":"/complex-command-handler-in-javascript/"}}},{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}}]},{"name":"Kafka","totalCount":1,"edges":[{"node":{"fields":{"slug":"/is-mysql-autoincrement-really-monotonic/"}}}]},{"name":"MySQL","totalCount":4,"edges":[{"node":{"fields":{"slug":"/mysql-decimal-format/"}}},{"node":{"fields":{"slug":"/is-mysql-autoincrement-really-monotonic/"}}},{"node":{"fields":{"slug":"/php-with-mysql-8/"}}},{"node":{"fields":{"slug":"/quick-import-of-mysql-database-dump/"}}}]},{"name":"MySQL 8","totalCount":1,"edges":[{"node":{"fields":{"slug":"/php-with-mysql-8/"}}}]},{"name":"Node.js","totalCount":1,"edges":[{"node":{"fields":{"slug":"/travis-problematic-python-and-node/"}}}]},{"name":"ORM","totalCount":1,"edges":[{"node":{"fields":{"slug":"/doctrine-new-dql-operator-and-objects/"}}}]},{"name":"PDO","totalCount":1,"edges":[{"node":{"fields":{"slug":"/php-with-mysql-8/"}}}]},{"name":"PDO_MYSQL","totalCount":1,"edges":[{"node":{"fields":{"slug":"/php-with-mysql-8/"}}}]},{"name":"PHP","totalCount":6,"edges":[{"node":{"fields":{"slug":"/doctrine-new-dql-operator-and-objects/"}}},{"node":{"fields":{"slug":"/mysql-decimal-format/"}}},{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}},{"node":{"fields":{"slug":"/php-with-mysql-8/"}}},{"node":{"fields":{"slug":"/elasticsearch-script-unknown-field-source-parser-not-found/"}}},{"node":{"fields":{"slug":"/how-to-write-a-phing-target-autocomplete-bash-script/"}}}]},{"name":"Phing","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-write-a-phing-target-autocomplete-bash-script/"}}}]},{"name":"Promise","totalCount":1,"edges":[{"node":{"fields":{"slug":"/complex-command-handler-in-javascript/"}}}]},{"name":"Python","totalCount":1,"edges":[{"node":{"fields":{"slug":"/travis-problematic-python-and-node/"}}}]},{"name":"React","totalCount":2,"edges":[{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}},{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"Redux","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}}]},{"name":"Redux store","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}}]},{"name":"SPA","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"Slack","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-does-slack-communicate-with-spotify/"}}}]},{"name":"Spotify","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-does-slack-communicate-with-spotify/"}}}]},{"name":"Travis","totalCount":2,"edges":[{"node":{"fields":{"slug":"/travis-problematic-python-and-node/"}}},{"node":{"fields":{"slug":"/elasticsearch-script-unknown-field-source-parser-not-found/"}}}]},{"name":"Vue","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"XDEBUG_CONFIG","totalCount":1,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}}]},{"name":"XDebug","totalCount":1,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}}]},{"name":"ai","totalCount":1,"edges":[{"node":{"fields":{"slug":"/weekly-ai-bites-last-manual-gap-qa-testing/"}}}]},{"name":"amazon","totalCount":1,"edges":[{"node":{"fields":{"slug":"/minio-as-s3-replacement-in-development-and-beyond/"}}}]},{"name":"apple-watch","totalCount":1,"edges":[{"node":{"fields":{"slug":"/aws-cognito-without-library/"}}}]},{"name":"asp.net core","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"async","totalCount":2,"edges":[{"node":{"fields":{"slug":"/async-await-with-express/"}}},{"node":{"fields":{"slug":"/complex-command-handler-in-javascript/"}}}]},{"name":"autocomplete","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-write-a-phing-target-autocomplete-bash-script/"}}}]},{"name":"await","totalCount":2,"edges":[{"node":{"fields":{"slug":"/async-await-with-express/"}}},{"node":{"fields":{"slug":"/complex-command-handler-in-javascript/"}}}]},{"name":"aws","totalCount":3,"edges":[{"node":{"fields":{"slug":"/minio-as-s3-replacement-in-development-and-beyond/"}}},{"node":{"fields":{"slug":"/time-out-of-sync-in-aws-ec2/"}}},{"node":{"fields":{"slug":"/aws-cognito-without-library/"}}}]},{"name":"aws.cli","totalCount":1,"edges":[{"node":{"fields":{"slug":"/travis-problematic-python-and-node/"}}}]},{"name":"bash","totalCount":2,"edges":[{"node":{"fields":{"slug":"/cross-system-xdebug-docker-compose-setup/"}}},{"node":{"fields":{"slug":"/how-to-write-a-phing-target-autocomplete-bash-script/"}}}]},{"name":"bitbucket","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-quickly-remove-merged-remote-branches/"}}}]},{"name":"blazor","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"build","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"builder pattern","totalCount":1,"edges":[{"node":{"fields":{"slug":"/make-jaxb-great-again/"}}}]},{"name":"c#","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"ci","totalCount":1,"edges":[{"node":{"fields":{"slug":"/unresolved-check-from-travis-on-github-pull-request/"}}}]},{"name":"claude-code","totalCount":1,"edges":[{"node":{"fields":{"slug":"/weekly-ai-bites-last-manual-gap-qa-testing/"}}}]},{"name":"cognito","totalCount":1,"edges":[{"node":{"fields":{"slug":"/aws-cognito-without-library/"}}}]},{"name":"colors","totalCount":1,"edges":[{"node":{"fields":{"slug":"/naming-sass-color-variables/"}}}]},{"name":"continuous integration","totalCount":1,"edges":[{"node":{"fields":{"slug":"/unresolved-check-from-travis-on-github-pull-request/"}}}]},{"name":"decimal","totalCount":1,"edges":[{"node":{"fields":{"slug":"/mysql-decimal-format/"}}}]},{"name":"docker","totalCount":1,"edges":[{"node":{"fields":{"slug":"/minio-as-s3-replacement-in-development-and-beyond/"}}}]},{"name":"ec2","totalCount":1,"edges":[{"node":{"fields":{"slug":"/time-out-of-sync-in-aws-ec2/"}}}]},{"name":"environment","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"equals","totalCount":1,"edges":[{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"flysystem","totalCount":1,"edges":[{"node":{"fields":{"slug":"/minio-as-s3-replacement-in-development-and-beyond/"}}}]},{"name":"front-end","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"git","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-quickly-remove-merged-remote-branches/"}}}]},{"name":"git commit","totalCount":1,"edges":[{"node":{"fields":{"slug":"/git-not-detecting-renames-quick-workarounds/"}}}]},{"name":"git reflog","totalCount":1,"edges":[{"node":{"fields":{"slug":"/git-reverting-branch-removal/"}}}]},{"name":"git status","totalCount":1,"edges":[{"node":{"fields":{"slug":"/git-not-detecting-renames-quick-workarounds/"}}}]},{"name":"github","totalCount":3,"edges":[{"node":{"fields":{"slug":"/pull-request-templates-on-github/"}}},{"node":{"fields":{"slug":"/unresolved-check-from-travis-on-github-pull-request/"}}},{"node":{"fields":{"slug":"/how-to-quickly-remove-merged-remote-branches/"}}}]},{"name":"guide","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"hashcode","totalCount":1,"edges":[{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"hibernate","totalCount":1,"edges":[{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"java","totalCount":2,"edges":[{"node":{"fields":{"slug":"/make-jaxb-great-again/"}}},{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"java.time","totalCount":1,"edges":[{"node":{"fields":{"slug":"/make-jaxb-great-again/"}}}]},{"name":"jaxb","totalCount":1,"edges":[{"node":{"fields":{"slug":"/make-jaxb-great-again/"}}}]},{"name":"jdk8","totalCount":1,"edges":[{"node":{"fields":{"slug":"/make-jaxb-great-again/"}}}]},{"name":"jpa","totalCount":1,"edges":[{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"maven","totalCount":1,"edges":[{"node":{"fields":{"slug":"/make-jaxb-great-again/"}}}]},{"name":"middleware","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}}]},{"name":"mono","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"multi-stage","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"naming","totalCount":1,"edges":[{"node":{"fields":{"slug":"/naming-sass-color-variables/"}}}]},{"name":"networking","totalCount":1,"edges":[{"node":{"fields":{"slug":"/time-out-of-sync-in-aws-ec2/"}}}]},{"name":"ntp","totalCount":1,"edges":[{"node":{"fields":{"slug":"/time-out-of-sync-in-aws-ec2/"}}}]},{"name":"nvm","totalCount":1,"edges":[{"node":{"fields":{"slug":"/travis-problematic-python-and-node/"}}}]},{"name":"persistence","totalCount":1,"edges":[{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"port 4381","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-does-slack-communicate-with-spotify/"}}}]},{"name":"pr","totalCount":1,"edges":[{"node":{"fields":{"slug":"/pull-request-templates-on-github/"}}}]},{"name":"production","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"project","totalCount":1,"edges":[{"node":{"fields":{"slug":"/pull-request-templates-on-github/"}}}]},{"name":"pull","totalCount":1,"edges":[{"node":{"fields":{"slug":"/pull-request-templates-on-github/"}}}]},{"name":"qa","totalCount":1,"edges":[{"node":{"fields":{"slug":"/weekly-ai-bites-last-manual-gap-qa-testing/"}}}]},{"name":"razor","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"reducer","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}}]},{"name":"regex","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-write-a-phing-target-autocomplete-bash-script/"}}}]},{"name":"repo","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-quickly-remove-merged-remote-branches/"}}}]},{"name":"repository","totalCount":1,"edges":[{"node":{"fields":{"slug":"/how-to-quickly-remove-merged-remote-branches/"}}}]},{"name":"s3","totalCount":1,"edges":[{"node":{"fields":{"slug":"/minio-as-s3-replacement-in-development-and-beyond/"}}}]},{"name":"sass","totalCount":1,"edges":[{"node":{"fields":{"slug":"/naming-sass-color-variables/"}}}]},{"name":"setup","totalCount":1,"edges":[{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"skills","totalCount":1,"edges":[{"node":{"fields":{"slug":"/weekly-ai-bites-last-manual-gap-qa-testing/"}}}]},{"name":"styles","totalCount":1,"edges":[{"node":{"fields":{"slug":"/naming-sass-color-variables/"}}}]},{"name":"symfony","totalCount":1,"edges":[{"node":{"fields":{"slug":"/minio-as-s3-replacement-in-development-and-beyond/"}}}]},{"name":"templates","totalCount":1,"edges":[{"node":{"fields":{"slug":"/pull-request-templates-on-github/"}}}]},{"name":"test-automation","totalCount":1,"edges":[{"node":{"fields":{"slug":"/weekly-ai-bites-last-manual-gap-qa-testing/"}}}]},{"name":"transaction","totalCount":1,"edges":[{"node":{"fields":{"slug":"/is-mysql-autoincrement-really-monotonic/"}}}]},{"name":"travis","totalCount":1,"edges":[{"node":{"fields":{"slug":"/unresolved-check-from-travis-on-github-pull-request/"}}}]},{"name":"tutorial","totalCount":2,"edges":[{"node":{"fields":{"slug":"/how-to-implement-redux-in-react/"}}},{"node":{"fields":{"slug":"/Dev-and-prod-ready-Docker-setup-for-SPA-app/"}}}]},{"name":"ubuntu","totalCount":1,"edges":[{"node":{"fields":{"slug":"/time-out-of-sync-in-aws-ec2/"}}}]},{"name":"unsupported","totalCount":1,"edges":[{"node":{"fields":{"slug":"/aws-cognito-without-library/"}}}]},{"name":"uuid","totalCount":1,"edges":[{"node":{"fields":{"slug":"/jpa-and-uuid/"}}}]},{"name":"variables","totalCount":1,"edges":[{"node":{"fields":{"slug":"/naming-sass-color-variables/"}}}]},{"name":"wasm","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]},{"name":"watchos","totalCount":1,"edges":[{"node":{"fields":{"slug":"/aws-cognito-without-library/"}}}]},{"name":"web","totalCount":1,"edges":[{"node":{"fields":{"slug":"/net-in-my-browser/"}}}]}]},"author":{"id":"37bda159-ff45-50c9-aeba-18326b7ca66b","childMarkdownRemark":{"html":"<p><strong>XSolve</strong> We are Agile Software House focused on PHP/Symfony, #JavaScript, #Java and #Mobile (iOS, Android, Windows Phone) #Inc5000 European company in 2018.\n<br>\n<a href=\"https://xsolve.software\">xsolve.software</a></p>"}},"footnote":{"id":"b4474c9b-7be6-5f34-866a-cdfffa885bce","childMarkdownRemark":{"html":"<ul>\n<li>From <a href=\"https://www.boldare.com/\">Boldare</a></li>\n<li>with <a href=\"https://github.com/greglobinski/gatsby-starter-personal-blog/\">Gatsby starter</a></li>\n<li>delivered by <a href=\"https://www.netlify.com/\">Netlify</a></li>\n</ul>"}},"site":{"siteMetadata":{"facebook":{"appId":""}}}},"pageContext":{"slug":"/net-in-my-browser/"}}