{"id":2889,"date":"2020-10-12T07:56:12","date_gmt":"2020-10-12T07:56:12","guid":{"rendered":"https:\/\/cloudsurfers.it\/?p=2889"},"modified":"2020-10-12T07:57:55","modified_gmt":"2020-10-12T07:57:55","slug":"blazor-creare-rich-web-ui","status":"publish","type":"post","link":"https:\/\/cloudsurfers.it\/index.php\/blazor-creare-rich-web-ui\/","title":{"rendered":"Blazor: creare Rich Web UI secondo Microsoft"},"content":{"rendered":"\n<p><img decoding=\"async\" class=\"wp-image-2263\" style=\"width: 1200px;\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/copertina.png\" alt=\"\" loading=\"lazy\" srcset=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/copertina.png 1452w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/copertina-300x169.png 300w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/copertina-1024x578.png 1024w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/copertina-768x434.png 768w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/copertina-600x339.png 600w\" sizes=\"(max-width: 1452px) 100vw, 1452px\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Che cosa \u00e8 Blazor?<\/h2>\n\n\n\n<p>Blazor \u00e8 un framework che permette di costruire Rich Web UI con l&#8217;ausilio di Microsoft .NET Framework.<\/p>\n\n\n\n<p>Blazor nasce dalla necessit\u00e0 di riuscire a creare applicazioni web complesse e ricche di interazioni utente senza dover necessariamente utilizzare Javascript, ma avvalendosi di un linguaggio moderno ed evoluto come C#.<\/p>\n\n\n\n<p>Come primo aspetto da dover analizzare prima di affrontare nel dettaglio il funzionamento di Blazor, sono i due modelli di hosting supportati.<\/p>\n\n\n\n<h1><br>Blazor Server vs Blazor WebAssembly<\/h1>\n\n\n\n<p><strong>Blazor Server<\/strong> (<em>Server-side<\/em>) Questa modalit\u00e0 \u00e8 stata rilasciata con .NET Core 3 e prevede che il componente Blazor giri su un server e che tutta la parte di comunicazione ed interazione con il client sia gestita con una real-time connection tramite WebSocket con SignalR.<\/p>\n\n\n\n<p><strong>Blazor WebAssembly<\/strong>&nbsp;(<em>Client-side<\/em>) Questa modalit\u00e0 \u00e8 stata rilasciata con .NET Core 3.1 ed in questo caso il componente Blazor viene impacchettato in un WebAssembly (<em>WA<\/em>) ed \u00e8 eseguito direttamente nel browser dell&#8217;utente.<br>Affinch\u00e9 questo tipo di approccio possa funzionare, \u00e8 necessario che siano soddisfatti due prerequisiti fondamentali:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Il browser utilizzato deve supportare l&#8217;uso dei WA. Questo vincolo dal 2017 non risulta essere un problema in quanto tutti i moderni browser ora supportano l&#8217;utilizzo dei WA.<\/li><li>E&#8217; necessario avere un .NET Runtime compilato in un WA. Anche questo punto per\u00f2 \u00e8 stato risolto con lo sviluppo di .NET Core 3.0.<\/li><\/ol>\n\n\n\n<p>E&#8217; importante sottolineare che la scelta di quale delle due modalit\u00e0 di hosting usare non \u00e8 vincolante, questo vuol dire che una Blazor App di tipo <em>Serever-Side<\/em> pu\u00f2 essere portata ad una versione <em>Client-Side<\/em> e viceversa senza dover sconvolgere il progetto. <\/p>\n\n\n\n<h2 class=\"has-text-align-center wp-block-heading\"><strong>Blazor Server<\/strong><\/h2>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"803\" height=\"732\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-server.png\" alt=\"\" class=\"wp-image-2821\" srcset=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-server.png 803w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-server-300x273.png 300w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-server-768x700.png 768w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-server-600x547.png 600w\" sizes=\"auto, (max-width: 803px) 100vw, 803px\" \/><\/figure><\/div>\n\n\n\n<p>Blazor \u00e8 un Component Oriented UI Framework, questo vuol dire che l&#8217;applicazione stessa, le pagine e gli elementi contenuti, sono tutti <em>Component <\/em>relazionati in gerarchia fra loro.<br>La sintassi utilizzata per programmare questi Component \u00e8 <em>Razor <\/em>ed ogni file .razor \u00e8 un Component.<\/p>\n\n\n\n<p>Vediamo come creare una nuova App Blazor Server con Visual Studio 2019:<br><br><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"550\" class=\"wp-image-2822\" style=\"width: 1024px;\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/VS2019-create-webapp.gif\" alt=\"\"><\/p>\n\n\n\n<p>In allegato a questo articolo \u00e8 possibile scaricare un progetto di esempio creato da <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.stevensanderson.com\/about.html\" target=\"_blank\">Steven Sanderson<\/a> e da lui trattato durante uno speech del 2019, in cui vengono illustrate alcune potenzialit\u00e0 di Blazor. <br>In particolare, nella solution <a href=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/MyCompanyApp.zip\">MyCompanyApp<\/a>, viene creato un semplice Component <em>bonus.razor<\/em> il cui scopo \u00e8 quello di mostrare degli slider di ripartizione di un bonus economico tra diversi reparti di un&#8217;azienda.<br>Di seguito il codice: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n@page &quot;\/bonus&quot;\n\n&lt;h3&gt;Bonus splitter&lt;\/h3&gt;\n\n&lt;div class=&quot;budget&quot;&gt;\n    @foreach (var item in budgetItems)\n    {\n        &lt;span&gt;@item.Name:&lt;\/span&gt;\n        &lt;span&gt;@item.Amount.ToString(&quot;c0&quot;)&lt;\/span&gt;\n        &lt;input type=&quot;range&quot; max=&quot;@(item.Amount + Remaining)&quot; step=&quot;1000&quot;\n               @bind=&quot;item.Amount&quot; @bind:event=&quot;oninput&quot;\n               style=&quot;width: @( Math.Round(100 * (item.Amount + Remaining) \/ totalBudget,0))%&quot; \/&gt;\n    }\n    &lt;span&gt;Remaining:&lt;\/span&gt;\n    &lt;span&gt;@Remaining.ToString(&quot;c0&quot;)&lt;\/span&gt;\n&lt;\/div&gt;\n\n&lt;button disabled=&quot;@(Remaining &gt; 0)&quot;&gt;Save&lt;\/button&gt;\n\n@code {\n    decimal totalBudget = 1000000;\n\n    decimal Remaining =&gt; totalBudget - budgetItems.Sum(x =&gt; x.Amount);\n\n    List&lt;BudgetItem&gt; budgetItems = new List&lt;BudgetItem&gt;\n    {\n        new BudgetItem { Name = &quot;Developers&quot; },\n        new BudgetItem { Name = &quot;Managers&quot; },\n        new BudgetItem { Name = &quot;Sales&quot; },\n    };\n}\n<\/pre><\/div>\n\n\n<p>Il primo aspetto e quello pi\u00f9 evidente \u00e8 l&#8217;utilizzo della sintassi di markup <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/mvc\/views\/razor?view=aspnetcore-3.1\" target=\"_blank\">Razor<\/a> tramite cui \u00e8 possibile includere codice server-based in una pagina web. Come si pu\u00f2 notare un file <em>.razor<\/em> \u00e8 un mix di codice <em>C#<\/em> e <em>HTML<\/em> connessi dagli appositi markup Razor.<br>Razor \u00e8 uno strumento che gi\u00e0 troviamo nel mondo Microsoft da diversi anni, quindi in questo articolo andremo ad analizzare quegli aspetti innovativi introdotti con Blazor. <br>All&#8217;interno di questo Component, seppur molto semplice nella sua logica, ci sono vari elementi di novit\u00e0 che ci possono far capire le potenzialit\u00e0 fornite da questo Framework senza dover scrivere una sola riga in Javascript:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><code>@page \"\/bonus\"<\/code> definisce che il componente in oggetto viene caricato seguendo la regola di route definita &#8220;<em>\/bonus<\/em>&#8220;<\/li><li><code>@bind<\/code> a riga 11 viene definito che il valore dell&#8217;elemento di input definito sul client deve essere in binding con la variabile <code>item.Amount<\/code>. Questo binding \u00e8 bidirezionale, quindi al variale del valore dell&#8217;input, la property <code>item.Amount<\/code> verr\u00e0 anch&#8217;essa aggiornata. <\/li><li><code>@bind:event=\"oninput\"<\/code> a riga 11 viene definito un&#8217;ulteriore opzione; di default l&#8217;aggiornamento del value dell&#8217;input (e quindi dell&#8217;<code>item.Amount<\/code>) avviene nel momento in cui rilascio il click del mouse e di fatto termino lo slide del range input control. Tramite il parametro <code>@bind:event<\/code> \u00e8 possibile ridefinire l&#8217;evento di default che governa il binding. Per questo esempio specifico si vuole imporre che l&#8217;aggiornamento del valore avvenga non appena viene modificata la posizione del range element.<\/li><li><code>@Remaining.ToString(\"c0\")<\/code> \u00e8 possibile sfruttare .NET per impostare le formattazioni dei valori<\/li><li><code>style=\"width: @( Math.Round(100 * (item.Amount + Remaining) \/ totalBudget,0))%\"<\/code>anche questo aspetto \u00e8 interessante, ovvero pensare di controllare dinamicamente gli stili grafici degli elementi client direttamente tramite delle espressioni C#.<\/li><\/ul>\n\n\n\n<h2 class=\"has-text-align-center wp-block-heading\"><strong>Blazor Client<\/strong><\/h2>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"508\" height=\"754\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-client.png\" alt=\"\" class=\"wp-image-2835\" srcset=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-client.png 508w, https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/balzor-host-client-202x300.png 202w\" sizes=\"auto, (max-width: 508px) 100vw, 508px\" \/><\/figure><\/div>\n\n\n\n<p>Gi\u00e0 in fase di creazione di un nuovo progetto in Visual Studio 2019 si ha la possibilit\u00e0 di scegliere se si desidera impostare il progetto per un hoting mode di tipo WebAssembly.<br>Scegliendo questo target, si abilita anche l&#8217;opzione <em>Progressive Web Application<\/em>, di cui analizzeremo in seguito le caratteristiche di questa ulteriore importante feature:<br><br><img decoding=\"async\" class=\"wp-image-2822\" style=\"width: 1024px;\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/VS2019-create-webapp-2.gif\" alt=\"\" loading=\"lazy\"><\/p>\n\n\n\n<p>Similmente a quanto fatto per precedente, utilizzeremo un altro esempio sempre realizzato da <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.stevensanderson.com\/about.html\" target=\"_blank\">Steven Sanderson<\/a>, scaricabile <a href=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/BlazorMart.zip\">qui<\/a>, che utilizzeremo come base per analizzare alcune delle importanti caratteristiche di questa tipologia di soluzione.<\/p>\n\n\n\n<p>La differenza principale che si nota immediatamente \u00e8 la suddivisione della solution in due progetti:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>BlazorMart.Server<\/strong>: applicazione back-end che si occupa di recuperare le info necessarie dalla base dati<\/li><li><strong>BlazorMart.Client<\/strong>: applicazione Blazor Web Assembly<\/li><\/ul>\n\n\n\n<p>Similmente a quanto fatto prima per la soluzione di tipo Blazor Server, proviamo a mettere il focus sul main Component del progetto <em>BlazorMart.Client<\/em>, ovvero sul file <em>App.razor<\/em><\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n@inject Cart Cart\n\n&lt;header&gt;\n    &lt;SearchBox OnItemChosen=&quot;HandleItemChosen&quot; \/&gt;\n&lt;\/header&gt;\n\n&lt;main&gt;\n    @foreach (var row in Cart.Rows)\n    {\n        &lt;CartRowDisplay @key=&quot;row.Key&quot; Data=&quot;row&quot; \/&gt;\n    }\n&lt;\/main&gt;\n\n@if (Cart.HasAnyProducts)\n{\n    &lt;footer&gt;\n        &lt;button class=&quot;remove-item&quot; @onclick=&quot;EnterRemoveMode&quot;&gt;\u2717 Remove item&lt;\/button&gt;\n        &lt;div&gt;Total: &lt;span class=&quot;price&quot;&gt;@Cart.GrandTotal.ToString(&quot;c&quot;)&lt;\/span&gt;&lt;\/div&gt;\n        &lt;button&gt;\ud83d\udcb3 Checkout&lt;\/button&gt;\n    &lt;\/footer&gt;\n}\n\n@if (isInRemoveMode)\n{\n    &lt;Overlay&gt;\n        &lt;p&gt;Scan an item to remove&lt;\/p&gt;\n        &lt;button @onclick=&quot;@(() =&gt; { isInRemoveMode = false; })&quot;&gt;Cancel&lt;\/button&gt;\n    &lt;\/Overlay&gt;\n}\n\n@code {\n    bool isInRemoveMode;\n\n    void EnterRemoveMode()\n    {\n        isInRemoveMode = true;\n    }\n\n    async Task HandleItemChosen(string ean)\n    {\n        if (isInRemoveMode)\n        {\n            Cart.RemoveItem(ean);\n            isInRemoveMode = false;\n        }\n        else\n        {\n            await Cart.AddItemAsync(ean);\n        }\n    }\n}\n<\/pre><\/div>\n\n\n<p>Non \u00e8 difficile notare immediatamente come possa essere semplice lavorare con un Framework Component Oriented. Infatti la Single Page App in questione \u00e8 suddivisa sostanzialmente in tre sezioni, ovvero <em><strong>header<\/strong><\/em>, <em><strong>main<\/strong><\/em> ed un <em><strong>footer<\/strong><\/em>.<br>Le sezioni al loro interno richiamano altri Component definiti in altri file <em>.razor<\/em> utilizzando come tag HTML il nome del Component stesso. Questo approccio facilita la riutilizzabilit\u00e0 del codice e ne favorisce anche la sua modularizzazione.<br>Alcune delle peculiarit\u00e0 di Blazor sono gi\u00e0 state affrontate nella sezione precedente, ora il focus verr\u00e0 posto su altre feature chiave per la soluzione di tipo Web Assembly.  <\/p>\n\n\n\n<h2 class=\"wp-block-heading\">gRPC<\/h2>\n\n\n\n<p>All&#8217;interno del file <em>App.razor<\/em> \u00e8 stata implementata una chiamata che aggiunge un nuovo elemento ad un oggetto di tipo carrello (<strong><em>Cart<\/em><\/strong>).<br>La chiamata <em>AddItemAsync <\/em>di fatto si occupa di recuperare i dettagli del prodotto dato un codice EAN in input. Questi dettagli per\u00f2 risiedono nel back-end del Server, quindi quello che normalmente si fa \u00e8 fare una chiamata API RESTful per ottenere le informazioni che servono. Questo approccio resta corretto logicamente, ma ha quale limite; nella maggior parte dei casi il content della risposta dal Server \u00e8 in formato JSON il quale, seppur semplice da usare, porta con s\u00e8 alcuni limiti intrinseci:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><strong>Verbose<\/strong>: le informazioni nel JSON sono molto ridondanti. Questo genera genera traffico superfluo.<\/li><li><strong>Javascript Object Notation (JSON)<\/strong>: di fatto \u00e8 un formato basato su Javascript ed in quanto tale \u00e8 per sua natura <em>loosely typed<\/em> e questa cosa pu\u00f2 essere spesso fonte di errori durante il processo di implementazione.  <\/li><\/ul>\n\n\n\n<p>La soluzione proposta in questo esempio \u00e8 di utilizzare <strong>gRPC<\/strong>, ovvero un sistema di chiamata di procedura remota open source sviluppato da Google. Lo schema \u00e8 semplice, viene definito un file <em>.proto<\/em> lato Server in cui vengono dichiarate tutte le chiamate e le strutture che il server espone. Da questo file <em>.proto<\/em> viene generata automaticamente una classe Service che crea lo scheletro degli oggetti e delle chiamate definite, quello che bisogna fare lato server \u00e8 l&#8217;<code>ovverride<\/code> dei metodi dichiarati con le rispettive implementazioni.<\/p>\n\n\n\n<p>Vediamo nel concreto dell&#8217;esempio in esame:<\/p>\n\n\n\n<ol class=\"wp-block-list\" start=\"1\"><li>Come prima cosa lato server occorre installare da NuGet i pacchetti <code>Grpc.AspNetCore<\/code> e <code>Knowit.Grpc.Web<\/code><\/li><li>Definire un file proto in cui vengono esposte le chiamate e le rispettive strutture dati<\/li><\/ol>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nsyntax = \"proto3\";\n\noption csharp_namespace = \"BlazorMart.Server\";\n\npackage Inventory;\n\nservice Inventory {\n  rpc Autocomplete (AutocompleteRequest) returns (AutocompleteReply);\n  rpc ProductDetails (ProductDetailsRequest) returns (ProductDetailsResponse);\n}\n\nmessage AutocompleteRequest {\n  string searchQuery = 1;\n}\n\nmessage AutocompleteReply {\n  repeated AutocompleteItem items = 1;\n}\n\nmessage AutocompleteItem {\n    string EAN = 1;\n    string name = 2;\n}\n\nmessage Product {\n\tstring EAN = 1;\n\tstring name = 2;\n\tint32 price = 3;\n\tstring description = 4;\n\tstring imageUrl = 5;\n}\n\nmessage ProductDetailsRequest {\n\tstring EAN = 1;\n}\n\nmessage ProductDetailsResponse {\n\tProduct Product = 1;\n}\n<\/pre><\/div>\n\n\n<ol class=\"wp-block-list\" start=\"3\"><li>Aggiungere la classe <em>InventoryService.cs<\/em> che implementa le chiamate esposte nel file proto<\/li><\/ol>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic class InventoryService : Inventory.InventoryBase\n    {\n        private static Product&#x5B;] _products = JsonSerializer.Deserialize&lt;Product&#x5B;]&gt;(\n            File.ReadAllText(&quot;products.json&quot;));\n\n        public override Task&lt;AutocompleteReply&gt; Autocomplete(AutocompleteRequest request, ServerCallContext context)\n        {\n            var result = new AutocompleteReply();\n\n            if (!string.IsNullOrEmpty(request.SearchQuery))\n            {\n                var matches = _products\n                    .Where(p =&gt; p.Name.StartsWith(request.SearchQuery, StringComparison.CurrentCultureIgnoreCase))\n                    .Select(p =&gt; new AutocompleteItem { EAN = p.EAN, Name = p.Name })\n                    .Take(10); \/\/ Limit to this many results\n                result.Items.AddRange(matches);\n            }\n\n            return Task.FromResult(result);\n        }\n\n        public override async Task&lt;ProductDetailsResponse&gt; ProductDetails(ProductDetailsRequest request, ServerCallContext context)\n        {\n            await Task.Delay(500); \/\/ Look busy\n            var product = _products.FirstOrDefault(p =&gt; p.EAN == request.EAN);\n            return new ProductDetailsResponse { Product = product };\n        }\n    }\n<\/pre><\/div>\n\n\n<ol class=\"wp-block-list\" start=\"4\"><li>A questo punto lato Server l&#8217;implementazione gRPC \u00e8 ultimata.<br>Ora spostiamo l&#8217;attenzione lato Client e cerchiamo di rispondere alla domanda: come si pu\u00f2 richiamare il Server tramite il servizio definito? Molto semplicemente andando ad aggiungere una reference all&#8217;interno del file .csproj del progetto Client<\/li><\/ol>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; highlight: [26,27,28]; title: ; notranslate\" title=\"\">\n&lt;Project Sdk=&quot;Microsoft.NET.Sdk.Web&quot;&gt;\n\n  &lt;PropertyGroup&gt;\n    &lt;TargetFramework&gt;netstandard2.1&lt;\/TargetFramework&gt;\n    &lt;OutputType&gt;Exe&lt;\/OutputType&gt;\n    &lt;LangVersion&gt;7.3&lt;\/LangVersion&gt;\n    &lt;RazorLangVersion&gt;3.0&lt;\/RazorLangVersion&gt;\n    \n    &lt;!-- Because the linker doesn&#039;t yet work with netstandard2.1 --&gt;\n    &lt;BlazorLinkOnBuild&gt;false&lt;\/BlazorLinkOnBuild&gt;\n  &lt;\/PropertyGroup&gt;\n\n  &lt;ItemGroup&gt;\n    &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Blazor&quot; Version=&quot;3.0.0-preview9.19457.4&quot; \/&gt;\n    &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Blazor.Build&quot; Version=&quot;3.0.0-preview9.19457.4&quot; PrivateAssets=&quot;all&quot; \/&gt;\n    &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Blazor.HttpClient&quot; Version=&quot;3.0.0-preview9.19457.4&quot; \/&gt;\n    &lt;PackageReference Include=&quot;Microsoft.AspNetCore.Blazor.DevServer&quot; Version=&quot;3.0.0-preview9.19457.4&quot; PrivateAssets=&quot;all&quot; \/&gt;\n\n    &lt;PackageReference Include=&quot;Google.Protobuf&quot; Version=&quot;3.13.0&quot; \/&gt;\n    &lt;PackageReference Include=&quot;Grpc.Net.Client&quot; Version=&quot;2.32.0&quot; \/&gt;\n    &lt;PackageReference Include=&quot;Grpc.Tools&quot; Version=&quot;2.32.0&quot; PrivateAssets=&quot;all&quot;&gt;\n      &lt;IncludeAssets&gt;build; analyzers; buildtransitive&lt;\/IncludeAssets&gt;\n    &lt;\/PackageReference&gt;\n  &lt;\/ItemGroup&gt;\n\n  &lt;ItemGroup&gt;\n    &lt;Protobuf Include=&quot;..\\BlazorMart.Server\\Protos\\inventory.proto&quot; GrpcServices=&quot;Client&quot; \/&gt;\n  &lt;\/ItemGroup&gt;\n\n&lt;\/Project&gt;\n<\/pre><\/div>\n\n\n<ol class=\"wp-block-list\" start=\"5\"><li>A questo punto lato Client sar\u00e0 possibile richiamare il Server sfruttando l&#8217;istanza autogenerata dell&#8217;Inventory Service. <\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">PWA<\/h2>\n\n\n\n<p>Con PWA si intende tipicamente una Single Page Application (SPA) che sfrutta le attuali API che i Browser moderni mettono a disposizione per far diventare un&#8217;applicazione da funzionante solamente via Web in un&#8217;applicazione Client a tutti gli effetti, quindi installata direttamente sul device in uso ed avviabile tramite un&#8217;icona presente sulla Home del dispositivo stesso.<\/p>\n\n\n\n<p>Non solo, ma avvalendosi di uno strumento chiamato Service Worker, \u00e8 possibile far girare l&#8217;applicazione Client anche offline, sfruttando la cache del dispositivo. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">PWA &#8211; Come creare un&#8217;applicazione installabile sul Client?<\/h4>\n\n\n\n<p>Basta aggiungere al progetto Client un file <strong>manifest.json<\/strong> che contiene alcune informazioni di base necessarie all&#8217;installazione.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n{\n  \"short_name\": \"BlazorMart\",\n  \"name\": \"BlazorMart\",\n  \"icons\": &#x5B;\n    {\n      \"src\": \"icon-512.png\",\n      \"type\": \"image\/png\",\n      \"sizes\": \"512x512\"\n    }\n  ],\n  \"start_url\": \"\/\",\n  \"background_color\": \"#006B41\",\n  \"display\": \"standalone\",\n  \"scope\": \"\/\",\n  \"theme_color\": \"#006B41\"\n}\n<\/pre><\/div>\n\n\n<p>Dopo aver creato il manifest file, \u00e8 necessario includere tale file all&#8217;interno dell&#8217; <code>&lt;head&gt;<\/code> della index page come segue: <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;link rel=&quot;manifest&quot; href=&quot;manifest.json&quot; \/&gt;\n<\/pre><\/div>\n\n\n<p>Quanto mostrato nel manifest precedente \u00e8 un esempio semplice con un subset di tutti i parametri disponibili, per avere maggiori dettagli di configurazione consultare il <a rel=\"noreferrer noopener\" href=\"https:\/\/www.w3.org\/TR\/appmanifest\/\" target=\"_blank\">link<\/a> ufficiale con le specifiche W3C<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">PWA &#8211; Service Worker<\/h4>\n\n\n\n<p>Il Service Worker di fatto \u00e8 un semplice file Javascript inserito all&#8217;interno del progetto Client che si occupa di intercettare tutte le richieste di rete fatte dall&#8217;applicazione e, nel caso di connessione attiva le gestisce in modo standard, in caso contrario sfrutta la cache del dispositivo. In questo modo l&#8217;applicazione risulta almeno in parte funzionante, ovviamente per tutte le interazioni che richiedono il recupero di informazioni presenti solo sul Server, si possono introdurre dei meccanismi di retry per mettere in attesa l&#8217;utente, senza bloccarlo completamente.<br>Vediamo un esempio di file <strong>service.worker.js<\/strong>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\nconst cacheName = &#039;offline-cache&#039;;\nlet handleAsOfflineUntil = 0;\n\nself.addEventListener(&#039;install&#039;, async event =&gt; {\n    console.log(&#039;Installing service worker...&#039;);\n    await Promise.all((await caches.keys()).map(key =&gt; caches.delete(key)));\n    await (await caches.open(cacheName)).addAll(&#x5B;&#039;loading.gif&#039;]);\n    self.skipWaiting();\n});\n\nself.addEventListener(&#039;fetch&#039;, event =&gt; {\n    \/\/ Don&#039;t interfere with API calls\n    if (event.request.method !== &#039;GET&#039; || event.request.url.indexOf(&#039;\/_framework\/debug&#039;) &gt;= 0) {\n        return;\n    }\n\n    event.respondWith(getFromNetworkOrCache(event.request));\n});\n  \nasync function getFromNetworkOrCache(request) {\n    if (new Date().valueOf() &gt; handleAsOfflineUntil) {\n        try {\n            const networkResponse = await fetchWithTimeout(request, 1000);\n            (await caches.open(cacheName)).put(request, networkResponse.clone());\n            console.info(&#039;Fetched from network: &#039; + request.url);\n            return networkResponse;\n        } catch (ex) {\n            handleAsOfflineUntil = new Date().valueOf() + 3000; \/\/ Next 3 seconds\n        }\n    }\n\n    \/\/ Fall back on cache\n    console.info(&#039;Fetching from cache: &#039; + request.url);\n    return caches.match(request);\n}\n\nfunction fetchWithTimeout(request, timeoutMs) {\n    return new Promise((resolve, reject) =&gt; {\n        setTimeout(() =&gt; reject(&#039;Timed out&#039;), timeoutMs);\n        fetch(request).then(resolve, reject);\n    });\n}\n<\/pre><\/div>\n\n\n<p>Similmente a quanto fatto per il file <em>manifest.json<\/em>, affinch\u00e8 il Service Worker venga installato \u00e8 necessario importarlo nella index page nel modo seguente:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: xml; title: ; notranslate\" title=\"\">\n&lt;script&gt;\n    navigator.serviceWorker.register(&#039;service-worker.js&#039;);\n&lt;\/script&gt;\n<\/pre><\/div>\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td class=\"has-text-align-center\" data-align=\"center\"><\/td><td class=\"has-text-align-left\" data-align=\"left\"><strong>Blazor Client<\/strong><\/td><td class=\"has-text-align-left\" data-align=\"left\"><strong>Blazor Server<\/strong><\/td><\/tr><tr><td class=\"has-text-align-center\" data-align=\"center\"><img loading=\"lazy\" decoding=\"async\" width=\"32\" height=\"32\" class=\"wp-image-2857\" style=\"width: 32px;\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/iconfinder_outlined_like_4280463.png\" alt=\"\"><\/td><td class=\"has-text-align-left\" data-align=\"left\">&#8211; PWA<br>&#8211; Funzionamento offline<\/td><td class=\"has-text-align-left\" data-align=\"left\">&#8211; Minimizzare i contenuti da scaricare<br>&#8211; Elaborazione maggiore demandata ai Server<\/td><\/tr><tr><td class=\"has-text-align-center\" data-align=\"center\"><img loading=\"lazy\" decoding=\"async\" width=\"32\" height=\"32\" class=\"wp-image-2858\" style=\"width: 32px;\" src=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/iconfinder_outlined_dislike_4280531.png\" alt=\"\"><\/td><td class=\"has-text-align-left\" data-align=\"left\">&#8211; Maggiore consumo CPU device<br>&#8211; Consumo dati iniziale per download Web Assembly<\/td><td class=\"has-text-align-left\" data-align=\"left\">&#8211; Maggiori interazioni di rete<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusioni<\/h1>\n\n\n\n<p>Le potenzialit\u00e0 di questo Framework sono molto chiare, non ci sono dubbi che utilizzare un linguaggio come C# per programmare delle Web UI sia un desiderio per tantissimi programmatori e fino ad oggi tutte le soluzioni proposte sul mercato non hanno mai trovato una soluzione efficiente e scalabile come quella di Blazor.<br>Il modello Blazor-Client con tecnologia WA sicuramente \u00e8 quello che offre pi\u00f9 prospettive e scenari, soprattutto in combinazione con la feature PWA, tuttavia per il momento non si pu\u00f2 non considerare che il download del WA con relativo .NET Core Runtime sicuramente \u00e8 un punto dolente. Anche se si parla di alcuni MB come ordine di grandezza, bisogna tener conto che la copertura Internet ad alta velocit\u00e0 \u00e8 tutt&#8217;altro che da dare per scontata e questo potrebbe essere un limite non trascurabile in un buon numero di scenari nel mondo mobile.<br>Blazor \u00e8 un Framework che lascia tante possibilit\u00e0 implementative e sar\u00e0 tanto pi\u00f9 diffuso maggiori saranno i Component pronti all&#8217;uso per gli sviluppatori unitamente al fatto che nelle prossime release verranno rilasciate delle migliorie che aumenteranno il grado di maturit\u00e0 del Framework stesso. <\/p>\n\n\n\n<p><em><strong>L&#8217;esempio <strong>Razor Server-side<\/strong><\/strong><\/em> <em><strong>presente in questa guida \u00e8 scaricabile a questo <a href=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/MyCompanyApp.zip\">link<\/a>.<\/strong><\/em><br><em><strong>L&#8217;esempio Razor Client-side presente in questa guida \u00e8 scaricabile a questo <a href=\"https:\/\/cloudsurfers.it\/wp-content\/uploads\/2020\/10\/BlazorMart.zip\">link<\/a>.<\/strong><\/em><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Link utili<\/h1>\n\n\n\n<ul class=\"wp-block-list\"><li><strong style=\"font-size: 0.875rem;\">Blazor<\/strong><span style=\"font-size: 0.875rem;\">: <\/span><a rel=\"noreferrer noopener\" style=\"font-size: 0.875rem;\" href=\"https:\/\/dotnet.microsoft.com\/apps\/aspnet\/web-apps\/blazor\" target=\"_blank\">https:\/\/dotnet.microsoft.com\/apps\/aspnet\/web-apps\/blazor<\/a><\/li><li><strong>gRPC<\/strong>: <a rel=\"noreferrer noopener\" href=\"https:\/\/grpc.io\/\" target=\"_blank\">https:\/\/grpc.io\/<\/a><\/li><li><strong>PWA (Progressive Web Applications)<\/strong>: <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/blazor\/progressive-web-app?view=aspnetcore-3.1&amp;tabs=visual-studio\" target=\"_blank\">https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/blazor\/progressive-web-app?view=aspnetcore-3.1&amp;tabs=visual-studio<\/a> <\/li><li><strong>W3C Manifest Official Documentation<\/strong>: <a rel=\"noreferrer noopener\" href=\"https:\/\/www.w3.org\/TR\/appmanifest\/\" target=\"_blank\">https:\/\/www.w3.org\/TR\/appmanifest\/<\/a><\/li><li><strong>Microsoft PWA<\/strong>: <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/blazor\/progressive-web-app?view=aspnetcore-3.1&amp;tabs=visual-studio\" target=\"_blank\">https:\/\/docs.microsoft.com\/en-us\/aspnet\/core\/blazor\/progressive-web-app?view=aspnetcore-3.1&amp;tabs=visual-studio<\/a><\/li><li><strong>Service Workers<\/strong>: <a href=\"https:\/\/developers.google.com\/web\/ilt\/pwa\/introduction-to-service-worker\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/developers.google.com\/web\/ilt\/pwa\/introduction-to-service-worker<\/a><\/li><\/ul>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Blazor \u00e8 il pi\u00f9 recente framework proposto da Microsoft che ha come scopo di consentire lo sviluppo di rich web UI utilizzando un linguaggio moderno come C#<\/p>\n","protected":false},"author":4,"featured_media":2816,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"wds_primary_category":0,"footnotes":""},"categories":[130,129],"tags":[94,128,111,93],"class_list":["post-2889","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-insights","category-blazor","tag-net-core","tag-blazor","tag-c","tag-dotnet-core"],"_links":{"self":[{"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/posts\/2889","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/comments?post=2889"}],"version-history":[{"count":0,"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/posts\/2889\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/media\/2816"}],"wp:attachment":[{"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/media?parent=2889"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/categories?post=2889"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudsurfers.it\/index.php\/wp-json\/wp\/v2\/tags?post=2889"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}